diff --git a/src/components/CompletionStatus.spec.tsx b/src/components/CompletionStatus.spec.tsx index c10be2e..e716322 100644 --- a/src/components/CompletionStatus.spec.tsx +++ b/src/components/CompletionStatus.spec.tsx @@ -9,7 +9,8 @@ describe('CompletionStatus', () => { props = { numberOfQuestions: 15, numberCompleted: 0, - handleClick: () => {console.log('click')} + handleNext: () => {console.log('next')}, + handleContinue: () => {console.log('continue')} } }); @@ -37,7 +38,7 @@ describe('CompletionStatus', () => { it('clicking triggers handler', () => { const mockEv = jest.fn(); const component = renderer.create( - + ); renderer.act(() => { diff --git a/src/components/CompletionStatus.stories.tsx b/src/components/CompletionStatus.stories.tsx index ae9fbc5..9de3bab 100644 --- a/src/components/CompletionStatus.stories.tsx +++ b/src/components/CompletionStatus.stories.tsx @@ -3,10 +3,24 @@ import { CompletionStatus, CompletionStatusProps } from './CompletionStatus'; const props: CompletionStatusProps = { numberOfQuestions: 15, numberCompleted: 0, - handleClick: () => {console.log('click')} + handleNext: () => {console.log('next')}, + handleContinue: () => {console.log('continue')}, }; +const unlimitedProps: CompletionStatusProps = { + numberOfQuestions: 15, + numberCompleted: 0, + handleNext: () => {console.log('next')}, + handleContinue: () => {console.log('continue')}, + handleRetry: () => {console.log('retry')}, + scoreSoFar: '5/15', + savedScore: '10/15' +} export const Default = () => ; export const PartialComplete = () => export const Complete = () => +export const unlimitedComplete = () => ; +export const unlimitedPartial = () => +export const unlimitedCompletedNoScore = () => ; +export const unlimitedPartialNoProgress = () => ; diff --git a/src/components/CompletionStatus.tsx b/src/components/CompletionStatus.tsx index 8e9cb50..4349d92 100644 --- a/src/components/CompletionStatus.tsx +++ b/src/components/CompletionStatus.tsx @@ -1,7 +1,7 @@ import styled, { createGlobalStyle } from "styled-components"; import { InnerStepCard } from "./Card"; import Button from "./Button"; - +import { colors } from '../theme'; const GlobalStyle = createGlobalStyle` :root { --content-text-scale: 1; @@ -11,8 +11,12 @@ const GlobalStyle = createGlobalStyle` export interface CompletionStatusProps { numberOfQuestions: number; numberCompleted: number; - handleClick: () => void; + handleContinue: () => void; + handleNext: () => void; className?: string; + scoreSoFar?: string; + savedScore?: string; + handleRetry?: () => void; } const CompletionStatusCard = styled(InnerStepCard)` @@ -22,7 +26,6 @@ const CompletionStatusCard = styled(InnerStepCard)` display: block; button { - min-width: 160px; height: 48px; } @@ -36,24 +39,121 @@ const CompletionHeader = styled.h2` margin: 0; `; +const ButtonGroup = styled.div` + display: flex; + margin: 0; + gap: 1rem; + + button { + height: 48px; + } +`; + +const ScoreGroup = styled.div` + display: flex; + margin: 0; + gap: 1rem; +`; + +const RetryResumeButton = styled(Button)` + background-color: ${colors.palette.white}; + color: ${colors.palette.black}; + border: 1px solid ${colors.palette.pale}; + font-weight: normal; + transition: background-color 0.2s ease; + + &:hover { + background-color: ${colors.palette.neutralBright} !important; + color: ${colors.palette.black} !important; + border: 1px solid ${colors.palette.pale} !important; + } + + &:active { + background-color: ${colors.palette.neutralLight} !important; + color: ${colors.palette.black} !important; + border: 1px solid ${colors.palette.pale} !important; + } +`; + export const CompletionStatus = styled(({ - numberOfQuestions, numberCompleted, handleClick, className + numberOfQuestions, + numberCompleted, + handleContinue, + handleNext, + className, + scoreSoFar, + savedScore, + handleRetry }: CompletionStatusProps) => { - const allCompleted = numberOfQuestions === numberCompleted; const someCompleted = numberCompleted > 0; - const buttonText = allCompleted ? 'Next' : ( - someCompleted ? 'Continue' : 'Start' - ); - return <> - - - {allCompleted ? 'You are done.' : (someCompleted ? 'Quiz is partially complete.' : 'No questions have been answered.')} -

{allCompleted ? 'Great job answering all the questions.' : (someCompleted ? `You've completed ${numberCompleted} of ${numberOfQuestions} questions.` : 'Begin working on the quiz.')}

- -
- + const buttonText = allCompleted || (numberCompleted === 0 && handleRetry) || (someCompleted && handleRetry) ? 'Next' : (someCompleted ? 'Continue' : 'Start'); + + const retryOrResume = allCompleted ? 'Retry Quiz' : 'Resume Quiz'; + const unlimitedDone = "Attempts for this quiz are unlimited. Your highest score will be saved."; + const unlimitedCurrent = "You are in the middle of a quiz attempt. Attempts for this quiz are unlimited. Your highest score will be saved."; + + // When allCompleted, clicking Retry/Resume should create a new attempt (handleRetry) + // When not completed, clicking Retry/Resume should resume (handleContinue) + const onRetryResumeClick = allCompleted + ? handleRetry + : handleContinue; + + // if unlimited attempts (handleRetry) is active always show next button + // if all is completed show next button + // if not unlimited and incomplete show and handle continue + const onNextContinueClick = allCompleted || handleRetry + ? handleNext + : handleContinue; + + return ( + <> + + + + {allCompleted + ? 'You are done.' + : (someCompleted ? 'Quiz is partially complete.' : 'No questions have been answered.')} + + + {handleRetry ? ( +
+

{allCompleted ? unlimitedDone : unlimitedCurrent}

+ +

+ Current Score: {scoreSoFar ?? 'Score unavailable'} | Saved Score: {savedScore ?? 'Score unavailable'} +

+
+
+ ) : ( +

+ {allCompleted + ? 'Great job answering all the questions.' + : (someCompleted + ? `You've completed ${numberCompleted} of ${numberOfQuestions} questions.` + : 'Begin working on the quiz.')} +

+ )} + + + {handleRetry ? ( + + {retryOrResume} + + ) : null} + + + +
+ + ); })``; diff --git a/src/components/Exercise.spec.tsx b/src/components/Exercise.spec.tsx index f4e864c..0f5cbe6 100644 --- a/src/components/Exercise.spec.tsx +++ b/src/components/Exercise.spec.tsx @@ -45,6 +45,7 @@ describe('Exercise', () => { }, questionNumber: 1, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, @@ -142,6 +143,7 @@ describe('Exercise', () => { numberOfQuestions: 1, scrollToQuestion: 1, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, hasFeedback: true, onAnswerChange: () => null, onAnswerSave: () => null, @@ -343,6 +345,7 @@ describe('Exercise', () => { }, questionNumber: 1, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, diff --git a/src/components/Exercise.stories.tsx b/src/components/Exercise.stories.tsx index 738f3a3..e8fbda2 100644 --- a/src/components/Exercise.stories.tsx +++ b/src/components/Exercise.stories.tsx @@ -51,6 +51,7 @@ const exerciseWithStepDataProps: ExerciseWithStepDataProps = { }, questionNumber: 1, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, @@ -116,6 +117,7 @@ const exerciseWithQuestionStatesProps = (uid?: string, correctness?: string): Ex questionNumber: 1, numberOfQuestions: 1, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, @@ -456,6 +458,7 @@ export const MultiPartHalfComplete = () => { questionNumber: 1, numberOfQuestions: 2, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, diff --git a/src/components/Exercise/index.tsx b/src/components/Exercise/index.tsx index d1c1dcd..c9c03e9 100644 --- a/src/components/Exercise/index.tsx +++ b/src/components/Exercise/index.tsx @@ -129,6 +129,8 @@ export interface ExerciseBaseProps { /** A boolean that enables showing the amount of attempts remaining. */ hasMultipleAttempts: boolean; /** A callback with the question_id when the Submit/Re-submit button is clicked. */ + hasUnlimitedAttempts: boolean; + /** A boolean that enables labeling for unlimited attempts. */ onAnswerSave: (question_id: number) => void; /** A callback with the current question index when the Next/Continue button is clicked. */ onNextStep: (currentIndex: number) => void; diff --git a/src/components/ExercisePreview.tsx b/src/components/ExercisePreview.tsx index 980b672..80299bf 100644 --- a/src/components/ExercisePreview.tsx +++ b/src/components/ExercisePreview.tsx @@ -89,6 +89,7 @@ export const ExercisePreview = ({ canAnswer: true, needsSaved: true, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => undefined, onAnswerSave: () => undefined, onNextStep: () => undefined, diff --git a/src/components/ExerciseQuestion.spec.tsx b/src/components/ExerciseQuestion.spec.tsx index 0910149..80bec54 100644 --- a/src/components/ExerciseQuestion.spec.tsx +++ b/src/components/ExerciseQuestion.spec.tsx @@ -30,6 +30,7 @@ describe('ExerciseQuestion', () => { questionNumber: 1, choicesEnabled: false, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, diff --git a/src/components/ExerciseQuestion.stories.tsx b/src/components/ExerciseQuestion.stories.tsx index 0a92d2e..37bed52 100644 --- a/src/components/ExerciseQuestion.stories.tsx +++ b/src/components/ExerciseQuestion.stories.tsx @@ -37,6 +37,7 @@ const props = { onChange: () => null, choicesEnabled: true, hasMultipleAttempts: false, + hasUnlimitedAttempts: false, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, @@ -63,12 +64,14 @@ export const FreeResponseEntered = () => export const MultipleAttemptsAllLeft = () => ; export const MultipleAttemptsOneLeft = () => attempt_number={1} incorrectAnswerId='2' />; -export const MultipleAttemptsNoneLeft = () => + +export const MultipleAttemptsOneLeftAndUnlimited = () => + ; + +export const MultipleAttemptsNoneLeftAndUnlimited = () => ; + +export const UnlimitedAttemptsOnly = () => + void; onAnswerSave: ExerciseBaseProps['onAnswerSave']; onNextStep: ExerciseBaseProps['onNextStep']; @@ -48,6 +49,12 @@ const AttemptsRemaining = ({ count }: { count: number }) => { ); } +const UnlimitedAttempts = () => { + return ( +
Unlimited quiz attempts left
+ ); +} + const PublishedComments = ({ published_comments }: { published_comments?: string }) => { if (!published_comments) { return null; } @@ -96,7 +103,7 @@ export const ExerciseQuestion = React.forwardRef((props: ExerciseQuestionProps, const { question, task, answer_id_order, onAnswerChange, feedback_html, correct_answer_feedback_html, is_completed, correct_answer_id, incorrectAnswerId, choicesEnabled, questionNumber, - answer_id, hasMultipleAttempts, attempts_remaining, published_comments, detailedSolution, + answer_id, hasMultipleAttempts, hasUnlimitedAttempts, attempts_remaining, published_comments, detailedSolution, canAnswer, needsSaved, attempt_number, apiIsPending, onAnswerSave, onNextStep, canUpdateCurrentStep, displaySolution, available_points, free_response, labelAnswers, show_all_feedback, tableFeedbackEnabled, hasFeedback, previewMode @@ -145,7 +152,10 @@ export const ExerciseQuestion = React.forwardRef((props: ExerciseQuestionProps, {hasMultipleAttempts && attempts_remaining > 0 && } + { hasUnlimitedAttempts ? : null} + + {detailedSolution && (
Detailed solution:
)} diff --git a/src/components/Print.stories.tsx b/src/components/Print.stories.tsx index 280dddd..2e03f24 100644 --- a/src/components/Print.stories.tsx +++ b/src/components/Print.stories.tsx @@ -84,6 +84,7 @@ export const Default = () => ( canAnswer={true} needsSaved={true} hasMultipleAttempts={false} + hasUnlimitedAttempts={false} onAnswerChange={() => undefined} onAnswerSave={() => undefined} onNextStep={() => undefined} diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index b05f344..3aa2b5e 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -33,7 +33,7 @@ const handleVariant = (variant: ProgressBarItemVariant) => { switch (variant) { case 'isStatus': return css` - background-color: ${colors.palette.neutralBright}; + background-color: ${colors.palette.softOrange}; `; case 'isCorrect': return css` diff --git a/src/components/__snapshots__/CompletionStatus.spec.tsx.snap b/src/components/__snapshots__/CompletionStatus.spec.tsx.snap index f3a8f37..1991f24 100644 --- a/src/components/__snapshots__/CompletionStatus.spec.tsx.snap +++ b/src/components/__snapshots__/CompletionStatus.spec.tsx.snap @@ -2,7 +2,7 @@ exports[`CompletionStatus matches snapshot 1`] = `

Begin working on the quiz.

- + +

`; exports[`CompletionStatus matches snapshot with all questions completed 1`] = `

Great job answering all the questions.

- + +

`; exports[`CompletionStatus matches snapshot with some questions completed 1`] = `

You've completed 5 of 15 questions.

- + +

`; diff --git a/src/components/__snapshots__/ProgressBar.spec.tsx.snap b/src/components/__snapshots__/ProgressBar.spec.tsx.snap index 7a0747e..fe9c57a 100644 --- a/src/components/__snapshots__/ProgressBar.spec.tsx.snap +++ b/src/components/__snapshots__/ProgressBar.spec.tsx.snap @@ -227,7 +227,7 @@ exports[`ProgressBar matches snapshot 1`] = `