perf: icon system as react components implemented

This commit is contained in:
Afra ‌ 2026-06-28 17:29:08 +03:30
parent 1dd4813a61
commit e88c2531f7
46 changed files with 562 additions and 468 deletions

View file

@ -37,6 +37,7 @@
"tailwindcss": "^4.3.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.61.1",
"vite": "^8.0.12"
"vite": "^8.0.12",
"vite-plugin-svgr": "^5.2.0"
}
}

327
pnpm-lock.yaml generated
View file

@ -81,6 +81,9 @@ importers:
vite:
specifier: ^8.0.12
version: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)
vite-plugin-svgr:
specifier: ^5.2.0
version: 5.2.0(typescript@5.9.3)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))
packages:
@ -349,6 +352,83 @@ packages:
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@rollup/pluginutils@5.4.0':
resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@svgr/babel-plugin-add-jsx-attribute@8.0.0':
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-remove-jsx-attribute@8.0.0':
resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0':
resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0':
resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-svg-dynamic-title@8.0.0':
resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-svg-em-dimensions@8.0.0':
resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-transform-react-native-svg@8.1.0':
resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-plugin-transform-svg-component@8.0.0':
resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
engines: {node: '>=12'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/babel-preset@8.1.0':
resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@svgr/core@8.1.0':
resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
engines: {node: '>=14'}
'@svgr/hast-util-to-babel-ast@8.0.0':
resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
engines: {node: '>=14'}
'@svgr/plugin-jsx@8.1.0':
resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
engines: {node: '>=14'}
peerDependencies:
'@svgr/core': '*'
'@tailwindcss/node@4.3.1':
resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==}
@ -569,6 +649,9 @@ packages:
ajv@6.15.0:
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@ -604,6 +687,14 @@ packages:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
caniuse-lite@1.0.30001799:
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
@ -618,6 +709,15 @@ packages:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
cosmiconfig@8.3.6:
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=4.9.5'
peerDependenciesMeta:
typescript:
optional: true
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@ -645,6 +745,9 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@ -656,6 +759,13 @@ packages:
resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@ -729,6 +839,9 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -847,10 +960,17 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@ -869,6 +989,10 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.3.0:
resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==}
hasBin: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@ -877,6 +1001,9 @@ packages:
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@ -972,10 +1099,16 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@ -1009,6 +1142,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-releases@2.0.47:
resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==}
engines: {node: '>=18'}
@ -1025,6 +1161,14 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@ -1033,6 +1177,10 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -1092,6 +1240,10 @@ packages:
resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==}
engines: {node: '>=0.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
rolldown@1.0.3:
resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -1120,10 +1272,16 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
svg-parser@2.0.4:
resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
tailwindcss@4.3.1:
resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==}
@ -1172,6 +1330,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
vite-plugin-svgr@5.2.0:
resolution: {integrity: sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==}
peerDependencies:
vite: '>=3.0.0'
vite@8.0.16:
resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -1493,6 +1656,82 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {}
'@rollup/pluginutils@5.4.0':
dependencies:
'@types/estree': 1.0.9
estree-walker: 2.0.2
picomatch: 4.0.4
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-preset@8.1.0(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7)
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7)
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7)
'@svgr/core@8.1.0(typescript@5.9.3)':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7)
camelcase: 6.3.0
cosmiconfig: 8.3.6(typescript@5.9.3)
snake-case: 3.0.4
transitivePeerDependencies:
- supports-color
- typescript
'@svgr/hast-util-to-babel-ast@8.0.0':
dependencies:
'@babel/types': 7.29.7
entities: 4.5.0
'@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))':
dependencies:
'@babel/core': 7.29.7
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7)
'@svgr/core': 8.1.0(typescript@5.9.3)
'@svgr/hast-util-to-babel-ast': 8.0.0
svg-parser: 2.0.4
transitivePeerDependencies:
- supports-color
'@tailwindcss/node@4.3.1':
dependencies:
'@jridgewell/remapping': 2.3.5
@ -1712,6 +1951,8 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
argparse@2.0.1: {}
asynckit@0.4.0: {}
autoprefixer@10.5.0(postcss@8.5.15):
@ -1754,6 +1995,10 @@ snapshots:
es-errors: 1.3.0
function-bind: 1.1.2
callsites@3.1.0: {}
camelcase@6.3.0: {}
caniuse-lite@1.0.30001799: {}
combined-stream@1.0.8:
@ -1764,6 +2009,15 @@ snapshots:
cookie@1.1.1: {}
cosmiconfig@8.3.6(typescript@5.9.3):
dependencies:
import-fresh: 3.3.1
js-yaml: 4.3.0
parse-json: 5.2.0
path-type: 4.0.0
optionalDependencies:
typescript: 5.9.3
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@ -1782,6 +2036,11 @@ snapshots:
detect-libc@2.1.2: {}
dot-case@3.0.4:
dependencies:
no-case: 3.0.4
tslib: 2.8.1
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -1795,6 +2054,12 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.3
entities@4.5.0: {}
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@ -1893,6 +2158,8 @@ snapshots:
estraverse@5.3.0: {}
estree-walker@2.0.2: {}
esutils@2.0.3: {}
fast-deep-equal@3.1.3: {}
@ -1995,8 +2262,15 @@ snapshots:
ignore@7.0.5: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
imurmurhash@0.1.4: {}
is-arrayish@0.2.1: {}
is-extglob@2.1.1: {}
is-glob@4.0.3:
@ -2009,10 +2283,16 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@4.3.0:
dependencies:
argparse: 2.0.1
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
json-parse-even-better-errors@2.3.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@ -2079,10 +2359,16 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
lines-and-columns@1.2.4: {}
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lower-case@2.0.2:
dependencies:
tslib: 2.8.1
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@ -2109,6 +2395,11 @@ snapshots:
natural-compare@1.4.0: {}
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
tslib: 2.8.1
node-releases@2.0.47: {}
optionator@0.9.4:
@ -2128,10 +2419,23 @@ snapshots:
dependencies:
p-limit: 3.1.0
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.29.7
error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-type@4.0.0: {}
picocolors@1.1.1: {}
picomatch@4.0.4: {}
@ -2178,6 +2482,8 @@ snapshots:
react@19.2.7: {}
resolve-from@4.0.0: {}
rolldown@1.0.3:
dependencies:
'@oxc-project/types': 0.133.0
@ -2213,8 +2519,15 @@ snapshots:
shebang-regex@3.0.0: {}
snake-case@3.0.4:
dependencies:
dot-case: 3.0.4
tslib: 2.8.1
source-map-js@1.2.1: {}
svg-parser@2.0.4: {}
tailwindcss@4.3.1: {}
tapable@2.3.3: {}
@ -2228,8 +2541,7 @@ snapshots:
dependencies:
typescript: 5.9.3
tslib@2.8.1:
optional: true
tslib@2.8.1: {}
type-check@0.4.0:
dependencies:
@ -2260,6 +2572,17 @@ snapshots:
dependencies:
punycode: 2.3.1
vite-plugin-svgr@5.2.0(typescript@5.9.3)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)):
dependencies:
'@rollup/pluginutils': 5.4.0
'@svgr/core': 8.1.0(typescript@5.9.3)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))
vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0):
dependencies:
lightningcss: 1.32.0

View file

@ -1,12 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2V5" stroke="#4D4D4D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 2V5" stroke="#4D4D4D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.5 9.08984H20.5" stroke="#4D4D4D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke="#4D4D4D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.6937 13.6992H15.7027" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.6937 16.6992H15.7027" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.9945 13.6992H12.0035" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.9945 16.6992H12.0035" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29529 13.6992H8.30427" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29529 16.6992H8.30427" stroke="#4D4D4D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" stroke="white" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 321 B

View file

