ronantakizawa/codereview-bench huggingface dataset:
Repository: freeCodeCamp/freeCodeCamp
File: client/src/components/settings/exam-token.tsx (TypeScript)

Code before:
            {t('buttons.copy')}
          </Button>
          <Spacer size='s' />
          <Button
            onClick={() => {
              setGeneratedExamToken(false);
              setCopySuccess(null);
              setCopyError(null);
            }}
          >
            {t('buttons.close')}
          </Button>
        </Modal.Footer>
      </Modal>
      <Panel variant='info' id='exam-environment-authorization-token'>
        <Panel.Heading>{t('exam-token.exam-token')}</Panel.Heading>
        <Panel.Body>
          <p>{t('exam-token.note')}</p>
          <strong>{t('exam-token.invalidation')}</strong>
          <Spacer size='s' />
          <Button
            block={true}
            disabled={recentlyGenerated}
            onClick={() => {
              setGeneratedExamToken(true);
              void getToken();
            }}
          >
            {t('exam-token.generate-exam-token')}
          </Button>
        </Panel.Body>
      </Panel>
    </FullWidthRow>
  );
}

ExamToken.displayName = 'ExamToken';

export default ExamToken;


Review comment (suggestion):
```suggestion
            onClick={() => void getToken()}
```

Code after:
            onClick={() => {
              setExamToken(null);
              setCopySuccess(null);
              setCopyError(null);
            }}
          >
            {t('buttons.close')}
          </Button>
        </Modal.Footer>
      </Modal>
      <Panel variant='info' id='exam-environment-authorization-token'>
        <Panel.Heading>{t('exam-token.exam-token')}</Panel.Heading>
        <Panel.Body>
          <p>{t('exam-token.note')}</p>
          <strong>{t('exam-token.invalidation')}</strong>
          <Spacer size='s' />
          <Button
            block={true}
            disabled={recentlyGenerated}
            onClick={() => void getToken()}
          >
            {t('exam-token.generate-exam-token')}
          </Button>
        </Panel.Body>
      </Panel>
    </FullWidthRow>
  );
}

ExamToken.displayName = 'ExamToken';

export default ExamToken;
Repository: freeCodeCamp/freeCodeCamp
File: api/src/routes/auth.ts (TypeScript)

Code before:
      },
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
      store: new MongoStoreRL({
        collectionName: 'UserRateLimit',
        uri: MONGOHQ_URL,
        expireTimeMs: 15 * 60 * 1000
      })
    })
  );

  // TODO(Post-MVP): move this into the app, so that we add this hook once for
  // all auth routes.
  fastify.addHook('onRequest', fastify.redirectIfSignedIn);

  fastify.get('/mobile-login', async req => {
    const email = await getEmailFromAuth0(req);

    await findOrCreateUser(fastify, email);
  });

  done();
};

/**
 * Route handler for authentication routes.
 *
 * @param fastify The Fastify instance.
 * @param _options Options passed to the plugin via `fastify.register(plugin, options)`.
 * @param done Callback to signal that the logic has completed.
 */
export const authRoutes: FastifyPluginCallback = (fastify, _options, done) => {
  // All routes are registered by the auth0 plugin, but we need an extra plugin
  // (this one) to encapsulate the auth0 decorators. Otherwise auth0OAuth will
  // be available globally.
  void fastify.register(auth0Client);
  done();
};


Review comment (suggestion):
This is now being returned for `typeof email !== 'string'`, which is not happening on the old api.

Also, where does `401` come from? I agree it should probably be that, but the old api does `400`.

