|
1 | 1 | import React from 'react'
|
2 | 2 | import toast from 'react-hot-toast'
|
3 | 3 | import { assembleClassNames } from '@/utils/assemble-classnames'
|
4 |
| -import { PASS_THRESHOLD } from '../store/cssExercisePageStore' |
| 4 | +import { |
| 5 | + AssertionStatus, |
| 6 | + GRACE_THRESHOLD, |
| 7 | + PASS_THRESHOLD, |
| 8 | +} from '../store/cssExercisePageStore' |
5 | 9 | import { CheckResult } from '../checks/runChecks'
|
| 10 | +import GraphicalIcon from '@/components/common/GraphicalIcon' |
| 11 | + |
| 12 | +const STATUS_COLORS: Record<AssertionStatus, { border: string; text: string }> = |
| 13 | + { |
| 14 | + pass: { |
| 15 | + border: 'var(--successColor)', |
| 16 | + text: '#2E8C70', |
| 17 | + }, |
| 18 | + fail: { |
| 19 | + border: '#D85050', |
| 20 | + text: '#D85050', |
| 21 | + }, |
| 22 | + grace: { |
| 23 | + border: '#DC8604', |
| 24 | + text: '#DC8604', |
| 25 | + }, |
| 26 | + } |
6 | 27 |
|
7 | 28 | export function showResultToast(
|
8 |
| - status: 'pass' | 'fail', |
| 29 | + status: AssertionStatus, |
9 | 30 | percentage: number,
|
10 | 31 | firstFailingCheck?: CheckResult | null
|
11 | 32 | ) {
|
12 | 33 | toast.custom(
|
13 | 34 | (t) => (
|
14 | 35 | <div
|
| 36 | + style={{ borderColor: STATUS_COLORS[status].border }} |
15 | 37 | className={assembleClassNames(
|
16 | 38 | t.visible ? 'animate-slideIn' : 'animate-slideOut',
|
17 |
| - status === 'pass' ? 'border-successColor' : 'border-danger', |
18 |
| - 'border-2 flex bg-white shadow-base text-14 rounded-5 p-8 gap-8 items-center' |
| 39 | + 'max-w-[500px] w-full', |
| 40 | + `border-${STATUS_COLORS[status].border}`, |
| 41 | + 'border-1 flex justify-between bg-white shadow-base text-14 rounded-8 p-8 gap-8 items-center relative' |
19 | 42 | )}
|
20 | 43 | >
|
21 |
| - {/* |
22 |
| - We always only show one message. |
23 |
| - Pixel-matching is top priority, then other checks' error message. |
24 |
| - Otherwise show a success message. |
25 |
| - */} |
26 |
| - {percentage < PASS_THRESHOLD ? ( |
27 |
| - <TextBlock |
28 |
| - status={status} |
29 |
| - text={`Your output isn't exactly the same as the target yet (${percentage}%).`} |
30 |
| - /> |
31 |
| - ) : firstFailingCheck ? ( |
32 |
| - <TextBlock |
33 |
| - status={status} |
34 |
| - text={firstFailingCheck.error_html || ''} |
35 |
| - /> |
36 |
| - ) : ( |
37 |
| - <TextBlock status={status} text="Congrats! All checks are passing!" /> |
38 |
| - )} |
39 |
| - |
| 44 | + <ToastText |
| 45 | + percentage={percentage} |
| 46 | + status={status} |
| 47 | + firstFailingCheck={firstFailingCheck} |
| 48 | + /> |
40 | 49 | <button
|
41 |
| - className="btn-xs btn-enhanced" |
| 50 | + className="rounded-circle bg-bootcamp-light-purple p-4 self-start" |
42 | 51 | onClick={() => toast.dismiss(t.id)}
|
43 | 52 | >
|
44 |
| - Got it |
| 53 | + <GraphicalIcon icon="close" height={12} width={12} /> |
45 | 54 | </button>
|
46 | 55 | </div>
|
47 | 56 | ),
|
48 | 57 | { duration: 6000 }
|
49 | 58 | )
|
50 | 59 | }
|
51 | 60 |
|
| 61 | +function ToastText({ |
| 62 | + status, |
| 63 | + percentage, |
| 64 | + firstFailingCheck, |
| 65 | +}: { |
| 66 | + status: AssertionStatus |
| 67 | + percentage: number |
| 68 | + firstFailingCheck?: CheckResult | null |
| 69 | +}) { |
| 70 | + if (firstFailingCheck) { |
| 71 | + return ( |
| 72 | + <TextBlock |
| 73 | + status={status} |
| 74 | + textHtml={firstFailingCheck.error_html || ''} |
| 75 | + /> |
| 76 | + ) |
| 77 | + } |
| 78 | + |
| 79 | + if (percentage < GRACE_THRESHOLD) { |
| 80 | + return ( |
| 81 | + <TextBlock |
| 82 | + status={status} |
| 83 | + textHtml={`Your output isn't exactly the same as the target yet (${percentage}%).`} |
| 84 | + /> |
| 85 | + ) |
| 86 | + } |
| 87 | + |
| 88 | + if (percentage < PASS_THRESHOLD) { |
| 89 | + return ( |
| 90 | + <div |
| 91 | + style={{ color: STATUS_COLORS[status].text }} |
| 92 | + className={assembleClassNames('font-medium px-8 py-4')} |
| 93 | + > |
| 94 | + <p className="text-14 leading-160"> |
| 95 | + You're currently at {percentage}%. You can <strong> complete </strong>{' '} |
| 96 | + the exercise if you have invested as much energy as you want to into |
| 97 | + it. But you might like to try to get to <strong>100%</strong> still! |
| 98 | + </p> |
| 99 | + </div> |
| 100 | + ) |
| 101 | + } |
| 102 | + |
| 103 | + return ( |
| 104 | + <TextBlock status={status} textHtml="Congrats! All checks are passing!" /> |
| 105 | + ) |
| 106 | +} |
| 107 | + |
52 | 108 | function TextBlock({
|
53 |
| - text, |
| 109 | + textHtml, |
54 | 110 | status,
|
55 | 111 | }: {
|
56 |
| - text: string |
57 |
| - status: 'pass' | 'fail' |
| 112 | + textHtml: string |
| 113 | + status: AssertionStatus |
58 | 114 | }) {
|
59 | 115 | return (
|
60 | 116 | <div
|
61 |
| - className={assembleClassNames( |
62 |
| - 'font-semibold', |
63 |
| - status === 'pass' ? 'text-darkSuccessGreen' : 'text-danger' |
64 |
| - )} |
65 |
| - > |
66 |
| - {text} |
67 |
| - </div> |
| 117 | + style={{ color: STATUS_COLORS[status].text }} |
| 118 | + className={assembleClassNames('font-medium px-8 py-4')} |
| 119 | + dangerouslySetInnerHTML={{ __html: textHtml }} |
| 120 | + /> |
68 | 121 | )
|
69 | 122 | }
|
0 commit comments