MEP-PPD

This commit is contained in:
Maxime Lalo 2024-07-23 18:05:26 +02:00
parent 3a567b166c
commit 8cbd9da9bb
90 changed files with 4708 additions and 0 deletions

View File

@ -0,0 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.19085 19.7757C10.4398 19.9238 11.7109 20 12.9997 20C13.4408 20 13.8798 19.9911 14.3165 19.9734M9.19085 19.7757C6.66458 19.4761 4.22854 18.8821 1.91797 18.0292C3.70052 16.0505 4.83399 13.4756 4.98297 10.6398M9.19085 19.7757C9.06692 20.1615 9 20.573 9 21C9 23.2091 10.7909 25 13 25C14.7152 25 16.1783 23.9204 16.7468 22.4036M19.3666 19.3666L25 25M19.3666 19.3666C20.9859 19.0397 22.5609 18.5905 24.0815 18.0292C22.1658 15.9027 20.9997 13.0875 20.9997 10V9.06558L21 9C21 4.58172 17.4183 1 13 1C9.7556 1 6.96228 2.93132 5.70701 5.70701M19.3666 19.3666L5.70701 5.70701M1 1L5.70701 5.70701" stroke="#005BCB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 785 B

View File

@ -0,0 +1,16 @@
<svg width="708" height="949" viewBox="0 0 708 949" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_290_22215)">
<path opacity="0.5" d="M1107.28 476.969V785.738C984.818 725.193 840.015 709.07 698.774 751.293C552.769 794.949 437.885 892.816 369.589 1016.28H49.1945L2.95426 862.102C-5.40172 834.252 10.4413 804.912 38.3332 796.568L1107.28 476.969Z" fill="#005BCB"/>
</g>
<g clip-path="url(#clip1_290_22215)">
<path d="M181.969 -76.2753L490.738 -76.2753C430.193 46.1825 414.07 190.986 456.293 332.226C499.949 478.232 597.816 593.115 721.277 661.411L721.277 981.806L567.102 1028.05C539.252 1036.4 509.912 1020.56 501.568 992.667L181.969 -76.2753Z" stroke="#005BCB" stroke-width="2" stroke-miterlimit="10"/>
</g>
<defs>
<clipPath id="clip0_290_22215">
<rect width="708" height="480.476" fill="white" transform="translate(0 483.523)"/>
</clipPath>
<clipPath id="clip1_290_22215">
<rect width="964" height="527" fill="white" transform="translate(181 964) rotate(-90)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1015 B

View File