@ -1,6 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 16.7397V4.6697C22 3.4697 21.02 2.5797 19.83 2.6797H19.77C17.67 2.8597 14.48 3.9297 12.7 5.0497L12.53 5.1597C12.24 5.3397 11.76 5.3397 11.47 5.1597L11.22 5.0097C9.44 3.8997 6.26 2.8397 4.16 2.6697C2.97 2.5697 2 3.4697 2 4.6597V16.7397C2 17.6997 2.78 18.5997 3.74 18.7197L4.03 18.7597C6.2 19.0497 9.55 20.1497 11.47 21.1997L11.51 21.2197C11.78 21.3697 12.21 21.3697 12.47 21.2197C14.39 20.1597 17.75 19.0497 19.93 18.7597L20.26 18.7197C21.22 18.5997 22 17.6997 22 16.7397Z" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 5.49023V20.4902" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.75 8.49023H5.5" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 11.4902H5.5" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 16.7397V4.6697C22 3.4697 21.02 2.5797 19.83 2.6797H19.77C17.67 2.8597 14.48 3.9297 12.7 5.0497L12.53 5.1597C12.24 5.3397 11.76 5.3397 11.47 5.1597L11.22 5.0097C9.44 3.8997 6.26 2.8397 4.16 2.6697C2.97 2.5697 2 3.4697 2 4.6597V16.7397C2 17.6997 2.78 18.5997 3.74 18.7197L4.03 18.7597C6.2 19.0497 9.55 20.1497 11.47 21.1997L11.51 21.2197C11.78 21.3697 12.21 21.3697 12.47 21.2197C14.39 20.1597 17.75 19.0497 19.93 18.7597L20.26 18.7197C21.22 18.5997 22 17.6997 22 16.7397Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 5.49023V20.4902" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.75 8.49023H5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 11.4902H5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1,013 B

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,12 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.5 9.08984H20.5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.6937 13.6992H15.7027" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.6937 16.6992H15.7027" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.9945 13.6992H12.0035" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.9945 16.6992H12.0035" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29529 13.6992H8.30427" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29529 16.6992H8.30427" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.97 18.33C21.97 18.69 21.89 19.06 21.72 19.42C21.55 19.78 21.33 20.12 21.04 20.44C20.55 20.98 20.01 21.37 19.4 21.62C18.8 21.87 18.15 22 17.45 22C16.43 22 15.34 21.76 14.19 21.27C13.04 20.78 11.89 20.12 10.75 19.29C9.6 18.45 8.51 17.52 7.47 16.49C6.44 15.45 5.51 14.36 4.68 13.22C3.86 12.08 3.2 10.94 2.72 9.81C2.24 8.67 2 7.58 2 6.54C2 5.86 2.12 5.21 2.36 4.61C2.6 4 2.98 3.44 3.51 2.94C4.15 2.31 4.85 2 5.59 2C5.87 2 6.15 2.06 6.4 2.18C6.66 2.3 6.89 2.48 7.07 2.74L9.39 6.01C9.57 6.26 9.7 6.49 9.79 6.71C9.88 6.92 9.93 7.13 9.93 7.32C9.93 7.56 9.86 7.8 9.72 8.03C9.59 8.26 9.4 8.5 9.16 8.74L8.4 9.53C8.29 9.64 8.24 9.77 8.24 9.93C8.24 10.01 8.25 10.08 8.27 10.16C8.3 10.24 8.33 10.3 8.35 10.36C8.53 10.69 8.84 11.12 9.28 11.64C9.73 12.16 10.21 12.69 10.73 13.22C11.27 13.75 11.79 14.24 12.32 14.69C12.84 15.13 13.27 15.43 13.61 15.61C13.66 15.63 13.72 15.66 13.79 15.69C13.87 15.72 13.95 15.73 14.04 15.73C14.21 15.73 14.34 15.67 14.45 15.56L15.21 14.81C15.46 14.56 15.7 14.37 15.93 14.25C16.16 14.11 16.39 14.04 16.64 14.04C16.83 14.04 17.03 14.08 17.25 14.17C17.47 14.26 17.7 14.39 17.95 14.56L21.26 16.91C21.52 17.09 21.7 17.3 21.81 17.55C21.91 17.8 21.97 18.05 21.97 18.33Z" stroke="#4D4D4D" stroke-width="1.5" stroke-miterlimit="10"/>
<path d="M21.97 18.33C21.97 18.69 21.89 19.06 21.72 19.42C21.55 19.78 21.33 20.12 21.04 20.44C20.55 20.98 20.01 21.37 19.4 21.62C18.8 21.87 18.15 22 17.45 22C16.43 22 15.34 21.76 14.19 21.27C13.04 20.78 11.89 20.12 10.75 19.29C9.6 18.45 8.51 17.52 7.47 16.49C6.44 15.45 5.51 14.36 4.68 13.22C3.86 12.08 3.2 10.94 2.72 9.81C2.24 8.67 2 7.58 2 6.54C2 5.86 2.12 5.21 2.36 4.61C2.6 4 2.98 3.44 3.51 2.94C4.15 2.31 4.85 2 5.59 2C5.87 2 6.15 2.06 6.4 2.18C6.66 2.3 6.89 2.48 7.07 2.74L9.39 6.01C9.57 6.26 9.7 6.49 9.79 6.71C9.88 6.92 9.93 7.13 9.93 7.32C9.93 7.56 9.86 7.8 9.72 8.03C9.59 8.26 9.4 8.5 9.16 8.74L8.4 9.53C8.29 9.64 8.24 9.77 8.24 9.93C8.24 10.01 8.25 10.08 8.27 10.16C8.3 10.24 8.33 10.3 8.35 10.36C8.53 10.69 8.84 11.12 9.28 11.64C9.73 12.16 10.21 12.69 10.73 13.22C11.27 13.75 11.79 14.24 12.32 14.69C12.84 15.13 13.27 15.43 13.61 15.61C13.66 15.63 13.72 15.66 13.79 15.69C13.87 15.72 13.95 15.73 14.04 15.73C14.21 15.73 14.34 15.67 14.45 15.56L15.21 14.81C15.46 14.56 15.7 14.37 15.93 14.25C16.16 14.11 16.39 14.04 16.64 14.04C16.83 14.04 17.03 14.08 17.25 14.17C17.47 14.26 17.7 14.39 17.95 14.56L21.26 16.91C21.52 17.09 21.7 17.3 21.81 17.55C21.91 17.8 21.97 18.05 21.97 18.33Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 226 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 6l-6 6 6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 216 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

View file

@ -1,5 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2594 3.59924L5.04936 12.2892C4.73936 12.6192 4.43936 13.2692 4.37936 13.7192L4.00936 16.9592C3.87936 18.1292 4.71936 18.9292 5.87936 18.7292L9.09936 18.1792C9.54936 18.0992 10.1794 17.7692 10.4894 17.4292L18.6994 8.73924C20.1194 7.23924 20.7594 5.52924 18.5494 3.43924C16.3494 1.36924 14.6794 2.09924 13.2594 3.59924Z" stroke="#223D5D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.8906 5.05078C12.3206 7.81078 14.5606 9.92078 17.3406 10.2008" stroke="#223D5D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 22H21" stroke="#223D5D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.2594 3.59924L5.04936 12.2892C4.73936 12.6192 4.43936 13.2692 4.37936 13.7192L4.00936 16.9592C3.87936 18.1292 4.71936 18.9292 5.87936 18.7292L9.09936 18.1792C9.54936 18.0992 10.1794 17.7692 10.4894 17.4292L18.6994 8.73924C20.1194 7.23924 20.7594 5.52924 18.5494 3.43924C16.3494 1.36924 14.6794 2.09924 13.2594 3.59924Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.8906 5.05078C12.3206 7.81078 14.5606 9.92078 17.3406 10.2008" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 22H21" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 868 B

View file

@ -1,4 +1,4 @@
<svg width="119" height="119" viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.9171 74.4333L74.1559 38.1342C76.724 35.5615 80.2279 34.9239 82.012 36.7033C83.7943 38.4839 83.1619 41.9888 80.5938 44.5622L44.3551 80.8595C41.7863 83.433 38.2831 84.0709 36.499 82.2904C34.716 80.5109 35.3483 77.006 37.9171 74.4333Z" fill="#C23B3B"/>
<path d="M80.8379 74.4333L44.5996 38.1342C42.0304 35.5615 38.5265 34.9239 36.7431 36.7033C34.9601 38.4839 35.5924 41.9888 38.1616 44.5622L74.4 80.8595C76.9681 83.433 80.472 84.0709 82.2554 82.2904C84.0384 80.5109 83.4061 77.006 80.8379 74.4333Z" fill="#C23B3B"/>
<path d="M59.5 0C43.7197 0 28.5856 6.26821 17.4278 17.4267C6.26891 28.5856 0 43.719 0 59.5C0 75.2803 6.26891 90.4144 17.4278 101.572C28.5856 112.731 43.7197 118.999 59.5 118.999C75.281 118.999 90.4144 112.73 101.572 101.572C112.731 90.4134 119 75.2803 119 59.5C119 43.7197 112.731 28.5856 101.572 17.4267C90.4144 6.26891 75.281 0 59.5 0ZM59.5 9.34303C72.8025 9.34303 85.5598 14.6275 94.9663 24.0337C104.373 33.4402 109.657 46.1968 109.657 59.5C109.657 72.8014 104.373 85.5591 94.9663 94.9653C85.5598 104.371 72.8025 109.656 59.5 109.656C46.1975 109.656 33.4409 104.371 24.0347 94.9653C14.6285 85.5591 9.34409 72.8014 9.34409 59.5C9.34409 46.1968 14.6285 33.4402 24.0347 24.0337C33.4409 14.6275 46.1975 9.34303 59.5 9.34303Z" fill="#C23B3B"/>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,3 +1,3 @@
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.08333 12.4167V9.08333M9.08333 5.75H9.09167M17.4167 9.08333C17.4167 13.6857 13.6857 17.4167 9.08333 17.4167C4.48096 17.4167 0.75 13.6857 0.75 9.08333C0.75 4.48096 4.48096 0.75 9.08333 0.75C13.6857 0.75 17.4167 4.48096 17.4167 9.08333Z" stroke="#376195" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.08333 12.4167V9.08333M9.08333 5.75H9.09167M17.4167 9.08333C17.4167 13.6857 13.6857 17.4167 9.08333 17.4167C4.48096 17.4167 0.75 13.6857 0.75 9.08333C0.75 4.48096 4.48096 0.75 9.08333 0.75C13.6857 0.75 17.4167 4.48096 17.4167 9.08333Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 440 B

