services/app/apps/codebattle/assets/js/widgets/pages/tournament/TournamentHeader.jsx
import React, { memo, useContext, useMemo } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import cn from 'classnames';
import i18next from 'i18next';
import moment from 'moment';
import CustomEventStylesContext from '@/components/CustomEventStylesContext';
import useTournamentStats from '@/utils/useTournamentStats';
import CopyButton from '../../components/CopyButton';
import GameLevelBadge from '../../components/GameLevelBadge';
import Loading from '../../components/Loading';
import TournamentType from '../../components/TournamentType';
import WaitingRoomStatus from '../../components/WaitingRoomStatus';
import TournamentStates from '../../config/tournament';
import TournamentTypes from '../../config/tournamentTypes';
import useTimer from '../../utils/useTimer';
import JoinButton from './JoinButton';
import TournamentMainControlButtons from './TournamentMainControlButtons';
const getIconByAccessType = accessType => (accessType === 'token' ? 'lock' : 'unlock');
const getBadgeTitle = (state, breakState, hideResults) => {
if (hideResults && state === TournamentStates.finished) {
return 'Waiting winner announcements';
}
switch (state) {
case TournamentStates.active:
return breakState === 'off' ? 'Active' : 'Round break';
case TournamentStates.waitingParticipants:
return 'Waiting Participants';
case TournamentStates.cancelled:
return 'Cancelled';
case TournamentStates.finished:
return 'Finished';
default:
return 'Loading';
}
};
const getDescriptionByState = state => {
switch (state) {
case TournamentStates.cancelled:
return i18next.t('The tournament is cancelled');
case TournamentStates.finished:
return i18next.t('The tournament is finished');
default:
return '';
}
};
function TournamentTimer({ startsAt, isOnline }) {
const [duration, seconds] = useTimer(startsAt);
if (!isOnline) {
return null;
}
return seconds > 0 ? (
<span>
{i18next.t('The tournament will start: %{duration}', { duration })}
</span>
) : (
<span>{i18next.t('The tournament will start soon')}</span>
);
}
function TournamentRemainingTimer({ startsAt, duration }) {
const endsAt = useMemo(
() => moment.utc(startsAt).add(duration, 'seconds'),
[startsAt, duration],
);
const [time, seconds] = useTimer(endsAt);
return seconds > 0 ? time : '';
}
function TournamentStateDescription({
state,
startsAt,
breakState,
breakDurationSeconds,
matchTimeoutSeconds,
roundTimeoutSeconds,
lastRoundStartedAt,
lastRoundEndedAt,
isOnline,
}) {
if (state === TournamentStates.waitingParticipants) {
return <TournamentTimer startsAt={startsAt} isOnline={isOnline} />;
}
if (state === TournamentStates.active && breakState === 'off') {
return (
<span>
{i18next.t('Round ends in ')}
<TournamentRemainingTimer
key={lastRoundStartedAt}
startsAt={lastRoundStartedAt}
duration={roundTimeoutSeconds || matchTimeoutSeconds}
/>
</span>
);
}
if (state === TournamentStates.active && breakState === 'on') {
return (
<span>
{i18next.t('Next round will start in ')}
<TournamentRemainingTimer
key={lastRoundEndedAt}
startsAt={lastRoundEndedAt}
duration={breakDurationSeconds}
/>
</span>
);
}
return getDescriptionByState(state);
}
function TournamentHeader({
id: tournamentId,
state,
streamMode,
breakState,
breakDurationSeconds,
matchTimeoutSeconds,
roundTimeoutSeconds,
lastRoundStartedAt,
lastRoundEndedAt,
startsAt,
type,
accessType,
accessToken,
isLive,
name,
players,
playersCount,
playersLimit,
currentUserId,
showBots = true,
hideResults = true,
level,
isOnline,
isOver,
canModerate,
toggleShowBots,
toggleStreamMode,
handleStartRound,
handleOpenDetails,
}) {
const { taskSolvedCount, maxPlayerTasks, activeGameId } = useTournamentStats({ type: 'tournament' });
const stateBadgeTitle = useMemo(
() => i18next.t(getBadgeTitle(state, breakState, hideResults)),
[state, breakState, hideResults],
);
const hasCustomEventStyle = useContext(CustomEventStylesContext);
const stateClassName = cn('badge mr-2', hasCustomEventStyle ? {
'cb-custom-event-badge-warning': state === TournamentStates.waitingParticipants,
'cb-custom-event-badge-success':
!hideResults && (breakState === 'off' || state === TournamentStates.finished),
'cb-custom-event-badge-light': state === TournamentStates.cancelled,
'cb-custom-event-badge-danger': breakState === 'on',
'cb-custom-event-badge-primary': hideResults && state === TournamentStates.finished,
} : {
'badge-warning': state === TournamentStates.waitingParticipants,
'badge-success':
!hideResults && (breakState === 'off' || state === TournamentStates.finished),
'badge-light': state === TournamentStates.cancelled,
'badge-danger': breakState === 'on',
'badge-primary': hideResults && state === TournamentStates.finished,
});
const copyBtnClassName = cn('btn btn-sm rounded-right', {
'btn-secondary': !hasCustomEventStyle,
'cb-custom-event-btn-secondary': hasCustomEventStyle,
});
const backBtnClassName = cn('btn rounded-lg ml-lg-2 ml-md-2 mr-2', {
'btn-primary': !hasCustomEventStyle,
'cb-custom-event-btn-primary': hasCustomEventStyle,
});
const canStart = isLive
&& state === TournamentStates.waitingParticipants
&& playersCount > 0;
const canStartRound = isLive
&& state === TournamentStates.active
&& breakState === 'on';
const canFinishRound = isLive
&& state === TournamentStates.active
&& !(['individual', 'team'].includes(type))
&& breakState === 'off';
const canRestart = !isLive
|| state === TournamentStates.active
|| state === TournamentStates.finished
|| state === TournamentStates.cancelled;
const canToggleShowBots = type === TournamentTypes.show;
return (
<>
<div className="col bg-white shadow-sm rounded-lg p-2">
<div className="d-flex flex-column flex-lg-row flex-md-row justify-content-between">
<div className="d-flex align-items-center pb-2">
<h2
title={name}
className="pb-1 m-0 text-capitalize text-nowrap cb-overflow-x-auto cb-overflow-y-hidden"
>
{name}
</h2>
<div
className="text-center ml-3"
data-toggle="tooltip"
data-placement="right"
title="Tournament level"
>
<GameLevelBadge level={level} />
</div>
<div
title={
accessType === 'token'
? 'Private tournament'
: 'Public tournament'
}
className="text-center ml-2"
>
<FontAwesomeIcon icon={getIconByAccessType(accessType)} />
</div>
{isOnline ? (
<div
title={isLive ? 'Active tournament' : 'Inactive tournament'}
className={cn('text-center ml-2', {
'text-primary': isLive,
'text-light': !isLive,
})}
>
<FontAwesomeIcon icon="wifi" />
</div>
) : (
<div className="text-center ml-2">
<Loading adaptive />
</div>
)}
</div>
<div className="d-flex">
{!streamMode && (
!isOver ? (
<div className="d-flex justify-items-center pb-2">
{type !== 'team' && (
<div className="mr-2 mr-lg-0">
<JoinButton
isShow={state !== TournamentStates.active || type === 'arena'}
isShowLeave={!(type === 'arena' && state === TournamentStates.active)}
isParticipant={!!players[currentUserId]}
disabled={!isOnline || !isLive}
/>
</div>
)}
</div>
) : (
<div className="d-flex justify-items-center pb-2">
<a
className={backBtnClassName}
href="/tournaments"
>
<FontAwesomeIcon className="mr-2" icon="undo" />
{i18next.t('Back to tournaments')}
</a>
</div>
)
)}
<div className="d-flex justify-items-center pb-2">
{canModerate && (
<TournamentMainControlButtons
accessType={accessType}
streamMode={streamMode}
tournamentId={tournamentId}
canStart={canStart}
canStartRound={canStartRound}
canFinishRound={canFinishRound}
canRestart={canRestart}
canToggleShowBots={canToggleShowBots}
showBots={showBots}
hideResults={hideResults}
disabled={!isOnline}
handleStartRound={handleStartRound}
handleOpenDetails={handleOpenDetails}
toggleShowBots={toggleShowBots}
toggleStreamMode={toggleStreamMode}
/>
)}
</div>
</div>
</div>
{canModerate && !streamMode && (
<div className="d-flex align-items-center small text-nowrap text-muted mt-1 cb-grid-divider overflow-auto border-top">
<div title={type} className="d-flex align-items-center">
Mode:
<span className="ml-2">
<TournamentType type={type} />
</span>
</div>
<span className="mx-2">|</span>
<div
title={`Players limit is ${playersLimit}`}
className="d-flex align-items-center"
>
{`Players limit: ${playersLimit}`}
</div>
<span className="mx-2">|</span>
<div
title={`Is live ${isLive}`}
className="d-flex align-items-center"
>
{`Is live: ${isLive}`}
</div>
{accessType === 'token' && (
<>
<span className="mx-2">|</span>
<div className="d-flex input-group ml-2">
<div title="Access token" className="input-group-prepend">
<span className="input-group-text">
<FontAwesomeIcon icon="key" />
</span>
</div>
<CopyButton
className={copyBtnClassName}
value={accessToken}
disabled={!isLive || !isOnline}
/>
</div>
</>
)}
</div>
)}
</div>
<div
className={cn(
'col bg-white shadow-sm rounded-lg p-2 mt-2 overflow-auto',
'd-flex align-items-center justify-content-between',
)}
>
<p className="h5 mb-0 text-nowrap">
<span className={stateClassName}>{stateBadgeTitle}</span>
<span className="h6 text-nowrap">
<TournamentStateDescription
state={state}
startsAt={startsAt}
breakState={breakState}
breakDurationSeconds={breakDurationSeconds}
matchTimeoutSeconds={matchTimeoutSeconds}
roundTimeoutSeconds={roundTimeoutSeconds}
lastRoundStartedAt={lastRoundStartedAt}
lastRoundEndedAt={lastRoundEndedAt}
isLive={isLive}
isOver={isOver}
isOnline={isOnline}
/>
</span>
</p>
{type === TournamentTypes.arena && state === TournamentStates.active && !!players[currentUserId] && breakState === 'off' && (
<div className="d-flex align-items-center">
<WaitingRoomStatus
page="tournament"
taskCount={taskSolvedCount}
tournamentState={state}
breakState={breakState}
maxPlayerTasks={maxPlayerTasks}
activeGameId={activeGameId}
/>
</div>
)}
</div>
</>
);
}
export default memo(TournamentHeader);