@ -0,0 +1,22 @@
<svg width="348" height="908" viewBox="0 0 348 908" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_38_40737)">
<path opacity="0.5" d="M-138.555 -474.276L170.214 -474.276C109.669 -351.818 93.546 -207.015 135.77 -65.7741C179.425 80.2311 277.292 195.115 400.753 263.411L400.753 583.806L246.578 630.046C218.728 638.402 189.388 622.559 181.045 594.667L-138.555 -474.276Z" fill="#005BCB"/>
</g>
<g clip-path="url(#clip1_38_40737)">
<g clip-path="url(#clip2_38_40737)">
<path d="M410.131 204.837L590.401 385.106C483.532 421.279 389.548 496.437 331.709 603.579C271.923 714.34 261.964 838.575 294.157 950.543L107.033 1137.67L-9.98578 1074.66C-31.126 1063.28 -39.0026 1036.9 -27.5838 1015.74L410.131 204.837Z" stroke="#005BCB" stroke-width="2" stroke-miterlimit="10"/>
</g>
</g>
<defs>
<clipPath id="clip0_38_40737">
<rect width="653.982" height="348" fill="white" transform="translate(3.05176e-05 653.981) rotate(-90)"/>
</clipPath>
<clipPath id="clip1_38_40737">
<rect width="348" height="626.271" fill="white" transform="translate(3.05176e-05 281.729)"/>
</clipPath>
<clipPath id="clip2_38_40737">
<rect width="796.226" height="435.126" fill="white" transform="translate(-198 811.836) rotate(-45)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.2668 11.4484C34.4117 11.6943 34.7078 11.814 34.985 11.7321L39.5905 10.3514C39.8362 10.2821 40 10.0552 40 9.80929V1.11585C40 0.498042 39.5023 0 38.8849 0H0.932437C0.371719 0 0.0315029 0.378271 0.0315029 0.926734L0 20.8353V38.8841C0 39.502 0.497719 40 1.11514 40H39.0676C39.6283 40 39.9685 39.6218 39.9685 39.067L40 19.1647V19.1016C40 18.3641 39.8929 17.8534 38.8912 18.1371L37.083 18.673C36.7932 18.7612 36.6042 19.0323 36.6168 19.3286L36.6483 19.9969C36.6483 27.9212 32.5217 33.6076 25.2386 35.8393C13.3249 39.4894 2.45708 30.134 3.42102 18.5532C4.08884 10.6036 10.4772 4.14814 18.4155 3.41685C25.1315 2.79904 31.1104 6.1718 34.2605 11.4358L34.2668 11.4484Z" fill="#005BCB"/>
<path d="M26.2526 27.9906C27.9348 26.8306 29.2326 25.1537 29.9193 23.1931C30.1021 22.6572 30.247 22.0962 30.3352 21.5225C30.3919 21.1758 30.0643 20.8858 29.7303 20.9929L26.6369 21.9638C26.5046 22.0079 26.3975 22.1088 26.3471 22.2412C25.3706 24.6809 22.8505 26.3263 20.0028 25.9795C17.2244 25.6391 15.0005 23.3318 14.7548 20.5454C14.6729 19.6123 14.8052 18.7171 15.1013 17.9039C15.9518 15.6092 18.1632 13.9701 20.7526 13.9701C22.2142 13.9701 23.5561 14.4933 24.602 15.3633C24.7217 15.4642 24.8855 15.502 25.043 15.4516L27.9789 14.5311C28.1427 14.4807 28.2498 14.3546 28.2939 14.2159C28.3317 14.0709 28.3128 13.907 28.2057 13.7809C26.3093 11.5051 23.386 10.112 20.1414 10.3074C20.1036 10.3137 20.0658 10.32 20.028 10.32L19.0263 7.13005C18.9003 6.71398 18.4593 6.48071 18.0435 6.60679L15.7628 7.31917C15.347 7.45156 15.1139 7.89285 15.2462 8.30893L15.9896 10.6856C16.1219 11.1017 15.9581 11.5556 15.5927 11.7888C13.7845 12.9362 12.367 14.6635 11.6299 16.7061C11.3274 17.5256 11.1384 18.3956 11.0754 19.2971C11.0565 19.6312 11.0502 19.959 11.0628 20.2806C11.0817 20.7155 10.7982 21.1064 10.3824 21.2388L7.87493 22.0457C7.45912 22.1781 7.22601 22.6194 7.35201 23.0355L8.06394 25.3176C8.19624 25.7337 8.63726 25.9606 9.05308 25.8345L11.6047 25.0087C12.0079 24.8826 12.4552 25.0276 12.6883 25.3806C14.4271 27.9843 17.3883 29.7053 20.7526 29.7053C20.8975 29.7053 21.0361 29.7053 21.1747 29.7053C21.6157 29.6801 22.0126 29.9638 22.1449 30.3799L22.9198 32.8763C23.0521 33.2924 23.4931 33.5257 23.9089 33.3933L26.1896 32.6809C26.6054 32.5548 26.8322 32.1072 26.7062 31.6911L25.8872 29.0812C25.7612 28.6777 25.9061 28.2365 26.2526 27.9969V27.9906Z" fill="#005BCB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.2668 11.4484C34.4117 11.6943 34.7078 11.814 34.985 11.7321L39.5905 10.3514C39.8362 10.2821 40 10.0552 40 9.80929V1.11585C40 0.498042 39.5023 0 38.8849 0H0.932437C0.371719 0 0.0315029 0.378271 0.0315029 0.926734L0 20.8353V38.8841C0 39.502 0.497719 40 1.11514 40H39.0676C39.6283 40 39.9685 39.6218 39.9685 39.067L40 19.1647V19.1016C40 18.3641 39.8929 17.8534 38.8912 18.1371L37.083 18.673C36.7932 18.7612 36.6042 19.0323 36.6168 19.3286L36.6483 19.9969C36.6483 27.9212 32.5217 33.6076 25.2386 35.8393C13.3249 39.4894 2.45708 30.134 3.42102 18.5532C4.08884 10.6036 10.4772 4.14814 18.4155 3.41685C25.1315 2.79904 31.1104 6.1718 34.2605 11.4358L34.2668 11.4484Z" fill="#24282E"/>
<path d="M26.2526 27.9906C27.9348 26.8306 29.2326 25.1537 29.9193 23.1931C30.1021 22.6572 30.247 22.0962 30.3352 21.5225C30.3919 21.1758 30.0643 20.8858 29.7303 20.9929L26.6369 21.9638C26.5046 22.0079 26.3975 22.1088 26.3471 22.2412C25.3706 24.6809 22.8505 26.3263 20.0028 25.9795C17.2244 25.6391 15.0005 23.3318 14.7548 20.5454C14.6729 19.6123 14.8052 18.7171 15.1013 17.9039C15.9518 15.6092 18.1632 13.9701 20.7526 13.9701C22.2142 13.9701 23.5561 14.4933 24.602 15.3633C24.7217 15.4642 24.8855 15.502 25.043 15.4516L27.9789 14.5311C28.1427 14.4807 28.2498 14.3546 28.2939 14.2159C28.3317 14.0709 28.3128 13.907 28.2057 13.7809C26.3093 11.5051 23.386 10.112 20.1414 10.3074C20.1036 10.3137 20.0658 10.32 20.028 10.32L19.0263 7.13005C18.9003 6.71398 18.4593 6.48071 18.0435 6.60679L15.7628 7.31917C15.347 7.45156 15.1139 7.89285 15.2462 8.30893L15.9896 10.6856C16.1219 11.1017 15.9581 11.5556 15.5927 11.7888C13.7845 12.9362 12.367 14.6635 11.6299 16.7061C11.3274 17.5256 11.1384 18.3956 11.0754 19.2971C11.0565 19.6312 11.0502 19.959 11.0628 20.2806C11.0817 20.7155 10.7982 21.1064 10.3824 21.2388L7.87493 22.0457C7.45912 22.1781 7.22601 22.6194 7.35201 23.0355L8.06394 25.3176C8.19624 25.7337 8.63726 25.9606 9.05308 25.8345L11.6047 25.0087C12.0079 24.8826 12.4552 25.0276 12.6883 25.3806C14.4271 27.9843 17.3883 29.7053 20.7526 29.7053C20.8975 29.7053 21.0361 29.7053 21.1747 29.7053C21.6157 29.6801 22.0126 29.9638 22.1449 30.3799L22.9198 32.8763C23.0521 33.2924 23.4931 33.5257 23.9089 33.3933L26.1896 32.6809C26.6054 32.5548 26.8322 32.1072 26.7062 31.6911L25.8872 29.0812C25.7612 28.6777 25.9061 28.2365 26.2526 27.9969V27.9906Z" fill="#24282E"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,15 @@
<svg width="264" height="40" viewBox="0 0 264 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.3379 32.3839H67.4132V36.3293H52.4526V6.53737H57.3379V32.3839Z" fill="#005BCB"/>
<path d="M91.7161 26.3419H73.6726C73.8172 28.2268 74.5144 29.745 75.7744 30.8863C77.0293 32.0276 78.5734 32.6008 80.4015 32.6008C83.0301 32.6008 84.8892 31.5008 85.9736 29.3009H91.2462C90.5336 31.475 89.2374 33.2515 87.3679 34.6355C85.4985 36.0195 83.1747 36.7166 80.4015 36.7166C78.1448 36.7166 76.1204 36.2105 74.3336 35.1932C72.5469 34.181 71.1474 32.7506 70.1352 30.907C69.123 29.0634 68.6118 26.9306 68.6118 24.4983C68.6118 22.066 69.1024 19.9332 70.0887 18.0896C71.0751 16.246 72.4591 14.8258 74.2459 13.824C76.0327 12.8221 78.0828 12.3212 80.3963 12.3212C82.7099 12.3212 84.6103 12.8066 86.3558 13.7775C88.1012 14.7484 89.4543 16.1117 90.4303 17.8727C91.4011 19.6285 91.8866 21.6528 91.8866 23.9354C91.8866 24.8236 91.8298 25.6241 91.7161 26.3367V26.3419ZM86.7844 22.3965C86.7534 20.5942 86.1131 19.1534 84.8582 18.0689C83.5981 16.9844 82.0437 16.4422 80.1846 16.4422C78.4959 16.4422 77.0552 16.9793 75.8571 18.0483C74.659 19.1224 73.9412 20.5684 73.714 22.3965H86.7844Z" fill="#005BCB"/>
<path d="M97.9488 13.5245C99.307 11.2109 101.151 9.40347 103.48 8.10211C105.809 6.80074 108.36 6.15006 111.133 6.15006C114.304 6.15006 117.128 6.92985 119.597 8.48425C122.071 10.0438 123.862 12.2489 124.978 15.1047H119.106C118.337 13.5348 117.263 12.3625 115.894 11.5879C114.526 10.8133 112.935 10.4311 111.138 10.4311C109.165 10.4311 107.41 10.8752 105.865 11.7583C104.321 12.6466 103.113 13.9169 102.245 15.5746C101.373 17.2323 100.939 19.1585 100.939 21.3585C100.939 23.5584 101.373 25.4898 102.245 27.1475C103.118 28.8052 104.327 30.0859 105.865 30.9844C107.404 31.883 109.165 32.3323 111.138 32.3323C112.94 32.3323 114.526 31.9449 115.894 31.1755C117.263 30.406 118.337 29.2338 119.106 27.6587H124.978C123.862 30.5145 122.071 32.7144 119.597 34.2585C117.123 35.8026 114.304 36.572 111.133 36.572C108.334 36.572 105.772 35.9213 103.459 34.62C101.145 33.3186 99.307 31.5112 97.9488 29.1976C96.5907 26.8841 95.9141 24.2659 95.9141 21.3533C95.9141 18.4407 96.5907 15.8225 97.9488 13.509V13.5245Z" fill="#005BCB"/>
<path d="M133.55 35.1932C131.748 34.181 130.333 32.7506 129.305 30.907C128.278 29.0634 127.761 26.9306 127.761 24.4983C127.761 22.066 128.288 19.9693 129.347 18.1102C130.405 16.2511 131.846 14.8258 133.674 13.824C135.502 12.8221 137.547 12.3212 139.804 12.3212C142.061 12.3212 144.106 12.8221 145.934 13.824C147.762 14.8258 149.208 16.2511 150.262 18.1102C151.32 19.9693 151.847 22.0969 151.847 24.4983C151.847 26.8996 151.305 29.0272 150.22 30.8863C149.136 32.7454 147.654 34.181 145.784 35.1932C143.915 36.2054 141.849 36.7166 139.592 36.7166C137.336 36.7166 135.347 36.2105 133.55 35.1932ZM143.125 31.568C144.225 30.9689 145.118 30.0652 145.805 28.8671C146.492 27.669 146.833 26.2076 146.833 24.4931C146.833 22.7786 146.502 21.3275 145.846 20.1449C145.19 18.9571 144.318 18.0637 143.233 17.4647C142.149 16.8657 140.976 16.5661 139.716 16.5661C138.456 16.5661 137.294 16.8657 136.225 17.4647C135.151 18.0637 134.304 18.9571 133.674 20.1449C133.044 21.3326 132.729 22.7786 132.729 24.4931C132.729 27.039 133.38 29.0014 134.681 30.3854C135.983 31.7694 137.62 32.4665 139.587 32.4665C140.842 32.4665 142.025 32.167 143.125 31.568Z" fill="#005BCB"/>
<path d="M161.964 16.6952V36.3241H157.037V16.6952H154.254V12.7085H157.037V11.0353C157.037 8.319 157.76 6.34114 159.201 5.09658C160.642 3.85202 162.909 3.23232 165.997 3.23232V7.30683C164.51 7.30683 163.467 7.58569 162.868 8.14342C162.269 8.70115 161.969 9.66684 161.969 11.0353V12.7085H166.343V16.6952H161.964Z" fill="#005BCB"/>
<path d="M177.049 16.6952V36.3241H172.122V16.6952H169.339V12.7085H172.122V11.0353C172.122 8.319 172.845 6.34114 174.286 5.09658C175.727 3.85202 177.994 3.23232 181.082 3.23232V7.30683C179.595 7.30683 178.552 7.58569 177.953 8.14342C177.354 8.70115 177.054 9.66684 177.054 11.0353V12.7085H181.428V16.6952H177.049Z" fill="#005BCB"/>
<path d="M192.587 13.3334C193.775 12.662 195.18 12.3264 196.811 12.3264V17.3821H195.567C193.651 17.3821 192.205 17.8675 191.219 18.8384C190.232 19.8092 189.742 21.4979 189.742 23.894V36.3241H184.856V12.7085H189.742V16.1375C190.454 14.9394 191.405 13.9996 192.592 13.3282L192.587 13.3334Z" fill="#005BCB"/>
<path d="M221.414 26.3419H203.37C203.515 28.2268 204.212 29.745 205.472 30.8863C206.727 32.0276 208.271 32.6008 210.099 32.6008C212.728 32.6008 214.587 31.5008 215.671 29.3009H220.944C220.231 31.475 218.935 33.2515 217.066 34.6355C215.196 36.0195 212.872 36.7166 210.099 36.7166C207.842 36.7166 205.818 36.2105 204.031 35.1932C202.244 34.181 200.845 32.7506 199.833 30.907C198.821 29.0634 198.309 26.9306 198.309 24.4983C198.309 22.066 198.8 19.9332 199.786 18.0896C200.773 16.246 202.157 14.8258 203.943 13.824C205.73 12.8221 207.78 12.3212 210.094 12.3212C212.408 12.3212 214.308 12.8066 216.053 13.7775C217.799 14.7484 219.152 16.1117 220.128 17.8727C221.099 19.6285 221.584 21.6528 221.584 23.9354C221.584 24.8236 221.527 25.6241 221.414 26.3367V26.3419ZM216.482 22.3965C216.451 20.5942 215.811 19.1534 214.556 18.0689C213.296 16.9844 211.741 16.4422 209.882 16.4422C208.194 16.4422 206.753 16.9793 205.555 18.0483C204.357 19.1224 203.639 20.5684 203.412 22.3965H216.482Z" fill="#005BCB"/>
<path d="M226.04 35.9833C225.725 35.6683 225.57 35.2707 225.57 34.7852C225.57 34.2998 225.725 33.9022 226.04 33.5871C226.355 33.2721 226.753 33.1172 227.238 33.1172C227.693 33.1172 228.08 33.2721 228.395 33.5871C228.71 33.9022 228.865 34.2998 228.865 34.7852C228.865 35.2707 228.71 35.6735 228.395 35.9833C228.08 36.2983 227.693 36.4533 227.238 36.4533C226.753 36.4533 226.35 36.2983 226.04 35.9833Z" fill="#005BCB"/>
<path d="M233.756 7.41528C233.441 7.11576 233.286 6.70779 233.286 6.19138C233.286 5.70595 233.441 5.30831 233.756 4.9933C234.071 4.67828 234.459 4.52336 234.913 4.52336C235.368 4.52336 235.755 4.67828 236.07 4.9933C236.385 5.30831 236.54 5.70595 236.54 6.19138C236.54 6.70779 236.385 7.11059 236.07 7.41528C235.755 7.7148 235.368 7.86456 234.913 7.86456C234.459 7.86456 234.071 7.7148 233.756 7.41528ZM235.858 13.0545V36.3293H233.927V13.0545H235.858Z" fill="#005BCB"/>
<path d="M246.145 35.2139C244.399 34.243 243.036 32.8487 242.05 31.0361C241.063 29.2235 240.573 27.101 240.573 24.6687C240.573 22.2364 241.074 20.1604 242.07 18.3478C243.072 16.5352 244.451 15.146 246.207 14.1906C247.963 13.2353 249.93 12.755 252.099 12.755C254.268 12.755 256.23 13.2353 257.971 14.1906C259.716 15.146 261.079 16.5248 262.066 18.3271C263.052 20.1294 263.543 22.2415 263.543 24.6687C263.543 27.0958 263.042 29.2183 262.045 31.0361C261.043 32.8487 259.665 34.243 257.909 35.2139C256.153 36.1847 254.185 36.6701 252.016 36.6701C249.847 36.6701 247.885 36.1847 246.145 35.2139ZM256.752 33.8195C258.224 33.0604 259.401 31.914 260.289 30.3699C261.172 28.8258 261.617 26.9254 261.617 24.6687C261.617 22.412 261.172 20.5529 260.289 19.0088C259.401 17.4647 258.234 16.3183 256.773 15.5591C255.316 14.8 253.741 14.423 252.058 14.423C250.374 14.423 248.809 14.8 247.364 15.5591C245.923 16.3183 244.756 17.4647 243.873 19.0088C242.984 20.5529 242.545 22.4378 242.545 24.6687C242.545 26.8996 242.979 28.8258 243.852 30.3699C244.725 31.914 245.881 33.0656 247.322 33.8195C248.763 34.5787 250.328 34.9557 252.016 34.9557C253.705 34.9557 255.28 34.5787 256.752 33.8195Z" fill="#005BCB"/>
<path d="M34.2668 11.4484C34.4117 11.6943 34.7078 11.814 34.985 11.7321L39.5905 10.3514C39.8362 10.2821 40 10.0552 40 9.80929V1.11585C40 0.498042 39.5023 0 38.8849 0H0.932437C0.371719 0 0.0315029 0.378271 0.0315029 0.926734L0 20.8353V38.8841C0 39.502 0.497719 40 1.11514 40H39.0676C39.6283 40 39.9685 39.6218 39.9685 39.067L40 19.1647V19.1016C40 18.3641 39.8929 17.8534 38.8912 18.1371L37.083 18.673C36.7932 18.7612 36.6042 19.0323 36.6168 19.3286L36.6483 19.9969C36.6483 27.9212 32.5217 33.6076 25.2386 35.8393C13.3249 39.4894 2.45708 30.134 3.42102 18.5532C4.08884 10.6036 10.4772 4.14814 18.4155 3.41685C25.1315 2.79904 31.1104 6.1718 34.2605 11.4358L34.2668 11.4484Z" fill="#005BCB"/>
<path d="M26.2526 27.9906C27.9348 26.8306 29.2326 25.1537 29.9193 23.1931C30.1021 22.6572 30.247 22.0962 30.3352 21.5225C30.3919 21.1758 30.0643 20.8858 29.7303 20.9929L26.6369 21.9638C26.5046 22.0079 26.3975 22.1088 26.3471 22.2412C25.3706 24.6809 22.8505 26.3263 20.0028 25.9795C17.2244 25.6391 15.0005 23.3318 14.7548 20.5454C14.6729 19.6123 14.8052 18.7171 15.1013 17.9039C15.9518 15.6092 18.1632 13.9701 20.7526 13.9701C22.2142 13.9701 23.5561 14.4933 24.602 15.3633C24.7217 15.4642 24.8855 15.502 25.043 15.4516L27.9789 14.5311C28.1427 14.4807 28.2498 14.3546 28.2939 14.2159C28.3317 14.0709 28.3128 13.907 28.2057 13.7809C26.3093 11.5051 23.386 10.112 20.1414 10.3074C20.1036 10.3137 20.0658 10.32 20.028 10.32L19.0263 7.13005C18.9003 6.71398 18.4593 6.48071 18.0435 6.60679L15.7628 7.31917C15.347 7.45156 15.1139 7.89285 15.2462 8.30893L15.9896 10.6856C16.1219 11.1017 15.9581 11.5556 15.5927 11.7888C13.7845 12.9362 12.367 14.6635 11.6299 16.7061C11.3274 17.5256 11.1384 18.3956 11.0754 19.2971C11.0565 19.6312 11.0502 19.959 11.0628 20.2806C11.0817 20.7155 10.7982 21.1064 10.3824 21.2388L7.87493 22.0457C7.45912 22.1781 7.22601 22.6194 7.35201 23.0355L8.06394 25.3176C8.19624 25.7337 8.63726 25.9606 9.05308 25.8345L11.6047 25.0087C12.0079 24.8826 12.4552 25.0276 12.6883 25.3806C14.4271 27.9843 17.3883 29.7053 20.7526 29.7053C20.8975 29.7053 21.0361 29.7053 21.1747 29.7053C21.6157 29.6801 22.0126 29.9638 22.1449 30.3799L22.9198 32.8763C23.0521 33.2924 23.4931 33.5257 23.9089 33.3933L26.1896 32.6809C26.6054 32.5548 26.8322 32.1072 26.7062 31.6911L25.8872 29.0812C25.7612 28.6777 25.9061 28.2365 26.2526 27.9969V27.9906Z" fill="#005BCB"/>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,15 @@
<svg width="264" height="40" viewBox="0 0 264 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.3379 32.3839H67.4132V36.3293H52.4526V6.53737H57.3379V32.3839Z" fill="#24282E"/>
<path d="M91.7161 26.3419H73.6726C73.8172 28.2268 74.5144 29.745 75.7744 30.8863C77.0293 32.0276 78.5734 32.6008 80.4015 32.6008C83.0301 32.6008 84.8892 31.5008 85.9736 29.3009H91.2462C90.5336 31.475 89.2374 33.2515 87.3679 34.6355C85.4985 36.0195 83.1747 36.7166 80.4015 36.7166C78.1448 36.7166 76.1204 36.2105 74.3336 35.1932C72.5469 34.181 71.1474 32.7506 70.1352 30.907C69.123 29.0634 68.6118 26.9306 68.6118 24.4983C68.6118 22.066 69.1024 19.9332 70.0887 18.0896C71.0751 16.246 72.4591 14.8258 74.2459 13.824C76.0327 12.8221 78.0828 12.3212 80.3963 12.3212C82.7099 12.3212 84.6103 12.8066 86.3558 13.7775C88.1012 14.7484 89.4543 16.1117 90.4303 17.8727C91.4011 19.6285 91.8866 21.6528 91.8866 23.9354C91.8866 24.8236 91.8298 25.6241 91.7161 26.3367V26.3419ZM86.7844 22.3965C86.7534 20.5942 86.1131 19.1534 84.8582 18.0689C83.5981 16.9844 82.0437 16.4422 80.1846 16.4422C78.4959 16.4422 77.0552 16.9793 75.8571 18.0483C74.659 19.1224 73.9412 20.5684 73.714 22.3965H86.7844Z" fill="#24282E"/>
<path d="M97.9488 13.5245C99.307 11.2109 101.151 9.40347 103.48 8.10211C105.809 6.80074 108.36 6.15006 111.133 6.15006C114.304 6.15006 117.128 6.92985 119.597 8.48425C122.071 10.0438 123.862 12.2489 124.978 15.1047H119.106C118.337 13.5348 117.263 12.3625 115.894 11.5879C114.526 10.8133 112.935 10.4311 111.138 10.4311C109.165 10.4311 107.41 10.8752 105.865 11.7583C104.321 12.6466 103.113 13.9169 102.245 15.5746C101.373 17.2323 100.939 19.1585 100.939 21.3585C100.939 23.5584 101.373 25.4898 102.245 27.1475C103.118 28.8052 104.327 30.0859 105.865 30.9844C107.404 31.883 109.165 32.3323 111.138 32.3323C112.94 32.3323 114.526 31.9449 115.894 31.1755C117.263 30.406 118.337 29.2338 119.106 27.6587H124.978C123.862 30.5145 122.071 32.7144 119.597 34.2585C117.123 35.8026 114.304 36.572 111.133 36.572C108.334 36.572 105.772 35.9213 103.459 34.62C101.145 33.3186 99.307 31.5112 97.9488 29.1976C96.5907 26.8841 95.9141 24.2659 95.9141 21.3533C95.9141 18.4407 96.5907 15.8225 97.9488 13.509V13.5245Z" fill="#24282E"/>
<path d="M133.55 35.1932C131.748 34.181 130.333 32.7506 129.305 30.907C128.278 29.0634 127.761 26.9306 127.761 24.4983C127.761 22.066 128.288 19.9693 129.347 18.1102C130.405 16.2511 131.846 14.8258 133.674 13.824C135.502 12.8221 137.547 12.3212 139.804 12.3212C142.061 12.3212 144.106 12.8221 145.934 13.824C147.762 14.8258 149.208 16.2511 150.262 18.1102C151.32 19.9693 151.847 22.0969 151.847 24.4983C151.847 26.8996 151.305 29.0272 150.22 30.8863C149.136 32.7454 147.654 34.181 145.784 35.1932C143.915 36.2054 141.849 36.7166 139.592 36.7166C137.336 36.7166 135.347 36.2105 133.55 35.1932ZM143.125 31.568C144.225 30.9689 145.118 30.0652 145.805 28.8671C146.492 27.669 146.833 26.2076 146.833 24.4931C146.833 22.7786 146.502 21.3275 145.846 20.1449C145.19 18.9571 144.318 18.0637 143.233 17.4647C142.149 16.8657 140.976 16.5661 139.716 16.5661C138.456 16.5661 137.294 16.8657 136.225 17.4647C135.151 18.0637 134.304 18.9571 133.674 20.1449C133.044 21.3326 132.729 22.7786 132.729 24.4931C132.729 27.039 133.38 29.0014 134.681 30.3854C135.983 31.7694 137.62 32.4665 139.587 32.4665C140.842 32.4665 142.025 32.167 143.125 31.568Z" fill="#24282E"/>
<path d="M161.964 16.6952V36.3241H157.037V16.6952H154.254V12.7085H157.037V11.0353C157.037 8.319 157.76 6.34114 159.201 5.09658C160.642 3.85202 162.909 3.23232 165.997 3.23232V7.30683C164.51 7.30683 163.467 7.58569 162.868 8.14342C162.269 8.70115 161.969 9.66684 161.969 11.0353V12.7085H166.343V16.6952H161.964Z" fill="#24282E"/>
<path d="M177.049 16.6952V36.3241H172.122V16.6952H169.339V12.7085H172.122V11.0353C172.122 8.319 172.845 6.34114 174.286 5.09658C175.727 3.85202 177.994 3.23232 181.082 3.23232V7.30683C179.595 7.30683 178.552 7.58569 177.953 8.14342C177.354 8.70115 177.054 9.66684 177.054 11.0353V12.7085H181.428V16.6952H177.049Z" fill="#24282E"/>
<path d="M192.587 13.3334C193.775 12.662 195.18 12.3264 196.811 12.3264V17.3821H195.567C193.651 17.3821 192.205 17.8675 191.219 18.8384C190.232 19.8092 189.742 21.4979 189.742 23.894V36.3241H184.856V12.7085H189.742V16.1375C190.454 14.9394 191.405 13.9996 192.592 13.3282L192.587 13.3334Z" fill="#24282E"/>
<path d="M221.414 26.3419H203.37C203.515 28.2268 204.212 29.745 205.472 30.8863C206.727 32.0276 208.271 32.6008 210.099 32.6008C212.728 32.6008 214.587 31.5008 215.671 29.3009H220.944C220.231 31.475 218.935 33.2515 217.066 34.6355C215.196 36.0195 212.872 36.7166 210.099 36.7166C207.842 36.7166 205.818 36.2105 204.031 35.1932C202.244 34.181 200.845 32.7506 199.833 30.907C198.821 29.0634 198.309 26.9306 198.309 24.4983C198.309 22.066 198.8 19.9332 199.786 18.0896C200.773 16.246 202.157 14.8258 203.943 13.824C205.73 12.8221 207.78 12.3212 210.094 12.3212C212.408 12.3212 214.308 12.8066 216.053 13.7775C217.799 14.7484 219.152 16.1117 220.128 17.8727C221.099 19.6285 221.584 21.6528 221.584 23.9354C221.584 24.8236 221.527 25.6241 221.414 26.3367V26.3419ZM216.482 22.3965C216.451 20.5942 215.811 19.1534 214.556 18.0689C213.296 16.9844 211.741 16.4422 209.882 16.4422C208.194 16.4422 206.753 16.9793 205.555 18.0483C204.357 19.1224 203.639 20.5684 203.412 22.3965H216.482Z" fill="#24282E"/>
<path d="M226.04 35.9833C225.725 35.6683 225.57 35.2707 225.57 34.7852C225.57 34.2998 225.725 33.9022 226.04 33.5871C226.355 33.2721 226.753 33.1172 227.238 33.1172C227.693 33.1172 228.08 33.2721 228.395 33.5871C228.71 33.9022 228.865 34.2998 228.865 34.7852C228.865 35.2707 228.71 35.6735 228.395 35.9833C228.08 36.2983 227.693 36.4533 227.238 36.4533C226.753 36.4533 226.35 36.2983 226.04 35.9833Z" fill="#24282E"/>
<path d="M233.756 7.41528C233.441 7.11576 233.286 6.70779 233.286 6.19138C233.286 5.70595 233.441 5.30831 233.756 4.9933C234.071 4.67828 234.459 4.52336 234.913 4.52336C235.368 4.52336 235.755 4.67828 236.07 4.9933C236.385 5.30831 236.54 5.70595 236.54 6.19138C236.54 6.70779 236.385 7.11059 236.07 7.41528C235.755 7.7148 235.368 7.86456 234.913 7.86456C234.459 7.86456 234.071 7.7148 233.756 7.41528ZM235.858 13.0545V36.3293H233.927V13.0545H235.858Z" fill="#24282E"/>
<path d="M246.145 35.2139C244.399 34.243 243.036 32.8487 242.05 31.0361C241.063 29.2235 240.573 27.101 240.573 24.6687C240.573 22.2364 241.074 20.1604 242.07 18.3478C243.072 16.5352 244.451 15.146 246.207 14.1906C247.963 13.2353 249.93 12.755 252.099 12.755C254.268 12.755 256.23 13.2353 257.971 14.1906C259.716 15.146 261.079 16.5248 262.066 18.3271C263.052 20.1294 263.543 22.2415 263.543 24.6687C263.543 27.0958 263.042 29.2183 262.045 31.0361C261.043 32.8487 259.665 34.243 257.909 35.2139C256.153 36.1847 254.185 36.6701 252.016 36.6701C249.847 36.6701 247.885 36.1847 246.145 35.2139ZM256.752 33.8195C258.224 33.0604 259.401 31.914 260.289 30.3699C261.172 28.8258 261.617 26.9254 261.617 24.6687C261.617 22.412 261.172 20.5529 260.289 19.0088C259.401 17.4647 258.234 16.3183 256.773 15.5591C255.316 14.8 253.741 14.423 252.058 14.423C250.374 14.423 248.809 14.8 247.364 15.5591C245.923 16.3183 244.756 17.4647 243.873 19.0088C242.984 20.5529 242.545 22.4378 242.545 24.6687C242.545 26.8996 242.979 28.8258 243.852 30.3699C244.725 31.914 245.881 33.0656 247.322 33.8195C248.763 34.5787 250.328 34.9557 252.016 34.9557C253.705 34.9557 255.28 34.5787 256.752 33.8195Z" fill="#24282E"/>
<path d="M34.2668 11.4484C34.4117 11.6943 34.7078 11.814 34.985 11.7321L39.5905 10.3514C39.8362 10.2821 40 10.0552 40 9.80929V1.11585C40 0.498042 39.5023 0 38.8849 0H0.932437C0.371719 0 0.0315029 0.378271 0.0315029 0.926734L0 20.8353V38.8841C0 39.502 0.497719 40 1.11514 40H39.0676C39.6283 40 39.9685 39.6218 39.9685 39.067L40 19.1647V19.1016C40 18.3641 39.8929 17.8534 38.8912 18.1371L37.083 18.673C36.7932 18.7612 36.6042 19.0323 36.6168 19.3286L36.6483 19.9969C36.6483 27.9212 32.5217 33.6076 25.2386 35.8393C13.3249 39.4894 2.45708 30.134 3.42102 18.5532C4.08884 10.6036 10.4772 4.14814 18.4155 3.41685C25.1315 2.79904 31.1104 6.1718 34.2605 11.4358L34.2668 11.4484Z" fill="#24282E"/>
<path d="M26.2526 27.9906C27.9348 26.8306 29.2326 25.1537 29.9193 23.1931C30.1021 22.6572 30.247 22.0962 30.3352 21.5225C30.3919 21.1758 30.0643 20.8858 29.7303 20.9929L26.6369 21.9638C26.5046 22.0079 26.3975 22.1088 26.3471 22.2412C25.3706 24.6809 22.8505 26.3263 20.0028 25.9795C17.2244 25.6391 15.0005 23.3318 14.7548 20.5454C14.6729 19.6123 14.8052 18.7171 15.1013 17.9039C15.9518 15.6092 18.1632 13.9701 20.7526 13.9701C22.2142 13.9701 23.5561 14.4933 24.602 15.3633C24.7217 15.4642 24.8855 15.502 25.043 15.4516L27.9789 14.5311C28.1427 14.4807 28.2498 14.3546 28.2939 14.2159C28.3317 14.0709 28.3128 13.907 28.2057 13.7809C26.3093 11.5051 23.386 10.112 20.1414 10.3074C20.1036 10.3137 20.0658 10.32 20.028 10.32L19.0263 7.13005C18.9003 6.71398 18.4593 6.48071 18.0435 6.60679L15.7628 7.31917C15.347 7.45156 15.1139 7.89285 15.2462 8.30893L15.9896 10.6856C16.1219 11.1017 15.9581 11.5556 15.5927 11.7888C13.7845 12.9362 12.367 14.6635 11.6299 16.7061C11.3274 17.5256 11.1384 18.3956 11.0754 19.2971C11.0565 19.6312 11.0502 19.959 11.0628 20.2806C11.0817 20.7155 10.7982 21.1064 10.3824 21.2388L7.87493 22.0457C7.45912 22.1781 7.22601 22.6194 7.35201 23.0355L8.06394 25.3176C8.19624 25.7337 8.63726 25.9606 9.05308 25.8345L11.6047 25.0087C12.0079 24.8826 12.4552 25.0276 12.6883 25.3806C14.4271 27.9843 17.3883 29.7053 20.7526 29.7053C20.8975 29.7053 21.0361 29.7053 21.1747 29.7053C21.6157 29.6801 22.0126 29.9638 22.1449 30.3799L22.9198 32.8763C23.0521 33.2924 23.4931 33.5257 23.9089 33.3933L26.1896 32.6809C26.6054 32.5548 26.8322 32.1072 26.7062 31.6911L25.8872 29.0812C25.7612 28.6777 25.9061 28.2365 26.2526 27.9969V27.9906Z" fill="#24282E"/>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,91 @@
@import "@Themes/constants.scss";
.root {
width: fit-content;
display: inline-flex;
padding: var(--spacing-2, 16px);
align-items: center;
gap: var(--spacing-lg, 24px);
border-radius: var(--alerts-radius, 0px);
border: 1px solid var(--alerts-info-border);
background: var(--alerts-info-background);
.content {
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
.text-container {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.button-container {
display: flex;
gap: var(--spacing-md, 16px);
}
}
.icon {
display: flex;
padding: var(--spacing-1, 8px);
align-items: center;
align-self: flex-start;
border-radius: var(--alerts-badge-radius, 360px);
border: 1px solid var(--alerts-badge-border, rgba(0, 0, 0, 0));
background: var(--alerts-badge-background, #fff);
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
svg {
width: 24px;
height: 24px;
min-width: 24px;
min-height: 24px;
stroke: var(--alerts-badge-contrast-info);
}
}
&.error {
border-color: var(--alerts-error-border);
background: var(--alerts-error-background);
.icon svg {
stroke: var(--alerts-badge-contrast-error);
}
}
&.warning {
border-color: var(--alerts-warning-border);
background: var(--alerts-warning-background);
.icon svg {
stroke: var(--alerts-badge-contrast-warning);
}
}
&.success {
border-color: var(--alerts-success-border);
background: var(--alerts-success-background);
.icon svg {
stroke: var(--alerts-badge-contrast-success);
}
}
&.neutral {
border-color: var(--alerts-neutral-border);
background: var(--alerts-neutral-background);
.icon svg {
stroke: var(--alerts-badge-contrast-neutral);
}
}
&.fullwidth {
width: 100%;
}
}

View File

@ -0,0 +1,81 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import React from "react";
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import classes from "./classes.module.scss";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant, IButtonProps } from "../Button";
import classNames from "classnames";
import IconButton from "../IconButton";
import useOpenable from "@Front/Hooks/useOpenable";
type IProps = {
variant: EAlertVariant;
title: string;
description: string;
icon?: React.ReactNode;
firstButton?: IButtonProps;
secondButton?: IButtonProps;
closeButton?: boolean;
fullWidth?: boolean;
};
export enum EAlertVariant {
INFO = "info",
SUCCESS = "success",
WARNING = "warning",
ERROR = "error",
NEUTRAL = "neutral",
}
const variantButtonMap: Record<EAlertVariant, EButtonVariant> = {
[EAlertVariant.INFO]: EButtonVariant.PRIMARY,
[EAlertVariant.SUCCESS]: EButtonVariant.SUCCESS,
[EAlertVariant.WARNING]: EButtonVariant.WARNING,
[EAlertVariant.ERROR]: EButtonVariant.ERROR,
[EAlertVariant.NEUTRAL]: EButtonVariant.NEUTRAL,
};
export default function Alert(props: IProps) {
const { isOpen, close } = useOpenable({ defaultOpen: true });
const { variant = EAlertVariant.INFO, title, description, firstButton, secondButton, closeButton, icon, fullWidth } = props;
if (!isOpen) return null;
return (
<div className={classNames(classes["root"], classes[variant], fullWidth && classes["fullwidth"])}>
<span className={classes["icon"]}>{icon ?? <InformationCircleIcon />}</span>
<div className={classes["content"]}>
<div className={classes["text-container"]}>
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
{title}
</Typography>
<Typography typo={ETypo.TEXT_MD_light} color={ETypoColor.COLOR_NEUTRAL_700}>
{description}
</Typography>
</div>
<div className={classes["button-container"]}>
{firstButton && (
<Button
{...firstButton}
size={firstButton.size ?? EButtonSize.MD}
variant={firstButton.variant ?? variantButtonMap[variant]}
styletype={firstButton.styletype ?? EButtonstyletype.OUTLINED}>
{firstButton.children}
</Button>
)}
{secondButton && (
<Button
{...secondButton}
size={secondButton.size ?? EButtonSize.MD}
variant={secondButton.variant ?? variantButtonMap[variant]}
styletype={secondButton.styletype ?? EButtonstyletype.OUTLINED}>
{secondButton.children}
</Button>
)}
</div>
</div>
{closeButton && <IconButton onClick={close} icon={<XMarkIcon />} />}
</div>
);
}

View File

@ -0,0 +1,24 @@
@import "@Themes/constants.scss";
.root {
display: flex;
align-items: center;
gap: var(--spacing-sm);
svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
.circleBackground {
fill: none;
stroke: var(--progress-circle-background);
}
.circleProgress {
fill: none;
stroke: var(--progress-circle-contrast);
transition: stroke-dashoffset 0.35s;
}
}
}