View file

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.61971 8.49C5.58971 -0.169998 18.4197 -0.159997 20.3797 8.5C21.5297 13.58 18.3697 17.88 15.5997 20.54C13.5897 22.48 10.4097 22.48 8.38971 20.54C5.62971 17.88 2.46971 13.57 3.61971 8.49Z" stroke="#4D4D4D" stroke-width="1.5"/>
<path d="M11.9989 13.4295C13.722 13.4295 15.1189 12.0326 15.1189 10.3095C15.1189 8.58633 13.722 7.18945 11.9989 7.18945C10.2758 7.18945 8.87891 8.58633 8.87891 10.3095C8.87891 12.0326 10.2758 13.4295 11.9989 13.4295Z" stroke="#4D4D4D" stroke-width="1.5"/>
<path d="M3.61971 8.49C5.58971 -0.169998 18.4197 -0.159997 20.3797 8.5C21.5297 13.58 18.3697 17.88 15.5997 20.54C13.5897 22.48 10.4097 22.48 8.38971 20.54C5.62971 17.88 2.46971 13.57 3.61971 8.49Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M11.9989 13.4295C13.722 13.4295 15.1189 12.0326 15.1189 10.3095C15.1189 8.58633 13.722 7.18945 11.9989 7.18945C10.2758 7.18945 8.87891 8.58633 8.87891 10.3095C8.87891 12.0326 10.2758 13.4295 11.9989 13.4295Z" stroke="currentColor" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 595 B

After

Width:  |  Height:  |  Size: 605 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 8H12.6673" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 220 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.75 2C7.94891 2 8.13968 2.07902 8.28033 2.21967C8.42098 2.36032 8.5 2.55109 8.5 2.75V7H12.75C12.9489 7 13.1397 7.07902 13.2803 7.21967C13.421 7.36032 13.5 7.55109 13.5 7.75C13.5 7.94891 13.421 8.13968 13.2803 8.28033C13.1397 8.42098 12.9489 8.5 12.75 8.5H8.5V12.75C8.5 12.9489 8.42098 13.1397 8.28033 13.2803C8.13968 13.421 7.94891 13.5 7.75 13.5C7.55109 13.5 7.36032 13.421 7.21967 13.2803C7.07902 13.1397 7 12.9489 7 12.75V8.5H2.75C2.55109 8.5 2.36032 8.42098 2.21967 8.28033C2.07902 8.13968 2 7.94891 2 7.75C2 7.55109 2.07902 7.36032 2.21967 7.21967C2.36032 7.07902 2.55109 7 2.75 7H7V2.75C7 2.55109 7.07902 2.36032 7.21967 2.21967C7.36032 2.07902 7.55109 2 7.75 2Z" fill="#223D5D"/>
<path d="M7.75 2C7.94891 2 8.13968 2.07902 8.28033 2.21967C8.42098 2.36032 8.5 2.55109 8.5 2.75V7H12.75C12.9489 7 13.1397 7.07902 13.2803 7.21967C13.421 7.36032 13.5 7.55109 13.5 7.75C13.5 7.94891 13.421 8.13968 13.2803 8.28033C13.1397 8.42098 12.9489 8.5 12.75 8.5H8.5V12.75C8.5 12.9489 8.42098 13.1397 8.28033 13.2803C8.13968 13.421 7.94891 13.5 7.75 13.5C7.55109 13.5 7.36032 13.421 7.21967 13.2803C7.07902 13.1397 7 12.9489 7 12.75V8.5H2.75C2.55109 8.5 2.36032 8.42098 2.21967 8.28033C2.07902 8.13968 2 7.94891 2 7.75C2 7.55109 2.07902 7.36032 2.21967 7.21967C2.36032 7.07902 2.55109 7 2.75 7H7V2.75C7 2.55109 7.07902 2.36032 7.21967 2.21967C7.36032 2.07902 7.55109 2 7.75 2Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 806 B

View file

@ -1,5 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.1207 12.7805C12.0507 12.7705 11.9607 12.7705 11.8807 12.7805C10.1207 12.7205 8.7207 11.2805 8.7207 9.51047C8.7207 7.70047 10.1807 6.23047 12.0007 6.23047C13.8107 6.23047 15.2807 7.70047 15.2807 9.51047C15.2707 11.2805 13.8807 12.7205 12.1207 12.7805Z" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.7398 19.3805C16.9598 21.0105 14.5998 22.0005 11.9998 22.0005C9.39977 22.0005 7.03977 21.0105 5.25977 19.3805C5.35977 18.4405 5.95977 17.5205 7.02977 16.8005C9.76977 14.9805 14.2498 14.9805 16.9698 16.8005C18.0398 17.5205 18.6398 18.4405 18.7398 19.3805Z" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#4D4D4D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.1207 12.7805C12.0507 12.7705 11.9607 12.7705 11.8807 12.7805C10.1207 12.7205 8.7207 11.2805 8.7207 9.51047C8.7207 7.70047 10.1807 6.23047 12.0007 6.23047C13.8107 6.23047 15.2807 7.70047 15.2807 9.51047C15.2707 11.2805 13.8807 12.7205 12.1207 12.7805Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.7398 19.3805C16.9598 21.0105 14.5998 22.0005 11.9998 22.0005C9.39977 22.0005 7.03977 21.0105 5.25977 19.3805C5.35977 18.4405 5.95977 17.5205 7.02977 16.8005C9.76977 14.9805 14.2498 14.9805 16.9698 16.8005C18.0398 17.5205 18.6398 18.4405 18.7398 19.3805Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1,015 B

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" stroke="#666666" stroke-width="1.3835" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 22L20 20" stroke="#666666" stroke-width="1.3835" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" stroke="currentColor" stroke-width="1.3835" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 22L20 20" stroke="currentColor" stroke-width="1.3835" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 446 B

View file

@ -1,4 +1,4 @@
<svg width="119" height="119" viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M87.6843 35.2269C86.0089 35.1574 84.1303 35.9432 82.6821 37.4859L49.8018 72.5197L36.1683 59.574C33.8655 57.3862 30.7441 56.96 29.1707 58.6174L28.278 59.556C26.7046 61.2137 27.2868 64.3147 29.5952 66.4964L46.7361 82.7002C48.7203 84.5114 52.1374 83.9803 54.4546 81.5114L89.6437 44.0199C91.961 41.5509 92.2732 38.1059 90.3456 36.2964C89.6222 35.6179 88.6899 35.2685 87.6843 35.2269Z" fill="#57A327"/>
<path d="M59.5005 0C43.7202 0 28.586 6.26823 17.4275 17.4268C6.26893 28.5857 0 43.7192 0 59.5002C0 75.2805 6.26893 90.4147 17.4275 101.573C28.586 112.731 43.7202 119 59.5005 119C75.2808 119 90.4144 112.73 101.573 101.573C112.732 90.4137 119 75.2805 119 59.5002C119 43.7199 112.732 28.5857 101.573 17.4268C90.4144 6.26893 75.2808 0 59.5005 0ZM59.5005 9.34306C72.803 9.34306 85.5608 14.6275 94.9663 24.0337C104.372 33.4403 109.657 46.197 109.657 59.5002C109.657 72.8017 104.372 85.5593 94.9663 94.9656C85.5608 104.372 72.803 109.656 59.5005 109.656C46.1984 109.656 33.4407 104.372 24.0344 94.9656C14.6282 85.5593 9.34377 72.8017 9.34377 59.5002C9.34377 46.197 14.6282 33.4403 24.0344 24.0337C33.4407 14.6275 46.1984 9.34306 59.5005 9.34306Z" fill="#57A327"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#223D5D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-minus-icon lucide-minus"><path d="M5 12h14"/></svg>

Before

Width:  |  Height:  |  Size: 248 B

View file

@ -1,10 +0,0 @@
export default function BookIcon() {
return (
<svg className="h-5 w-5 text-[#4D4D4D]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M22 16.7397V4.6697C22 3.4697 21.02 2.5797 19.83 2.6797H19.77C17.67 2.8597 14.48 3.9297 12.7 5.0497L12.53 5.1597C12.24 5.3397 11.76 5.3397 11.47 5.1597L11.22 5.0097C9.44 3.8997 6.26 2.8397 4.16 2.6697C2.97 2.5697 2 3.4697 2 4.6597V16.7397C2 17.6997 2.78 18.5997 3.74 18.7197L4.03 18.7597C6.2 19.0497 9.55 20.1497 11.47 21.1997L11.51 21.2197C11.78 21.3697 12.21 21.3697 12.47 21.2197C14.39 20.1597 17.75 19.0497 19.93 18.7597L20.26 18.7197C21.22 18.5997 22 17.6997 22 16.7397Z" />
<path d="M12 5.49023V20.4902" />
<path d="M7.75 8.49023H5.5" />
<path d="M8.5 11.4902H5.5" />
</svg>
)
}

