Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

- Fixed empty player token dialog if no player token exists.
- Fixed incorrect form when creating a player token.
- Fixed player token box not displayed on page refresh.

## 1.9.2 - 2025-03-22

Expand Down
210 changes: 116 additions & 94 deletions src/components/settings/Tokens.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,17 @@ import TokenWidget from 'components/generics/TokenWidget'
import { IsLibraryManager } from 'permissions/components/Library'
import { IsPlaylistManager } from 'permissions/components/Playlist'
import { Status } from 'reducers/alterationsResponse'
import { karaokePropType, playerTokenPropType } from 'serverPropTypes/playlist'
import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup'

function PlayerTokenBox() {
const playerTokenState = useSelector((state) => state.playlist.playerToken)
const responseOfCreatePlayerToken = useSelector(
(state) => state.alterationsResponse.unique.createPlayerToken
)
function PlayerTokenBoxDisplay({ playerToken, karaoke }) {
const responseOfRevokePlayerToken = useSelector(
(state) => state.alterationsResponse.unique.revokePlayerToken
)
const karaokeState = useSelector((state) => state.playlist.karaoke)

const [confirmDisplayed, setConfirmDisplayed] = useState(false)

const dispatch = useDispatch()

const { data: karaoke } = karaokeState

useEffect(
() => {
// load player token as soon as the karaoke ID has been fetched
if (!playerTokenState.status && karaoke.id) {
dispatch(loadPlayerToken(karaoke.id))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const [confirmDisplayed, setConfirmDisplayed] = useState(false)

const displayConfirm = useCallback(() => {
setConfirmDisplayed(true)
Expand All @@ -51,82 +34,111 @@ function PlayerTokenBox() {
setConfirmDisplayed(false)
}, [])

const doConfirm = useCallback(
() => {
clearConfirm()
dispatch(revokePlayerToken(karaoke.id))
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[karaoke]
return (
<div className="player-token-box-display flow">
<TokenWidget token={playerToken.key} />
<div className="ribbon info copy-help">
<p className="message">
You can use this token to authenticate the player.
</p>
</div>
<div className="revoke controls notifiable">
<CSSTransitionLazy
in={confirmDisplayed}
classNames="notified"
timeout={{
enter: 300,
exit: 150,
}}
>
<ConfirmationBar
onConfirm={() => {
dispatch(revokePlayerToken(karaoke.id))
}}
onCancel={clearConfirm}
/>
</CSSTransitionLazy>
<Notification
alterationResponse={responseOfRevokePlayerToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to revoke player token"
/>
<button className="control primary" onClick={displayConfirm}>
Revoke player token
</button>
</div>
</div>
)
}

PlayerTokenBoxDisplay.propTypes = {
playerToken: playerTokenPropType.isRequired,
karaoke: karaokePropType.isRequired,
}

function PlayerTokenBoxCreate({ karaoke }) {
const responseOfCreatePlayerToken = useSelector(
(state) => state.alterationsResponse.unique.createPlayerToken
)

const dispatch = useDispatch()

return (
<div className="player-token-box-create flow">
<p>Create a token that can be used to authenticate the player.</p>
<div className="controls notifiable">
<Notification
alterationResponse={responseOfCreatePlayerToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to create player token"
/>
<button
className="control primary"
onClick={() => {
dispatch(createPlayerToken(karaoke.id))
}}
>
Create player token
</button>
</div>
</div>
)
}

PlayerTokenBoxCreate.propTypes = {
karaoke: karaokePropType.isRequired,
}

function PlayerTokenBox() {
const playerTokenState = useSelector((state) => state.playlist.playerToken)
const karaokeState = useSelector((state) => state.playlist.karaoke)

const dispatch = useDispatch()

const { data: karaoke } = karaokeState
const { id: karaokeId } = karaoke

const { data: playerToken, status: playerTokenStatus } = playerTokenState
const keyExists = !!playerToken.key

// NOTE If the token is not found, the state is still `successful` as this
useEffect(
() => {
// load player token as soon as the karaoke ID has been fetched
if (!playerTokenStatus && karaokeId) {
dispatch(loadPlayerToken(karaokeId))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[playerTokenStatus, karaokeId]
)

// NOTE If the token is not found, the status is still `successful` as this
// is a valid case. Check the reducer to see how this case is handled.

let playerTokenBox
if (playerTokenStatus === Status.successful) {
let playerTokenBoxContent
if (keyExists) {
// display token
playerTokenBoxContent = (
<>
<TokenWidget token={playerToken.key} />
<div className="ribbon info copy-help">
<p className="message">
You can use this token to authenticate the player.
</p>
</div>
<div className="revoke controls notifiable">
<CSSTransitionLazy
in={confirmDisplayed}
classNames="notified"
timeout={{
enter: 300,
exit: 150,
}}
>
<ConfirmationBar onConfirm={doConfirm} onCancel={clearConfirm} />
</CSSTransitionLazy>
<Notification
alterationResponse={responseOfRevokePlayerToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to revoke player token"
/>
<button className="control primary" onClick={displayConfirm}>
Revoke player token
</button>
</div>
</>
)
} else {
// display button to create token
playerTokenBoxContent = (
<>
<p>Create a token that can be used to authenticate the player.</p>
<div className="controls notifiable">
<button
className="control primary"
onClick={() => {
dispatch(createPlayerToken(karaoke.id))
}}
>
Create player token
</button>
</div>
<Notification
alterationResponse={responseOfCreatePlayerToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to create player token"
/>
</>
)
}

playerTokenBox = (
<CSSTransition
in={keyExists}
Expand All @@ -136,7 +148,11 @@ function PlayerTokenBox() {
exit: 150,
}}
>
{playerTokenBoxContent}
{keyExists ? (
<PlayerTokenBoxDisplay playerToken={playerToken} karaoke={karaoke} />
) : (
<PlayerTokenBoxCreate karaoke={karaoke} />
)}
</CSSTransition>
)
} else if (playerTokenStatus === Status.failed) {
Expand All @@ -145,6 +161,12 @@ function PlayerTokenBox() {
<p>Unable to get player token.</p>
</div>
)
} else {
playerTokenBox = (
<div className="ribbon">
<p>Pending…</p>
</div>
)
}

return (
Expand Down Expand Up @@ -204,16 +226,16 @@ export default function Tokens() {
onCancel={clearConfirm}
/>
</CSSTransitionLazy>
<Notification
alterationResponse={responseOfRevokeToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to revoke token"
/>
<button className="control primary" onClick={displayConfirm}>
Revoke token
</button>
</div>
<Notification
alterationResponse={responseOfRevokeToken}
pendingMessage={null}
successfulMessage={null}
failedMessage="Unable to revoke token"
/>
</div>
<IsPlaylistManager user={user}>
<PlayerTokenBox />
Expand Down
18 changes: 12 additions & 6 deletions src/style/components/settings/_tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,30 @@

#tokens {
.token-box {
// approximate min and max height
$token-player-min-height: calc(
1.25em + sizes.$row-height + sizes.$gap-vertical
);
$token-player-max-height: calc(
3 * sizes.$row-height + 2 * sizes.$gap-vertical
);

.token-player-enter {
// approximate min and max height
height: calc(2em + sizes.$row-height + sizes.$gap-vertical);
height: $token-player-min-height;
overflow-y: hidden;

&.token-player-enter-active {
height: 2 * sizes.$row-height + 2 * sizes.$gap-vertical;
height: $token-player-max-height;
transition: height 300ms ease-out;
}
}

.token-player-exit {
// approximate min and max height
height: 2 * sizes.$row-height + 2 * sizes.$gap-vertical;
height: $token-player-max-height;
overflow-y: hidden;

&.token-player-exit-active {
height: calc(2em + sizes.$row-height + sizes.$gap-vertical);
height: $token-player-min-height;
transition: height 150ms ease-out;
}
}
Expand Down
Loading