View File

@ -0,0 +1,62 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss";
type IProps = {
percentage: number;
};
export default function CircleProgress(props: IProps) {
const { percentage } = props;
const [animatedProgress, setAnimatedProgress] = useState(0);
const requestRef = useRef<number>();
const animate = useCallback(() => {
setAnimatedProgress((prev) => {
if (prev < percentage) {
return prev + 1;
} else {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current);
}
return prev;
}
});
requestRef.current = requestAnimationFrame(animate);
}, [percentage]);
useEffect(() => {
setAnimatedProgress(0); // Reset progress
requestRef.current = requestAnimationFrame(animate);
return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current);
}
};
}, [percentage, animate]);
const radius = 11;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (animatedProgress / 100) * circumference;
return (
<div className={classes["root"]}>
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewBox="0 0 27 27" fill="none">
<circle className={classes["circleBackground"]} cx="13.5" cy="13.5" r={radius} strokeWidth="3" />
<circle
className={classes["circleProgress"]}
cx="13.5"
cy="13.5"
r={radius}
strokeWidth="3"
strokeDasharray={circumference}
strokeDashoffset={offset}
/>
</svg>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{Math.round(percentage)}%
</Typography>
</div>
);
}

View File

@ -0,0 +1,28 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: var(--spacing-3, 24px);
gap: var(--spacing-lg, 24px);
border-radius: var(--radius-minimal, 8px);
background: var(--primary-weak-higlight, #e5eefa);
text-align: center;
.text {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--spacing-md, 16px);
}
svg {
width: 32px;
stroke: var(--primary-weak-contrast);
}
}

View File

@ -0,0 +1,30 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import React from "react";
import classes from "./classes.module.scss";
type IProps = {
icon: React.ReactNode;
title: string;
description: string;
footer?: React.ReactNode;
};
export default function EmptyAlert(props: IProps) {
const { icon, title, description, footer } = props;
return (
<div className={classes["root"]}>
{icon}
<div className={classes["text"]}>
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
{title}
</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
{description}
</Typography>
</div>
{footer}
</div>
);
}

View File

@ -0,0 +1,101 @@
@import "@Themes/constants.scss";
.root {
cursor: pointer;
border-radius: var(--button-icon-button-radius, 8px);
display: inline-flex;
padding: var(--spacing-sm, 8px);
align-items: flex-start;
svg {
width: 24px;
min-width: 24px;
stroke: var(--button-icon-button-default-default);
}
&:hover {
svg {
stroke: var(--button-icon-button-default-hovered);
}
}
&.neutral {
background: var(--button-icon-button-neutral-default);
svg {
stroke: var(--button-icon-button-default-default);
}
&:hover {
background: var(--button-icon-button-neutral-hovered);
svg {
stroke: var(--button-icon-button-default-hovered);
}
}
}
&.primary {
background: var(--button-icon-button-primary-default);
svg {
stroke: var(--button-icon-button-primary-contrast);
}
&:hover {
background: var(--button-icon-button-primary-hovered);
}
}
&.error {
background: var(--button-icon-button-error-default);
svg {
stroke: var(--button-icon-button-error-contrast);
}
&:hover {
background: var(--button-icon-button-error-hovered);
}
}
&.success {
background: var(--button-icon-button-success-default);
svg {
stroke: var(--button-icon-button-success-contrast);
}
&:hover {
background: var(--button-icon-button-success-hovered);
}
}
&.warning {
background: var(--button-icon-button-warning-default);
svg {
stroke: var(--button-icon-button-warning-contrast);
}
&:hover {
background: var(--button-icon-button-warning-hovered);
}
}
&.info {
background: var(--button-icon-button-info-default);
svg {
stroke: var(--button-icon-button-info-contrast);
}
&:hover {
background: var(--button-icon-button-info-hovered);
}
}
&.disabled {
cursor: default;
opacity: var(--opacity-disabled, 0.3);
}
}

View File

@ -0,0 +1,32 @@
import classNames from "classnames";
import React from "react";
import classes from "./classes.module.scss";
export enum EIconButtonVariant {
DEFAULT = "default",
NEUTRAL = "neutral",
PRIMARY = "primary",
ERROR = "error",
SUCCESS = "success",
WARNING = "warning",
INFO = "info",
}
type IProps = {
icon: React.ReactNode;
onClick?: React.MouseEventHandler<HTMLSpanElement> | undefined;
variant?: EIconButtonVariant;
disabled?: boolean;
className?: string;
};
export default function IconButton(props: IProps) {
const { icon, onClick, className, variant = EIconButtonVariant.DEFAULT, disabled = false } = props;
return (
<span onClick={onClick} className={classNames(classes["root"], className, classes[variant], disabled && classes["disabled"])}>
{icon}
</span>
);
}

View File

@ -0,0 +1,22 @@
@import "@Themes/constants.scss";
.button-container {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 8px;
button {
flex: 1;
}
.sub-container {
flex: 1;
display: flex;
}
@media (max-width: $screen-s) {
flex-direction: column-reverse;
gap: 8px;
}
}

View File

@ -0,0 +1,54 @@
import React from "react";
import OldModal, { IProps as IPropsModal } from "..";
import Button, { EButtonVariant } from "../../Button";
import classes from "./classes.module.scss";
type IProps = IPropsModal & {
closeText: string | JSX.Element;
};
type IState = {
isOpen: boolean;
};
export default class Alert extends React.Component<IProps, IState> {
static defaultProps = {
closeText: "Ok",
...OldModal.defaultProps,
};
constructor(props: IProps) {
super(props);
this.state = {
isOpen: this.props.isOpen ?? true,
};
this.onClose = this.onClose.bind(this);
}
public override render(): JSX.Element | null {
return (
<OldModal
closeBtn={this.props.closeBtn}
isOpen={this.state.isOpen}
onClose={this.onClose}
header={this.props.header}
footer={this.footer()}>
{this.props.children}
</OldModal>
);
}
private footer(): JSX.Element {
return (
<div className={classes["button-container"]}>
<Button variant={EButtonVariant.SECONDARY} onClick={this.onClose}>
{this.props.closeText}
</Button>
</div>
);
}
private onClose() {
this.setState({ isOpen: false });
this.props.onClose?.();
}
}

View File

@ -0,0 +1,22 @@
@import "@Themes/constants.scss";
.buttons-container {
display: flex;
justify-content: space-between;
gap: 16px;
margin-top: 8px;
button {
flex: 1;
}
.sub-container {
flex: 1;
display: flex;
}
@media (max-width: $screen-s) {
flex-direction: column-reverse;
gap: 8px;
}
}

View File