Code after:
      legacyHeaders: false,
      keyGenerator: req => {
        return (req.headers['x-forwarded-for'] as string) || 'localhost';
      },
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
      store: new MongoStoreRL({
        collectionName: 'UserRateLimit',
        uri: MONGOHQ_URL,
        expireTimeMs: 15 * 60 * 1000
      })
    })
  );

  // TODO(Post-MVP): move this into the app, so that we add this hook once for
  // all auth routes.
  fastify.addHook('onRequest', fastify.redirectIfSignedIn);

  fastify.get('/mobile-login', async (req, reply) => {
    const email = await getEmailFromAuth0(req);

    if (!email) {
      return reply.status(401).send({
        message: 'We could not log you in, please try again in a moment.',
        type: 'danger'
      });
    }
    if (!isEmail(email)) {
      return reply.status(400).send({
        message: 'The email is incorrectly formatted',
        type: 'danger'
      });
    }

    const { id } = await findOrCreateUser(fastify, email);

    reply.setAccessTokenCookie(createAccessToken(id));
  });

  done();
};

/**
 * Route handler for authentication routes.
 *
 * @param fastify The Fastify instance.
 * @param _options Options passed to the plugin via `fastify.register(plugin, options)`.
 * @param done Callback to signal that the logic has completed.
 */
export const authRoutes: FastifyPluginCallback = (fastify, _options, done) => {
  // All routes are registered by the auth0 plugin, but we need an extra plugin
  // (this one) to encapsulate the auth0 decorators. Otherwise auth0OAuth will
Repository: freeCodeCamp/freeCodeCamp
File: client/src/utils/session-storage.ts (TypeScript)

Code before:
};

export const incrementSessionCompletedChallenges = () => {
  const key = 'session-completed-challenges';
  let challengesCompleted = getSessionCompletedChallengesLength();

  // Update the existing value or set it to 1 if it doesn't exist
  challengesCompleted += 1;

  // Convert the number back to a string before saving
  sessionStorage.setItem(key, challengesCompleted.toString());
};

export const setCompletionCountWhenShownProgressModal = () => {
  const key = 'completion-count-progress-modal';
  const challengesCompleted = getSessionCompletedChallengesLength();
  sessionStorage.setItem(key, challengesCompleted.toString());
};

export const getCompletionCountWhenShownProgressModal = () => {
  const key = 'completion-count-progress-modal';
  const storedValue = sessionStorage.getItem(key);
  const challengesCompleted =
    storedValue == null ? null : parseInt(storedValue, 10);
  return challengesCompleted;
};


Review comment (suggestion):
```suggestion
// this key stores the last time the progress modal has been shown
export const setCompletionCountWhenShownProgressModal = () => {
  const key = 'completion-count-progress-modal';
  const challengesCompleted = getSessionCompletedChallengesLength();
  sessionStorage.setItem(key, challengesCompleted.toString());
};

export const getCompletionCountWhenShownProgressModal = () => {
  const key = 'completion-count-progress-modal';
  const storedValue = sessionStorage.getItem(key);
  const challengesCompleted =
    storedValue == null ? null : parseInt(storedValue, 10);
  return challengesCompleted;
};
```

Code after:
const getCurrentCount = () =>
  parseCount(sessionStorage.getItem(CURRENT_COUNT_KEY));

const getSavedCount = () => parseCount(sessionStorage.getItem(SAVED_COUNT_KEY));

export const incrementCurrentCount = () => {
  const newCount = getCurrentCount() + 1;
  sessionStorage.setItem(CURRENT_COUNT_KEY, newCount.toString());
};

/**
 * Saves the current count so we can compare it later.
 */
export const saveCurrentCount = () => {
  sessionStorage.setItem(SAVED_COUNT_KEY, getCurrentCount().toString());
};

const isSaved = () => sessionStorage.getItem(SAVED_COUNT_KEY) !== null;

type AfterSave = {
  isSaved: true;
  currentCount: number;
  countSinceSave: number;
};

type BeforeSave = {
  isSaved: false;
  currentCount: number;
};

type SessionChallengeData = AfterSave | BeforeSave;

