diff --git a/.env.example b/.env.example index 3264164..9721336 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,6 @@ VITE_APP_ENV= VITE_GA_MEASUREMENT_ID= VITE_UMAMI_SRC= VITE_UMAMI_WEBSITE_ID= + +# Vite dev server proxy for NotifyComp token requests. +NOTIFY_COMP_TOKEN_ORIGIN=https://www.competitiongroups.com diff --git a/README.md b/README.md index 6ab7c79..de0c493 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,11 @@ yarn test yarn storybook yarn build-storybook ``` + +During `yarn dev`, NotifyComp token requests are proxied to +`https://www.competitiongroups.com`, so production WCA sign-in and NotifyComp +authorization work without running Netlify Functions locally. The deployed +function checks the WCA access token and competition permissions. Set +`NOTIFY_COMP_TOKEN_ORIGIN` in `.env.local` to use another deployed token issuer +or a local Netlify Dev server. Use a token issuer connected to the same WCA and +NotifyComp environments as the frontend, and restart Vite after changing it. diff --git a/src/components/NotifyCompRemoteBar/NotifyCompRemoteBar.tsx b/src/components/NotifyCompRemoteBar/NotifyCompRemoteBar.tsx index d0655ca..10e3fe9 100644 --- a/src/components/NotifyCompRemoteBar/NotifyCompRemoteBar.tsx +++ b/src/components/NotifyCompRemoteBar/NotifyCompRemoteBar.tsx @@ -5,6 +5,8 @@ import { RemoteActivitySummaryList } from '@/components/RemoteActivitySummaryLis import { useCompetitionRemoteControl } from '@/hooks/useCompetitionRemoteControl'; import { useNotifyCompWebSocketStatus } from '@/hooks/useNotifyCompWebSocketStatus'; import { useNow } from '@/hooks/useNow'; +import { parseActivityCodeFlexible } from '@/lib/activityCodes'; +import { isOfficialEventId, shortEventNameById } from '@/lib/events'; import { RemoteActivityGroup } from '@/lib/notifyCompRemoteActivities'; import { canUseNotifyCompRemoteControls } from '@/lib/notifyCompWebSocketStatus'; import { useConfirm } from '@/providers/ConfirmProvider'; @@ -23,6 +25,25 @@ const iconButtonClassName = const primaryButtonClassName = 'flex h-10 w-10 items-center justify-center rounded-full border border-blue-300 bg-blue-200 text-lg leading-none text-gray-900 shadow-sm hover-transition hover:bg-blue-300 disabled:cursor-not-allowed disabled:opacity-40 dark:border-blue-600 dark:bg-blue-700 dark:text-white dark:hover:bg-blue-600'; +const shortGroupName = (group: RemoteActivityGroup) => { + const activityCode = group.scheduledActivities[0]?.activityCode; + if (!activityCode || !isOfficialEventId(activityCode.split('-')[0])) { + return group.name; + } + + const { eventId, roundNumber, groupNumber, attemptNumber } = + parseActivityCodeFlexible(activityCode); + + return [ + shortEventNameById(eventId), + roundNumber && `R${roundNumber}`, + groupNumber && `G${groupNumber}`, + attemptNumber && `A${attemptNumber}`, + ] + .filter(Boolean) + .join(' '); +}; + export function NotifyCompRemoteBar({ competitionId }: NotifyCompRemoteBarProps) { const confirm = useConfirm(); const remote = useCompetitionRemoteControl({ competitionId }); @@ -41,6 +62,10 @@ export function NotifyCompRemoteBar({ competitionId }: NotifyCompRemoteBarProps) const hasActiveGroups = activeNames.length > 0; const currentTitle = activeNames.length > 0 ? activeNames.join(', ') : 'No active activity'; const nextTitle = remote.nextGroup?.name || 'No next activity'; + const currentShortTitle = hasActiveGroups + ? remote.activeGroups.map(shortGroupName).join(', ') + : currentTitle; + const nextShortTitle = remote.nextGroup ? shortGroupName(remote.nextGroup) : nextTitle; const elapsed = formatElapsedDuration(remote.activeGroups, now); const nextOffset = formatNextActivityOffset(remote.nextGroup, now); const progress = getRemoteBarProgress({ @@ -148,7 +173,10 @@ export function NotifyCompRemoteBar({ competitionId }: NotifyCompRemoteBarProps) )}>