diff --git a/src/components/ErrorPopover.tsx b/src/components/ErrorPopover.tsx new file mode 100644 index 00000000..378dbdde --- /dev/null +++ b/src/components/ErrorPopover.tsx @@ -0,0 +1,43 @@ +import { Popover } from '@headlessui/react'; +import Icon from 'src/components/Icon'; + +interface ErrorType { + message: string; + type: string; +} + +interface ErrorPopoverProps { + errors: ErrorType[]; +} + +const ErrorPopover: React.FC = ({ errors }) => { + return ( + + + + + + + {errors.length === 0 ? ( +
No errors.
+ ) : ( + errors.map(({ message, type }) => ( +
+ +
+ + {message.split(':').pop()?.trim() || message} + +
+
+ )) + )} +
+
+ ); +}; + +export default ErrorPopover; diff --git a/src/components/Icon/selection.json b/src/components/Icon/selection.json index 6b0bdbd8..de0e2a17 100644 --- a/src/components/Icon/selection.json +++ b/src/components/Icon/selection.json @@ -1 +1 @@ -{"generatorSource":"svgps.app","IcoMoonType":"selection","icons":[{"icon":{"paths":["M822.592 192h14.272c0.015-0 0.032-0 0.049-0 15.82 0 28.958 11.479 31.54 26.562l0.027 0.19 21.312 128c0.276 1.578 0.433 3.394 0.433 5.248 0 17.668-14.318 31.992-31.984 32l-49.345 0-39.040 546.304c-1.239 16.654-15.055 29.696-31.917 29.696-0.007 0-0.013-0-0.020-0l-452.095 0c-0.025 0-0.054 0-0.083 0-16.862 0-30.678-13.043-31.911-29.59l-0.006-0.106-38.912-546.304h-49.152c-17.667-0.008-31.985-14.332-31.985-32 0-1.854 0.158-3.67 0.46-5.438l-0.027 0.19 21.312-128c2.609-15.273 15.747-26.752 31.567-26.752 0.017 0 0.035 0 0.052 0l-0.003-0h14.016l-6.72-93.696c-0.053-0.689-0.083-1.493-0.083-2.304 0-17.673 14.327-32 32-32 0.007 0 0.013 0 0.020 0l571.007-0c0.006-0 0.012-0 0.019-0 17.673 0 32 14.327 32 32 0 0.811-0.030 1.615-0.089 2.41l0.006-0.106-6.72 93.696zM758.464 192l4.544-64h-502.272l4.544 64h493.184zM210.304 320h610.176l-10.688-64h-595.584l-10.688 64h6.784zM279.040 384l36.544 512h392.576l36.544-512h-465.664z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["EpCoffee"]},"attrs":[{}],"properties":{"order":5,"id":11,"name":"coffee","prevSize":32,"code":59648},"setIdx":0,"setId":3,"iconIdx":0},{"icon":{"paths":["M512 896c-0.093 0.001-0.203 0.002-0.313 0.002-5.617 0-10.887-1.484-15.44-4.082l0.153 0.081c-17.2-9.6-416.4-236.4-416.4-524 0-132.548 107.452-240 240-240 78.248 0 147.75 37.446 191.563 95.396l0.437 0.604c44.25-58.554 113.752-96 192-96 132.548 0 240 107.452 240 240v0c0 122.4-70.8 248-210.4 373.6-60.727 54.775-128.139 104.486-200.184 147.208l-5.816 3.192c-4.399 2.517-9.67 4.002-15.287 4.002-0.11 0-0.22-0.001-0.33-0.002l0.017 0zM320 192c-97.202 0-176 78.798-176 176v0c0 220.8 296 414.8 368 458.8 72-44 368-238 368-458.8 0-0.048 0-0.104 0-0.16 0-97.202-78.798-176-176-176-72.716 0-135.132 44.098-161.964 107.011l-0.436 1.149c-4.916 11.742-16.312 19.841-29.6 19.841s-24.684-8.099-29.521-19.63l-0.079-0.211c-27.065-63.949-89.288-108.001-161.8-108.001-0.211 0-0.422 0-0.632 0.001l0.033-0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["PhHeart"]},"attrs":[{}],"properties":{"order":4,"id":10,"name":"hearth","prevSize":32,"code":59649},"setIdx":0,"setId":3,"iconIdx":1},{"icon":{"paths":["M1024 608l-192-192v-288h-128v160l-192-192-512 512v32h128v320h320v-192h128v192h320v-320h128z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["home","house"],"defaultCode":59650,"grid":16},"attrs":[],"properties":{"ligatures":"home3, house3","name":"home","order":10,"id":0,"prevSize":32,"code":59650},"setIdx":0,"setId":3,"iconIdx":2},{"icon":{"paths":["M768 64c105.87 0 192 86.13 192 192v192h-128v-192c0-35.29-28.71-64-64-64h-128c-35.29 0-64 28.71-64 64v192h16c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48h-544c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h400v-192c0-105.87 86.13-192 192-192h128z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["unlocked","lock-open"],"defaultCode":59792,"grid":16},"attrs":[],"properties":{"ligatures":"unlocked, lock-open","name":"unlocked","order":9,"id":2,"prevSize":32,"code":59792},"setIdx":0,"setId":3,"iconIdx":4},{"icon":{"paths":["M1024 226.4c-37.6 16.8-78.2 28-120.6 33 43.4-26 76.6-67.2 92.4-116.2-40.6 24-85.6 41.6-133.4 51-38.4-40.8-93-66.2-153.4-66.2-116 0-210 94-210 210 0 16.4 1.8 32.4 5.4 47.8-174.6-8.8-329.4-92.4-433-219.6-18 31-28.4 67.2-28.4 105.6 0 72.8 37 137.2 93.4 174.8-34.4-1-66.8-10.6-95.2-26.2 0 0.8 0 1.8 0 2.6 0 101.8 72.4 186.8 168.6 206-17.6 4.8-36.2 7.4-55.4 7.4-13.6 0-26.6-1.4-39.6-3.8 26.8 83.4 104.4 144.2 196.2 146-72 56.4-162.4 90-261 90-17 0-33.6-1-50.2-3 93.2 59.8 203.6 94.4 322.2 94.4 386.4 0 597.8-320.2 597.8-597.8 0-9.2-0.2-18.2-0.6-27.2 41-29.4 76.6-66.4 104.8-108.6z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["twitter","brand","tweet","social"],"defaultCode":60054,"grid":16},"attrs":[],"properties":{"ligatures":"twitter, brand16","name":"twitter","order":6,"id":5,"prevSize":32,"code":60054},"setIdx":0,"setId":3,"iconIdx":7},{"icon":{"paths":["M512.008 12.642c-282.738 0-512.008 229.218-512.008 511.998 0 226.214 146.704 418.132 350.136 485.836 25.586 4.738 34.992-11.11 34.992-24.632 0-12.204-0.48-52.542-0.696-95.324-142.448 30.976-172.504-60.41-172.504-60.41-23.282-59.176-56.848-74.916-56.848-74.916-46.452-31.778 3.51-31.124 3.51-31.124 51.4 3.61 78.476 52.766 78.476 52.766 45.672 78.27 119.776 55.64 149.004 42.558 4.588-33.086 17.852-55.68 32.506-68.464-113.73-12.942-233.276-56.85-233.276-253.032 0-55.898 20.004-101.574 52.76-137.428-5.316-12.9-22.854-64.972 4.952-135.5 0 0 43.006-13.752 140.84 52.49 40.836-11.348 84.636-17.036 128.154-17.234 43.502 0.198 87.336 5.886 128.256 17.234 97.734-66.244 140.656-52.49 140.656-52.49 27.872 70.528 10.35 122.6 5.036 135.5 32.82 35.856 52.694 81.532 52.694 137.428 0 196.654-119.778 239.95-233.79 252.624 18.364 15.89 34.724 47.046 34.724 94.812 0 68.508-0.596 123.644-0.596 140.508 0 13.628 9.222 29.594 35.172 24.566 203.322-67.776 349.842-259.626 349.842-485.768 0-282.78-229.234-511.998-511.992-511.998z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["github","brand","octacat","social"],"defaultCode":60080,"grid":16},"attrs":[],"properties":{"ligatures":"github, brand40","name":"github","order":5,"id":6,"prevSize":32,"code":60080},"setIdx":0,"setId":3,"iconIdx":8},{"icon":{"paths":["M128 128v64M128 896v-256m0 0l118.2-29.6a384 384 0 0 1 264.9 29.1l4.6 2.3a384 384 0 0 0 259.6 30.3l132.9-31.2a2070.4 2070.4 0 0 1-0.2-448l-132.7 31.3a384 384 0 0 1-259.6-30.4l-4.6-2.3a384 384 0 0 0-264.9-29.1L128 192M128 640V192"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"flag"}},{"icon":{"paths":["M672 736V880C672 906.5 650.5 928 624 928H208C181.5 928 160 906.5 160 880V336C160 309.5 181.5 288 208 288H288C309.8 288 331.2 289.8 352 293.3M672 736H816C842.5 736 864 714.5 864 688V480C864 289.7 725.6 131.8 544 101.3 523.2 97.8 501.8 96 480 96H400C373.5 96 352 117.5 352 144V293.3M672 736H400C373.5 736 352 714.5 352 688V293.3M864 576V496C864 416.5 799.5 352 720 352H656C629.5 352 608 330.5 608 304V240C608 160.5 543.5 96 464 96H416"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"document-duplicate"}},{"icon":{"paths":["M553.4 169.4C565.9 156.9 586.1 156.9 598.6 169.4L918.6 489.4C924.6 495.4 928 503.5 928 512 928 520.5 924.6 528.6 918.6 534.6L598.6 854.6C586.1 867.1 565.9 867.1 553.4 854.6 540.9 842.1 540.9 821.9 553.4 809.4L818.7 544H128C110.3 544 96 529.7 96 512 96 494.3 110.3 480 128 480H818.7L553.4 214.6C540.9 202.1 540.9 181.9 553.4 169.4Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"arrow-right"}},{"icon":{"paths":["M470.6 169.4C483.1 181.9 483.1 202.1 470.6 214.6L205.3 480H896C913.7 480 928 494.3 928 512 928 529.7 913.7 544 896 544H205.3L470.6 809.4C483.1 821.9 483.1 842.1 470.6 854.6 458.1 867.1 437.9 867.1 425.4 854.6L105.4 534.6C92.9 522.1 92.9 501.9 105.4 489.4L425.4 169.4C437.9 156.9 458.1 156.9 470.6 169.4Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"arrow-left"}},{"icon":{"paths":["M512 64C388.3 64 288 164.3 288 288V416C217.3 416 160 473.3 160 544V832C160 902.7 217.3 960 288 960H736C806.7 960 864 902.7 864 832V544C864 473.3 806.7 416 736 416V288C736 164.3 635.7 64 512 64ZM672 416V288C672 199.6 600.4 128 512 128 423.6 128 352 199.6 352 288V416H672Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"lock-closed"}},{"icon":{"paths":["M768 64C891.7 64 992 164.3 992 288V448C992 465.7 977.7 480 960 480 942.3 480 928 465.7 928 448V288C928 199.6 856.4 128 768 128 679.6 128 608 199.6 608 288V416C678.7 416 736 473.3 736 544V832C736 902.7 678.7 960 608 960H160C89.3 960 32 902.7 32 832V544C32 473.3 89.3 416 160 416H544V288C544 164.3 644.3 64 768 64Z"],"attrs":[],"width":1024},"properties":{"name":"lock-open"}},{"icon":{"paths":["M672 448L873.4 246.6C893.5 226.5 928 240.7 928 269.3V754.7C928 783.3 893.5 797.5 873.4 777.4L672 576M192 800H576C629 800 672 757 672 704V320C672 267 629 224 576 224H192C139 224 96 267 96 320V704C96 757 139 800 192 800Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round"}],"width":1024},"properties":{"name":"video-camera"}},{"icon":{"paths":["M320 352H704M320 480H512M96 544.4C96 612.7 143.9 672.2 211.5 682.1 259.7 689.2 308.4 694.6 357.5 698.3 372.5 699.4 386.1 707.2 394.5 719.7L512 896 629.5 719.7C637.9 707.2 651.5 699.4 666.5 698.3 715.6 694.6 764.3 689.2 812.5 682.1 880.1 672.2 928 612.7 928 544.4V287.6C928 219.3 880.1 159.8 812.5 149.9 714.4 135.5 614.1 128 512 128 409.9 128 309.6 135.5 211.5 149.9 143.9 159.8 96 219.3 96 287.6V544.4Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"chat-bubble"}},{"icon":{"paths":["M192 544L448 800 832 224"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"check"}},{"icon":{"paths":["M224 241.2C224 204.7 263.1 181.5 295.1 199.1L787.5 470C820.7 488.2 820.7 535.8 787.5 554.1L295.1 824.9C263.1 842.5 224 819.3 224 782.8V241.2Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"play"}}]} \ No newline at end of file +{"generatorSource":"svgps.app","IcoMoonType":"selection","icons":[{"icon": {"paths": ["M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896z m0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768z m48-176a48 48 0 1 1-96 0 48 48 0 0 1 96 0z m-48-464a32 32 0 0 1 32 32v288a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32z"],"attrs": [{}],"isMulticolor": false,"isMulticolor2": false,"grid": 16,"tags": ["error", "warning", "alert"]},"attrs": [{}],"properties": {"order": 6,"id": 12,"name": "error","prevSize": 32,"code": 59650},"setIdx": 0,"setId": 3,"iconIdx": 2},{"icon":{"paths":["M822.592 192h14.272c0.015-0 0.032-0 0.049-0 15.82 0 28.958 11.479 31.54 26.562l0.027 0.19 21.312 128c0.276 1.578 0.433 3.394 0.433 5.248 0 17.668-14.318 31.992-31.984 32l-49.345 0-39.040 546.304c-1.239 16.654-15.055 29.696-31.917 29.696-0.007 0-0.013-0-0.020-0l-452.095 0c-0.025 0-0.054 0-0.083 0-16.862 0-30.678-13.043-31.911-29.59l-0.006-0.106-38.912-546.304h-49.152c-17.667-0.008-31.985-14.332-31.985-32 0-1.854 0.158-3.67 0.46-5.438l-0.027 0.19 21.312-128c2.609-15.273 15.747-26.752 31.567-26.752 0.017 0 0.035 0 0.052 0l-0.003-0h14.016l-6.72-93.696c-0.053-0.689-0.083-1.493-0.083-2.304 0-17.673 14.327-32 32-32 0.007 0 0.013 0 0.020 0l571.007-0c0.006-0 0.012-0 0.019-0 17.673 0 32 14.327 32 32 0 0.811-0.030 1.615-0.089 2.41l0.006-0.106-6.72 93.696zM758.464 192l4.544-64h-502.272l4.544 64h493.184zM210.304 320h610.176l-10.688-64h-595.584l-10.688 64h6.784zM279.040 384l36.544 512h392.576l36.544-512h-465.664z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["EpCoffee"]},"attrs":[{}],"properties":{"order":5,"id":11,"name":"coffee","prevSize":32,"code":59648},"setIdx":0,"setId":3,"iconIdx":0},{"icon":{"paths":["M512 896c-0.093 0.001-0.203 0.002-0.313 0.002-5.617 0-10.887-1.484-15.44-4.082l0.153 0.081c-17.2-9.6-416.4-236.4-416.4-524 0-132.548 107.452-240 240-240 78.248 0 147.75 37.446 191.563 95.396l0.437 0.604c44.25-58.554 113.752-96 192-96 132.548 0 240 107.452 240 240v0c0 122.4-70.8 248-210.4 373.6-60.727 54.775-128.139 104.486-200.184 147.208l-5.816 3.192c-4.399 2.517-9.67 4.002-15.287 4.002-0.11 0-0.22-0.001-0.33-0.002l0.017 0zM320 192c-97.202 0-176 78.798-176 176v0c0 220.8 296 414.8 368 458.8 72-44 368-238 368-458.8 0-0.048 0-0.104 0-0.16 0-97.202-78.798-176-176-176-72.716 0-135.132 44.098-161.964 107.011l-0.436 1.149c-4.916 11.742-16.312 19.841-29.6 19.841s-24.684-8.099-29.521-19.63l-0.079-0.211c-27.065-63.949-89.288-108.001-161.8-108.001-0.211 0-0.422 0-0.632 0.001l0.033-0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["PhHeart"]},"attrs":[{}],"properties":{"order":4,"id":10,"name":"hearth","prevSize":32,"code":59649},"setIdx":0,"setId":3,"iconIdx":1},{"icon":{"paths":["M1024 608l-192-192v-288h-128v160l-192-192-512 512v32h128v320h320v-192h128v192h320v-320h128z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["home","house"],"defaultCode":59650,"grid":16},"attrs":[],"properties":{"ligatures":"home3, house3","name":"home","order":10,"id":0,"prevSize":32,"code":59650},"setIdx":0,"setId":3,"iconIdx":2},{"icon":{"paths":["M768 64c105.87 0 192 86.13 192 192v192h-128v-192c0-35.29-28.71-64-64-64h-128c-35.29 0-64 28.71-64 64v192h16c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48h-544c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h400v-192c0-105.87 86.13-192 192-192h128z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["unlocked","lock-open"],"defaultCode":59792,"grid":16},"attrs":[],"properties":{"ligatures":"unlocked, lock-open","name":"unlocked","order":9,"id":2,"prevSize":32,"code":59792},"setIdx":0,"setId":3,"iconIdx":4},{"icon":{"paths":["M1024 226.4c-37.6 16.8-78.2 28-120.6 33 43.4-26 76.6-67.2 92.4-116.2-40.6 24-85.6 41.6-133.4 51-38.4-40.8-93-66.2-153.4-66.2-116 0-210 94-210 210 0 16.4 1.8 32.4 5.4 47.8-174.6-8.8-329.4-92.4-433-219.6-18 31-28.4 67.2-28.4 105.6 0 72.8 37 137.2 93.4 174.8-34.4-1-66.8-10.6-95.2-26.2 0 0.8 0 1.8 0 2.6 0 101.8 72.4 186.8 168.6 206-17.6 4.8-36.2 7.4-55.4 7.4-13.6 0-26.6-1.4-39.6-3.8 26.8 83.4 104.4 144.2 196.2 146-72 56.4-162.4 90-261 90-17 0-33.6-1-50.2-3 93.2 59.8 203.6 94.4 322.2 94.4 386.4 0 597.8-320.2 597.8-597.8 0-9.2-0.2-18.2-0.6-27.2 41-29.4 76.6-66.4 104.8-108.6z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["twitter","brand","tweet","social"],"defaultCode":60054,"grid":16},"attrs":[],"properties":{"ligatures":"twitter, brand16","name":"twitter","order":6,"id":5,"prevSize":32,"code":60054},"setIdx":0,"setId":3,"iconIdx":7},{"icon":{"paths":["M512.008 12.642c-282.738 0-512.008 229.218-512.008 511.998 0 226.214 146.704 418.132 350.136 485.836 25.586 4.738 34.992-11.11 34.992-24.632 0-12.204-0.48-52.542-0.696-95.324-142.448 30.976-172.504-60.41-172.504-60.41-23.282-59.176-56.848-74.916-56.848-74.916-46.452-31.778 3.51-31.124 3.51-31.124 51.4 3.61 78.476 52.766 78.476 52.766 45.672 78.27 119.776 55.64 149.004 42.558 4.588-33.086 17.852-55.68 32.506-68.464-113.73-12.942-233.276-56.85-233.276-253.032 0-55.898 20.004-101.574 52.76-137.428-5.316-12.9-22.854-64.972 4.952-135.5 0 0 43.006-13.752 140.84 52.49 40.836-11.348 84.636-17.036 128.154-17.234 43.502 0.198 87.336 5.886 128.256 17.234 97.734-66.244 140.656-52.49 140.656-52.49 27.872 70.528 10.35 122.6 5.036 135.5 32.82 35.856 52.694 81.532 52.694 137.428 0 196.654-119.778 239.95-233.79 252.624 18.364 15.89 34.724 47.046 34.724 94.812 0 68.508-0.596 123.644-0.596 140.508 0 13.628 9.222 29.594 35.172 24.566 203.322-67.776 349.842-259.626 349.842-485.768 0-282.78-229.234-511.998-511.992-511.998z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["github","brand","octacat","social"],"defaultCode":60080,"grid":16},"attrs":[],"properties":{"ligatures":"github, brand40","name":"github","order":5,"id":6,"prevSize":32,"code":60080},"setIdx":0,"setId":3,"iconIdx":8},{"icon":{"paths":["M128 128v64M128 896v-256m0 0l118.2-29.6a384 384 0 0 1 264.9 29.1l4.6 2.3a384 384 0 0 0 259.6 30.3l132.9-31.2a2070.4 2070.4 0 0 1-0.2-448l-132.7 31.3a384 384 0 0 1-259.6-30.4l-4.6-2.3a384 384 0 0 0-264.9-29.1L128 192M128 640V192"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"flag"}},{"icon":{"paths":["M672 736V880C672 906.5 650.5 928 624 928H208C181.5 928 160 906.5 160 880V336C160 309.5 181.5 288 208 288H288C309.8 288 331.2 289.8 352 293.3M672 736H816C842.5 736 864 714.5 864 688V480C864 289.7 725.6 131.8 544 101.3 523.2 97.8 501.8 96 480 96H400C373.5 96 352 117.5 352 144V293.3M672 736H400C373.5 736 352 714.5 352 688V293.3M864 576V496C864 416.5 799.5 352 720 352H656C629.5 352 608 330.5 608 304V240C608 160.5 543.5 96 464 96H416"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"document-duplicate"}},{"icon":{"paths":["M553.4 169.4C565.9 156.9 586.1 156.9 598.6 169.4L918.6 489.4C924.6 495.4 928 503.5 928 512 928 520.5 924.6 528.6 918.6 534.6L598.6 854.6C586.1 867.1 565.9 867.1 553.4 854.6 540.9 842.1 540.9 821.9 553.4 809.4L818.7 544H128C110.3 544 96 529.7 96 512 96 494.3 110.3 480 128 480H818.7L553.4 214.6C540.9 202.1 540.9 181.9 553.4 169.4Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"arrow-right"}},{"icon":{"paths":["M470.6 169.4C483.1 181.9 483.1 202.1 470.6 214.6L205.3 480H896C913.7 480 928 494.3 928 512 928 529.7 913.7 544 896 544H205.3L470.6 809.4C483.1 821.9 483.1 842.1 470.6 854.6 458.1 867.1 437.9 867.1 425.4 854.6L105.4 534.6C92.9 522.1 92.9 501.9 105.4 489.4L425.4 169.4C437.9 156.9 458.1 156.9 470.6 169.4Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"arrow-left"}},{"icon":{"paths":["M512 64C388.3 64 288 164.3 288 288V416C217.3 416 160 473.3 160 544V832C160 902.7 217.3 960 288 960H736C806.7 960 864 902.7 864 832V544C864 473.3 806.7 416 736 416V288C736 164.3 635.7 64 512 64ZM672 416V288C672 199.6 600.4 128 512 128 423.6 128 352 199.6 352 288V416H672Z"],"attrs":[{"fillRule":"evenodd","clipRule":"evenodd"}],"width":1024},"properties":{"name":"lock-closed"}},{"icon":{"paths":["M768 64C891.7 64 992 164.3 992 288V448C992 465.7 977.7 480 960 480 942.3 480 928 465.7 928 448V288C928 199.6 856.4 128 768 128 679.6 128 608 199.6 608 288V416C678.7 416 736 473.3 736 544V832C736 902.7 678.7 960 608 960H160C89.3 960 32 902.7 32 832V544C32 473.3 89.3 416 160 416H544V288C544 164.3 644.3 64 768 64Z"],"attrs":[],"width":1024},"properties":{"name":"lock-open"}},{"icon":{"paths":["M672 448L873.4 246.6C893.5 226.5 928 240.7 928 269.3V754.7C928 783.3 893.5 797.5 873.4 777.4L672 576M192 800H576C629 800 672 757 672 704V320C672 267 629 224 576 224H192C139 224 96 267 96 320V704C96 757 139 800 192 800Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round"}],"width":1024},"properties":{"name":"video-camera"}},{"icon":{"paths":["M320 352H704M320 480H512M96 544.4C96 612.7 143.9 672.2 211.5 682.1 259.7 689.2 308.4 694.6 357.5 698.3 372.5 699.4 386.1 707.2 394.5 719.7L512 896 629.5 719.7C637.9 707.2 651.5 699.4 666.5 698.3 715.6 694.6 764.3 689.2 812.5 682.1 880.1 672.2 928 612.7 928 544.4V287.6C928 219.3 880.1 159.8 812.5 149.9 714.4 135.5 614.1 128 512 128 409.9 128 309.6 135.5 211.5 149.9 143.9 159.8 96 219.3 96 287.6V544.4Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"chat-bubble"}},{"icon":{"paths":["M192 544L448 800 832 224"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"check"}},{"icon":{"paths":["M224 241.2C224 204.7 263.1 181.5 295.1 199.1L787.5 470C820.7 488.2 820.7 535.8 787.5 554.1L295.1 824.9C263.1 842.5 224 819.3 224 782.8V241.2Z"],"attrs":[{"fill":"none","strokeWidth":64,"strokeLinecap":"round","strokeLinejoin":"round"}],"width":1024},"properties":{"name":"play"}}]} \ No newline at end of file diff --git a/src/components/InteractiveArea.tsx b/src/components/InteractiveArea.tsx index 60807efd..38993f5b 100644 --- a/src/components/InteractiveArea.tsx +++ b/src/components/InteractiveArea.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useCallback, useContext } from 'react'; +import { useState, useEffect, useRef, useCallback, useContext, isValidElement } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import useEventListener from '@use-it/event-listener'; import cx from 'clsx'; @@ -7,6 +7,7 @@ import confetti from 'canvas-confetti'; const ReportStep = dynamic(import('src/components/ReportStep'), { ssr: false }); const Hint = dynamic(import('src/components/Hint'), { ssr: false }); +const ErrorPopover = dynamic(import('src/components/ErrorPopover'), { ssr: false }); import FlagBox from 'src/components/FlagBox'; import Icon from 'src/components/Icon'; import setCaretPosition from 'src/utils/setCaretPosition'; @@ -39,6 +40,8 @@ const InteractiveArea = ({ isShow, setIsOpenModal }: Props) => { const { formatMessage } = useIntl(); const regexInput = useRef(null); const [regex, setRegex] = useState(data.initialValue || ''); + const [isvalidRegexSyntax, setIsvalidRegexSyntax] = useState(true); + const [errors, setErrors] = useState([]); const [flags, setFlags] = useState(data.initialFlags || ''); const [content, setContent] = useState(''); const [isChanged, setIsChanged] = useState(false); @@ -104,15 +107,21 @@ const InteractiveArea = ({ isShow, setIsOpenModal }: Props) => { if (!checkBrowserSupport()) return; } - const { isSuccess, isMatch, err, regex: grouppedRegex } = checkRegex(data, { regex, flags }); + const { isSuccess, isMatch, error, regex: grouppedRegex } = checkRegex(data, { regex, flags }); - if (err) { + if (error) { setError(true); setMatch(false); setSuccess(false); + if (error.type === 'InvalidRegex') { + setIsvalidRegexSyntax(false); + setErrors([error]); + } else { + setIsvalidRegexSyntax(true); + setErrors([]); + } return; } - setError(false); setMatch(isMatch); setSuccess(isSuccess); @@ -146,11 +155,11 @@ const InteractiveArea = ({ isShow, setIsOpenModal }: Props) => { const onFocus = e => { if (data.readOnly) { - return; + return; } onChange(e); - } + }; const focusInput = () => { regexInput?.current?.focus(); @@ -205,7 +214,8 @@ const InteractiveArea = ({ isShow, setIsOpenModal }: Props) => { }).toLowerCase(); return ( -
{ {!data.noHint && ( )} -
- +
+
+ +
+
{errors.length > 0 && }
{data.videoURL && (
{ const matches = [...regex.matchAll(/\\(\d+)/g)] .map(item => Number(item[1])) @@ -14,40 +22,136 @@ const createGrouppedRegex = (regex: string, flags?: string) => { return new RegExp(`(${newRegex})`, flags); }; -type CheckRegex = ( - data, - regexObj: { regex: string; flags?: string }, -) => { - err?: Error; - isMatch?: boolean; - isSuccess?: boolean; +/** + * Defines the structure of the regex object. + */ +interface RegexObj { + regex: string; + flags?: string; +} + +/** + * Defines the possible error types. + */ +type RegexError = + | { type: 'InvalidRegex'; message: string } + | { type: 'IncorrectMatches'; message: string; expected: string[]; actual: string[] } + | { type: 'InvalidFlags'; message: string } + | { type: 'ValidationFailed'; message: string }; + +/** + * Defines the structure of the result returned by checkRegex. + */ +interface CheckRegexResult { + isMatch: boolean; + isSuccess: boolean; regex?: RegExp; -}; + error?: RegexError; +} -const checkRegex: CheckRegex = (data, { regex, flags }) => { - const isGlobal = flags?.includes('g'); - const isValidRegex = data.regex.includes(regex) || data.customValidate?.(regex); - const isValidFlags = xor(data.flags.split(''), flags.split('')).length === 0; +/** + * Checks the provided regex against the data and returns detailed results. + * + * @param data - The data object containing content, expected answers, and validation methods. + * @param regexObj - An object containing the regex pattern and optional flags. + * @returns An object detailing the match results and any errors encountered. + */ +const checkRegex = (data: LessonData, { regex, flags }: RegexObj): CheckRegexResult => { + let grouppedRegex: RegExp; + let results: string[] = []; + // Step 1: Validate Regex Syntax try { - const grouppedRegex = createGrouppedRegex(regex, flags); - let results; + grouppedRegex = createGrouppedRegex(regex, flags); + } catch (syntaxError) { + return { + isMatch: false, + isSuccess: false, + error: { + type: 'InvalidRegex', + message: `The provided regex has invalid syntax: ${(syntaxError as Error).message}`, + }, + }; + } + + // Step 2: Validate Flags + const expectedFlags = data.flags ? data.flags.split('') : []; + const providedFlags = flags ? flags.split('') : []; + const invalidFlags = xor(expectedFlags, providedFlags); + if (invalidFlags.length > 0) { + return { + isMatch: false, + isSuccess: false, + error: { + type: 'InvalidFlags', + message: `The provided flags "${flags}" do not match the expected flags "${data.flags}".`, + }, + }; + } - if (isGlobal) { + // Step 3: Validate Regex Inclusion or Custom Validation + const isValidRegex = data.regex + ? data.regex.includes(regex) + : data.customValidate + ? data.customValidate(regex) + : false; + + if (!isValidRegex) { + return { + isMatch: false, + isSuccess: false, + error: { + type: 'ValidationFailed', + message: 'The provided regex does not pass the validation checks.', + }, + }; + } + + // Step 4: Perform Matching + try { + if (flags?.includes('g')) { results = [...data.content.matchAll(grouppedRegex)].map(result => result[0]).filter(Boolean); } else { - results = [...data.content.match(grouppedRegex)].filter(Boolean); + const match = data.content.match(grouppedRegex); + if (match) { + results = [match[0]]; + } } + } catch (matchError) { + return { + isMatch: false, + isSuccess: false, + error: { + type: 'InvalidRegex', + message: `Error during regex matching: ${(matchError as Error).message}`, + }, + }; + } - const isMatch = - data.answer?.length === results.length && xor(data.answer, results).length === 0; - const isSuccess = isMatch && isValidRegex && isValidFlags; + // Step 5: Compare Results with Expected Answers + const expected = data.answer || []; + const isMatch = expected.length === results.length && xor(expected, results).length === 0; - return { isMatch, isSuccess, regex: grouppedRegex }; - } catch (err) { - console.error(err); - return { err }; + if (!isMatch) { + return { + isMatch, + isSuccess: false, + regex: grouppedRegex, + error: { + type: 'IncorrectMatches', + message: 'The regex does not produce the expected matches.', + expected, + actual: results, + }, + }; } + + // If all checks pass + return { + isMatch: true, + isSuccess: true, + regex: grouppedRegex, + }; }; export default checkRegex;