View file

@ -1,18 +0,0 @@
export default function FailIcon({ className = 'mx-auto h-28 w-28' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M37.9171 74.4333L74.1559 38.1342C76.724 35.5615 80.2279 34.9239 82.012 36.7033C83.7943 38.4839 83.1619 41.9888 80.5938 44.5622L44.3551 80.8595C41.7863 83.433 38.2831 84.0709 36.499 82.2904C34.716 80.5109 35.3483 77.006 37.9171 74.4333Z"
fill="#C23B3B"
/>
<path
d="M80.8379 74.4333L44.5996 38.1342C42.0304 35.5615 38.5265 34.9239 36.7431 36.7033C34.9601 38.4839 35.5924 41.9888 38.1616 44.5622L74.4 80.8595C76.9681 83.433 80.472 84.0709 82.2554 82.2904C84.0384 80.5109 83.4061 77.006 80.8379 74.4333Z"
fill="#C23B3B"
/>
<path
d="M59.5 0C43.7197 0 28.5856 6.26821 17.4278 17.4267C6.26891 28.5856 0 43.719 0 59.5C0 75.2803 6.26891 90.4144 17.4278 101.572C28.5856 112.731 43.7197 118.999 59.5 118.999C75.281 118.999 90.4144 112.73 101.572 101.572C112.731 90.4134 119 75.2803 119 59.5C119 43.7197 112.731 28.5856 101.572 17.4267C90.4144 6.26891 75.281 0 59.5 0ZM59.5 9.34303C72.8025 9.34303 85.5598 14.6275 94.9663 24.0337C104.373 33.4402 109.657 46.1968 109.657 59.5C109.657 72.8014 104.373 85.5591 94.9663 94.9653C85.5598 104.371 72.8025 109.656 59.5 109.656C46.1975 109.656 33.4409 104.371 24.0347 94.9653C14.6285 85.5591 9.34409 72.8014 9.34409 59.5C9.34409 46.1968 14.6285 33.4402 24.0347 24.0337C33.4409 14.6275 46.1975 9.34303 59.5 9.34303Z"
fill="#C23B3B"
/>
</svg>
)
}

View file

@ -0,0 +1,62 @@
import { memo, type CSSProperties, type SVGProps } from 'react'
import { icons } from './icons'
// `keyof typeof icons` derives the union straight from icons.ts's keys — add/remove/rename
// a key there and `name`'s autocomplete (and every existing <Icon name="..."> call site,
// via a type error) updates with it. No second list of names to keep in sync.
export type IconName = keyof typeof icons
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'color'> {
/** Which icon to render. Autocompletes from icons.ts. */
name: IconName
/** Sets width and height together. Ignored for either one passed explicitly. Defaults to 24. */
size?: number | string
/**
* Recolors the icon by setting the CSS `color` property, which every icon in icons.ts
* paints with via `stroke="currentColor"`/`fill="currentColor"` — EXCEPT `success` and
* `fail`, whose fills are hardcoded brand colors (green/red) by design and won't move.
*
* Left undefined (the default) on purpose, rather than defaulting it to the literal
* string `'currentColor'`: an inline `style.color` always beats a `className` text-color
* utility (e.g. Tailwind's `text-gray-400`) regardless of specificity, since inline
* styles win outright. Only setting `style.color` when the caller actually passes
* `color` lets `className`-based coloring keep working untouched, while still landing on
* `currentColor` at rest — which is what "inherit the ambient text color" already means.
*/
color?: string
}
function IconImpl({ name, size = 24, width, height, color, style, ...rest }: IconProps) {
const Svg = icons[name]
const mergedStyle: CSSProperties | undefined = color ? { color, ...style } : style
// Decorative by default (aria-hidden) — the WAI-ARIA "img" pattern only applies once
// the icon carries its own meaning, signaled here by the caller passing `aria-label`.
// Spreading `...rest` last lets an explicit `role`/`aria-hidden`/etc. from the caller
// override these computed defaults.
const a11yProps = rest['aria-label'] ? { role: 'img' as const } : { 'aria-hidden': true as const }
return (
<Svg
width={width ?? size}
height={height ?? size}
// A bare <svg> isn't part of the tab order in any current browser, but old
// IE/Edge would focus one — `focusable="false"` is the standard belt-and-suspenders
// fix. Every clickable icon in this app is already wrapped in a real <button> by its
// caller (that's how keyboard/Enter-activation and the native click semantics work),
// so Icon itself stays a plain graphic and never adds its own tabIndex/role="button".
focusable="false"
style={mergedStyle}
{...a11yProps}
{...rest}
/>
)
}
// Most call sites pass the same `name`/`className` on every render, so memo skips
// re-rendering (and re-diffing the SVG's child <path> nodes) when a parent re-renders
// for unrelated state — icons are leaf nodes with no children of their own to bail out on.
const Icon = memo(IconImpl)
Icon.displayName = 'Icon'
export default Icon

View file

@ -0,0 +1,47 @@
// Each icon is a plain .svg file under src/assets/icons/, imported with the `?react`
// suffix so vite-plugin-svgr compiles it into a React component at build time instead
// of shipping the raw markup as a string or a separate network request. Every import
// below is static (no `import()`/glob magic), so Vite/Rollup can see exactly which
// icons are referenced and tree-shake the rest — this file is the *only* place all 17
// icons are ever imported, so an icon that no call site uses never makes it into a chunk.
import ArrowRight from '../../assets/icons/arrow-right.svg?react'
import Book from '../../assets/icons/book.svg?react'
import Calendar from '../../assets/icons/calendar.svg?react'
import Call from '../../assets/icons/call.svg?react'
import Check from '../../assets/icons/check.svg?react'
import ChevronDown from '../../assets/icons/chevron-down.svg?react'
import ChevronLeft from '../../assets/icons/chevron-left.svg?react'
import ChevronRight from '../../assets/icons/chevron-right.svg?react'
import Edit from '../../assets/icons/edit.svg?react'
import Fail from '../../assets/icons/fail.svg?react'
import Info from '../../assets/icons/info.svg?react'
import Location from '../../assets/icons/location.svg?react'
import Minus from '../../assets/icons/minus.svg?react'
import Plus from '../../assets/icons/plus.svg?react'
import ProfileCircle from '../../assets/icons/profile-circle.svg?react'
import Search from '../../assets/icons/search.svg?react'
import Success from '../../assets/icons/success.svg?react'
// The single source of truth for available icons — keys are what callers pass to
// `<Icon name="..." />`. `IconName` (in Icon.tsx) is `keyof typeof icons`, so adding or
// renaming a key here immediately updates autocomplete/type-checking everywhere `Icon`
// is used, with no second list to keep in sync.
export const icons = {
arrowRight: ArrowRight,
book: Book,
calendar: Calendar,
call: Call,
check: Check,
chevronDown: ChevronDown,
chevronLeft: ChevronLeft,
chevronRight: ChevronRight,
edit: Edit,
fail: Fail,
info: Info,
location: Location,
minus: Minus,
plus: Plus,
profileCircle: ProfileCircle,
search: Search,
success: Success,
} as const

View file

@ -0,0 +1,2 @@
export { default } from './Icon'
export type { IconName, IconProps } from './Icon'

View file

@ -1,15 +0,0 @@
export default function InfoIcon({ className = 'h-5 w-5 shrink-0 text-gray-400' }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 19 19"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M9.08333 12.4167V9.08333M9.08333 5.75H9.09167M17.4167 9.08333C17.4167 13.6857 13.6857 17.4167 9.08333 17.4167C4.48096 17.4167 0.75 13.6857 0.75 9.08333C0.75 4.48096 4.48096 0.75 9.08333 0.75C13.6857 0.75 17.4167 4.48096 17.4167 9.08333Z" />
</svg>
)
}

View file