export const getSessionChallengeData = (): SessionChallengeData => {
  if (isSaved()) {
    return {
      isSaved: true,
      currentCount: getCurrentCount(),
      countSinceSave: getCurrentCount() - getSavedCount()
    };
  } else {
    return {
      isSaved: false,
      currentCount: getCurrentCount()
    };
  }
};
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:
    correctAnswerCount
  } = useQuiz({
    initialQuestions: initialQuizData,
    validationMessages: {
      correct: t('learn.quiz.correct-answer'),
      incorrect: t('learn.quiz.incorrect-answer')
    }
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
    container.current?.focus();
    // This effect should be run once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    const unansweredQuestions = quizData.reduce(
      (accumulator, currentQuestion, currentIndex) => {
        if (currentQuestion.selectedAnswer == null) {
          accumulator.push(currentIndex + 1);
        }
        return accumulator;
      },
      [] as number[]
    );


Review comment (suggestion):
We don't show the help modal here, so I guess `helpCategory` isn't needed.

But `updateChallengeMeta` takes an `ChallengeMeta` object, which lists `helpCategory` as a required property.

https://github.com/freeCodeCamp/freeCodeCamp/blob/c39208360c78a426102493a4c2007fb95cd859c5/client/src/redux/prop-types.ts#L399

Code after:
      openCompletionModal();
      setIsPassed(true);
    },
    onFailure: () => setIsPassed(false)
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
    container.current?.focus();
    // This effect should be run once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    validateAnswers();
    setHasSubmitted(true);
  };

  const handleSubmitAndGo = () => {
    openCompletionModal();
  };

  function getErrorMessage() {
    if (!hasSubmitted) return '';

    const unansweredList = quizData.reduce<number[]>(
      (acc, curr, id) => (curr.selectedAnswer == null ? [...acc, id + 1] : acc),
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:
    // This effect should be run once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    const unansweredQuestions = quizData.reduce(
      (accumulator, currentQuestion, currentIndex) => {
        if (currentQuestion.selectedAnswer == null) {
          accumulator.push(currentIndex + 1);
        }
        return accumulator;
      },
      [] as number[]
    );

    if (unansweredQuestions.length) {
      setErrorMessage(
        t('learn.quiz.unanswered-questions', {
          unansweredQuestions: unansweredQuestions.join(', ')
        })
      );
      return;
    }

    validateAnswers();
    setHasSubmitted(true);
  };

  const handleSubmitAndGo = () => {
    openCompletionModal();
  };

  // Handle error message display on submit.
  useEffect(() => {

Review comment (suggestion):
When campers click the Check button, the completion modal will show up if they have all questions correct. Campers have a choice to either move on to the next challenge or dismiss the modal and stay on the quiz.

If they decide to close the modal and stay on the page (maybe to review their answers), both the button text and functionality need to be changed:
- The button should say "Submit and go" rather than "Check your answers" because quiz is disabled immediately when campers first click the submit button, hence there isn't anything to check.
- The button shouldn't call the `handleAnswersCheck` function again, but rather, just trigger the completion modal on click.


Code after:

  useEffect(() => {
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    validateAnswers();
    setHasSubmitted(true);
  };

  const handleSubmitAndGo = () => {
    openCompletionModal();
  };

  function getErrorMessage() {
    if (!hasSubmitted) return '';

    const unansweredList = quizData.reduce<number[]>(
      (acc, curr, id) => (curr.selectedAnswer == null ? [...acc, id + 1] : acc),
      []
    );

    if (unansweredList.length > 0) {
      return t('learn.quiz.unanswered-questions', {
        unansweredQuestions: unansweredList.join(', ')
      });
    }

    return t('learn.quiz.have-n-correct-questions', {
      correctAnswerCount,
      total: quiz.length
    });
  }

  const errorMessage = getErrorMessage();

  return (
    <Hotkeys
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:
  // `random` needs to be a ref object so that it won't change between renders.
  const random = useRef(Math.floor(Math.random() * quizzes.length));
  const quiz = quizzes[random.current].questions;

  // Initialize the data passed to `useQuiz`
  const initialQuizData = quiz.map(question => {
    const distractors = question.distractors.map((distractor, index) => {
      return {
        label: (
          <PrismFormatted className='quiz-answer-label' text={distractor} />
        ),
        value: index + 1
      };
    });

    const answer = {
      label: (
        <PrismFormatted className='quiz-answer-label' text={question.answer} />
      ),
      value: 4
    };

    return {
      question: <PrismFormatted text={question.text} />,
      answers: shuffle([...distractors, answer]),
      correctAnswer: answer.value
    };
  });

  const {
    questions: quizData,
    validateAnswers,
    correctAnswerCount
  } = useQuiz({
    initialQuestions: initialQuizData,
    validationMessages: {
      correct: t('learn.quiz.correct-answer'),
      incorrect: t('learn.quiz.incorrect-answer')
    }
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
    container.current?.focus();

Review comment (suggestion):
Tip for reviewers: You can add a `selectedAnswer` property to the object to save some clicks.

```ts
// Pre-select an answer
return {
  // ...
  selectedAnswer: 1
}

// Pre-select the correct answer
return {
  // ...
  selectedAnswer: answer.value
}
```

Code after:
        };
      });

      const answer = {
        label: (
          <PrismFormatted
            className='quiz-answer-label'
            text={question.answer}
          />
        ),
        value: 4
      };

      return {
        question: <PrismFormatted text={question.text} />,
        answers: shuffleArray([...distractors, answer]),
        correctAnswer: answer.value
      };
    })
  );

  const {
    questions: quizData,
    validateAnswers,
    correctAnswerCount
  } = useQuiz({
    initialQuestions: initialQuizData,
    validationMessages: {
      correct: t('learn.quiz.correct-answer'),
      incorrect: t('learn.quiz.incorrect-answer')
    },
    onSuccess: () => {
      openCompletionModal();
      setIsPassed(true);
    },
    onFailure: () => setIsPassed(false)
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
    container.current?.focus();
    // This effect should be run once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:
                <Button
                  block={true}
                  variant='primary'
                  onClick={handleAnswersCheck}
                >
                  {t('buttons.check-answer')}
                </Button>
              ) : (
                <Button
                  block={true}
                  variant='primary'
                  onClick={handleSubmitAndGo}
                >
                  {t('buttons.submit-and-go')}
                </Button>
              )}
              <Spacer size='large' />
            </Col>
            <CompletionModal />
          </Row>
        </Container>
      </LearnLayout>
    </Hotkeys>
  );
};