@ -0,0 +1,73 @@
import Link from "next/link";
import React from "react";
import OldModal, { IProps as IPropsModal } from "..";
import Button, { EButtonstyletype, EButtonVariant } from "../../Button";
import classes from "./classes.module.scss";
type IProps = IPropsModal & {
onAccept?: () => void;
cancelText: string | JSX.Element;
cancelPath?: string;
confirmText: string | JSX.Element;
showCancelButton: boolean;
showButtons: boolean;
canConfirm: boolean;
};
type IState = {};
export default class Confirm extends React.Component<IProps, IState> {
static defaultProps = {
showCancelButton: true,
cancelText: "Cancel",
confirmText: "Confirm",
canConfirm: true,
showButtons: true,
...OldModal.defaultProps,
};
public override render(): JSX.Element | null {
return (
<OldModal
closeBtn={this.props.closeBtn}
header={this.props.header}
footer={this.footer()}
animationDelay={this.props.animationDelay}
{...this.props}>
{this.props.children}
</OldModal>
);
}
private footer(): JSX.Element | null {
if (!this.props.showButtons) return null;
return (
<div className={classes["buttons-container"]}>
{this.props.showCancelButton &&
(this.props.cancelPath ? (
<Link href={this.props.cancelPath} className={classes["sub-container"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={this.props.onClose}>
{this.props.cancelText}
</Button>
</Link>
) : (
<div className={classes["sub-container"]}>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
onClick={this.props.onClose}
className={classes["sub-container"]}>
{this.props.cancelText}
</Button>
</div>
))}
<div className={classes["sub-container"]}>
<Button variant={EButtonVariant.PRIMARY} onClick={this.props.onAccept} disabled={!this.props.canConfirm} fullwidth>
{this.props.confirmText}
</Button>
</div>
</div>
);
}
}

View File

@ -0,0 +1,5 @@
@import "@Themes/constants.scss";
.root {
margin-top: 24px;
}

View File

@ -0,0 +1,12 @@
import React from "react";
import classes from "./classes.module.scss";
type IProps = {
content: JSX.Element;
};
export default class Footer extends React.Component<IProps> {
public override render(): JSX.Element {
return <footer className={classes["root"]}>{this.props.content}</footer>;
}
}

View File

@ -0,0 +1,12 @@
@import "@Themes/constants.scss";
.root {
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: flex-start;
h5 {
color: var(--color-neutral-900);
}
}

View File

@ -0,0 +1,17 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
type IProps = {
content: string | JSX.Element;
};
export default class Header extends React.Component<IProps> {
public override render(): JSX.Element {
return (
<header className={classes["root"]}>
<Typography typo={ETypo.TITLE_H3}>{this.props.content}</Typography>
</header>
);
}
}

View File

@ -0,0 +1,13 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
margin: 0;
padding: 40px;
margin-top: 24px;
}

View File

@ -0,0 +1,20 @@
// import Loader from "Components/Elements/Loader";
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
type IProps = {
text?: string | JSX.Element;
};
export default class PopUpLoader extends React.Component<IProps> {
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
{/* <Loader /> */}
TODO: INTEGRER LOARDER ISLOADING
<Typography typo={ETypo.TEXT_MD_REGULAR}>{this.props.text && this.props.text}</Typography>
</div>
);
}
}

View File

@ -0,0 +1,125 @@
@import "@Themes/constants.scss";
@keyframes smooth-appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes smooth-disappear {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.root {
position: fixed;
z-index: 6;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
--animation-delay: 1ms;
animation: smooth-appear var(--animation-delay) $custom-easing;
&[data-will-close="true"] {
animation: smooth-disappear var(--animation-delay) $custom-easing;
opacity: 0;
}
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: $modal-background;
}
.container {
position: relative;
width: 610px;
max-height: 90%;
background: var(--color-generic-white);
box-shadow: 0px 6px 12px rgba(255, 255, 255, 0.11);
overflow: auto;
padding: 32px;
@media (max-width: $screen-s) {
width: 90%;
max-width: 493px;
}
.cross {
display: flex;
flex-direction: row-reverse;
.close-icon {
height: 24px;
width: 24px;
cursor: pointer;
}
}
}
.transparant-background {
background-color: transparent;
box-shadow: none;
}
&[data-side-background="true"] {
.container {
max-width: 711px;
.sub-container {
padding: 0;
display: flex;
p {
max-width: 711px;
}
@media (max-width: $screen-s) {
display: block;
}
.banner {
@media (max-width: $screen-s) {
overflow: hidden;
}
}
}
}
.side-image {
height: 100%;
@media (max-width: $screen-s) {
display: none;
}
}
.top-image {
@media (min-width: $screen-s) {
display: none;
}
@media (max-width: $screen-s) {
width: 100%;
max-height: 82px;
min-height: 82px;
}
}
}
}

View File

@ -0,0 +1,111 @@
import CrossIcon from "@Assets/Icons/cross.svg";
import Image from "next/image";
import React from "react";
import Typography, { ETypo } from "../Typography";
import classes from "./classes.module.scss";
import Footer from "./Elements/Footer";
import Header from "./Elements/Header";
import Loader from "./Elements/Loader";
export type IProps = {
closeBtn?: boolean;
header?: string | JSX.Element;
footer?: JSX.Element | null;
textLoader?: string | JSX.Element;
isOpen: boolean;
onClose: () => void;
hasTransparentBackground?: boolean;
hasContainerClosable?: boolean;
withSideBackground?: boolean;
children?: React.ReactNode;
animationDelay?: number;
};
type IState = {
willClose: boolean;
isOpen: boolean;
};
export default class OldModal extends React.Component<IProps, IState> {
static defaultProps = {
animationDelay: 250,
};
public rootRefElement = React.createRef<HTMLDivElement>();
constructor(props: IProps) {
super(props);
this.close = this.close.bind(this);
this.state = {
willClose: false,
isOpen: this.props.isOpen,
};
}
public override render(): JSX.Element | null {
if (!this.state.isOpen) return null;
return (
<div
ref={this.rootRefElement}
className={classes["root"]}
data-side-background={this.props.withSideBackground}
data-will-close={this.state.willClose.toString()}>
<div className={classes["background"]} onClick={this.close} />
<div className={[classes["container"], this.props.hasTransparentBackground && classes["transparant-background"]].join(" ")}>
{this.props.closeBtn && (
<div className={classes["cross"]}>
<Image alt="Unplugged" src={CrossIcon} className={classes["close-icon"]} onClick={this.close} />
</div>
)}
<div className={classes["sub-container"]}>
{this.props.header && <Header content={this.props.header} />}
<Typography typo={ETypo.TEXT_MD_REGULAR}>
<>{this.props.children ? this.props.children : <Loader text={this.props.textLoader} />}</>
</Typography>
{this.props.children && this.props.footer && <Footer content={this.props.footer} />}
</div>
</div>
</div>
);
}
public override componentDidUpdate(prevProps: IProps): void {
if (prevProps.isOpen !== this.props.isOpen && !this.props.isOpen) {
this.setState({ willClose: true });
window.setTimeout(() => {
this.setState({
isOpen: false,
willClose: false,
});
}, this.props.animationDelay);
document.body.style.overflow = "auto";
}
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen) {
this.setState({ isOpen: true });
document.body.style.overflow = "hidden";
}
this.rootRefElement.current?.style.setProperty("--animation-delay", this.props.animationDelay!.toString().concat("ms"));
}
public override componentDidMount(): void {
document.addEventListener("keydown", this.handleKeyDown);
}
public override componentWillUnmount(): void {
document.body.style.overflow = "auto";
document.removeEventListener("keydown", this.handleKeyDown);
}
protected close() {
if (this.props.hasContainerClosable === false) return;
if (this.state.willClose) return;
this.props.onClose();
}
private handleKeyDown = (e: KeyboardEvent): void => {
if (e.key === "Escape" || e.key === "Esc") {
this.props.onClose();
}
};
}

View File

@ -0,0 +1,38 @@
.root {
.head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
overflow: hidden;
.text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
svg {
cursor: pointer;
min-width: 21px;
fill: var(--table-header-contrast);
}
}
.cell {
overflow: hidden;
word-wrap: break-word;
.content {
max-width: 270px;
width: 100%;
word-break: break-word;
}
}
.row {
&:hover {
background-color: var(--table-background-hovered);
}
}
}

View File