@ -1,5 +1,5 @@
import { createPortal } from 'react-dom'
import FailIcon from './FailIcon'
import Icon from './Icon'
interface PaymentFailModalProps {
onGoHome: () => void
@ -13,7 +13,7 @@ export default function PaymentFailModal({ onGoHome }: PaymentFailModalProps) {
<div className="fixed inset-0 z-1100 bg-black/70" />
<div className="fixed inset-0 z-1100 flex items-center justify-center p-4">
<div className="w-full max-w-sm rounded-3xl bg-white p-6 text-center shadow-lg">
<FailIcon />
<Icon name="fail" className="mx-auto h-28 w-28" />
<p className="mt-6 text-gray-900">رزرو با خطا مواجه شد. لطفا دوباره تلاش کنید.</p>
<button
type="button"

View file

@ -8,7 +8,7 @@ import { formatPersianNumber } from "../utils/formatNumber";
import { toE164 } from "../utils/phone";
import { setPendingReservation } from "../utils/pendingReservation";
import { getUserPhone } from "../utils/userPhone";
import InfoIcon from "./InfoIcon";
import Icon from "./Icon";
interface ReservationModalProps {
isOpen: boolean;
@ -22,25 +22,6 @@ interface ReservationModalProps {
// these are fixed test wallet ids given directly for now, replace once that exists
const WALLET_UUIDS = ["e7c9d4d1-4d1f-43b2-96f7-4d4a168f480d", "af7d967f-30c0-409b-9066-2549f2da5e5e"];
function MinusIcon({ className = "" }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 8H12.6673" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function PlusIcon({ className = "" }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7.75 2C7.94891 2 8.13968 2.07902 8.28033 2.21967C8.42098 2.36032 8.5 2.55109 8.5 2.75V7H12.75C12.9489 7 13.1397 7.07902 13.2803 7.21967C13.421 7.36032 13.5 7.55109 13.5 7.75C13.5 7.94891 13.421 8.13968 13.2803 8.28033C13.1397 8.42098 12.9489 8.5 12.75 8.5H8.5V12.75C8.5 12.9489 8.42098 13.1397 8.28033 13.2803C8.13968 13.421 7.94891 13.5 7.75 13.5C7.55109 13.5 7.36032 13.421 7.21967 13.2803C7.07902 13.1397 7 12.9489 7 12.75V8.5H2.75C2.55109 8.5 2.36032 8.42098 2.21967 8.28033C2.07902 8.13968 2 7.94891 2 7.75C2 7.55109 2.07902 7.36032 2.21967 7.21967C2.36032 7.07902 2.55109 7 2.75 7H7V2.75C7 2.55109 7.07902 2.36032 7.21967 2.21967C7.36032 2.07902 7.55109 2 7.75 2Z"
fill="currentColor"
/>
</svg>
);
}
function PaymentRow({
label,
amount,
@ -67,7 +48,7 @@ function PaymentRow({
{label}
</span>
{infoIconParam && (
<InfoIcon className="h-5 w-5 shrink-0 text-[#376195]" />
<Icon name="info" className="h-5 w-5 shrink-0 text-[#376195]" />
)}
</div>
<span
@ -211,7 +192,7 @@ export default function ReservationModal({
: "text-[#223D5D]"
}`}
>
<MinusIcon className="h-4 w-4" />
<Icon name="minus" className="h-4 w-4" />
</button>
<span dir="rtl" className="text-center font-medium text-gray-900">
{formatPersianNumber(count)} نفر
@ -221,7 +202,7 @@ export default function ReservationModal({
onClick={() => setCount((prev) => prev + 1)}
className="flex h-6 w-6 items-center justify-center text-[#223D5D]"
>
<PlusIcon className="h-4 w-4" />
<Icon name="plus" className="h-4 w-4" />
</button>
</div>
</div>
@ -246,7 +227,7 @@ export default function ReservationModal({
/>
<div className="mt-4 flex items-center justify-end gap-2 rounded-2xl bg-[#ECF2F8] px-4 py-3">
<InfoIcon />
<Icon name="info" className="h-5 w-5 shrink-0 text-gray-400" />
<p className="text-right text-sm text-gray-500">
توجه داشته باشید که مسئولیت رزرو و حضور به‌موقع در کافه بر عهده
کاربر است.

View file

@ -1,14 +0,0 @@
export default function SuccessIcon({ className = 'mx-auto h-28 w-28' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M87.6843 35.2269C86.0089 35.1574 84.1303 35.9432 82.6821 37.4859L49.8018 72.5197L36.1683 59.574C33.8655 57.3862 30.7441 56.96 29.1707 58.6174L28.278 59.556C26.7046 61.2137 27.2868 64.3147 29.5952 66.4964L46.7361 82.7002C48.7203 84.5114 52.1374 83.9803 54.4546 81.5114L89.6437 44.0199C91.961 41.5509 92.2732 38.1059 90.3456 36.2964C89.6222 35.6179 88.6899 35.2685 87.6843 35.2269Z"
fill="#57A327"
/>
<path
d="M59.5005 0C43.7202 0 28.586 6.26823 17.4275 17.4268C6.26893 28.5857 0 43.7192 0 59.5002C0 75.2805 6.26893 90.4147 17.4275 101.573C28.586 112.731 43.7202 119 59.5005 119C75.2808 119 90.4144 112.73 101.573 101.573C112.732 90.4137 119 75.2805 119 59.5002C119 43.7199 112.732 28.5857 101.573 17.4268C90.4144 6.26893 75.2808 0 59.5005 0ZM59.5005 9.34306C72.803 9.34306 85.5608 14.6275 94.9663 24.0337C104.372 33.4403 109.657 46.197 109.657 59.5002C109.657 72.8017 104.372 85.5593 94.9663 94.9656C85.5608 104.372 72.803 109.656 59.5005 109.656C46.1984 109.656 33.4407 104.372 24.0344 94.9656C14.6282 85.5593 9.34377 72.8017 9.34377 59.5002C9.34377 46.197 14.6282 33.4403 24.0344 24.0337C33.4407 14.6275 46.1984 9.34306 59.5005 9.34306Z"
fill="#57A327"
/>
</svg>
)
}

View file

@ -5,6 +5,7 @@ import { createCafeSchedule, MatchEvent } from '../services/campaignApi'
import { useMatchEvents } from '../hooks/useMatchEvents'
import { formatMatchLabel } from '../utils/matchCountdown'
import { getCafeUuid } from '../utils/cafe'
import Icon from '../components/Icon'
const ENTRY_FEE_STEP = 10000
const DESCRIPTION_MAX_LENGTH = 200
@ -14,41 +15,6 @@ function toEnglishDigits(value: string) {
return value.replace(/[۰-۹]/g, (digit) => String(persianDigits.indexOf(digit)))
}
function ChevronIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
)
}
function ChevronDownIcon({ className = '' }: { className?: string }) {
return (
<svg className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${className}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 9l6 6 6-6" />
</svg>
)
}
function MinusIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 8H12.6673" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
function PlusIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7.75 2C7.94891 2 8.13968 2.07902 8.28033 2.21967C8.42098 2.36032 8.5 2.55109 8.5 2.75V7H12.75C12.9489 7 13.1397 7.07902 13.2803 7.21967C13.421 7.36032 13.5 7.55109 13.5 7.75C13.5 7.94891 13.421 8.13968 13.2803 8.28033C13.1397 8.42098 12.9489 8.5 12.75 8.5H8.5V12.75C8.5 12.9489 8.42098 13.1397 8.28033 13.2803C8.13968 13.421 7.94891 13.5 7.75 13.5C7.55109 13.5 7.36032 13.421 7.21967 13.2803C7.07902 13.1397 7 12.9489 7 12.75V8.5H2.75C2.55109 8.5 2.36032 8.42098 2.21967 8.28033C2.07902 8.13968 2 7.94891 2 7.75C2 7.55109 2.07902 7.36032 2.21967 7.21967C2.36032 7.07902 2.55109 7 2.75 7H7V2.75C7 2.55109 7.07902 2.36032 7.21967 2.21967C7.36032 2.07902 7.55109 2 7.75 2Z"
fill="currentColor"
/>
</svg>
)
}
function MatchSelect({
value,
options,
@ -80,7 +46,7 @@ function MatchSelect({
? 'بازی مورد نظر را انتخاب کنید'
: 'بازی فعالی موجود نیست'}
</span>
<ChevronDownIcon className={open ? 'rotate-180' : ''} />
<Icon name="chevronDown" className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`} />
</button>
{open && hasMatches && (
@ -145,7 +111,7 @@ function Stepper({
disabled={atMin}
className={`flex h-6 w-6 items-center justify-center ${atMin ? 'cursor-not-allowed text-[#999999]' : 'text-[#223D5D]'}`}
>
<MinusIcon className="h-4 w-4" />
<Icon name="minus" className="h-4 w-4" />
</button>
{editable ? (
@ -170,7 +136,7 @@ function Stepper({
)}
<button type="button" onClick={() => onChange(value + step)} className="flex h-6 w-6 items-center justify-center text-[#223D5D]">
<PlusIcon className="h-4 w-4" />
<Icon name="plus" className="h-4 w-4" />
</button>
</div>
)
@ -227,7 +193,7 @@ export default function AddEvent() {
<div className="h-16 w-full bg-gray-200" />
<button type="button" onClick={() => navigate(-1)} className="mt-4 flex w-full items-center justify-start gap-2 px-4">
<ChevronIcon className="h-5 w-5 -scale-x-100 text-gray-900" />
<Icon name="arrowRight" className="h-5 w-5 -scale-x-100 text-gray-900" />
<span className="text-lg font-bold text-gray-900">افزودن ایونت</span>
</button>
@ -255,7 +221,7 @@ export default function AddEvent() {
className="pointer-events-auto flex items-center gap-2 rounded-full bg-[#223D5D] px-6 py-3 font-semibold text-white disabled:opacity-50"
>
{isSubmitting ? 'در حال ارسال...' : 'ادامه'}
{!isSubmitting && <ChevronIcon className="h-4 w-4" />}
{!isSubmitting && <Icon name="arrowRight" className="h-4 w-4" />}
</button>
</div>
</div>

View file

@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom'
import FailIcon from '../components/FailIcon'
import Icon from '../components/Icon'
export default function AddEventFail() {
const navigate = useNavigate()
@ -9,7 +9,7 @@ export default function AddEventFail() {
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<FailIcon />
<Icon name="fail" className="mx-auto h-28 w-28" />
<h1 className="mt-6 text-xl font-light text-gray-900">ثبت ایونت با خطا مواجه شد. لطفا دوباره تلاش کنید.</h1>
</div>

View file

@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom'
import SuccessIcon from '../components/SuccessIcon'
import Icon from '../components/Icon'
export default function AddEventSuccess() {
const navigate = useNavigate()
@ -9,7 +9,7 @@ export default function AddEventSuccess() {
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<SuccessIcon />
<Icon name="success" className="mx-auto h-28 w-28" />
<h1 className="mt-6 text-xl font-bold text-gray-900">ایونت با موفقیت ثبت شد!</h1>
</div>

View file

@ -14,83 +14,9 @@ import { useNow } from "../hooks/useNow";
import { formatMatchLabel } from "../utils/matchCountdown";
import { formatPersianNumber } from "../utils/formatNumber";
import { fromE164 } from "../utils/phone";
import InfoIcon from "../components/InfoIcon";
import Icon from "../components/Icon";
import ReservationModal from "../components/ReservationModal";
function ChevronLeftIcon() {
return (
<svg
className="h-5 w-5 text-gray-700"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 6l-6 6 6 6" />
</svg>
);
}
function ChevronRightIcon() {
return (
<svg
className="h-5 w-5 text-gray-700"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 6l6 6-6 6" />
</svg>
);
}
function PhoneIcon() {
return (
<svg
className="h-5 w-5 shrink-0 text-gray-500"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
>
<path
strokeMiterlimit="10"
d="M21.97 18.33C21.97 18.69 21.89 19.06 21.72 19.42C21.55 19.78 21.33 20.12 21.04 20.44C20.55 20.98 20.01 21.37 19.4 21.62C18.8 21.87 18.15 22 17.45 22C16.43 22 15.34 21.76 14.19 21.27C13.04 20.78 11.89 20.12 10.75 19.29C9.6 18.45 8.51 17.52 7.47 16.49C6.44 15.45 5.51 14.36 4.68 13.22C3.86 12.08 3.2 10.94 2.72 9.81C2.24 8.67 2 7.58 2 6.54C2 5.86 2.12 5.21 2.36 4.61C2.6 4 2.98 3.44 3.51 2.94C4.15 2.31 4.85 2 5.59 2C5.87 2 6.15 2.06 6.4 2.18C6.66 2.3 6.89 2.48 7.07 2.74L9.39 6.01C9.57 6.26 9.7 6.49 9.79 6.71C9.88 6.92 9.93 7.13 9.93 7.32C9.93 7.56 9.86 7.8 9.72 8.03C9.59 8.26 9.4 8.5 9.16 8.74L8.4 9.53C8.29 9.64 8.24 9.77 8.24 9.93C8.24 10.01 8.25 10.08 8.27 10.16C8.3 10.24 8.33 10.3 8.35 10.36C8.53 10.69 8.84 11.12 9.28 11.64C9.73 12.16 10.21 12.69 10.73 13.22C11.27 13.75 11.79 14.24 12.32 14.69C12.84 15.13 13.27 15.43 13.61 15.61C13.66 15.63 13.72 15.66 13.79 15.69C13.87 15.72 13.95 15.73 14.04 15.73C14.21 15.73 14.34 15.67 14.45 15.56L15.21 14.81C15.46 14.56 15.7 14.37 15.93 14.25C16.16 14.11 16.39 14.04 16.64 14.04C16.83 14.04 17.03 14.08 17.25 14.17C17.47 14.26 17.7 14.39 17.95 14.56L21.26 16.91C21.52 17.09 21.7 17.3 21.81 17.55C21.91 17.8 21.97 18.05 21.97 18.33Z"
/>
</svg>
);
}
function MapPinIcon() {
return (
<svg
className="h-5 w-5 shrink-0 text-gray-500"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
>
<path d="M3.61971 8.49C5.58971 -0.169998 18.4197 -0.159997 20.3797 8.5C21.5297 13.58 18.3697 17.88 15.5997 20.54C13.5897 22.48 10.4097 22.48 8.38971 20.54C5.62971 17.88 2.46971 13.57 3.61971 8.49Z" />
<path d="M11.9989 13.4295C13.722 13.4295 15.1189 12.0326 15.1189 10.3095C15.1189 8.58633 13.722 7.18945 11.9989 7.18945C10.2758 7.18945 8.87891 8.58633 8.87891 10.3095C8.87891 12.0326 10.2758 13.4295 11.9989 13.4295Z" />
</svg>
);
}
function ChevronDownIcon({ className = "" }: { className?: string }) {
return (
<svg
className={`h-4 w-4 shrink-0 text-gray-400 transition-transform ${className}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 9l6 6 6-6" />
</svg>
);
}
function MatchPicker({
value,
options,
@ -115,7 +41,7 @@ function MatchPicker({
className="flex items-center gap-1 text-right text-sm text-gray-500"
>
<span>{selected ? formatMatchLabel(selected.match_event, now) : "بازی را انتخاب کنید"}</span>
<ChevronDownIcon className={open ? "rotate-180" : ""} />
<Icon name="chevronDown" className={`h-4 w-4 shrink-0 text-gray-400 transition-transform ${open ? "rotate-180" : ""}`} />
</button>
{open && (
@ -170,7 +96,7 @@ function ImageCarousel({ images }: { images: string[] }) {
aria-label="قبلی"
className="absolute left-3 top-1/2 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-white shadow"
>
<ChevronLeftIcon />
<Icon name="chevronLeft" className="h-5 w-5 text-gray-700" />
</button>
<button
type="button"
@ -178,7 +104,7 @@ function ImageCarousel({ images }: { images: string[] }) {
aria-label="بعدی"
className="absolute right-3 top-1/2 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-white shadow"
>
<ChevronRightIcon />
<Icon name="chevronRight" className="h-5 w-5 text-gray-700" />
</button>
<div className="absolute inset-x-0 bottom-3 flex items-center justify-center gap-1.5">
{Array.from({ length: slideCount }).map((_, i) => (
@ -261,7 +187,7 @@ export default function CafeDetail() {
aria-label="بازگشت"
className="absolute left-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white shadow"
>
<ChevronLeftIcon />
<Icon name="chevronLeft" className="h-5 w-5 text-gray-700" />
</button>
</div>
@ -316,7 +242,7 @@ export default function CafeDetail() {
<div className="mt-4 border border-gray-200 rounded-2xl p-4">
<div className="flex items-center justify-start gap-4 py-3">
<PhoneIcon />
<Icon name="call" className="h-5 w-5 shrink-0 text-gray-500" />
<div className="flex flex-col items-start gap-1">
<p>شماره تماس</p>
<a href={`tel:${cafe.phone_number}`} className="text-gray-900">
@ -325,7 +251,7 @@ export default function CafeDetail() {
</div>
</div>
<div className="flex items-center justify-start gap-4 py-3">
<MapPinIcon />
<Icon name="location" className="h-5 w-5 shrink-0 text-gray-500" />
<div className="flex flex-col items-start gap-1">
<p>آدرس</p>
<span className="text-gray-900">{cafe.address}</span>
@ -342,7 +268,7 @@ export default function CafeDetail() {
</div>
<div className="mt-4 flex items-center justify-start gap-3 rounded-2xl bg-[#ECF2F8] px-4 py-3">
<InfoIcon />
<Icon name="info" className="h-5 w-5 shrink-0 text-gray-400" />
<p className="text-right text-sm text-gray-600">
توجه داشته باشید که مسئولیت رزرو و حضور به‌موقع در کافه بر عهده
کاربر است.

View file

@ -1,47 +1,12 @@
import { ReactNode, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import BookIcon from '../components/BookIcon'
import Icon from '../components/Icon'
import { useNow } from '../hooks/useNow'
import { CafeSchedule, fetchCafeSchedules } from '../services/campaignApi'
import { getCafeUuid } from '../utils/cafe'
import { formatPersianNumber } from '../utils/formatNumber'
import { formatMatchLabel } from '../utils/matchCountdown'
function ChevronIcon({ className = 'h-5 w-5 text-gray-400' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
)
}
function ProfileCircleIcon() {
return (
<svg className="h-5 w-5 text-[#4D4D4D]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M12.1207 12.7805C12.0507 12.7705 11.9607 12.7705 11.8807 12.7805C10.1207 12.7205 8.7207 11.2805 8.7207 9.51047C8.7207 7.70047 10.1807 6.23047 12.0007 6.23047C13.8107 6.23047 15.2807 7.70047 15.2807 9.51047C15.2707 11.2805 13.8807 12.7205 12.1207 12.7805Z" />
<path d="M18.7398 19.3805C16.9598 21.0105 14.5998 22.0005 11.9998 22.0005C9.39977 22.0005 7.03977 21.0105 5.25977 19.3805C5.35977 18.4405 5.95977 17.5205 7.02977 16.8005C9.76977 14.9805 14.2498 14.9805 16.9698 16.8005C18.0398 17.5205 18.6398 18.4405 18.7398 19.3805Z" />
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" />
</svg>
)
}
function CalendarIcon() {
return (
<svg className="h-5 w-5 text-[#4D4D4D]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M8 2V5" />
<path d="M16 2V5" />
<path d="M3.5 9.08984H20.5" />
<path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" />
<path strokeWidth={2} d="M15.6937 13.6992H15.7027" />
<path strokeWidth={2} d="M15.6937 16.6992H15.7027" />
<path strokeWidth={2} d="M11.9945 13.6992H12.0035" />
<path strokeWidth={2} d="M11.9945 16.6992H12.0035" />
<path strokeWidth={2} d="M8.29529 13.6992H8.30427" />
<path strokeWidth={2} d="M8.29529 16.6992H8.30427" />
</svg>
)
}
function PanelRow({ icon, label, onClick }: { icon: ReactNode; label: string; onClick?: () => void }) {
return (
<button type="button" onClick={onClick} className="flex w-full items-center justify-between py-4">
@ -49,7 +14,7 @@ function PanelRow({ icon, label, onClick }: { icon: ReactNode; label: string; on
{icon}
<span className="text-gray-900">{label}</span>
</div>
<ChevronIcon />
<Icon name="arrowRight" className="h-5 w-5 text-gray-400" />
</button>
)
}
@ -76,11 +41,11 @@ export default function CafeOwnerPanel() {
<div className="mt-2 divide-y divide-gray-100">
<PanelRow
icon={<ProfileCircleIcon />}
icon={<Icon name="profileCircle" className="h-5 w-5 text-[#4D4D4D]" />}
label="اطلاعات کافه"
onClick={() => navigate('/owner/register', { state: { step: 2 } })}
/>
<PanelRow icon={<BookIcon />} label="افزودن رویداد" onClick={() => navigate('/owner/events/new')} />
<PanelRow icon={<Icon name="book" className="h-5 w-5 text-[#4D4D4D]" />} label="افزودن رویداد" onClick={() => navigate('/owner/events/new')} />
</div>
<h2 className="mt-6 text-right font-semibold text-gray-900">رویدادهای فعال شما</h2>
@ -108,7 +73,7 @@ export default function CafeOwnerPanel() {
</div>
<div>
<PanelRow
icon={<CalendarIcon />}
icon={<Icon name="calendar" className="h-5 w-5 text-[#4D4D4D]" />}
label="مشاهده رزروها"
onClick={() =>
navigate(`/owner/events/${schedule.uuid}/reservations`, {

View file

@ -3,7 +3,7 @@ import { useLocation, useNavigate } from "react-router-dom";
import { MapContainer, TileLayer, Marker, ZoomControl, useMapEvents } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "../utils/leafletIcons";
import InfoIcon from "../components/InfoIcon";
import Icon from "../components/Icon";
import {
CafeFeature,
City,
@ -21,52 +21,6 @@ import { fromE164, toE164 } from "../utils/phone";
// Tehran — default pin until the owner picks their cafe's actual location
const DEFAULT_CAFE_LOCATION = { lat: 35.6892, lng: 51.389 };
function ArrowIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
);
}
function ChevronDownIcon({ className = "" }: { className?: string }) {
return (
<svg
className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${className}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 9l6 6 6-6" />
</svg>
);
}
function CheckIcon() {
return (
<svg
className="h-3 w-3 text-white"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" />
</svg>
);
}
function StepCircle({
active,
children,
@ -161,7 +115,10 @@ function CitySelectField({
<span className={selectedCity ? "text-gray-900" : "font-light text-[#999999]"}>
{selectedCity ? selectedCity.city_name : "شهر کافه را انتخاب کنید"}
</span>
<ChevronDownIcon className={open ? "rotate-180" : ""} />
<Icon
name="chevronDown"
className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${open ? "rotate-180" : ""}`}
/>
</button>
{open && (
@ -289,7 +246,7 @@ function FeatureChip({
checked ? "bg-[#223D5D]" : "bg-transparent"
}`}
>
{checked && <CheckIcon />}
{checked && <Icon name="check" className="h-3 w-3 text-white" />}
</span>
<span>{label}</span>
</button>
@ -424,7 +381,7 @@ export default function CafeOwnerRegister() {
{step === 1 ? (
<>
<div className="flex items-center justify-end gap-2 rounded-2xl bg-[#ECF2F8] px-4 py-4">
<InfoIcon />
<Icon name="info" className="h-5 w-5 shrink-0 text-gray-400" />
<p className="text-right text-md leading-relaxed text-gray-600">
لطفاً یک{" "}
<span className="font-semibold text-gray-900">نماینده</span>{" "}
@ -528,7 +485,7 @@ export default function CafeOwnerRegister() {
className="pointer-events-auto flex items-center gap-2 rounded-full bg-[#223D5D] px-6 py-3 font-semibold text-white disabled:opacity-50"
>
{isSubmitting ? "در حال ارسال..." : isEditMode ? "ذخیره تغییرات" : "ادامه"}
{!isSubmitting && !isEditMode && <ArrowIcon className="h-4 w-4" />}
{!isSubmitting && !isEditMode && <Icon name="arrowRight" className="h-4 w-4" />}
</button>
</div>
</div>

View file

@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom'
import FailIcon from '../components/FailIcon'
import Icon from '../components/Icon'
export default function CafeRegisterFail() {
const navigate = useNavigate()
@ -9,7 +9,7 @@ export default function CafeRegisterFail() {
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<FailIcon />
<Icon name="fail" className="mx-auto h-28 w-28" />
<h1 className="mt-6 text-xl font-light text-gray-900">ثبت کافه با خطا مواجه شد. لطفا دوباره تلاش کنید.</h1>
</div>

View file

@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom'
import SuccessIcon from '../components/SuccessIcon'
import Icon from '../components/Icon'
export default function CafeRegisterSuccess() {
const navigate = useNavigate()
@ -9,7 +9,7 @@ export default function CafeRegisterSuccess() {
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<SuccessIcon />
<Icon name="success" className="mx-auto h-28 w-28" />
<h1 className="mt-6 text-xl font-bold text-gray-900">کافه شما با موفقیت ثبت شد!</h1>
<p className="mt-3 leading-relaxed text-gray-500">
با اضافه کردن ایونت بازی‌ها، کافه شما در کمپین نمایش داده می‌شود و کاربران می‌توانند برای تماشای هر مسابقه در کافه شما، میز رزرو کنند.

View file

@ -1,59 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import Icon from "../components/Icon";
import { CafeEvent, mockCafeEvents } from "../data/mockCafeEvents";
import { EventReservation, fetchEventReservations } from "../services/campaignApi";
import { getCafeUuid } from "../utils/cafe";
import { formatPersianNumber } from "../utils/formatNumber";
function ChevronIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
);
}
function SearchIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.3835}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" />
<path d="M22 22L20 20" />
</svg>
);
}
function CheckIcon() {
return (
<svg
className="h-3 w-3 text-white"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" />
</svg>
);
}
function toEnglishDigits(value: string) {
const persianDigits = "۰۱۲۳۴۵۶۷۸۹";
return value.replace(/[۰-۹]/g, (digit) =>
@ -120,7 +72,7 @@ function ReservationCard({
onClick={onToggle}
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 border-[#223D5D] ${selected ? "bg-[#223D5D]" : "bg-transparent"}`}
>
{selected && <CheckIcon />}
{selected && <Icon name="check" className="h-3 w-3 text-white" />}
</button>
</div>
);
@ -179,7 +131,7 @@ export default function EventReservations() {
onClick={() => navigate(-1)}
className="mt-4 flex w-full items-center justify-start gap-2 px-4"
>
<ChevronIcon className="h-5 w-5 -scale-x-100 text-gray-900" />
<Icon name="arrowRight" className="h-5 w-5 -scale-x-100 text-gray-900" />
<span className="text-md font-semibold text-gray-900">
رزروهای {event?.matchLabel ?? ""}
</span>
@ -193,7 +145,10 @@ export default function EventReservations() {
placeholder="جستجو"
className="w-full rounded-2xl shadow-[0px_4px_12px_0px_#0000001F] bg-white py-3 pr-6 pl-4 text-right placeholder:font-light placeholder:text-[#999999]"
/>
<SearchIcon className="pointer-events-none absolute left-8 top-1/2 h-5 w-5 -translate-y-1/2 text-[#666666]" />
<Icon
name="search"
className="pointer-events-none absolute left-8 top-1/2 h-5 w-5 -translate-y-1/2 text-[#666666]"
/>
</div>
<div className="mt-4 flex flex-col gap-3 px-4">

View file

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import BookIcon from '../components/BookIcon'
import Icon from '../components/Icon'
import { Cafe, City, fetchCafes, fetchCities, MatchEvent } from '../services/campaignApi'
import { useMatchEvents } from '../hooks/useMatchEvents'
import { formatMatchLabel } from '../utils/matchCountdown'
@ -10,28 +10,6 @@ function findTehran(cities: City[]): City | undefined {
return cities.find((c) => c.city_name === 'تهران' || c.city_name.toLowerCase() === 'tehran')
}
function ChevronIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
)
}
function ChevronDownIcon({ className = '' }: { className?: string }) {
return (
<svg
className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${className}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 9l6 6 6-6" />
</svg>
)
}
// MapIcon/ListIcon — only used by the disabled نقشه/لیست toggle above, commented out together with it
// function MapIcon() {
// return (
@ -93,7 +71,10 @@ function CitySelectorRow({
className="w-full flex items-center justify-between gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3"
>
<span className="text-gray-900">{selectedCity?.city_name ?? ''}</span>
<ChevronDownIcon className={open ? 'rotate-180' : ''} />
<Icon
name="chevronDown"
className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
/>
</button>
{open && (
@ -149,7 +130,10 @@ function MatchSelectorRow({
<span className={selectedMatch ? 'text-gray-900' : 'text-gray-400'}>
{selectedMatch ? formatMatchLabel(selectedMatch, now) : !hasMatches ? 'بازی فعالی موجود نیست' : ''}
</span>
<ChevronDownIcon className={open ? 'rotate-180' : ''} />
<Icon
name="chevronDown"
className={`h-5 w-5 shrink-0 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
/>
</button>
{open && hasMatches && (
@ -186,10 +170,10 @@ function MyReservationsRow() {
className="flex w-full items-center justify-between px-4 py-6"
>
<div className="flex items-center gap-2">
<BookIcon />
<Icon name="book" className="h-5 w-5 text-[#4D4D4D]" />
<span className="text-gray-900">رزروهای شما</span>
</div>
<ChevronIcon className="h-5 w-5 text-gray-400" />
<Icon name="arrowRight" className="h-5 w-5 text-gray-400" />
</button>
)
}

View file

@ -1,26 +1,11 @@
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import Icon from "../components/Icon";
import { requestOtp } from "../services/authApi";
import { toPersianDigits } from "../utils/formatNumber";
import { toE164 } from "../utils/phone";
import { setUserRole } from "../utils/role";
function CheckIcon() {
return (
<svg
className="h-3 w-3 text-white"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" />
</svg>
);
}
export default function Login() {
const navigate = useNavigate();
const location = useLocation();
@ -98,7 +83,7 @@ export default function Login() {
acceptedTerms ? "bg-[#223D5D]" : "bg-transparent"
}`}
>
{acceptedTerms && <CheckIcon />}
{acceptedTerms && <Icon name="check" className="h-3 w-3 text-white" />}
</button>
<p className="text-sm text-gray-600">
<a href="https://winsoo.ir/terms/" className="text-[#223D5D] underline">

View file

@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import Icon from '../components/Icon'
import { requestOtp, verifyOtp } from '../services/authApi'
import { fetchMyCafes } from '../services/campaignApi'
import { storeAuthTokens } from '../utils/authToken'
@ -17,19 +18,6 @@ interface OTPCredential extends Credential {
code: string
}
function EditIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
<path
strokeMiterlimit={10}
d="M13.2594 3.59924L5.04936 12.2892C4.73936 12.6192 4.43936 13.2692 4.37936 13.7192L4.00936 16.9592C3.87936 18.1292 4.71936 18.9292 5.87936 18.7292L9.09936 18.1792C9.54936 18.0992 10.1794 17.7692 10.4894 17.4292L18.6994 8.73924C20.1194 7.23924 20.7594 5.52924 18.5494 3.43924C16.3494 1.36924 14.6794 2.09924 13.2594 3.59924Z"
/>
<path strokeMiterlimit={10} d="M11.8906 5.05078C12.3206 7.81078 14.5606 9.92078 17.3406 10.2008" />
<path strokeMiterlimit={10} d="M3 22H21" />
</svg>
)
}
function formatCountdown(seconds: number) {
const mm = String(Math.floor(seconds / 60)).padStart(2, '0')
const ss = String(seconds % 60).padStart(2, '0')
@ -195,7 +183,7 @@ export default function LoginOtp() {
<div className="mt-2 flex items-center justify-start gap-2">
<span className="text-[#223D5D]">{phone}</span>
<button type="button" onClick={() => navigate(loginPath, { state: { phone } })} aria-label="ویرایش شماره تماس">
<EditIcon className="h-5 w-5 text-[#223D5D]" />
<Icon name="edit" className="h-5 w-5 text-[#223D5D]" />
</button>
</div>

View file

@ -1,8 +1,7 @@
import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { useNavigate } from 'react-router-dom'
import InfoIcon from '../components/InfoIcon'
import SuccessIcon from '../components/SuccessIcon'
import Icon from '../components/Icon'
import { fetchMyReservationByUuid, fetchMyReservations, MyReservation } from '../services/campaignApi'
import { formatPersianNumber } from '../utils/formatNumber'
@ -16,14 +15,6 @@ function formatReservationDateTime(iso: string): string {
}).format(new Date(iso))
}
function ChevronIcon({ className = '' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
)
}
function MyReservationCard({ reservation, onClick }: { reservation: MyReservation; onClick: () => void }) {
return (
<button
@ -77,7 +68,7 @@ function ReservationDetailModal({ uuid, onClose }: { uuid: string; onClose: () =
className="max-h-[90vh] w-full overflow-y-auto rounded-3xl bg-white p-6"
onClick={(e) => e.stopPropagation()}
>
<SuccessIcon className="mx-auto h-24 w-24" />
<Icon name="success" className="mx-auto h-24 w-24" />
<h2 className="mt-4 text-right font-semibold text-gray-900">جزئیات رزرو</h2>
{reservation && (
@ -93,7 +84,7 @@ function ReservationDetailModal({ uuid, onClose }: { uuid: string; onClose: () =
)}
<div className="mt-4 flex items-center justify-end gap-2 rounded-2xl bg-[#ECF2F8] px-4 py-3">
<InfoIcon />
<Icon name="info" className="h-5 w-5 shrink-0 text-gray-400" />
<p className="text-right text-sm text-gray-500">
کد رزرو را برای ورود نزد خود نگه دارید. اطلاعات ورود و کد دیگری برای دقایقی شما پیامک می‌شود.
</p>
@ -124,7 +115,7 @@ export default function MyReservations() {
<div className="h-16 w-full bg-gray-200" />
<button type="button" onClick={() => navigate(-1)} className="mt-4 flex w-full items-center justify-start gap-2 px-4">
<ChevronIcon className="h-5 w-5 -scale-x-100 text-gray-900" />
<Icon name="arrowRight" className="h-5 w-5 -scale-x-100 text-gray-900" />
<span className="text-lg font-bold text-gray-900">رزروهای شما</span>
</button>

View file

@ -1,8 +1,7 @@
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import InfoIcon from '../components/InfoIcon'
import Icon from '../components/Icon'
import PaymentFailModal from '../components/PaymentFailModal'
import SuccessIcon from '../components/SuccessIcon'
import { createReservation, ReservationResult } from '../services/campaignApi'
import { formatPersianNumber } from '../utils/formatNumber'
import { fromE164 } from '../utils/phone'
@ -48,7 +47,7 @@ export default function PaymentSuccess() {
{!isFail && (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="w-full max-w-sm rounded-3xl bg-white p-6 shadow-lg">
<SuccessIcon />
<Icon name="success" className="mx-auto h-28 w-28" />
{reservation ? (
<>
@ -61,7 +60,7 @@ export default function PaymentSuccess() {
</div>
<div className="mt-4 flex items-center justify-end gap-2 rounded-2xl bg-[#ECF2F8] px-4 py-3">
<InfoIcon />
<Icon name="info" className="h-5 w-5 shrink-0 text-gray-400" />
<p className="text-right text-sm text-gray-600">
کد رزرو را برای ورود نزد خود نگه دارید. اطلاعات رزرو و کد ورود تا دقایقی دیگر برای شما پیامک می‌شود.
</p>

2
src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />

View file

@ -1,8 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import svgr from 'vite-plugin-svgr'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
// svgr only transforms imports ending in `?react` (e.g. `import Foo from './foo.svg?react'`),
// so plain `?url`/default `.svg` imports elsewhere (favicons, <img src>, etc.) are untouched
plugins: [react(), tailwindcss(), svgr()],
})