ShowQuiz.displayName = 'ShowQuiz';

export default connect(mapStateToProps, mapDispatchToProps)(ShowQuiz);

export const query = graphql`
  query QuizChallenge($id: String!) {
    challengeNode(id: { eq: $id }) {
      challenge {
        title
        description
        challengeType
        helpCategory
        superBlock
        block
        fields {
          blockName
          slug
          tests {
            text
            testString
          }
        }
        quizzes {
          questions {
            distractors

Review comment (bug):
This challenge type doesn't have any tests, but the submitter would fail if `tests` is null.

Code after:
      </LearnLayout>
    </Hotkeys>
  );
};

ShowQuiz.displayName = 'ShowQuiz';

export default connect(mapStateToProps, mapDispatchToProps)(ShowQuiz);

export const query = graphql`
  query QuizChallenge($id: String!) {
    challengeNode(id: { eq: $id }) {
      challenge {
        title
        description
        challengeType
        helpCategory
        superBlock
        block
        fields {
          blockName
          slug
          tests {
            text
            testString
          }
        }
        quizzes {
          questions {
            distractors
            text
            answer
          }
        }
        translationPending
      }
    }
  }
`;
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:

  // `random` needs to be a ref object so that it won't change between renders.
  const random = useRef(Math.floor(Math.random() * quizzes.length));
  const quiz = quizzes[random.current].questions;

  // Initialize the data passed to `useQuiz`
  const initialQuizData = quiz.map(question => {
    const distractors = question.distractors.map((distractor, index) => {
      return {
        label: (
          <PrismFormatted className='quiz-answer-label' text={distractor} />
        ),
        value: index + 1
      };
    });

    const answer = {
      label: (
        <PrismFormatted className='quiz-answer-label' text={question.answer} />
      ),
      value: 4
    };

    return {
      question: <PrismFormatted text={question.text} />,
      answers: shuffle([...distractors, answer]),
      correctAnswer: answer.value
    };
  });

  const {
    questions: quizData,
    validateAnswers,
    correctAnswerCount
  } = useQuiz({
    initialQuestions: initialQuizData,
    validationMessages: {
      correct: t('learn.quiz.correct-answer'),
      incorrect: t('learn.quiz.incorrect-answer')
    }
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);

Review comment (suggestion):
We can't shuffle here, since it shuffles each render. It makes the UX a bit trippy.

Instead, we can use the same trick as for random: wrap the whole thing in `useState` so it's randomized, but only once.

Code after:
          value: index + 1
        };
      });

      const answer = {
        label: (
          <PrismFormatted
            className='quiz-answer-label'
            text={question.answer}
          />
        ),
        value: 4
      };

      return {
        question: <PrismFormatted text={question.text} />,
        answers: shuffleArray([...distractors, answer]),
        correctAnswer: answer.value
      };
    })
  );

  const {
    questions: quizData,
    validateAnswers,
    correctAnswerCount
  } = useQuiz({
    initialQuestions: initialQuizData,
    validationMessages: {
      correct: t('learn.quiz.correct-answer'),
      incorrect: t('learn.quiz.incorrect-answer')
    },
    onSuccess: () => {
      openCompletionModal();
      setIsPassed(true);
    },
    onFailure: () => setIsPassed(false)
  });

  useEffect(() => {
    initTests(tests);
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
    container.current?.focus();
    // This effect should be run once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
Repository: freeCodeCamp/freeCodeCamp
File: client/src/templates/Challenges/quiz/show.tsx (TypeScript)

Code before:
  }, []);

  useEffect(() => {
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    const unansweredQuestions = quizData.reduce(
      (accumulator, currentQuestion, currentIndex) => {
        if (currentQuestion.selectedAnswer == null) {
          accumulator.push(currentIndex + 1);
        }
        return accumulator;
      },
      [] as number[]
    );

    if (unansweredQuestions.length) {
      setErrorMessage(
        t('learn.quiz.unanswered-questions', {
          unansweredQuestions: unansweredQuestions.join(', ')
        })
      );
      return;
    }

    validateAnswers();
    setHasSubmitted(true);
  };

  const handleSubmitAndGo = () => {
    openCompletionModal();
  };

  // Handle error message display on submit.
  // We keep the logic in a useEffect hook in order to subscribe to the `correctAnswerCount` change
  // as `correctAnswerCount` is a React state and its value is updated asynchronously,
  useEffect(() => {

Review comment (suggestion):
```suggestion
    const unansweredQuestions = quizData.reduce<number[]>(
      (acc, curr, id) => (curr.selectedAnswer == null ? [...acc, id + 1] : acc),
```
Style nit. Slightly cleaner, imo :man_shrugging: 

Code after:
    updateChallengeMeta({
      ...challengeMeta,
      title,
      challengeType,
      helpCategory
    });
    challengeMounted(challengeMeta.id);
  }, [
    title,
    challengeMeta,
    challengeType,
    helpCategory,
    challengeMounted,
    updateChallengeMeta
  ]);

  const handleAnswersCheck = () => {
    validateAnswers();
    setHasSubmitted(true);
  };

  const handleSubmitAndGo = () => {
    openCompletionModal();
  };

  function getErrorMessage() {
    if (!hasSubmitted) return '';

    const unansweredList = quizData.reduce<number[]>(
      (acc, curr, id) => (curr.selectedAnswer == null ? [...acc, id + 1] : acc),
      []
    );

    if (unansweredList.length > 0) {
      return t('learn.quiz.unanswered-questions', {
        unansweredQuestions: unansweredList.join(', ')
      });
    }

    return t('learn.quiz.have-n-correct-questions', {
      correctAnswerCount,
      total: quiz.length
    });
  }

  const errorMessage = getErrorMessage();

  return (
    <Hotkeys
      executeChallenge={!isPassed ? handleAnswersCheck : handleSubmitAndGo}
      containerRef={container}