export default function BorderCountdownTimer()

in app-dev/party-game/app/components/border-countdown-timer.tsx [28:141]


export default function BorderCountdownTimer({game, children, gameRef}: { game: Game, children: React.ReactNode, gameRef: DocumentReference }) {
  const [timeLeft, setTimeLeft] = useState<number>(game.timePerQuestion);
  const [localCounter, setLocalCounter] = useState<number>(0);
  const gameId = gameRef.id;
  const authUser = useFirebaseAuthentication();
  const isShowingCorrectAnswers = game.state === gameStates.SHOWING_CORRECT_ANSWERS;
  const displayTime = MANUAL === game.questionAdvancement && isShowingCorrectAnswers ? 0 : Math.max(Math.floor(timeLeft), 0);
  const timeToCountDown = isShowingCorrectAnswers ? game.timePerAnswer : game.timePerQuestion;
  const totalPlayersWhoMadeAGuess = Object.values(game.questions[game.currentQuestionIndex].playerGuesses || []).length;

  const nudgeGame = useCallback(async ({gameId, desiredState}: { gameId: string, desiredState: GameStateUpdate }) => {
    if (game.state === desiredState.state && game.currentQuestionIndex === desiredState.currentQuestionIndex) return;
    // all times are in seconds unless noted as `InMillis`
    const timeElapsedInMillis = Timestamp.now().toMillis() - game.currentStateStartTime.seconds * 1000;
    const timeElapsed = timeElapsedInMillis / 1000;
    if (authUser.uid === game.leader.uid && timeElapsed > 2) {
      const tokens = await getTokens();
      nudgeGameAction({gameId, desiredState, tokens});
    }
  }, [authUser.uid, game.currentQuestionIndex, game.currentStateStartTime.seconds, game.leader.uid, game.state]);

  useEffect(() => {
    // all times are in seconds unless noted as `InMillis`
    const timeElapsedInMillis = Timestamp.now().toMillis() - game.currentStateStartTime.seconds * 1000;
    const timeElapsed = timeElapsedInMillis / 1000;
    const isShowingCorrectAnswers = game.state === gameStates.SHOWING_CORRECT_ANSWERS;

    if (isShowingCorrectAnswers) {
      const timeLeft = game.timePerAnswer - timeElapsed;
      if (timeLeft < 0 && game.questionAdvancement === questionAdvancements.AUTOMATIC) {
        const desiredState = {
          state: gameStates.AWAITING_PLAYER_ANSWERS,
          currentQuestionIndex: game.currentQuestionIndex + 1,
        };
        nudgeGame({gameId, desiredState});
      }
      setTimeLeft(timeLeft);
    } else {
      const timeLeft = game.timePerQuestion - timeElapsed;
      if (timeLeft < 0) {
        const desiredState = {
          state: gameStates.SHOWING_CORRECT_ANSWERS,
          currentQuestionIndex: game.currentQuestionIndex,
        };
        nudgeGame({gameId, desiredState});
      }
      setTimeLeft(timeLeft);
    }
  }, [localCounter, game.currentStateStartTime, game.currentQuestionIndex, game.timePerAnswer, game.timePerQuestion, isShowingCorrectAnswers, game.state, game.leader.uid, game.questionAdvancement, authUser.uid, gameId, nudgeGame]);

  useEffect(() => {
    // save timeoutIdOne to clear the timeout when the
    // component re-renders
    const timeoutIdOne = setTimeout(() => {
      setLocalCounter(localCounter + 1);
    }, 1000);

    // clear timeout on re-render to avoid memory leaks
    return () => clearTimeout(timeoutIdOne);
  }, [localCounter, game.state]);

  const limitPercents = (num: number) => Math.max(Math.min(num, 100), 0);

  // this is the percent of the entire animation that has completed
  // the `+ 1` allows the animation to target where it "should" be in one second
  const timeToCountDivisibleByFour = Math.floor(timeToCountDown / 4) * 4;
  const animationCompletionPercentage = limitPercents((timeToCountDivisibleByFour - displayTime + 1) / timeToCountDivisibleByFour * 100);

  const topBorderPercentage = limitPercents(isShowingCorrectAnswers ? 400 - animationCompletionPercentage * 4 : animationCompletionPercentage * 4);
  const rightBorderPercentage = limitPercents(isShowingCorrectAnswers ? 300 - animationCompletionPercentage * 4 : animationCompletionPercentage * 4 - 100);
  const bottomBorderPercentage = limitPercents(isShowingCorrectAnswers ? 200 - animationCompletionPercentage * 4 : animationCompletionPercentage * 4 - 200);
  const leftBorderPercentage = limitPercents(isShowingCorrectAnswers ? 100 - animationCompletionPercentage * 4 : animationCompletionPercentage * 4 - 300);


  return (
    <>
      <div className={`relative p-4 h-[50dvh] overflow-hidden`}>
        <div className="absolute border-2 border-gray-100 h-full w-full top-0 left-0" />
        <div className="timer-top-border absolute top-0 left-0 bg-[var(--google-cloud-red)]" style={{height: '8px', width: `${topBorderPercentage}%`, transition: 'width 1s linear'}} />
        <div className="timer-right-border absolute top-0 right-0 bg-[var(--google-cloud-blue)]" style={{height: `${rightBorderPercentage}%`, width: '8px', transition: 'height 1s linear'}} />
        <div className="timer-bottom-border absolute bottom-0 right-0 bg-[var(--google-cloud-green)]" style={{height: '8px', width: `${bottomBorderPercentage}%`, transition: 'width 1s linear'}} />
        <div className="timer-left-border absolute bottom-0 left-0 bg-[var(--google-cloud-yellow)]" style={{height: `${leftBorderPercentage}%`, width: '8px', transition: 'height 1s linear'}} />
        <div className="h-full w-full border-8 border-transparent">
          <div className="bg-gray-100 py-1 px-2 float-right relative">
            {displayTime < 10 && '0'}
            {displayTime}
            {authUser.uid === game.leader.uid && (<>
              <div>
                {totalPlayersWhoMadeAGuess < 10 && '0'}
                {totalPlayersWhoMadeAGuess}
              </div>
              <div>&nbsp;</div>
              <div className="my-1 absolute bottom-0">
                <button
                  onClick={() => {
                    const desiredState = {
                      state: isShowingCorrectAnswers ? gameStates.AWAITING_PLAYER_ANSWERS : gameStates.SHOWING_CORRECT_ANSWERS,
                      currentQuestionIndex: isShowingCorrectAnswers ? game.currentQuestionIndex + 1 : game.currentQuestionIndex,
                    };
                    console.log('clicked');
                    nudgeGame({gameId, desiredState});
                  }}
                >
                  {'→'}
                </button>
              </div>
            </>)}
          </div>
          {children}
        </div>
      </div>
    </>
  );
}