diff --git a/src/data/index.js b/src/data/index.js index 5e2400f2..27ef04a1 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -13,6 +13,7 @@ export { getExamReviewPolicy, pingAttempt, resetExam, + getAllowProctoringOptOut, } from './thunks'; export { default as store } from './store'; diff --git a/src/data/slice.js b/src/data/slice.js index 6866fba8..9b94c842 100644 --- a/src/data/slice.js +++ b/src/data/slice.js @@ -7,12 +7,16 @@ export const examSlice = createSlice({ isLoading: true, timeIsOver: false, activeAttempt: null, + allowProctoringOptOut: false, proctoringSettings: {}, exam: {}, verification: {}, apiErrorMsg: '', }, reducers: { + setAllowProctoringOptOut: (state, { payload }) => { + state.allowProctoringOptOut = payload.allowProctoringOptOut; + }, setIsLoading: (state, { payload }) => { state.isLoading = payload.isLoading; }, @@ -46,7 +50,7 @@ export const examSlice = createSlice({ export const { setIsLoading, setExamState, getExamId, expireExamAttempt, setActiveAttempt, setProctoringSettings, setVerificationData, - setReviewPolicy, setApiError, + setReviewPolicy, setApiError, setAllowProctoringOptOut, } = examSlice.actions; export default examSlice.reducer; diff --git a/src/data/thunks.js b/src/data/thunks.js index 960f99c8..5ba37e86 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -22,6 +22,7 @@ import { setVerificationData, setReviewPolicy, setApiError, + setAllowProctoringOptOut, } from './slice'; import { ExamStatus } from '../constants'; import { workerPromiseForEventNames, pingApplication } from './messages/handlers'; @@ -359,3 +360,9 @@ export function getExamReviewPolicy() { dispatch(setReviewPolicy({ policy: data.review_policy })); }; } + +export function getAllowProctoringOptOut(allowProctoringOptOut) { + return (dispatch) => { + dispatch(setAllowProctoringOptOut({ allowProctoringOptOut })); + }; +} diff --git a/src/exam/ExamWrapper.jsx b/src/exam/ExamWrapper.jsx index ee9dd217..45c7c2c4 100644 --- a/src/exam/ExamWrapper.jsx +++ b/src/exam/ExamWrapper.jsx @@ -12,8 +12,8 @@ const ExamWrapper = ({ children, ...props }) => { const loadInitialData = async () => { await state.getExamAttemptsData(courseId, sequence.id); + await state.getAllowProctoringOptOut(sequence.allowProctoringOptOut); state.getProctoringSettings(); - state.getVerificationData(); }; useEffect(() => { @@ -31,6 +31,7 @@ ExamWrapper.propTypes = { sequence: PropTypes.shape({ id: PropTypes.string.isRequired, isTimeLimited: PropTypes.bool, + allowProctoringOptOut: PropTypes.bool, }).isRequired, courseId: PropTypes.string.isRequired, children: PropTypes.element.isRequired, diff --git a/src/instructions/EntranceInstructions.jsx b/src/instructions/EntranceInstructions.jsx index 4bbe635a..e545d092 100644 --- a/src/instructions/EntranceInstructions.jsx +++ b/src/instructions/EntranceInstructions.jsx @@ -4,7 +4,7 @@ import { Container } from '@edx/paragon'; import { ExamType } from '../constants'; import { EntranceProctoredExamInstructions } from './proctored_exam'; import { EntranceOnboardingExamInstructions } from './onboarding_exam'; -import EntrancePracticeExamInstructions from './practice_exam'; +import { EntrancePracticeExamInstructions } from './practice_exam'; import { StartTimedExamInstructions, TimedExamFooter } from './timed_exam'; import Footer from './proctored_exam/Footer'; diff --git a/src/instructions/ErrorInstructions.jsx b/src/instructions/ErrorInstructions.jsx new file mode 100644 index 00000000..2aadb35d --- /dev/null +++ b/src/instructions/ErrorInstructions.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Container } from '@edx/paragon'; +import { ExamType } from '../constants'; +import { ErrorPracticeExamInstructions } from './practice_exam'; +import { ErrorOnboardingExamInstructions } from './onboarding_exam'; +import { ErrorProctoredExamInstructions } from './proctored_exam'; +import Footer from './proctored_exam/Footer'; + +const ErrorExamInstructions = ({ examType }) => { + const renderInstructions = () => { + switch (examType) { + case ExamType.ONBOARDING: + return ; + case ExamType.PRACTICE: + return ; + case ExamType.PROCTORED: + return ; + default: + return null; + } + }; + + return ( +
+ + {renderInstructions()} + + {examType !== ExamType.TIMED &&
} +
+ ); +}; + +ErrorExamInstructions.propTypes = { + examType: PropTypes.string.isRequired, +}; + +export default ErrorExamInstructions; diff --git a/src/instructions/Instructions.test.jsx b/src/instructions/Instructions.test.jsx index 14c2b1d4..6be720c1 100644 --- a/src/instructions/Instructions.test.jsx +++ b/src/instructions/Instructions.test.jsx @@ -55,14 +55,12 @@ describe('SequenceExamWrapper', () => { status: 'none', can_verify: true, }, - activeAttempt: { - attempt_status: 'started', - }, + activeAttempt: {}, exam: { time_limit_mins: 30, type: ExamType.PROCTORED, attempt: { - attempt_status: 'started', + attempt_status: ExamStatus.STARTED, }, }, }, @@ -163,12 +161,12 @@ describe('SequenceExamWrapper', () => { examState: { isLoading: false, timeIsOver: true, + allowProctoringOptOut: true, proctoringSettings: { platform_name: 'Your Platform', }, activeAttempt: {}, exam: { - allow_proctoring_opt_out: true, is_proctored: true, time_limit_mins: 30, attempt: {}, @@ -261,13 +259,12 @@ describe('SequenceExamWrapper', () => { status: 'none', can_verify: true, }, - activeAttempt: { - attempt_status: 'error', - }, + activeAttempt: {}, exam: { + type: ExamType.PROCTORED, time_limit_mins: 30, attempt: { - attempt_status: 'error', + attempt_status: ExamStatus.ERROR, }, }, }, @@ -296,14 +293,12 @@ describe('SequenceExamWrapper', () => { status: 'none', can_verify: true, }, - activeAttempt: { - attempt_status: 'ready_to_resume', - }, + activeAttempt: {}, exam: { - type: 'proctored', + type: ExamType.PROCTORED, time_limit_mins: 30, attempt: { - attempt_status: 'ready_to_resume', + attempt_status: ExamStatus.READY_TO_RESUME, }, }, }, @@ -331,14 +326,12 @@ describe('SequenceExamWrapper', () => { can_verify: true, }, proctoringSettings: {}, - activeAttempt: { - attempt_status: 'ready_to_submit', - }, + activeAttempt: {}, exam: { - type: 'timed', + type: ExamType.TIMED, time_limit_mins: 30, attempt: { - attempt_status: 'ready_to_submit', + attempt_status: ExamStatus.READY_TO_SUBMIT, }, }, }, @@ -365,13 +358,12 @@ describe('SequenceExamWrapper', () => { can_verify: true, }, proctoringSettings: {}, - activeAttempt: { - attempt_status: 'submitted', - }, + activeAttempt: {}, exam: { + type: ExamType.TIMED, time_limit_mins: 30, attempt: { - attempt_status: 'submitted', + attempt_status: ExamStatus.SUBMITTED, }, }, }, @@ -398,13 +390,12 @@ describe('SequenceExamWrapper', () => { can_verify: true, }, proctoringSettings: {}, - activeAttempt: { - attempt_status: 'submitted', - }, + activeAttempt: {}, exam: { + type: ExamType.TIMED, time_limit_mins: 30, attempt: { - attempt_status: 'submitted', + attempt_status: ExamStatus.SUBMITTED, }, }, }, @@ -496,4 +487,189 @@ describe('SequenceExamWrapper', () => { expect(getByTestId('submit-onboarding-exam')).toBeInTheDocument(); }); + + it('Shows error onboarding exam instructions if exam is onboarding and attempt status is error', () => { + store.getState = () => ({ + examState: { + isLoading: false, + timeIsOver: false, + proctoringSettings: { + platform_name: 'Your Platform', + }, + activeAttempt: {}, + exam: { + is_proctored: true, + type: ExamType.ONBOARDING, + time_limit_mins: 30, + attempt: { + attempt_status: ExamStatus.ERROR, + }, + prerequisite_status: {}, + }, + verification: {}, + }, + }); + + render( + + +
Sequence
+
+
, + { store }, + ); + + expect(screen.getByText('Error: There was a problem with your onboarding session')).toBeInTheDocument(); + expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam'); + }); + + it('Shows submitted onboarding exam instructions if exam is onboarding and attempt status is submitted', () => { + store.getState = () => ({ + examState: { + isLoading: false, + timeIsOver: false, + proctoringSettings: { + platform_name: 'Your Platform', + integration_specific_email: 'test@example.com', + learner_notification_from_email: 'test_notification@example.com', + }, + activeAttempt: {}, + exam: { + is_proctored: true, + type: ExamType.ONBOARDING, + time_limit_mins: 30, + attempt: { + attempt_status: ExamStatus.SUBMITTED, + }, + prerequisite_status: {}, + }, + verification: {}, + }, + }); + + render( + + +
Sequence
+
+
, + { store }, + ); + + const retryExamButton = screen.getByTestId('retry-exam-button'); + expect(retryExamButton).toHaveTextContent('Retry my exam'); + expect(screen.getByText('You have submitted this onboarding exam')).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'test@example.com' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'test_notification@example.com' })).toBeInTheDocument(); + + expect(retryExamButton).toBeDisabled(); + fireEvent.click(screen.getByRole('button', { name: 'I understand and want to reset this onboarding exam.' })); + expect(retryExamButton).not.toBeDisabled(); + }); + + it('Shows verified onboarding exam instructions if exam is onboarding and attempt status is verified', () => { + store.getState = () => ({ + examState: { + isLoading: false, + timeIsOver: false, + proctoringSettings: { + platform_name: 'Your Platform', + integration_specific_email: 'test@example.com', + }, + activeAttempt: {}, + exam: { + is_proctored: true, + type: ExamType.ONBOARDING, + time_limit_mins: 30, + attempt: { + attempt_status: ExamStatus.VERIFIED, + }, + prerequisite_status: {}, + }, + verification: {}, + }, + }); + + render( + + +
Sequence
+
+
, + { store }, + ); + + expect(screen.getByText('Your onboarding profile was reviewed successfully')).toBeInTheDocument(); + expect(screen.getByRole('link')).toHaveTextContent('test@example.com'); + }); + + it('Shows error practice exam instructions if exam is onboarding and attempt status is error', () => { + store.getState = () => ({ + examState: { + isLoading: false, + timeIsOver: false, + proctoringSettings: { + platform_name: 'Your Platform', + }, + activeAttempt: {}, + exam: { + is_proctored: true, + type: ExamType.PRACTICE, + time_limit_mins: 30, + attempt: { + attempt_status: ExamStatus.ERROR, + }, + prerequisite_status: {}, + }, + verification: {}, + }, + }); + + render( + + +
Sequence
+
+
, + { store }, + ); + + expect(screen.getByText('There was a problem with your practice proctoring session')).toBeInTheDocument(); + expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam'); + }); + + it('Shows submitted practice exam instructions if exam is onboarding and attempt status is submitted', () => { + store.getState = () => ({ + examState: { + isLoading: false, + timeIsOver: false, + proctoringSettings: { + platform_name: 'Your Platform', + }, + activeAttempt: {}, + exam: { + is_proctored: true, + type: ExamType.PRACTICE, + time_limit_mins: 30, + attempt: { + attempt_status: ExamStatus.SUBMITTED, + }, + prerequisite_status: {}, + }, + verification: {}, + }, + }); + + render( + + +
Sequence
+
+
, + { store }, + ); + + expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); + expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam'); + }); }); diff --git a/src/instructions/SubmittedInstructions.jsx b/src/instructions/SubmittedInstructions.jsx new file mode 100644 index 00000000..08579723 --- /dev/null +++ b/src/instructions/SubmittedInstructions.jsx @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Container } from '@edx/paragon'; +import { ExamType } from '../constants'; +import { SubmittedPracticeExamInstructions } from './practice_exam'; +import { SubmittedProctoredExamInstructions } from './proctored_exam'; +import { SubmittedOnboardingExamInstructions } from './onboarding_exam'; +import { SubmittedTimedExamInstructions } from './timed_exam'; +import Footer from './proctored_exam/Footer'; + +const SubmittedExamInstructions = ({ examType }) => { + const renderInstructions = () => { + switch (examType) { + case ExamType.ONBOARDING: + return ; + case ExamType.PRACTICE: + return ; + case ExamType.PROCTORED: + return ; + case ExamType.TIMED: + return ; + default: + return null; + } + }; + + return ( +
+ + {renderInstructions()} + + {examType !== ExamType.TIMED &&
} +
+ ); +}; + +SubmittedExamInstructions.propTypes = { + examType: PropTypes.string.isRequired, +}; + +export default SubmittedExamInstructions; diff --git a/src/instructions/VerifiedInstructions.jsx b/src/instructions/VerifiedInstructions.jsx new file mode 100644 index 00000000..7d9a2705 --- /dev/null +++ b/src/instructions/VerifiedInstructions.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Container } from '@edx/paragon'; +import { ExamType } from '../constants'; +import { VerifiedOnboardingExamInstructions } from './onboarding_exam'; +import { VerifiedProctoredExamInstructions } from './proctored_exam'; +import Footer from './proctored_exam/Footer'; + +const VerifiedExamInstructions = ({ examType }) => { + const renderInstructions = () => { + switch (examType) { + case ExamType.ONBOARDING: + return ; + case ExamType.PROCTORED: + return ; + default: + return null; + } + }; + + return ( +
+ + {renderInstructions()} + + {examType !== ExamType.TIMED &&
} +
+ ); +}; + +VerifiedExamInstructions.propTypes = { + examType: PropTypes.string.isRequired, +}; + +export default VerifiedExamInstructions; diff --git a/src/instructions/index.jsx b/src/instructions/index.jsx index 7b42979d..107e03fa 100644 --- a/src/instructions/index.jsx +++ b/src/instructions/index.jsx @@ -1,11 +1,7 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import SubmittedExamInstructions from './SubmittedExamInstructions'; import { - ErrorProctoredExamInstructions, VerificationProctoredExamInstructions, - SubmittedProctoredExamInstructions, - VerifiedProctoredExamInstructions, DownloadSoftwareProctoredExamInstructions, ReadyToStartProctoredExamInstructions, PrerequisitesProctoredExamInstructions, @@ -17,10 +13,13 @@ import ExamStateContext from '../context'; import EntranceExamInstructions from './EntranceInstructions'; import SubmitExamInstructions from './SubmitInstructions'; import RejectedInstructions from './RejectedInstructions'; +import ErrorExamInstructions from './ErrorInstructions'; +import SubmittedExamInstructions from './SubmittedInstructions'; +import VerifiedExamInstructions from './VerifiedInstructions'; const Instructions = ({ children }) => { const state = useContext(ExamStateContext); - const { exam, verification } = state; + const { exam, verification, getVerificationData } = state; const { attempt, type: examType, prerequisite_status: prerequisitesData } = exam || {}; const prerequisitesPassed = prerequisitesData ? prerequisitesData.are_prerequisites_satisifed : true; let verificationStatus = verification.status || ''; @@ -28,6 +27,12 @@ const Instructions = ({ children }) => { const [skipProctoring, toggleSkipProctoring] = useState(false); const toggleSkipProctoredExam = () => toggleSkipProctoring(!skipProctoring); + useEffect(() => { + if (examType === ExamType.PROCTORED) { + getVerificationData(); + } + }, []); + // The API does not explicitly return 'expired' status, so we have to check manually. // expires attribute is returned only for approved status, so it is safe to do this // (meaning we won't override 'must_reverify' status for example) @@ -47,9 +52,9 @@ const Instructions = ({ children }) => { : : ; case attempt.attempt_status === ExamStatus.CREATED: - return verificationStatus === VerificationStatus.APPROVED - ? - : ; + return examType === ExamType.PROCTORED && verificationStatus !== VerificationStatus.APPROVED + ? + : ; case attempt.attempt_status === ExamStatus.DOWNLOAD_SOFTWARE_CLICKED: return ; case attempt.attempt_status === ExamStatus.READY_TO_START: @@ -57,15 +62,13 @@ const Instructions = ({ children }) => { case attempt.attempt_status === ExamStatus.READY_TO_SUBMIT: return ; case attempt.attempt_status === ExamStatus.SUBMITTED: - return examType === ExamType.PROCTORED - ? - : ; + return ; case attempt.attempt_status === ExamStatus.VERIFIED: - return ; + return ; case attempt.attempt_status === ExamStatus.REJECTED: return ; case attempt.attempt_status === ExamStatus.ERROR: - return ; + return ; case attempt.attempt_status === ExamStatus.READY_TO_RESUME: return ; default: diff --git a/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx new file mode 100644 index 00000000..d6f264b2 --- /dev/null +++ b/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Button } from '@edx/paragon'; +import ExamStateContext from '../../context'; + +const ErrorOnboardingExamInstructions = () => { + const state = useContext(ExamStateContext); + const { resetExam } = state; + + return ( +
+

+ +

+

+ +

+ +
+ ); +}; + +export default ErrorOnboardingExamInstructions; diff --git a/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx new file mode 100644 index 00000000..506a55d7 --- /dev/null +++ b/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx @@ -0,0 +1,98 @@ +import React, { useContext } from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Button, MailtoLink, useToggle } from '@edx/paragon'; +import ExamStateContext from '../../context'; + +const SubmittedOnboardingExamInstructions = () => { + const [isConfirm, confirm] = useToggle(false); + const state = useContext(ExamStateContext); + const { proctoringSettings, resetExam } = state; + const { + learner_notification_from_email: learnerNotificationFromEmail, + integration_specific_email: integrationSpecificEmail, + } = proctoringSettings || {}; + + return ( +
+

+ +

+

+ +

+

+ + + {learnerNotificationFromEmail} + + +

+

+ +

+

+ +

+

+ +

+ +

+ + + {integrationSpecificEmail} + + +

+
+ ); +}; + +export default SubmittedOnboardingExamInstructions; diff --git a/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx new file mode 100644 index 00000000..2f26ba97 --- /dev/null +++ b/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx @@ -0,0 +1,44 @@ +import React, { useContext } from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { MailtoLink } from '@edx/paragon'; +import ExamStateContext from '../../context'; + +const VerifiedOnboardingExamInstructions = () => { + const state = useContext(ExamStateContext); + const { + integration_specific_email: integrationSpecificEmail, + } = state.proctoringSettings || {}; + + return ( +
+

+ +

+

+ +

+

+ + + {integrationSpecificEmail} + + +

+
+ ); +}; + +export default VerifiedOnboardingExamInstructions; diff --git a/src/instructions/onboarding_exam/index.js b/src/instructions/onboarding_exam/index.js index 47865c94..971f4280 100644 --- a/src/instructions/onboarding_exam/index.js +++ b/src/instructions/onboarding_exam/index.js @@ -1,2 +1,5 @@ export { default as EntranceOnboardingExamInstructions } from './EntranceOnboardingExamInstructions'; export { default as RejectedOnboardingExamInstructions } from './RejectedOnboardingExamInstructions'; +export { default as ErrorOnboardingExamInstructions } from './ErrorOnboardingExamInstructions'; +export { default as SubmittedOnboardingExamInstructions } from './SubmittedOnboardingExamInstructions'; +export { default as VerifiedOnboardingExamInstructions } from './VerifiedOnboardingExamInstructions'; diff --git a/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx b/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx new file mode 100644 index 00000000..1b3de41f --- /dev/null +++ b/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx @@ -0,0 +1,51 @@ +import React, { useContext } from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Button } from '@edx/paragon'; +import ExamStateContext from '../../context'; + +const ErrorPracticeExamInstructions = () => { + const state = useContext(ExamStateContext); + const { resetExam } = state; + + return ( +
+

+ +

+

+ + + + +

+

+ +

+ +
+ ); +}; + +export default ErrorPracticeExamInstructions; diff --git a/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx b/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx new file mode 100644 index 00000000..5ab89fc0 --- /dev/null +++ b/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Button } from '@edx/paragon'; +import ExamStateContext from '../../context'; + +const SubmittedPracticeExamInstructions = () => { + const state = useContext(ExamStateContext); + const { resetExam } = state; + + return ( +
+

+ +

+

+ +

+ +
+ ); +}; + +export default SubmittedPracticeExamInstructions; diff --git a/src/instructions/practice_exam/index.js b/src/instructions/practice_exam/index.js index 51b782d7..a7d57f0a 100644 --- a/src/instructions/practice_exam/index.js +++ b/src/instructions/practice_exam/index.js @@ -1 +1,3 @@ -export { default } from './EntrancePracticeExamInstructions'; +export { default as EntrancePracticeExamInstructions } from './EntrancePracticeExamInstructions'; +export { default as ErrorPracticeExamInstructions } from './ErrorPracticeExamInstructions'; +export { default as SubmittedPracticeExamInstructions } from './SubmittedPracticeExamInstructions'; diff --git a/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx b/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx index 1ad69ac2..fe9080f9 100644 --- a/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx +++ b/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx @@ -8,8 +8,8 @@ import SkipProctoredExamButton from './SkipProctoredExamButton'; const EntranceProctoredExamInstructions = ({ skipProctoredExam }) => { const state = useContext(ExamStateContext); - const { exam, createProctoredExamAttempt } = state; - const { attempt, allow_proctoring_opt_out: allowProctoringOptOut } = exam || {}; + const { exam, createProctoredExamAttempt, allowProctoringOptOut } = state; + const { attempt } = exam || {}; const { total_time: totalTime = 0 } = attempt; return ( diff --git a/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx b/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx index 32aa4d0b..a108f073 100644 --- a/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx +++ b/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx @@ -1,8 +1,7 @@ import React, { useContext } from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container, Hyperlink, MailtoLink } from '@edx/paragon'; +import { Hyperlink, MailtoLink } from '@edx/paragon'; import ExamStateContext from '../../context'; -import Footer from './Footer'; const ErrorProctoredExamInstructions = () => { const state = useContext(ExamStateContext); @@ -39,18 +38,15 @@ const ErrorProctoredExamInstructions = () => { return (
- -
- -
-

- {renderBody()} -

-
-
+
+ +
+

+ {renderBody()} +

); }; diff --git a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx index 5dbeb890..acb0da60 100644 --- a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx +++ b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx @@ -21,6 +21,7 @@ describe('SequenceExamWrapper', () => { examState: { isLoading: false, activeAttempt: null, + allowProctoringOptOut: true, proctoringSettings: { platform_name: 'Your Platform', }, @@ -30,7 +31,6 @@ describe('SequenceExamWrapper', () => { }, exam: { type: ExamType.PROCTORED, - allow_proctoring_opt_out: true, is_proctored: true, time_limit_mins: 30, attempt: {}, diff --git a/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx b/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx index 6e56bc45..b75b3693 100644 --- a/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx +++ b/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx @@ -1,51 +1,46 @@ import React from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container } from '@edx/paragon'; -import Footer from './Footer'; const SubmittedProctoredExamInstructions = () => (
- -

+

+ +

+
    +
  • - -
      -
    • - -
        -
      • - -
      • -
      -
    • -
    • - - -
    • -
    -

    +

      +
    • + +
    • +
    +
  • +
  • -

    - -
    + +
  • +
+

+ +

); diff --git a/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx b/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx index c0137ed1..11aea312 100644 --- a/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx +++ b/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx @@ -1,20 +1,15 @@ import React from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container } from '@edx/paragon'; -import Footer from './Footer'; const VerifiedProctoredExamInstructions = () => (
- -

- -

-
-
+

+ +

); diff --git a/src/instructions/proctored_exam/prerequisites-instructions/index.jsx b/src/instructions/proctored_exam/prerequisites-instructions/index.jsx index 60ae06c0..e3ebdc6d 100644 --- a/src/instructions/proctored_exam/prerequisites-instructions/index.jsx +++ b/src/instructions/proctored_exam/prerequisites-instructions/index.jsx @@ -9,8 +9,8 @@ import Footer from '../Footer'; const PrerequisitesProctoredExamInstructions = ({ skipProctoredExam }) => { const state = useContext(ExamStateContext); - const { exam, proctoringSettings } = state; - const { prerequisite_status: prerequisitesData, allow_proctoring_opt_out: allowProctoringOptOut } = exam; + const { exam, proctoringSettings, allowProctoringOptOut } = state; + const { prerequisite_status: prerequisitesData } = exam; const { pending_prerequisites: pending, failed_prerequisites: failed } = prerequisitesData; const { platform_name: platformName } = proctoringSettings; diff --git a/src/instructions/SubmittedExamInstructions.jsx b/src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx similarity index 81% rename from src/instructions/SubmittedExamInstructions.jsx rename to src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx index a9bf3897..81a66cd7 100644 --- a/src/instructions/SubmittedExamInstructions.jsx +++ b/src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx @@ -1,13 +1,12 @@ import React, { useContext } from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container } from '@edx/paragon'; -import ExamStateContext from '../context'; +import ExamStateContext from '../../context'; -const SubmittedExamInstructions = () => { +const SubmittedTimedExamInstructions = () => { const state = useContext(ExamStateContext); return ( - + <>

{state.timeIsOver ? ( @@ -30,8 +29,8 @@ const SubmittedExamInstructions = () => { + ' but you cannot change your answers.'} />

- + ); }; -export default SubmittedExamInstructions; +export default SubmittedTimedExamInstructions; diff --git a/src/instructions/timed_exam/index.jsx b/src/instructions/timed_exam/index.jsx index b3113b03..0fc34a67 100644 --- a/src/instructions/timed_exam/index.jsx +++ b/src/instructions/timed_exam/index.jsx @@ -1,3 +1,4 @@ export { default as StartTimedExamInstructions } from './StartTimedExamInstructions'; export { default as SubmitTimedExamInstructions } from './SubmitTimedExamInstructions'; +export { default as SubmittedTimedExamInstructions } from './SubmittedTimedExamInstructions'; export { default as TimedExamFooter } from './TimedExamFooter';