@ -0,0 +1,102 @@
import InfiniteScroll from "@Front/Components/Elements/InfiniteScroll";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Typography, { ETypo, ETypoColor } from "../../Typography";
import classes from "./classes.module.scss";
export type IRowProps = { key: string } & Record<string, React.ReactNode>;
type IRow = {
key?: string;
content: Record<string, CellContent>;
};
type IProps = {
header: readonly IHead[];
rows: IRowProps[];
onNext?: ((release: () => void, reset?: () => void) => Promise<void> | void) | null;
};
export type IHead = {
key: string;
title?: string;
};
type CellContent = {
key: string;
value: React.ReactNode;
};
export default function MuiTable(props: IProps) {
const rows: IRow[] = props.rows.map((rowProps) => {
const row: IRow = {
key: rowProps.key,
content: {},
};
props.header.forEach((column) => {
const cellContent: CellContent = {
key: column.key + rowProps.key,
value: rowProps[column.key],
};
row.content[column.key] = cellContent;
});
return row;
});
return (
<InfiniteScroll orientation="vertical" onNext={props.onNext} offset={0}>
<TableContainer
className={classes["root"]}
sx={{ maxHeight: "80vh", overflowY: "auto", overflowX: "hidden", backgroundColor: "var(--table-background-default)" }}>
<Table aria-label="simple table" sx={{ border: "0" }}>
<TableHead sx={{ position: "sticky", top: "0", borderBottom: "1px solid var(--table-header-border)" }}>
<TableRow>
{props.header.map((column) => (
<TableCell key={column.key} align={"left"} sx={{ border: 0, padding: "4px 8px" }}>
{column.title && (
<span className={classes["head"]}>
<Typography
className={classes["text"]}
typo={ETypo.TEXT_SM_REGULAR}
color={ETypoColor.COLOR_NEUTRAL_600}>
{column.title}
</Typography>
{/* <ChevronDownIcon width={21} /> */}
</span>
)}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => {
return (
<TableRow key={row.key} sx={{ verticalAlign: "middle" }} className={classes["row"]}>
{Object.values(row.content).map((cell) => (
<TableCell
className={classes["cell"]}
key={cell.key}
align="left"
sx={{ border: 0, padding: "4px 8px", height: "53px" }}>
<Typography
className={classes["content"]}
typo={ETypo.TEXT_MD_REGULAR}
color={ETypoColor.COLOR_NEUTRAL_900}>
{cell.value}
</Typography>
</TableCell>
))}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</InfiniteScroll>
);
}

View File

@ -0,0 +1,40 @@
.root {
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
width: 100%;
border: 1px solid var(--Wild-Sand-100, #efefef);
background-color: var(--Wild-Sand-50, #f6f6f6);
box-sizing: border-box;
.input-element {
padding: 10.5px 16px;
flex: 1;
border: 0;
background-color: transparent;
&:focus,
input:focus {
outline: none;
}
&::placeholder {
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
.icon {
position: absolute;
right: 10px;
width: 24px;
height: 24px;
pointer-events: none;
path {
stroke: var(--Wild-Sand-600, #bfbfbf);
}
}
}

View File

@ -0,0 +1,33 @@
import { ChangeEvent, useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useDebounce } from "@uidotdev/usehooks";
type IProps = {
onChange?: (search: string) => void;
placeholder: string;
};
export default function SearchBar(props: IProps) {
const { placeholder, onChange } = props;
const [search, setSearch] = useState<string | null>(null);
const debouncedSearch = useDebounce(search, 200);
const onSearch = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setSearch(e.currentTarget.value);
}, []);
useEffect(() => {
if (debouncedSearch === null) return;
onChange?.(debouncedSearch);
}, [debouncedSearch, onChange]);
return (
<div className={classes["root"]}>
<input className={classes["input-element"]} onChange={onSearch} type="text" placeholder={placeholder} />
<MagnifyingGlassIcon className={classes["icon"]} />
</div>
);
}

View File

@ -0,0 +1,13 @@
.root {
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
.input-container {
width: 300px;
cursor: text;
}
}
}

View File

@ -0,0 +1,45 @@
import classNames from "classnames";
import MuiTable, { IRowProps, IHead } from "./MuiTable";
import SearchBarTable from "./SearchBar";
import classes from "./classes.module.scss";
import { useCallback, useRef } from "react";
type IProps = {
header: readonly IHead[];
rows: IRowProps[];
count?: number;
className?: string;
onNext?: ((release: () => void, reset?: () => void) => Promise<void> | void) | null;
searchBar?: {
placeholder?: string;
onSearch?: (search: string) => void;
};
};
export default function Table(props: IProps) {
const { className, header, rows, searchBar } = props;
const keyId = useRef<number>(0);
const onSearch = useCallback(
(search: string) => {
keyId.current++;
searchBar?.onSearch?.(search);
},
[searchBar],
);
return (
<div className={classNames(classes["root"], className)}>
{searchBar && (
<div className={classes["header"]}>
<div>{props.count ?? rows.length} resultats</div>
<div className={classes["input-container"]}>
<SearchBarTable onChange={onSearch} placeholder={searchBar.placeholder ?? ""} />
</div>
</div>
)}
<MuiTable key={keyId.current} header={header} rows={rows} onNext={props.onNext} />
</div>
);
}

View File

@ -0,0 +1,27 @@
@import "@Themes/constants.scss";
.root {
width: fit-content;
padding: 2px 8px;
border-radius: 360px;
display: flex;
align-items: center;
justify-content: center;
&.info {
background-color: var(--tag-info-background);
}
&.success {
background-color: var(--tag-success-background);
}
&.warning {
background-color: var(--tag-warning-background);
}
&.error {
background-color: var(--tag-error-background);
}
}

View File

@ -0,0 +1,48 @@
import classNames from "classnames";
import React from "react";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss";
export enum ETagColor {
INFO = "info",
SUCCESS = "success",
ERROR = "error",
WARNING = "warning",
}
export enum ETagVariant {
REGULAR = "regular",
SEMI_BOLD = "semi_bold",
}
type IProps = {
label: string;
color: ETagColor;
variant?: ETagVariant;
className?: string;
};
const colorMap: Record<ETagColor, ETypoColor> = {
[ETagColor.INFO]: ETypoColor.COLOR_INFO_900,
[ETagColor.SUCCESS]: ETypoColor.COLOR_SUCCESS_700,
[ETagColor.ERROR]: ETypoColor.COLOR_SECONDARY_700,
[ETagColor.WARNING]: ETypoColor.COLOR_WARNING_700,
};
const typoMap: Record<ETagVariant, ETypo> = {
[ETagVariant.REGULAR]: ETypo.TEXT_MD_REGULAR,
[ETagVariant.SEMI_BOLD]: ETypo.TEXT_XS_SEMIBOLD,
};
export default function Tag(props: IProps) {
const { className, label, color, variant = ETagVariant.REGULAR } = props;
return (
<div className={classNames(classes["root"], className, classes[color])}>
<Typography typo={typoMap[variant]} color={colorMap[color]}>
{label}
</Typography>
</div>
);
}

View File

@ -0,0 +1,23 @@
.menu-item-wrapper {
width: 100%;
.menu-item {
display: flex;
padding: var(--spacing-md, 16px);
justify-content: flex-start;
align-items: center;
gap: var(--spacing-lg, 24px);
cursor: pointer;
> svg {
width: 24px;
height: 24px;
transition: all ease-in-out 0.1s;
}
}
.separator {
width: 100%;
height: 1px;
background-color: var(--separator-stroke-light, #d7dce0);
}
}

View File

@ -0,0 +1,50 @@
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import useHoverable from "@Front/Hooks/useHoverable";
import { ISubElement } from "..";
type IProps = {
element: ISubElement;
closeMenuCb: () => void;
};
export default function SubMenuItem(props: IProps) {
const { element, closeMenuCb } = props;
const router = useRouter();
const handleClickElement = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
closeMenuCb();
const link = e.currentTarget.getAttribute("data-link");
if (link) router.push(link);
if (element.onClick) element.onClick();
},
[closeMenuCb, element, router],
);
const { handleMouseEnter, handleMouseLeave, isHovered } = useHoverable();
// The element has a link or an onClick but not both, if it has a link, show it has a link, if it has an onClick, show it has an onClick
const getColor = useCallback(() => {
if (isHovered && element.color !== ETypoColor.ERROR_WEAK_CONTRAST) return ETypoColor.CONTRAST_HOVERED;
if (element.color) return element.color;
return ETypoColor.CONTRAST_DEFAULT;
}, [element.color, isHovered]);
return (
<div
className={classes["menu-item-wrapper"]}
onClick={handleClickElement}
data-link={element.link}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}>
<div className={classes["menu-item"]}>
{React.cloneElement(element.icon, { color: `var(${getColor()})` })}
<Typography typo={ETypo.TEXT_LG_REGULAR} color={getColor()}>
{element.text}
</Typography>
</div>
{element.hasSeparator && <div className={classes["separator"]} />}
</div>
);
}

View File

@ -0,0 +1,29 @@
.root {
position: relative;
width: fit-content;
.sub-menu {
position: absolute;
top: 48px;
display: inline-flex;
padding: var(--spacing-05, 4px) var(--spacing-2, 16px);
flex-direction: column;
align-items: flex-start;
border-radius: var(--menu-radius, 0px);
border: 1px solid var(--menu-border, #d7dce0);
background: var(--color-generic-white, #fff);
text-wrap: nowrap;
/* shadow/sm */
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
z-index: 2;
&[data-opening-side="left"] {
left: auto;
right: 0px;
}
&[data-opening-side="right"] {
left: 0px;
right: auto;
}
}
}

View File

@ -0,0 +1,78 @@
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
import classes from "./classes.module.scss";
import { ETypoColor } from "@Front/Components/DesignSystem/Typography";
import React, { useEffect, useRef, useState } from "react";
import SubMenuItem from "./SubMenuItem";
type ISubElementBase = {
icon: JSX.Element;
text: string;
hasSeparator?: boolean;
color?: ETypoColor;
};
type ISubElementWithLink = ISubElementBase & {
link: string;
onClick?: never;
};
type ISubElementWithOnClick = ISubElementBase & {
onClick: () => void;
link?: never;
};
export type ISubElement = ISubElementWithLink | ISubElementWithOnClick;
type IProps = {
icon: React.ReactNode;
text?: string;
subElements: ISubElement[];
openingSide?: "left" | "right";
};
export default function ButtonWithSubMenu(props: IProps) {
const { openingSide = "left" } = props;
const [isSubMenuOpened, setIsSubMenuOpened] = useState(false);
const subMenuRef = useRef<HTMLDivElement>(null);
const iconRef = useRef<HTMLDivElement>(null);
const handleClick = () => {
setIsSubMenuOpened((prev) => !prev);
};
const closeMenu = () => {
setIsSubMenuOpened(false);
};
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
subMenuRef.current &&
!subMenuRef.current.contains(e.target as Node) &&
iconRef.current &&
!iconRef.current.contains(e.target as Node)
) {
setIsSubMenuOpened(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (
<div className={classes["root"]}>
<div className={classes["main"]} onClick={handleClick} ref={iconRef}>
<IconButton icon={props.icon} variant={EIconButtonVariant.NEUTRAL} />
</div>
{isSubMenuOpened && (
<div className={classes["sub-menu"]} ref={subMenuRef} data-opening-side={openingSide}>
{props.subElements.map((element, index) => {
return <SubMenuItem element={element} key={index} closeMenuCb={closeMenu} />;
})}
</div>
)}
</div>
);
}

View File

@ -0,0 +1,105 @@
import React from "react";
/**
* @Documentation
* This component is used to create an infinite scroll effect.
*
* Usage:
*
* No pagination: front-end/src/components/pages/Products/index.tsx
const onSearch = useCallback((searchParam: string) => productService.search(searchParam).then((products) => setRows(buildRows(products))), []);
useEffect(() => {
productService.getLastProductSheets().then((products) => setRows(buildRows(products)));
}, []);
*
*
*
* With pagination: front-end/src/components/pages/Clients/index.tsx
*
const fetchClients = useCallback(
() =>
clientService.get(pagination.current, search.current).then((clients) => {
if (clients.length === 0) return [];
setRows((_rows) => [..._rows, ...buildRows(clients)]);
pagination.current.skip += pagination.current.take;
return clients;
}),
[],
);
const onNext = useCallback(
(release: () => void) => {
fetchClients().then((clients) => {
if (!clients.length) return console.warn("No more value to load");
release();
});
},
[fetchClients],
);
const onSearch = useCallback((searchParam: string) => {
pagination.current.skip = 0;
search.current = (searchParam && searchParam.trim()) || null;
setRows([]);
}, []);
*
*/
export type IPagination = {
take: number;
skip: number;
};
type IProps = {
offset?: number;
orientation?: "vertical" | "horizontal";
/**
* @description
* If `onNext` is set to `null`, it indicates that there is no pagination and the infinite scroll effect will not be triggered.
*/
onNext?: ((release: () => void, reset?: () => void) => Promise<void> | void) | null;
children: React.ReactElement;
};
export default function InfiniteScroll({ children, onNext, offset = 20, orientation = "vertical" }: IProps) {
const isWaiting = React.useRef<boolean>(false);
const elementRef = React.useRef<HTMLElement>();
const onChange = React.useCallback(() => {
if (!onNext) return;
const element = elementRef.current;
if (!element || isWaiting.current) return;
const { scrollTop, scrollLeft, clientHeight, clientWidth, scrollHeight, scrollWidth } = element;
let isChange = false;
if (orientation === "vertical") isChange = scrollTop + clientHeight >= scrollHeight - offset;
if (orientation === "horizontal") isChange = scrollLeft + clientWidth >= scrollWidth - offset;
if (isChange) {
isWaiting.current = true;
onNext(() => (isWaiting.current = false));
}
}, [onNext, offset, orientation]);
React.useEffect(() => onChange(), [onChange]);
React.useEffect(() => {
const observer = new MutationObserver(onChange);
elementRef.current && observer.observe(elementRef.current, { childList: true, subtree: true });
window.addEventListener("resize", onChange);
return () => {
observer.disconnect();
window.removeEventListener("resize", onChange);
};
}, [onChange]);
if (!onNext) return children;
const clonedChild = React.cloneElement(children, {
onScroll: onChange,
ref: elementRef,
});
return clonedChild;
}

View File

@ -0,0 +1,12 @@
.root {
padding: 8px 16px;
font-size: 16px;
letter-spacing: 0.08px;
border-bottom: 1px solid var(--tabs-stroke, #d7dce0);
cursor: pointer;
&[data-is-selected="true"] {
border-bottom: 2px solid var(--tabs-contrast-actived, #24282e);
}
}

View File

@ -0,0 +1,34 @@
import { useCallback } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import useHoverable from "@Front/Hooks/useHoverable";
import { ITabValue } from "..";
export type ITab = {
label: React.ReactNode;
};
export type IProps<T> = {
onSelect: (value: ITabValue<T>) => void;
value: ITabValue<T>;
isSelected: boolean;
} & ITab;
export default function HorizontalTabs<T>(props: IProps<T>) {
const onClick = useCallback(() => props.onSelect(props.value), [props]);
const { isHovered, handleMouseEnter, handleMouseLeave } = useHoverable();
return (
<div
className={classes["root"]}
onClick={onClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-is-selected={props.isSelected}>
<Typography
typo={props.isSelected ? ETypo.TEXT_MD_SEMIBOLD : ETypo.TEXT_MD_REGULAR}
color={!isHovered && !props.isSelected ? ETypoColor.TABS_CONTRAST_DEFAULT : ETypoColor.TABS_CONTRAST_ACTIVATED}>
{props.label}
</Typography>
</div>
);
}

View File

@ -0,0 +1,4 @@
.root {
padding: var(--spacing-md, 16px);
cursor: pointer;
}

View File

@ -0,0 +1,24 @@
import { useCallback } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { ITabValue } from "..";
export type ITab = {
label: React.ReactNode;
};
export type IProps<T> = {
onSelect: (value: ITabValue<T>) => void;
value: ITabValue<T>;
isSelected: boolean;
} & ITab;
export default function VerticalTabs<T>(props: IProps<T>) {
const onClick = useCallback(() => props.onSelect(props.value), [props]);
return (
<div className={classes["root"]} onClick={onClick}>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={props.isSelected ? ETypoColor.COLOR_NEUTRAL_950 : ETypoColor.COLOR_NEUTRAL_700}>
{props.label}
</Typography>
</div>
);
}

View File

@ -0,0 +1,50 @@
.root {
.mirror-shadow-element {
display: flex;
overflow: hidden;
background: red;
height: 0px;
}
.horizontal-container {
display: flex;
flex-direction: row;
flex: 1;
.horizontal-tab {
display: flex;
justify-content: space-evenly;
overflow: hidden;
}
}
.show-more-container {
position: relative;
border-bottom: 1px solid var(--color-neutral-500);
.vertical-container {
position: absolute;
display: flex;
flex-direction: column;
top: 50px;
padding: var(--spacing-05, 4px) var(--spacing-2, 16px);
background: var(--color-generic-white, #fff);
border: 1px solid var(--menu-border, #d7dce0);
border-radius: var(--menu-radius, 0px);
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
&[data-visible="false"] {
display: none;
}
}
}
.show-more {
padding: 8px 16px;
display: flex;
color: white;
font-size: 16px;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
}
}

View File

@ -0,0 +1,142 @@
import { useCallback, useEffect, useRef, useState } from "react";
import classes from "./classes.module.scss";
import HorizontalTab, { ITab } from "./HorizontalTab";
import VerticalTabs from "./VerticalTabs";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { useWindowSize } from "@uidotdev/usehooks";
import useOpenable from "@Front/Hooks/useOpenable";
export type ITabValue<T> = T & {
id: unknown;
}
type ITabInternal<T> = ITab & {
key?: string;
value: ITabValue<T>;
};
type IProps<T> = {
tabs: ITabInternal<T>[];
onSelect: (value: T) => void;
};
export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
const tabs = useRef(propsTabs);
const shadowElementRef = useRef<HTMLDivElement>(null);
const [visibleElements, setVisibleElements] = useState<ITabInternal<T>[]>([]);
const [overflowedElements, setOverflowedElements] = useState<ITabInternal<T>[]>([]);
const [selectedTab, setSelectedTab] = useState<ITabValue<T>>(tabs.current[0]!.value);
const { close, isOpen, toggle } = useOpenable();
const windowSize = useWindowSize();
const calculateVisibleElements = useCallback(() => {
const shadowElement = shadowElementRef.current;
if (!shadowElement) return;
const shadowElementWidth = shadowElement.offsetWidth;
// The first element is the show more element, it needs to be ignored in the calculation
let totalWidth = (shadowElement.children[0]! as HTMLElement).offsetWidth;
let visibleCount = 0;
for (let i = 1; i < shadowElement.children.length; i++) {
totalWidth += (shadowElement.children[i]! as HTMLElement).offsetWidth;
if (totalWidth > shadowElementWidth) {
break;
}
visibleCount++;
}
setVisibleElements(tabs.current.slice(0, visibleCount));
setOverflowedElements(tabs.current.slice(visibleCount));
}, []);
useEffect(() => {
tabs.current = propsTabs;
}, [propsTabs]);
useEffect(() => {
calculateVisibleElements();
}, [calculateVisibleElements, windowSize]);
const handleSelect = useCallback(
(value: ITabValue<T>) => {
setSelectedTab(value);
onSelect(value);
close();
calculateVisibleElements();
},
[close, onSelect, calculateVisibleElements],
);
const handleVerticalSelect = useCallback(
(value: ITabValue<T>) => {
const index = tabs.current.findIndex((tab) => tab.value.id === value.id);
const newTabs = [...tabs.current];
newTabs.splice(index, 1);
newTabs.unshift(tabs.current[index]!);
tabs.current = newTabs;
console.log("Updated values ; ", tabs.current);
handleSelect(value);
},
[handleSelect],
);
return (
<div className={classes["root"]}>
<div className={classes["mirror-shadow-element"]} ref={shadowElementRef}>
<div className={classes["show-more"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
{overflowedElements.length}&nbsp;de&nbsp;plus...
</Typography>
</div>
{tabs.current.map((element, index) => (
<HorizontalTab<T>
label={element.label}
key={element.key ?? index}
value={element.value}
onSelect={handleSelect}
isSelected={element.value.id === selectedTab.id}
/>
))}
</div>
<div className={classes["horizontal-container"]}>
<div className={classes["horizontal-tab"]}>
{visibleElements.map((element, index) => (
<HorizontalTab<T>
label={element.label}
key={element.key ?? index}
value={element.value}
onSelect={handleSelect}
isSelected={element.value.id === selectedTab.id}
/>
))}
</div>
{overflowedElements.length > 0 && (
<div className={classes["show-more-container"]}>
<div className={classes["show-more"]} onClick={toggle}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
{overflowedElements.length}&nbsp;de&nbsp;plus...
</Typography>
</div>
<div className={classes["vertical-container"]} data-visible={isOpen}>
{overflowedElements.length > 0 &&
overflowedElements.map((element, index) => (
<VerticalTabs<T>
label={element.label}
key={element.key ?? index}
value={element.value}
onSelect={handleVerticalSelect}
isSelected={selectedTab === element.value}
/>
))}
</div>
</div>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,24 @@
.root {
display: flex;
flex-direction: column;
gap: 32px;
.components {
.inputs {
display: flex;
flex-direction: column;
gap: 24px;
}
display: flex;
flex-direction: column;
gap: 24px;
.rows {
display: flex;
gap: 16px;
}
}
.table {
max-width: 800px;
}
}

View File

@ -0,0 +1,803 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
import Modal from "@Front/Components/DesignSystem/Modal";
import Newsletter from "@Front/Components/DesignSystem/Newsletter";
import Table from "@Front/Components/DesignSystem/Table";
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import NumberPicker from "@Front/Components/Elements/NumberPicker";
import Tabs from "@Front/Components/Elements/Tabs";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import useOpenable from "@Front/Hooks/useOpenable";
import {
ArchiveBoxIcon,
ArrowLongLeftIcon,
ArrowLongRightIcon,
EllipsisHorizontalIcon,
PencilSquareIcon,
UsersIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { useCallback, useMemo, useState } from "react";
import classes from "./classes.module.scss";
import ButtonWithSubMenu from "@Front/Components/Elements/ButtonWithSubMenu";
export default function DesignSystem() {
const { isOpen, open, close } = useOpenable();
const userDb = useMemo(
() => [
{
username: "Maxime",
id: 1,
},
{
username: "Vincent",
id: 2,
},
{
username: "Massi",
id: 3,
},
{
username: "Maxime",
id: 4,
},
{
username: "Arnaud",
id: 5,
},
],
[],
);
const userDbArray = useMemo(
() =>
userDb.map((user) => ({
label: user.username,
key: user.id.toString(),
value: user,
})),
[userDb],
);
const [selectedTab, setSelectedTab] = useState<(typeof userDb)[number]>(userDb[0]!);
const onSelect = useCallback((value: (typeof userDb)[number]) => {
setSelectedTab(value);
}, []);
return (
<DefaultTemplate title={"DesignSystem"}>
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
<Newsletter isOpen />
<div className={classes["root"]}>
<div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Button icon with menu</Typography>
<ButtonWithSubMenu
icon={<EllipsisHorizontalIcon />}
subElements={[
{
icon: <UsersIcon />,
text: "Modifier les collaborateurs",
onClick: () => alert("yo"),
hasSeparator: true,
},
{
icon: <PencilSquareIcon />,
text: "Modifier les informations du dossier",
link: "/",
hasSeparator: true,
},
{
icon: <ArchiveBoxIcon />,
text: "Archiver le dossier",
link: "/",
color: ETypoColor.ERROR_WEAK_CONTRAST,
},
]}
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Inputs</Typography>
<Typography typo={ETypo.TEXT_SM_REGULAR}>Number picker avec min à 1 et max à 10</Typography>
<NumberPicker defaultValue={1} onChange={() => {}} min={1} max={10} />
<Form className={classes["inputs"]}>
<TextField label="Label" placeholder="Placeholder" canCopy />
<TextField label="Without copy" placeholder="Placeholder" />
<TextField label="Disabled" placeholder="Placeholder" disabled canCopy />
<TextField label="Disabled without copy" placeholder="Placeholder" disabled />
<TextAreaField label="Textarea" placeholder="Placeholder" />
</Form>
<Typography typo={ETypo.TEXT_LG_BOLD}>Modal</Typography>
<Button onClick={open}>Open Modal</Button>
<Modal
isOpen={isOpen}
onClose={close}
title={"Modal"}
firstButton={{ children: "Annuler", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
secondButton={{ children: "Confirmer", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}>
<Typography typo={ETypo.TEXT_LG_REGULAR}>Modal Content</Typography>
</Modal>
<Typography typo={ETypo.TEXT_LG_BOLD}>Tabs</Typography>
<Tabs<(typeof userDb)[number]> tabs={userDbArray} onSelect={onSelect} />
<Typography typo={ETypo.TEXT_MD_REGULAR}>
{selectedTab.id} - {selectedTab.username}
</Typography>
<Typography typo={ETypo.TEXT_LG_BOLD}>Circle Progress</Typography>
<div className={classes["rows"]}>
<CircleProgress percentage={0} />
<CircleProgress percentage={30} />
<CircleProgress percentage={60} />
<CircleProgress percentage={100} />
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Tags</Typography>
<div className={classes["rows"]}>
<Tag color={ETagColor.INFO} variant={ETagVariant.REGULAR} label="Info" />
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.REGULAR} label="Success" />
<Tag color={ETagColor.WARNING} variant={ETagVariant.REGULAR} label="Warning" />
<Tag color={ETagColor.ERROR} variant={ETagVariant.REGULAR} label="Error" />
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Table Tags</Typography>
<div className={classes["rows"]}>
<Tag color={ETagColor.INFO} variant={ETagVariant.SEMI_BOLD} label="INFO" />
<Tag color={ETagColor.SUCCESS} variant={ETagVariant.SEMI_BOLD} label="SUCCESS" />
<Tag color={ETagColor.WARNING} variant={ETagVariant.SEMI_BOLD} label="WARNING" />
<Tag color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} label="ERROR" />
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Table</Typography>
<Table
className={classes["table"]}
header={[
{
key: "name",
title: "Nom",
},
{
key: "firstname",
title: "Prénom",
},
{
key: "button",
},
]}
rows={[
{
key: "1",
name: "Doe",
firstname: "John",
button: (
<Button size={EButtonSize.SM} variant={EButtonVariant.PRIMARY}>
Send email
</Button>
),
},
{
key: "2",
name: "Doe",
firstname: "Jane",
button: <Tag color={ETagColor.SUCCESS} variant={ETagVariant.SEMI_BOLD} label="Actif" />,
},
{
key: "3",
name: "Doe",
firstname: "Jack",
button: <Tag color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} label="Inactif" />,
},
]}
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Buttons</Typography>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
size={EButtonSize.SM}>
Primary SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Primary SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Primary SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
size={EButtonSize.MD}>
Primary MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Primary MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Primary MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
size={EButtonSize.LG}>
Primary LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Primary LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Primary LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
size={EButtonSize.SM}>
Secondary SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Secondary SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Secondary SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
size={EButtonSize.MD}>
Secondary MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Secondary MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Secondary MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
size={EButtonSize.LG}>
Secondary LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Secondary LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Secondary LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
size={EButtonSize.SM}>
Neutral SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Neutral SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Neutral SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
size={EButtonSize.MD}>
Neutral MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Neutral MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Neutral MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
size={EButtonSize.LG}>
Neutral LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Neutral LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.NEUTRAL}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Neutral LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
size={EButtonSize.SM}>
Error SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Error SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Error SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
size={EButtonSize.MD}>
Error MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Error MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Error MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
size={EButtonSize.LG}>
Error LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Error LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Error LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
size={EButtonSize.SM}>
Warning SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Warning SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Warning SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
size={EButtonSize.MD}>
Warning MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Warning MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Warning MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
size={EButtonSize.LG}>
Warning LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Warning LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Warning LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
size={EButtonSize.SM}>
Success SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Success SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Success SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
size={EButtonSize.MD}>
Success MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Success MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Success MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
size={EButtonSize.LG}>
Success LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Success LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.SUCCESS}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Success LG
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
size={EButtonSize.SM}>
Info SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.SM}>
Info SM
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.SM}>
Info SM
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
size={EButtonSize.MD}>
Info MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.MD}>
Info MD
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.MD}>
Info MD
</Button>
</div>
<div className={classes["rows"]}>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
size={EButtonSize.LG}>
Info LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.OUTLINED}
size={EButtonSize.LG}>
Info LG
</Button>
<Button
leftIcon={<ArrowLongLeftIcon />}
rightIcon={<ArrowLongRightIcon />}
variant={EButtonVariant.INFO}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}>
Info LG
</Button>
</div>
<div className={classes["rows"]}>
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.DEFAULT} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.NEUTRAL} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.PRIMARY} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.ERROR} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.SUCCESS} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.WARNING} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.INFO} />
<IconButton icon={<XMarkIcon />} variant={EIconButtonVariant.INFO} disabled />
</div>
<Typography typo={ETypo.TEXT_LG_BOLD}>Alerts</Typography>
<Alert
title="Alert line which displays the main function or reason of the alert."
description="Description"
firstButton={{
children: "Button",
leftIcon: <ArrowLongLeftIcon />,
rightIcon: <ArrowLongRightIcon />,
}}
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
variant={EAlertVariant.INFO}
closeButton
/>
<Alert
title="Alert line which displays the main function or reason of the alert."
description="Description"
firstButton={{
children: "Button",
leftIcon: <ArrowLongLeftIcon />,
rightIcon: <ArrowLongRightIcon />,
}}
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
variant={EAlertVariant.ERROR}
/>
<Alert
title="Alert line which displays the main function or reason of the alert."
description="Description"
firstButton={{
children: "Button",
leftIcon: <ArrowLongLeftIcon />,
rightIcon: <ArrowLongRightIcon />,
}}
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
variant={EAlertVariant.WARNING}
/>
<Alert
title="Alert line which displays the main function or reason of the alert."
description="Description"
firstButton={{
children: "Button",
leftIcon: <ArrowLongLeftIcon />,
rightIcon: <ArrowLongRightIcon />,
}}
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
variant={EAlertVariant.SUCCESS}
/>
<Alert
title="Alert line which displays the main function or reason of the alert."
description="Description"
firstButton={{
children: "Button",
leftIcon: <ArrowLongLeftIcon />,
rightIcon: <ArrowLongRightIcon />,
}}
secondButton={{ children: "Button", leftIcon: <ArrowLongLeftIcon />, rightIcon: <ArrowLongRightIcon /> }}
variant={EAlertVariant.NEUTRAL}
/>
</div>
</div>
</DefaultTemplate>
);
}

View File

@ -0,0 +1,33 @@
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react";
type IProps = {
customerUid: string;
isOpen: boolean;
onClose?: () => void;
onDelete: (customerUid: string) => void;
};
export default function DeleteCustomerModal(props: IProps) {
const { isOpen, onClose, onDelete } = props;
const handleDelete = useCallback(() => {
onDelete(props.customerUid);
}, [onDelete, props.customerUid]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Êtes-vous sûr de vouloir supprimer ce client du dossier ?"}
firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer le client", onClick: handleDelete }}>
<Typography typo={ETypo.TEXT_MD_light}>
Cette action retirera le client de ce dossier. Vous ne pourrez plus récupérer les informations associées à ce client dans ce
dossier une fois supprimées.
</Typography>
</Modal>
);
}

View File

@ -0,0 +1,22 @@
@import "@Themes/constants.scss";
.root {
display: flex;
width: fit-content;
padding: var(--spacing-md, 16px);
flex-direction: column;
gap: var(--spacing-md, 16px);
background: var(--primary-weak-higlight, #e5eefa);
min-width: 300px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.delete-button {
margin: auto;
}
}

View File

@ -0,0 +1,141 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import useOpenable from "@Front/Hooks/useOpenable";
import { PencilSquareIcon, TrashIcon, UsersIcon } from "@heroicons/react/24/outline";
import { ICustomer } from "..";
import { AnchorStatus } from "../..";
import classes from "./classes.module.scss";
import DeleteCustomerModal from "./DeleteCustomerModal";
import Module from "@Front/Config/Module";
import { useCallback } from "react";
import { Note } from "le-coffre-resources/dist/Customer";
import ButtonWithSubMenu from "@Front/Components/Elements/ButtonWithSubMenu";
import Modal from "@Front/Components/DesignSystem/Modal";
type IProps = {
customer: ICustomer;
anchorStatus: AnchorStatus;
folderUid: string | undefined;
customerNote: Note | null;
onDelete: (customerUid: string) => void;
};
export default function ClientBox(props: IProps) {
const { customer, anchorStatus, folderUid, customerNote } = props;
const { isOpen: isDeleteModalOpen, open: openDeleteModal, close: closeDeleteModal } = useOpenable();
const { isOpen: isErrorModalOpen, open: openErrorModal, close: closeErrorModal } = useOpenable();
const handleDelete = useCallback(
(customerUid: string) => {
if (customer.documents && customer.documents.length > 0) {
closeDeleteModal();
openErrorModal();
return;
}
props.onDelete(customerUid);
},
[closeDeleteModal, customer.documents, openErrorModal, props],
);
let createOrUpdateNotePath = Module.getInstance()
.get()
.modules.pages.Notes.pages.EditNote.props.path.replace("[noteUid]", customerNote?.uid ?? "");
if (!customerNote) {
createOrUpdateNotePath = Module.getInstance()
.get()
.modules.pages.Notes.pages.CreateNote.props.path.replace("[folderUid]", folderUid ?? "");
createOrUpdateNotePath = createOrUpdateNotePath.replace("[customerUid]", customer.uid ?? "");
}
return (
<div className={classes["root"]}>
<div className={classes["header"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_PRIMARY_500}>
{customer.contact?.first_name} {customer.contact?.last_name}
</Typography>
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
<ButtonWithSubMenu
icon={<PencilSquareIcon />}
openingSide="right"
subElements={[
{
icon: <UsersIcon />,
text: "Modifier les informations",
link: Module.getInstance()
.get()
.modules.pages.Folder.pages.EditClient.props.path.replace("[folderUid]", folderUid ?? "")
.replace("[customerUid]", customer.uid ?? ""),
hasSeparator: true,
},
{
icon: <PencilSquareIcon />,
text: "Modifier la note",
link: createOrUpdateNotePath,
},
]}
/>
// <Link
// href={}>
// <IconButton variant={EIconButtonVariant.NEUTRAL} />
// </Link>
)}
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Numéro de téléphone
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{customer.contact?.cell_phone_number ?? customer.contact?.phone_number ?? "_"}
</Typography>
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
E-mail
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{customer.contact?.email ?? "_"}
</Typography>
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Note client
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
{customerNote?.content ?? "-"}
</Typography>
</div>
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
<>
<Button
className={classes["delete-button"]}
size={EButtonSize.SM}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.TEXT}
rightIcon={<TrashIcon />}
onClick={openDeleteModal}>
Supprimer le client
</Button>
<DeleteCustomerModal
isOpen={isDeleteModalOpen}
onClose={close}
customerUid={customer.uid ?? ""}
onDelete={handleDelete}
/>
</>
)}
<Modal
isOpen={isErrorModalOpen}
onClose={closeErrorModal}
title={"Suppression impossible"}
secondButton={{ children: "Fermer", onClick: closeErrorModal, fullwidth: true }}>
<Typography typo={ETypo.TEXT_MD_light}>
Ce client ne peut pas être supprimé car des documents sont associés à son dossier. Veuillez d'abord gérer ou supprimer
ces documents avant de tenter de supprimer le client.
</Typography>
</Modal>
</div>
);
}

View File

@ -0,0 +1,37 @@
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react";
type IProps = {
documentUid: string;
isOpen: boolean;
onClose?: () => void;
onDeleteSuccess: (uid: string) => void;
};
export default function DeleteAskedDocumentModal(props: IProps) {
const { isOpen, onClose, documentUid, onDeleteSuccess } = props;
const onDelete = useCallback(
() =>
Documents.getInstance()
.delete(documentUid)
.then(() => onDeleteSuccess(documentUid))
.then(onClose)
.catch((error) => console.warn(error)),
[documentUid, onClose, onDeleteSuccess],
);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Êtes-vous sûr de vouloir supprimer cette demande de document ?"}
firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer la demande", onClick: onDelete }}>
<Typography typo={ETypo.TEXT_MD_light}>Cette action annulera la demande du document en cours.</Typography>
</Modal>
);
}

