diff --git a/package.json b/package.json index 9b4743f..b173fb3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "lucide-react": "^0.468.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-router-dom": "^7.18.2", "tailwindcss": "^4.3.3" }, "devDependencies": { @@ -20,6 +21,7 @@ "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.0.1" + "vite": "^6.0.1", + "vite-plugin-svgr": "^5.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a76094..0b23bb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.2.7(react@19.2.7) + react-router-dom: + specifier: ^7.18.2 + version: 7.18.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tailwindcss: specifier: ^4.3.3 version: 4.3.3 @@ -39,6 +42,9 @@ importers: vite: specifier: ^6.0.1 version: 6.4.3(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0) + vite-plugin-svgr: + specifier: ^5.2.0 + version: 5.2.0(rollup@4.62.4)(typescript@5.9.3)(vite@6.4.3(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)) packages: @@ -306,6 +312,15 @@ packages: '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@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 + '@rollup/rollup-android-arm-eabi@4.62.4': resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] @@ -431,6 +446,74 @@ packages: cpu: [x64] os: [win32] + '@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.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -553,6 +636,9 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + baseline-browser-mapping@2.10.42: resolution: {integrity: sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==} engines: {node: '>=6.0.0'} @@ -563,12 +649,33 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + 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.30001803: resolution: {integrity: sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: + 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 + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -585,6 +692,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + electron-to-chromium@1.5.388: resolution: {integrity: sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw==} @@ -592,6 +702,13 @@ packages: resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} 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==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -601,6 +718,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -622,6 +742,13 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -629,11 +756,18 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -709,6 +843,12 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -728,10 +868,25 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-releases@2.0.50: resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} engines: {node: '>=18'} + 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-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -752,10 +907,31 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-router-dom@7.18.2: + resolution: {integrity: sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.18.2: + resolution: {integrity: sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react@19.2.7: 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'} + rollup@4.62.4: resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -768,10 +944,19 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + 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.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -783,6 +968,9 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -797,6 +985,11 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + vite-plugin-svgr@5.2.0: + resolution: {integrity: sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==} + peerDependencies: + vite: '>=3.0.0' + vite@6.4.3: resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1056,6 +1249,14 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/pluginutils@5.4.0(rollup@4.62.4)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.5 + optionalDependencies: + rollup: 4.62.4 + '@rollup/rollup-android-arm-eabi@4.62.4': optional: true @@ -1131,6 +1332,76 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.4': optional: true + '@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.3': dependencies: '@jridgewell/remapping': 2.3.5 @@ -1247,6 +1518,8 @@ snapshots: transitivePeerDependencies: - supports-color + argparse@2.0.1: {} + baseline-browser-mapping@2.10.42: {} browserslist@4.28.5: @@ -1257,10 +1530,25 @@ snapshots: node-releases: 2.0.50 update-browserslist-db: 1.2.3(browserslist@4.28.5) + callsites@3.1.0: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001803: {} convert-source-map@2.0.0: {} + cookie@1.1.1: {} + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + csstype@3.2.3: {} debug@4.4.3: @@ -1269,6 +1557,11 @@ snapshots: detect-libc@2.1.2: {} + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + electron-to-chromium@1.5.388: {} enhanced-resolve@5.24.5: @@ -1276,6 +1569,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 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -1307,6 +1606,8 @@ snapshots: escalade@3.2.0: {} + estree-walker@2.0.2: {} + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -1318,12 +1619,25 @@ snapshots: graceful-fs@4.2.11: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + is-arrayish@0.2.1: {} + jiti@2.7.0: {} js-tokens@4.0.0: {} + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} + json5@2.2.3: {} lightningcss-android-arm64@1.32.0: @@ -1375,6 +1689,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -1391,8 +1711,26 @@ snapshots: nanoid@3.3.15: {} + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-releases@2.0.50: {} + 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-type@4.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.5: {} @@ -1410,8 +1748,24 @@ snapshots: react-refresh@0.17.0: {} + react-router-dom@7.18.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.18.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + react-router@7.18.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie: 1.1.1 + react: 19.2.7 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + react@19.2.7: {} + resolve-from@4.0.0: {} + rollup@4.62.4: dependencies: '@types/estree': 1.0.9 @@ -1448,8 +1802,17 @@ snapshots: semver@6.3.1: {} + set-cookie-parser@2.7.2: {} + + 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.3: {} tapable@2.3.3: {} @@ -1459,6 +1822,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tslib@2.8.1: {} + typescript@5.9.3: {} undici-types@7.18.2: @@ -1470,6 +1835,17 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + vite-plugin-svgr@5.2.0(rollup@4.62.4)(typescript@5.9.3)(vite@6.4.3(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)): + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@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: 6.4.3(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + vite@6.4.3(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: esbuild: 0.25.12 diff --git a/src/App.tsx b/src/App.tsx index 7eb3070..525f489 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,100 +1,11 @@ -import { useState } from "react"; -import { HomePage } from "./pages/HomePage"; -import { LoginPage, SignupPage, TermsPage } from "./pages/AuthPages"; -import { MissionRecordPage, PhotoCertificationPage } from "./pages/MissionPages"; -import type { BottomNavKey } from "./components/UI"; -import { MapPage } from "./pages/MapPage"; -import { getMissionById } from "./data/quespot"; -import { MissionDetailPage } from "./pages/MissionDetailPage"; -import { MyPage } from "./pages/MyPage"; -import { QuestyCustomizePage } from "./pages/QuestyCustomizePage"; - -type AppScreen = "login" | "signup" | "terms" | "home" | "map" | "missionDetail" | "my" | "questy" | "photo" | "record"; +import { RouterProvider } from "react-router-dom"; +import { router } from "./routes/router"; function App() { - const [screenStack, setScreenStack] = useState(["login"]); - const [selectedMissionId, setSelectedMissionId] = useState("tea-house"); - const activeScreen = screenStack[screenStack.length - 1]; - - const navigate = (screen: AppScreen) => { - setScreenStack((stack) => (stack[stack.length - 1] === screen ? stack : [...stack, screen])); - }; - - const resetTo = (screen: AppScreen) => { - setScreenStack([screen]); - }; - - const goBack = () => { - setScreenStack((stack) => (stack.length > 1 ? stack.slice(0, -1) : stack)); - }; - - const openMissionDetail = (missionId: string) => { - setSelectedMissionId(missionId); - navigate("missionDetail"); - }; - - const handleBottomNav = (key: BottomNavKey) => { - if (key === "mission") { - navigate("photo"); - return; - } - - if (key === "map") { - navigate("map"); - return; - } - - if (key === "my") { - navigate("my"); - return; - } - - if (key === "reward") { - navigate("record"); - return; - } - - navigate("home"); - }; - - const renderScreen = () => { - switch (activeScreen) { - case "login": - return resetTo("home")} onSignup={() => navigate("signup")} />; - case "signup": - return ; - case "terms": - return ; - case "home": - return ( - navigate("record")} - onStartMission={() => navigate("photo")} - /> - ); - case "map": - return navigate("photo")} />; - case "missionDetail": - return navigate("photo")} />; - case "my": - return navigate("questy")} />; - case "questy": - return ; - case "photo": - return ; - case "record": - return ; - default: - return resetTo("home")} />; - } - }; - return ( -
-
{renderScreen()}
-
+ <> + + ); } diff --git a/src/assets/icons/home.svg b/src/assets/icons/home.svg new file mode 100644 index 0000000..794f9db --- /dev/null +++ b/src/assets/icons/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/map.svg b/src/assets/icons/map.svg new file mode 100644 index 0000000..2edddbd --- /dev/null +++ b/src/assets/icons/map.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/mission.svg b/src/assets/icons/mission.svg new file mode 100644 index 0000000..c1a90fe --- /dev/null +++ b/src/assets/icons/mission.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/my.svg b/src/assets/icons/my.svg new file mode 100644 index 0000000..dded5e3 --- /dev/null +++ b/src/assets/icons/my.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/reward.svg b/src/assets/icons/reward.svg new file mode 100644 index 0000000..3b5eb6f --- /dev/null +++ b/src/assets/icons/reward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/UI.tsx b/src/components/UI.tsx index 5d28705..eaa5130 100644 --- a/src/components/UI.tsx +++ b/src/components/UI.tsx @@ -1,23 +1,29 @@ -import type { ButtonHTMLAttributes, InputHTMLAttributes, PropsWithChildren, ReactNode } from "react"; -import { - Landmark, - MapPin, - Search, -} from "lucide-react"; -import homeIcon from "../icons/Icon.svg"; -import missionIcon from "../icons/Icon (1).svg"; -import mapIcon from "../icons/Icon (2).svg"; -import rewardIcon from "../icons/Icon (3).svg"; -import myIcon from "../icons/Icon (4).svg"; +import type { + ButtonHTMLAttributes, + InputHTMLAttributes, + PropsWithChildren, + ReactNode, +} from "react"; +import { Landmark, MapPin, Search } from "lucide-react"; type ButtonProps = ButtonHTMLAttributes & { variant?: "primary" | "secondary" | "ghost" | "disabled"; icon?: ReactNode; }; -export function Button({ children, className = "", icon, variant = "primary", ...props }: ButtonProps) { +export function Button({ + children, + className = "", + icon, + variant = "primary", + ...props +}: ButtonProps) { return ( - @@ -37,11 +43,20 @@ export function TextField({ label, ...props }: TextFieldProps) { ); } -export function Panel({ children, className = "" }: PropsWithChildren<{ className?: string }>) { +export function Panel({ + children, + className = "", +}: PropsWithChildren<{ className?: string }>) { return
{children}
; } -export function WireImage({ label = "코스 이미지", tall = false }: { label?: string; tall?: boolean }) { +export function WireImage({ + label = "코스 이미지", + tall = false, +}: { + label?: string; + tall?: boolean; +}) { return (
@@ -51,36 +66,6 @@ export function WireImage({ label = "코스 이미지", tall = false }: { label? ); } -export type BottomNavKey = "home" | "mission" | "map" | "reward" | "my"; - -const navItems: Array<{ key: BottomNavKey; label: string; icon: string }> = [ - { key: "home", label: "홈", icon: homeIcon }, - { key: "mission", label: "미션", icon: missionIcon }, - { key: "map", label: "지도", icon: mapIcon }, - { key: "reward", label: "보상", icon: rewardIcon }, - { key: "my", label: "MY", icon: myIcon }, -]; - -export function BottomNav({ - active = "home", - onSelect, -}: { - active?: BottomNavKey; - onSelect?: (key: BottomNavKey) => void; -}) { - - return ( - - ); -} - export function SearchInput({ placeholder }: { placeholder: string }) { return (
+ -
- -
- 소도시 미션에서 추가 보너스 포인트! -

인구감소지역 미션 완료 시 +20% 보너스

-
-
+
+
+

+ 내 주변 스팟 +

+ +
+
+ {nearbySpots.map((spot) => ( +
+ + + {spot.done ? ( + + ) : null} + {spot.badge ? {spot.badge} : null} + +
+ {spot.name} +

+ {spot.distance} + + {spot.status === "완료" ? ( + 완료 + ) : ( + `미션 ${spot.missionCount}개` + )} +

+
+ + + {spot.rating} + + +
+ ))} +
+
+ +
+ +
+ 소도시 미션에서 추가 보너스 포인트! +

인구감소지역 미션 완료 시 +20% 보너스

+
- - + ); } diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 4b369a1..1315e43 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,7 +1,10 @@ import { courseCards, figmaAssets } from "../data/quespot"; import { Button, WireImage } from "../components/UI"; +import { useNavigate } from "react-router-dom"; +import { PATH } from "@/routes/paths"; -export function LandingPage() { +export default function LandingPage() { + const navigate = useNavigate(); return (
@@ -28,9 +31,13 @@ export function LandingPage() {
미션을 수행하며 보상을 받아보세요.

- + - Quespot 캐릭터 + Quespot 캐릭터
diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index 4a9e3d2..e29f1f0 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -1,25 +1,47 @@ -import { Bell, Crosshair, LocateFixed, MapPin, Navigation, Route, Search, Star, Target } from "lucide-react"; +import { + Bell, + Crosshair, + LocateFixed, + MapPin, + Navigation, + Route, + Search, + Star, + Target, +} from "lucide-react"; import { DeviceFrame, SubHeader } from "../components/DeviceFrame"; -import { BottomNav, Button, LocationBadge, type BottomNavKey } from "../components/UI"; - -type MapPageProps = { - onBack?: () => void; - onNavigate?: (screen: BottomNavKey) => void; - onStartMission?: () => void; -}; +import { Button, LocationBadge } from "../components/UI"; +import { useNavigate } from "react-router-dom"; +import { PATH } from "@/routes/paths"; const mapSpots = [ - { name: "인사동 전통찻집", area: "종로구 인사동", point: "240P", className: "pin-a" }, - { name: "북촌 골목 산책", area: "북촌 한옥마을", point: "180P", className: "pin-b" }, - { name: "청계천 포토존", area: "청계광장", point: "160P", className: "pin-c" }, + { + name: "인사동 전통찻집", + area: "종로구 인사동", + point: "240P", + className: "pin-a", + }, + { + name: "북촌 골목 산책", + area: "북촌 한옥마을", + point: "180P", + className: "pin-b", + }, + { + name: "청계천 포토존", + area: "청계광장", + point: "160P", + className: "pin-c", + }, ]; -export function MapPage({ onBack, onNavigate, onStartMission }: MapPageProps) { +export default function MapPage() { + const navigate = useNavigate(); return ( - + <> navigate(-1)} action={
- - - - + ); } diff --git a/src/pages/MissionPages.tsx b/src/pages/MissionPages.tsx deleted file mode 100644 index 7de232f..0000000 --- a/src/pages/MissionPages.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { DeviceFrame, SubHeader } from "../components/DeviceFrame"; -import { Button, Panel } from "../components/UI"; -import { figmaAssets } from "../data/quespot"; -import { Camera, Check, ClipboardList, Coffee, ImageUp, Lightbulb, MessageCircle, PencilLine, Plus, X } from "lucide-react"; - -type BackablePageProps = { - onBack?: () => void; -}; - -export function PhotoCertificationPage({ onBack }: BackablePageProps) { - return ( - - - - 힌트 - - } - /> -
- -

- - 인증 안내 -

- 인사동 전통찻집 방문 -

전통찻집 간판이 보이도록 외관을 촬영하거나, 내부에서 차를 즐기는 사진을 찍어 제출하세요.

-
- - -

예시 사진

-
-
-
- 좋은 인증 예시 - - 좋은 예 - -
-
✅ 간판·내부 포함
-
-
-
- 나쁜 인증 예시 - - 나쁜 예 - -
-
❌ 장소 식별 불가
-
-
-
- - -
- -
-

사진을 촬영하거나 업로드하세요

-
- - -
-
- -
-
- ); -} - -export function MissionRecordPage({ onBack }: BackablePageProps) { - return ( - - -
-
- - - -
- 인사동 전통찻집 - 2026년 7월 8일 방문 -
-
- - -
-

- - 작성 프롬프트 -

- -
-
"오늘 방문한 장소에서 가장 인상 깊었던 점은?"
-