View File

@ -0,0 +1,20 @@
import Modal from "@Front/Components/DesignSystem/Modal";
import { File } from "le-coffre-resources/dist/Customer";
import React from "react";
type IProps = {
file: File;
url: string;
isOpen: boolean;
onClose?: () => void;
};
export default function FilePreviewModal(props: IProps) {
const { isOpen, onClose, file, url } = props;
return (
<Modal key={file.uid} isOpen={isOpen} onClose={onClose} fullscreen>
<object data={url} type={file.mimetype} width="100%" height="800px" />
</Modal>
);
}

View File

@ -0,0 +1,14 @@
@import "@Themes/constants.scss";
.root {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.title {
display: flex;
justify-content: space-between;
align-items: center;
}
}

View File

@ -0,0 +1,226 @@
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import IconButton from "@Front/Components/DesignSystem/IconButton";
import Table from "@Front/Components/DesignSystem/Table";
import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable";
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module";
import useOpenable from "@Front/Hooks/useOpenable";
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Document } from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss";
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
type IProps = {
documents: Document[];
folderUid: string;
};
const header: readonly IHead[] = [
{
key: "document_type",
title: "Type de document",
},
{
key: "document_status",
title: "Statut",
},
{
key: "created_at",
title: "Demandé le",
},
{
key: "actions",
title: "Actions",
},
];
const tradDocumentStatus: Record<EDocumentStatus, string> = {
[EDocumentStatus.ASKED]: "Demandé",
[EDocumentStatus.DEPOSITED]: "À valider",
[EDocumentStatus.VALIDATED]: "Validé",
[EDocumentStatus.REFUSED]: "Refusé",
};
export default function DocumentTables(props: IProps) {
const { documents: documentsProps, folderUid } = props;
const [documents, setDocuments] = useState<Document[]>(documentsProps);
const [documentUid, setDocumentUid] = useState<string | null>(null);
const deleteAskedOocumentModal = useOpenable();
useEffect(() => {
setDocuments(documentsProps);
}, [documentsProps]);
const openDeleteAskedDocumentModal = useCallback(
(uid: string | undefined) => {
if (!uid) return;
setDocumentUid(uid);
deleteAskedOocumentModal.open();
},
[deleteAskedOocumentModal],
);
const onDownload = useCallback((doc: Document) => {
const file = doc.files?.[0];
if (!file || !file?.uid) return;
return Files.getInstance()
.download(file.uid)
.then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = file.file_name ?? "file";
a.click();
URL.revokeObjectURL(url);
})
.catch((e) => console.warn(e));
}, []);
const askedDocuments: IRowProps[] = useMemo(
() =>
documents
.map((document) => {
if (document.document_status !== EDocumentStatus.ASKED) return null;
return {
key: document.uid,
document_type: document.document_type?.name ?? "_",
document_status: (
<Tag
color={ETagColor.INFO}
variant={ETagVariant.SEMI_BOLD}
label={tradDocumentStatus[document.document_status].toUpperCase()}
/>
),
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
actions: <IconButton icon={<TrashIcon onClick={() => openDeleteAskedDocumentModal(document.uid)} />} />,
};
})
.filter((document) => document !== null) as IRowProps[],
[documents, openDeleteAskedDocumentModal],
);
const toValidateDocuments: IRowProps[] = useMemo(
() =>
documents
.map((document) => {
if (document.document_status !== EDocumentStatus.DEPOSITED) return null;
return {
key: document.uid,
document_type: document.document_type?.name ?? "_",
document_status: (
<Tag
color={ETagColor.WARNING}
variant={ETagVariant.SEMI_BOLD}
label={tradDocumentStatus[document.document_status].toUpperCase()}
/>
),
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
actions: (
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.ViewDocuments.props.path.replace("[folderUid]", folderUid)
.replace("[documentUid]", document.uid ?? "")}>
<IconButton icon={<EyeIcon />} />
</Link>
),
};
})
.filter((document) => document !== null) as IRowProps[],
[documents, folderUid],
);
const validatedDocuments: IRowProps[] = useMemo(
() =>
documents
.map((document) => {
if (document.document_status !== EDocumentStatus.VALIDATED) return null;
return {
key: document.uid,
document_type: document.document_type?.name ?? "_",
document_status: (
<Tag
color={ETagColor.SUCCESS}
variant={ETagVariant.SEMI_BOLD}
label={tradDocumentStatus[document.document_status].toUpperCase()}
/>
),
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
actions: (
<div className={classes["actions"]}>
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.ViewDocuments.props.path.replace("[folderUid]", folderUid)
.replace("[documentUid]", document.uid ?? "")}>
<IconButton icon={<EyeIcon />} />
</Link>
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
</div>
),
};
})
.filter((document) => document !== null) as IRowProps[],
[documents, folderUid, onDownload],
);
const refusedDocuments: IRowProps[] = useMemo(
() =>
documents
.map((document) => {
if (document.document_status !== EDocumentStatus.REFUSED) return null;
return {
key: document.uid,
document_type: document.document_type?.name ?? "_",
document_status: (
<Tag
color={ETagColor.ERROR}
variant={ETagVariant.SEMI_BOLD}
label={tradDocumentStatus[document.document_status].toUpperCase()}
/>
),
created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
actions: "",
};
})
.filter((document) => document !== null) as IRowProps[],
[documents],
);
const progress = useMemo(() => {
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length;
if (total === 0) return 0;
return (validatedDocuments.length / total) * 100;
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
return (
<div className={classes["root"]}>
<div className={classes["title"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
Documents
</Typography>
<CircleProgress percentage={progress} />
</div>
{askedDocuments.length > 0 && <Table header={header} rows={askedDocuments} />}
{toValidateDocuments.length > 0 && <Table header={header} rows={toValidateDocuments} />}
{validatedDocuments.length > 0 && <Table header={header} rows={validatedDocuments} />}
{refusedDocuments.length > 0 && <Table header={header} rows={refusedDocuments} />}
{documentUid && (
<DeleteAskedDocumentModal
isOpen={deleteAskedOocumentModal.isOpen}
onClose={deleteAskedOocumentModal.close}
onDeleteSuccess={(uid: string) => setDocuments(documents.filter((document) => document.uid !== uid))}
documentUid={documentUid}
/>
)}
</div>
);
}

View File

@ -0,0 +1,7 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
}

View File

@ -0,0 +1,20 @@
import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { DocumentIcon } from "@heroicons/react/24/outline";
import classes from "./classes.module.scss";
export default function NoDocument() {
return (
<div className={classes["root"]}>
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_NEUTRAL_950}>
Documents
</Typography>
<EmptyAlert
icon={<DocumentIcon />}
title="Aucune demande de document"
description="Vous n'avez encore demandé aucun document pour ce client. Pour commencer, cliquez sur le bouton ci-dessous pour créer une nouvelle demande de document."
/>
</div>
);
}

View File

@ -0,0 +1,30 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.tab-container {
display: flex;
gap: var(--spacing-md, 16px);
justify-content: space-between;
align-items: center;
.tabs {
width: calc(100% - 210px);
}
}
.content {
display: flex;
gap: var(--spacing-lg, 24px);
.client-box {
display: flex;
flex-direction: column;
gap: var(--spacing-lg, 24px);
}
}
}

View File

@ -0,0 +1,115 @@
import Tabs from "@Front/Components/Elements/Tabs";
import Customer from "le-coffre-resources/dist/Customer";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { useCallback, useMemo, useState } from "react";
import { AnchorStatus } from "..";
import classes from "./classes.module.scss";
import ClientBox from "./ClientBox";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
import Module from "@Front/Config/Module";
import Link from "next/link";
import NoDocument from "./NoDocument";
import DocumentTables from "./DocumentTables";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
type IProps = {
folder: OfficeFolder;
anchorStatus: AnchorStatus;
};
export type ICustomer = Customer & { id: string };
export default function ClientView(props: IProps) {
const { folder, anchorStatus } = props;
const customers: ICustomer[] = useMemo(
() =>
folder?.customers?.map((customer) => ({
id: customer.uid ?? "",
...customer,
})) ?? [],
[folder],
);
const [customer, setCustomer] = useState<(typeof customers)[number]>(customers[0]!);
const tabs = useMemo(
() =>
customers.map((customer) => ({
label: `${customer.contact?.first_name} ${customer.contact?.last_name}`,
key: customer.uid,
value: customer,
})),
[customers],
);
const doesCustomerHaveDocument = useMemo(() => customer.documents && customer.documents.length > 0, [customer]);
const handleClientDelete = useCallback(
(customerUid: string) => {
if (!folder.uid) return;
Folders.getInstance().put(
folder.uid,
OfficeFolder.hydrate<OfficeFolder>({
...folder,
customers: folder.customers?.filter((customer) => customer.uid !== customerUid),
}),
);
window.location.reload();
},
[folder],
);
return (
<section className={classes["root"]}>
<div className={classes["tab-container"]}>
<div className={classes["tabs"]}>{tabs && <Tabs<ICustomer> tabs={tabs} onSelect={setCustomer} />}</div>
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folder.uid ?? "")}>
<Button
size={EButtonSize.MD}
rightIcon={<UserPlusIcon />}
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}>
Ajouter un client
</Button>
</Link>
)}
</div>
<div className={classes["content"]}>
<div className={classes["client-box"]}>
<ClientBox
customer={customer}
anchorStatus={anchorStatus}
folderUid={folder.uid}
onDelete={handleClientDelete}
customerNote={folder.notes!.find((value) => value.customer?.uid === customer.uid) ?? null}
/>
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
<Link
href={Module.getInstance()
.get()
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.uid ?? "")
.replace("[customerUid]", customer.uid ?? "")}>
<Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth>
Demander un document
</Button>
</Link>
)}
</div>
{doesCustomerHaveDocument ? (
<DocumentTables documents={customer.documents ?? []} folderUid={folder?.uid ?? ""} />
) : (
<NoDocument />
)}
</div>
</section>
);
}

View File

@ -0,0 +1,50 @@
@import "@Themes/constants.scss";
.root {
display: flex;
gap: var(--spacing-lg, 40px);
.info-box1 {
display: flex;
width: 100%;
flex-direction: column;
gap: var(--spacing-sm, 8px);
.open-date {
display: flex;
gap: var(--spacing-sm, 8px);
}
}
.info-box2 {
display: flex;
flex-direction: column;
gap: var(--spacing-lg, 24px);
width: 100%;
max-width: 400px;
.progress-container {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.icon-container {
display: flex;
gap: var(--spacing-md, 8px);
}
}
.description-container {
.text {
max-height: 60px;
overflow-y: auto;
}
}
}
.separator {
background-color: var(--separator-stroke-light);
width: 1px;
align-self: stretch;
}
}

View File

@ -0,0 +1,103 @@
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module";
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import classes from "./classes.module.scss";
import { AnchorStatus } from "..";
import ButtonWithSubMenu, { ISubElement } from "@Front/Components/Elements/ButtonWithSubMenu";
import { useCallback } from "react";
type IProps = {
folder: OfficeFolder | null;
progress: number;
onArchive: () => void;
anchorStatus: AnchorStatus;
isArchived: boolean;
};
export default function InformationSection(props: IProps) {
const { folder, progress, onArchive, anchorStatus, isArchived } = props;
const getSubMenuElement = useCallback(() => {
let elements: ISubElement[] = [];
// Creating the three elements and adding them conditionnally
const modifyCollaboratorsElement = {
icon: <UsersIcon />,
text: "Modifier les collaborateurs",
link: Module.getInstance()
.get()
.modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true,
};
const modifyInformationsElement = {
icon: <PencilSquareIcon />,
text: "Modifier les informations du dossier",
link: Module.getInstance()
.get()
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true,
};
const archiveElement = {
icon: <ArchiveBoxIcon />,
text: "Archiver le dossier",
onClick: onArchive,
color: ETypoColor.ERROR_WEAK_CONTRAST,
};
// If the folder is not anchored, we can modify the collaborators and the informations
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
elements.push(modifyCollaboratorsElement);
// Remove the separator if it's the last item (if the folder is not archived)
if (isArchived) modifyInformationsElement.hasSeparator = false;
elements.push(modifyInformationsElement);
}
// If the folder is not archived, we can archive it
if (!isArchived) {
elements.push(archiveElement);
}
return elements;
}, [anchorStatus, folder?.uid, isArchived, onArchive]);
return (
<section className={classes["root"]}>
<div className={classes["info-box1"]}>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder?.folder_number}</Typography>
<Typography typo={ETypo.TITLE_H4}>{folder?.name}</Typography>
</div>
<Tag color={ETagColor.INFO} label={folder?.deed?.deed_type?.name ?? ""} />
<div className={classes["open-date"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR}>Ouverture du dossier</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_PRIMARY_500}>
{folder?.created_at ? new Date(folder.created_at).toLocaleDateString() : ""}
</Typography>
</div>
</div>
<div className={classes["separator"]} />
<div className={classes["info-box2"]}>
<div className={classes["progress-container"]}>
<CircleProgress percentage={progress} />
<div className={classes["icon-container"]}>
<ButtonWithSubMenu icon={<EllipsisHorizontalIcon />} subElements={getSubMenuElement()} />
</div>
</div>
<div className={classes["description-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Note du dossier
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} className={classes["text"]}>
{folder?.description}
</Typography>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,34 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert";
import Module from "@Front/Config/Module";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import React, { useMemo } from "react";
type IProps = {
folderUid: string;
};
export default function AddClientSection(props: IProps) {
const { folderUid } = props;
const addClientPath = useMemo(() => {
if (!folderUid) return "";
return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folderUid);
}, [folderUid]);
return (
<EmptyAlert
icon={<UserPlusIcon />}
title="Ajouter des clients au dossier"
description="Pour pouvoir faire une demande de document, vous devez d'abord ajouter un ou plusieurs clients à ce dossier. Cette étape est essentielle pour assurer le suivi et la gestion des documents."
footer={
<Link href={addClientPath}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} size={EButtonSize.MD}>
Ajouter un client
</Button>
</Link>
}
/>
);
}

View File

@ -0,0 +1,42 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
type IProps = {
isOpen: boolean;
onClose?: () => void;
folder: OfficeFolder;
};
export default function DeleteFolderModal(props: IProps) {
const { isOpen, onClose, folder } = props;
const router = useRouter();
const onDelete = useCallback(() => {
if (!folder.uid) return;
if ((folder?.customers?.length ?? 0) > 0 || (folder?.documents?.length ?? 0) > 0)
return console.warn("Cannot delete folder with customers or documents");
return Folders.getInstance()
.delete(folder.uid)
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
.then(onClose);
}, [folder, router, onClose]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Êtes-vous sûr de vouloir supprimer ce dossier ?"}
firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Supprimer le dossier", onClick: onDelete }}>
<Typography typo={ETypo.TEXT_MD_light}>
Cette action est irréversible. En supprimant ce dossier, toutes les informations associées seront définitivement perdues.
</Typography>
</Modal>
);
}

View File

@ -0,0 +1,11 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.delete-button {
align-self: flex-end;
}
}

View File

@ -0,0 +1,42 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { TrashIcon } from "@heroicons/react/24/outline";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { useMemo } from "react";
import AddClientSection from "./AddClientSection";
import classes from "./classes.module.scss";
import DeleteFolderModal from "./DeleteFolderModal";
import useOpenable from "@Front/Hooks/useOpenable";
import { AnchorStatus } from "..";
type IProps = {
folder: OfficeFolder;
anchorStatus: AnchorStatus;
};
export default function NoClientView(props: IProps) {
const { folder, anchorStatus } = props;
const deleteFolderModal = useOpenable();
const canDeleteFolder = useMemo(() => folder.documents?.length === 0 && folder.customers?.length === 0, [folder]);
return (
<section className={classes["root"]}>
{anchorStatus === AnchorStatus.NOT_ANCHORED && <AddClientSection folderUid={folder?.uid ?? ""} />}
{canDeleteFolder && (
<>
<Button
className={classes["delete-button"]}
variant={EButtonVariant.ERROR}
styletype={EButtonstyletype.TEXT}
rightIcon={<TrashIcon />}
onClick={deleteFolderModal.open}
size={EButtonSize.SM}>
Supprimer le dossier
</Button>
<DeleteFolderModal isOpen={deleteFolderModal.isOpen} onClose={deleteFolderModal.close} folder={folder} />
</>
)}
</section>
);
}

View File

@ -0,0 +1,25 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
import { LockClosedIcon } from "@heroicons/react/24/outline";
type IProps = {
onAnchor: () => void;
};
export default function AnchoringAlertInfo(props: IProps) {
const { onAnchor } = props;
return (
<Alert
title="Validation et Certification du Dossier"
description="Votre dossier est désormais complet à 100%. Vous pouvez maintenant procéder à la validation et à l'ancrage des documents dans la blockchain. Cette étape garantit la sécurité et l'authenticité de vos documents."
firstButton={{
children: "Ancrer et certifier",
styletype: EButtonstyletype.CONTAINED,
rightIcon: <LockClosedIcon />,
onClick: onAnchor,
}}
variant={EAlertVariant.INFO}
fullWidth
/>
);
}

View File

@ -0,0 +1,36 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
import { ArrowDownOnSquareIcon, CheckIcon } from "@heroicons/react/24/outline";
type IProps = {
onDownloadAnchoringProof: () => void;
onArchive: () => void;
isArchived: boolean;
};
export default function AnchoringAlertSuccess(props: IProps) {
const { onDownloadAnchoringProof, onArchive, isArchived } = props;
return (
<Alert
title="Félicitations ! Dossier ancré avec succès"
description="Votre dossier a été validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos archives."
firstButton={{
children: "Télécharger la preuve d'ancrage",
styletype: EButtonstyletype.CONTAINED,
rightIcon: <ArrowDownOnSquareIcon />,
onClick: onDownloadAnchoringProof,
}}
secondButton={
isArchived
? undefined
: {
children: "Archiver le dossier",
onClick: onArchive,
}
}
variant={EAlertVariant.SUCCESS}
icon={<CheckIcon />}
fullWidth
/>
);
}

View File

@ -0,0 +1,11 @@
.anchoring {
display: flex;
flex-direction: column;
gap: 24px;
.validate-gif {
width: 100%;
height: 100%;
object-fit: contain;
}
}

View File

@ -0,0 +1,62 @@
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif";
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import Image from "next/image";
import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss";
type IProps = {
isOpen: boolean;
onClose?: () => void;
folderUid: string;
onAnchorSuccess: () => void;
};
export default function AnchoringModal(props: IProps) {
const { isOpen, onClose, folderUid, onAnchorSuccess } = props;
const [isAnchoring, setIsAnchoring] = useState(false);
const anchor = useCallback(() => {
const timeoutDelay = 9800;
const timeoutPromise = new Promise((resolve) => {
setTimeout(resolve, timeoutDelay);
});
setIsAnchoring(true);
return OfficeFolderAnchors.getInstance()
.post(folderUid)
.then(() => timeoutPromise)
.then(() => setIsAnchoring(false))
.then(onAnchorSuccess)
.then(onClose)
.catch((e) => {
console.warn(e);
setIsAnchoring(false);
});
}, [folderUid, onAnchorSuccess, onClose]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={!isAnchoring ? "Êtes-vous sûr de vouloir certifier et ancrer ce dossier ?" : "Ancrage en cours..."}
firstButton={!isAnchoring ? { children: "Non, Annuler", onClick: onClose } : undefined}
secondButton={!isAnchoring ? { children: "Oui, certifier et ancrer", onClick: anchor } : undefined}>
{!isAnchoring ? (
<Typography typo={ETypo.TEXT_MD_light}>
La certification et l'ancrage de ce dossier dans la blockchain sont des actions définitives et garantiront la sécurité
et l'authenticité de tous les documents. Veuillez confirmer que vous souhaitez continuer.
</Typography>
) : (
<div className={classes["anchoring"]}>
<Typography typo={ETypo.TEXT_MD_light}>
Vos documents sont en train d'être ancrés dans la blockchain. Cela peut prendre quelques instants. Merci de votre
patience.
</Typography>
<Image src={ValidateAnchoringGif} alt="Anchoring animation" className={classes["validate-gif"]} />
</div>
)}
</Modal>
);
}

View File

@ -0,0 +1,14 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { ArrowPathIcon } from "@heroicons/react/24/outline";
export default function AnchoringProcessingInfo() {
return (
<Alert
title="Ancrage en cours..."
description="Vos documents sont en train d'être ancrés dans la blockchain. Cela peut prendre quelques instants. Merci de votre patience."
variant={EAlertVariant.INFO}
icon={<ArrowPathIcon />}
fullWidth
/>
);
}

View File

@ -0,0 +1,48 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
import Module from "@Front/Config/Module";
import { ArchiveBoxArrowDownIcon, ArchiveBoxIcon, ArrowDownOnSquareIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";
import { useCallback } from "react";
type IProps = {
onDownloadAnchoringProof: () => void;
folderUid: string;
};
export default function ArchiveAlertWarning(props: IProps) {
const { onDownloadAnchoringProof, folderUid } = props;
const router = useRouter();
const restoreArchive = useCallback(() => {
Folders.getInstance()
.restore(folderUid)
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
.catch((e) => {
console.warn(e);
});
}, [folderUid, router]);
return (
<Alert
title="Dossier archivé"
description="Ce dossier a été archivé et ne peut plus être modifié. Vous pouvez télécharger la preuve d'ancrage pour vos archives ou restaurer le dossier si nécessaire."
firstButton={{
children: "Télécharger la preuve dancrage",
styletype: EButtonstyletype.CONTAINED,
rightIcon: <ArrowDownOnSquareIcon />,
onClick: onDownloadAnchoringProof,
}}
secondButton={{
children: "Restaurer le dossier",
onClick: restoreArchive,
rightIcon: <ArchiveBoxArrowDownIcon />,
}}
variant={EAlertVariant.WARNING}
icon={<ArchiveBoxIcon />}
fullWidth
/>
);
}

View File

@ -0,0 +1,5 @@
.root {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}

View File

@ -0,0 +1,48 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module";
import { useRouter } from "next/router";
import React, { useCallback } from "react";
import classes from "./classes.module.scss";
type IProps = {
isOpen: boolean;
onClose?: () => void;
folderUid: string;
};
export default function ArchiveModal(props: IProps) {
const { isOpen, onClose, folderUid } = props;
const router = useRouter();
const archive = useCallback(() => {
const description = (document.querySelector("textarea[name='archived_description']") as HTMLTextAreaElement).value ?? "";
Folders.getInstance()
.archive(folderUid, description)
.then(onClose)
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
.catch((e) => {
console.warn(e);
});
}, [folderUid, onClose, router]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Voulez-vous archiver ce dossier ?"}
firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Archiver le dossier", onClick: archive }}>
<div className={classes["root"]}>
<Typography typo={ETypo.TEXT_MD_light}>
Archiver ce dossier le déplacera dans la section des dossiers archivés. Vous pouvez ajouter une note de dossier avant
d'archiver si vous le souhaitez.
</Typography>
<TextAreaField name="archived_description" placeholder="Ecrire une note" />
</div>
</Modal>
);
}

View File

@ -0,0 +1,47 @@
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import React, { useCallback } from "react";
type IProps = {
isOpen: boolean;
onClose?: () => void;
folder: OfficeFolder;
};
export default function DownloadAnchoringProofModal(props: IProps) {
const { isOpen, onClose, folder } = props;
const downloadAnchoringProof = useCallback(async () => {
if (!folder?.uid) return;
try {
const file = await OfficeFolderAnchors.getInstance().download(folder.uid);
const url = window.URL.createObjectURL(file);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = `anchoring_proof_${folder?.folder_number}_${folder?.name}.zip`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
onClose?.();
} catch (e) {
console.warn(e);
}
}, [folder?.folder_number, folder?.name, folder.uid, onClose]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Félicitations ! Dossier ancré avec succès"}
firstButton={{ children: "Fermer", onClick: onClose }}
secondButton={{ children: "Télécharger la preuve dancrage", onClick: downloadAnchoringProof }}>
<Typography typo={ETypo.TEXT_MD_light}>
Votre dossier a é validé et ancré dans la blockchain. Vous pouvez maintenant télécharger la preuve d'ancrage pour vos
archives.
</Typography>
</Modal>
);
}

View File

@ -0,0 +1,11 @@
.anchoring {
display: flex;
flex-direction: column;
gap: 24px;
.validate-gif {
width: 100%;
height: 100%;
object-fit: contain;
}
}

View File

@ -0,0 +1,32 @@
import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react";
type IProps = {
isOpen: boolean;
onClose: () => void;
onAnchor: () => void;
};
export default function RequireAnchoringModal(props: IProps) {
const { isOpen, onClose, onAnchor: onAnchorProps } = props;
const onAnchor = useCallback(() => {
onAnchorProps();
onClose();
}, [onAnchorProps, onClose]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={"Archiver le dossier, action requise : Ancrer et certifier le dossier"}
firstButton={{ children: "Annuler", onClick: onClose }}
secondButton={{ children: "Ancrer le dossier", onClick: onAnchor }}>
<Typography typo={ETypo.TEXT_MD_light}>
Pour archiver ce dossier, il est nécessaire de l'ancrer dans la blockchain afin de garantir la sécurité et l'authenticité
des documents. Veuillez procéder à l'ancrage avant de continuer.
</Typography>
</Modal>
);
}

View File

@ -0,0 +1,29 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.form {
margin-top: 32px;
.password_indication {
margin-top: 8px;
margin-bottom: 24px;
}
.submit_button {
margin-top: 32px;
}
}
}

View File

@ -0,0 +1,43 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
};
export default function PasswordForgotten(props: IProps) {
const { onSubmit, validationErrors } = props;
return (
<div className={classes["root"]}>
<Typography typo={ETypo.DISPLAY_LARGE}>
<div className={classes["title"]}>Réinitialisez votre mot de passe</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Mot de passe"
name="password"
validationError={validationErrors.find((error) => error.property === "password")}
password
/>
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500} className={classes["password_indication"]}>
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
</Typography>
<TextField
placeholder="Confirmation du mot de passe"
name="confirm_password"
validationError={validationErrors.find((error) => error.property === "confirm_password")}
password
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Valider
</Button>
</Form>
</div>
);
}

View File

@ -0,0 +1,32 @@
.root {
width: 472px;
margin: auto;
margin-top: 80px;
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.header {
display: flex;
flex-direction: column;
gap: var(--spacing-sm, 8px);
}
.content {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
.section {
.section-title {
margin-bottom: var(--spacing-xl, 32px);
}
> form {
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
}
}
}
}

View File

@ -0,0 +1,194 @@
import React, { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
//import Image from "next/image";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button from "@Front/Components/DesignSystem/Button";
//import franceConnectLogo from "../france-connect.svg";
// import { useRouter } from "next/router";
// import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
import { ValidationError } from "class-validator";
import Image from "next/image";
import LogoSmallBlue from "@Assets/logo_small_blue.svg";
import idNoteLogo from "@Assets/Icons/id-note-logo.svg";
import { useRouter } from "next/router";
import { FrontendVariables } from "@Front/Config/VariablesFront";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
};
export default function StepEmail(props: IProps) {
const { onSubmit, validationErrors } = props;
const [isErrorModalOpen, setIsErrorModalOpen] = useState(0);
/* const router = useRouter();
const redirectCustomerOnConnection = useCallback(() => {
async function getCustomer() {
try {
const loginRes = await Customers.getInstance().login();
router.push(loginRes.enrollment.franceConnectUrl);
} catch (e) {
console.error(e);
}
}
getCustomer();
}, [router]); */
const router = useRouter();
const error = router.query["error"];
const redirectUserOnConnection = useCallback(() => {
const variables = FrontendVariables.getInstance();
router.push(
`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${
variables.FRONT_APP_HOST
}/authorized-client&scope=openid,profile&response_type=code`,
);
}, [router]);
const openErrorModal = useCallback((index: number) => {
setIsErrorModalOpen(index);
}, []);
const closeErrorModal = useCallback(() => {
setIsErrorModalOpen(0);
}, []);
const closeNoEmailModal = useCallback(() => {
setIsErrorModalOpen(0);
router.push("https://connexion.idnot.fr/");
}, [router]);
const closeContactAdminModal = () => {
setIsErrorModalOpen(0);
window.open("https://www.lecoffre.io/contact", "_blank");
};
useEffect(() => {
openErrorModal(parseInt(error as string));
}, [error, openErrorModal]);
return (
<div className={classes["root"]}>
<div className={classes["header"]}>
<Image src={LogoSmallBlue} alt="Logo small blue" height="56" width="56" />
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}>
Bienvenue !
</Typography>
<Typography typo={ETypo.TITLE_H5}>Connectez-vous pour accéder à votre espace sécurisé.</Typography>
</div>
<div className={classes["content"]}>
<div className={classes["section"]}>
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT} className={classes["section-title"]}>
Pour les notaires :
</Typography>
<Button onClick={redirectUserOnConnection} rightIcon={<Image alt="id-not-logo" src={idNoteLogo} />}>
S'identifier avec ID.not
</Button>
</div>
<div className={classes["section"]}>
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT} className={classes["section-title"]}>
Pour les clients :
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Renseigner votre email"
label="E-mail"
name="email"
validationError={validationErrors.find((err) => err.property === "email")}
/>
<Button type="submit">Se connecter</Button>
</Form>
</div>
</div>
<Confirm
isOpen={isErrorModalOpen === 1}
onClose={closeErrorModal}
showCancelButton={false}
onAccept={closeErrorModal}
closeBtn
header={"Erreur"}
confirmText={"OK"}>
<div className={classes["modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Vous ne disposez pas d'un abonnement, veuillez contacter l'administrateur de votre office.
</Typography>
</div>
</Confirm>
<Confirm
isOpen={isErrorModalOpen === 2}
onClose={closeErrorModal}
showCancelButton={false}
onAccept={closeErrorModal}
closeBtn
header={"Session expirée"}
confirmText={"OK"}>
<div className={classes["modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Veuillez vous reconnecter.
</Typography>
</div>
</Confirm>
<Confirm
isOpen={isErrorModalOpen === 3}
onClose={closeErrorModal}
onAccept={closeNoEmailModal}
closeBtn
header={"Echec de connexion"}
confirmText={"Accéder à mon compte ID.not"}
cancelText={"OK"}>
<div className={classes["modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Votre compte ID.not doit être associé à une adresse email @notaires.fr (onglet Mettre à jour mes données
professionnelles)
</Typography>
</div>
</Confirm>
<Confirm
isOpen={isErrorModalOpen === 4}
onClose={closeErrorModal}
onAccept={closeContactAdminModal}
closeBtn
header={"Vous nêtes pas bêta-testeur"}
confirmText={"Contacter l'administrateur"}
cancelText={"OK"}>
<div className={classes["modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
L'accès à la version bêta de lecoffre.io est limité à un groupe restreint d'utilisateurs autorisés.
</Typography>
<ul>
<li>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Si vous êtes intéressé par la participation à notre programme de bêta-test, veuillez nous compléter le
formulaire :{" "}
<a
href="https://www.lecoffre.io/contact"
target="_blank"
style={{ color: "blue", textDecoration: "underline" }}>
https://www.lecoffre.io/contact
</a>
</Typography>
</li>
<div style={{ marginBottom: "10px" }}></div>
<li>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Si vous avez déjà un compte bêta-testeur, veuillez vous connecter sur{" "}
<a
href="https://compte.idnot.fr/home"
target="_blank"
style={{ color: "blue", textDecoration: "underline" }}>
https://compte.idnot.fr/home
</a>{" "}
et vérifier que l'adresse mail renseignée sur votre espace est identique à celle que vous nous avez
communiquée.
</Typography>
</li>
</ul>
</div>
</Confirm>
</div>
);
}

View File

@ -0,0 +1,29 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.form {
margin-top: 32px;
.password_indication {
margin-top: 8px;
margin-bottom: 24px;
}
.submit_button {
margin-top: 32px;
}
}
}

View File

@ -0,0 +1,43 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
};
export default function StepNewPassword(props: IProps) {
const { onSubmit, validationErrors } = props;
return (
<div className={classes["root"]}>
<Typography typo={ETypo.DISPLAY_LARGE}>
<div className={classes["title"]}>Configurez votre mot de passe</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Mot de passe"
name="password"
validationError={validationErrors.find((error) => error.property === "password")}
password
/>
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500} className={classes["password_indication"]}>
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
</Typography>
<TextField
placeholder="Confirmation du mot de passe"
name="confirm_password"
validationError={validationErrors.find((error) => error.property === "confirm_password")}
password
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Valider
</Button>
</Form>
</div>
);
}

View File

@ -0,0 +1,30 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.form {
margin-top: 32px;
.submit_button {
margin-top: 32px;
}
.forgot-password {
margin-top: 8px;
text-decoration: underline;
cursor: pointer;
}
}
}

View File

@ -0,0 +1,70 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
onPasswordForgotClicked: () => void;
};
export default function StepPassword(props: IProps) {
const { onSubmit, validationErrors, onPasswordForgotClicked } = props;
const [isModalOpened, setIsModalOpened] = React.useState(false);
const closeModal = () => {
setIsModalOpened(false);
};
const openModal = () => {
setIsModalOpened(true);
};
const onModalAccept = () => {
onPasswordForgotClicked();
setIsModalOpened(false);
};
return (
<div className={classes["root"]}>
<Typography typo={ETypo.DISPLAY_LARGE}>
<div className={classes["title"]}>Entrez votre mot de passe</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Mot de passe"
name="password"
validationError={validationErrors.find((error) => error.property === "password")}
password
/>
<div onClick={openModal}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["forgot-password"]}>
Mot de passe oublié ?
</Typography>
</div>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Valider
</Button>
</Form>
<Confirm
isOpen={isModalOpened}
onClose={closeModal}
showCancelButton={true}
onAccept={onModalAccept}
closeBtn
header={"Mot de passe oublié ?"}
confirmText={"Valider"}
cancelText={"Annuler"}>
<div className={classes["modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Un code à usage unique va vous être envoyé par sms pour réinitialiser votre mot de passe.
</Typography>
</div>
</Confirm>
</div>
);
}

View File

@ -0,0 +1,43 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: 220px auto;
.title {
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.form {
margin-top: 32px;
.submit_button {
margin-top: 32px;
}
}
.ask-another-code {
margin-top: 48px;
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
.new-code-button {
&[data-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
}
.new-code-timer {
display: flex;
gap: 6px;
align-items: center;
}
}
}

View File

@ -0,0 +1,77 @@
import React, { useEffect } from "react";
import classes from "./classes.module.scss";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
partialPhoneNumber: string;
onSendAnotherCode: () => void;
};
export default function StepTotp(props: IProps) {
const { onSubmit, validationErrors, partialPhoneNumber, onSendAnotherCode } = props;
const [disableNewCodeButton, setDisableNewCodeButton] = React.useState(false);
const [secondsBeforeNewCode, setSecondsBeforeNewCode] = React.useState(0);
useEffect(() => {
const interval = setInterval(() => {
if (secondsBeforeNewCode > 0) {
setSecondsBeforeNewCode(secondsBeforeNewCode - 1);
if (secondsBeforeNewCode === 1) {
setDisableNewCodeButton(false);
}
}
}, 1000);
return () => clearInterval(interval);
}, [secondsBeforeNewCode]);
const sendAnotherCode = () => {
onSendAnotherCode();
setDisableNewCodeButton(true);
setSecondsBeforeNewCode(30);
};
return (
<div className={classes["root"]}>
<Typography typo={ETypo.DISPLAY_LARGE}>
<div className={classes["title"]}>
Votre code a é envoyé par SMS au ** ** ** {partialPhoneNumber.replace(/(.{2})/g, "$1 ")}
</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Code à usage unique"
name="totpCode"
validationError={validationErrors.find((error) => error.property === "totpCode")}
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant
</Button>
</Form>
<div className={classes["ask-another-code"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR}>Vous n'avez rien reçu ?</Typography>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.TEXT}
disabled={disableNewCodeButton}
data-disabled={disableNewCodeButton.toString()}
onClick={sendAnotherCode}
className={classes["new-code-button"]}>
Envoyer un nouveau code
</Button>
{secondsBeforeNewCode !== 0 && (
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} className={classes["new-code-timer"]}>
Redemandez un code dans
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_SECONDARY_500}>
00:{secondsBeforeNewCode < 10 ? `0${secondsBeforeNewCode}` : secondsBeforeNewCode}
</Typography>
</Typography>
)}
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,44 @@
// $screen-xl: 2559px;
$screen-l: 1439px;
$screen-ls: 1280px;
$screen-m: 1023px;
$screen-s: 767px;
// $screen-xs: 424px;
$custom-easing: cubic-bezier(0.645, 0.045, 0.355, 1);
// Generic colors
$black: #000000;
$white: #ffffff;
// Flash colors
$green-flash: #12bf4d;
$blue-flash: #005176;
$turquoise-flash: #3fa79e;
$purple-flash: #320756;
$purple-hover: #4e1480;
$orange-flash: #ffb017;
$red-flash: #a63a23;
$re-hover: #cc4c31;
$pink-flash: #bd4b91;
$pink-hover: #e34ba9;
// Soft colors
$green-soft: #baf2cd;
$blue-soft: #a7c6d4;
$orange-soft: #ffdc99;
$turquoise-soft: #c3eae6;
$purple-soft: #c5b2d4;
$orange-soft: #ffdc99;
$red-soft: #f08771;
$pink-soft: #f8b9df;
$orange-soft-hover: #ffd078;
$grey: #939393;
$grey-medium: #e7e7e7;
$grey-soft: #f9f9f9;
$modal-background: rgba(0, 0, 0, 0.44);
$shadow-nav: 0px 8px 10px rgba(0, 0, 0, 0.07);
$shadow-tooltip: 0px 4px 24px rgba(0, 0, 0, 0.15);

View File

@ -0,0 +1,36 @@
@import "@Themes/constants.scss";
:root {
--root-max-width: 1440px;
--root-margin: auto;
--root-padding: 64px 120px;
--font-text-family: "Inter", sans-serif;
--green-flash: #{$green-flash};
--blue-flash: #{$blue-flash};
--turquoise-flash: #{$turquoise-flash};
--purple-flash: #{$purple-flash};
--purple-hover: #{$purple-hover};
--orange-flash: #{$orange-flash};
--red-flash: #{$red-flash};
--re-hover: #{$re-hover};
--pink-flash: #{$pink-flash};
--pink-hover: #{$pink-hover};
--green-soft: #{$green-soft};
--blue-soft: #{$blue-soft};
--turquoise-soft: #{$turquoise-soft};
--purple-soft: #{$purple-soft};
--orange-soft: #{$orange-soft};
--orange-soft-hover: #{$orange-soft-hover};
--red-soft: #{$red-soft};
--pink-soft: #{$pink-soft};
--grey: #{$grey};
--grey-medium: #{$grey-medium};
--grey-soft: #{$grey-soft};
--black: #{$black};
--white: #{$white};
}

View File

@ -0,0 +1,5 @@
import DesignSystem from "@Front/Components/Layouts/DesignSystem";
export default function Route() {
return <DesignSystem />;
}