diff --git a/emotion.d.ts b/emotion.d.ts new file mode 100644 index 0000000..762b0d0 --- /dev/null +++ b/emotion.d.ts @@ -0,0 +1,15 @@ +import "@emotion/react"; +import { Theme as MuiTheme } from "@mui/material/styles"; + +declare module "@mui/material/styles" { + interface PaletteColor { + nonFocus?: string; + } + interface SimplePaletteColorOptions { + nonFocus?: string; + } +} + +declare module "@emotion/react" { + export interface Theme extends MuiTheme {} +} diff --git a/package.json b/package.json index 489cc59..5933f99 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,13 @@ "notistack": "^3.0.2", "react": "^19.0.0", "react-dom": "^19.0.0", - "remeda": "^2.21.3" + "react-router-dom": "^7.5.3" }, + "devDependencies": { "@eslint/js": "^9.21.0", + "@types/node": "^22.15.3", "@tanstack/react-query-devtools": "^5.74.4", - "@types/node": "^22.14.1", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@typescript-eslint/parser": "^8.29.1", @@ -51,10 +52,13 @@ "globals": "^15.15.0", "iamport-typings": "^1.4.0", "prettier": "^3.5.3", + "remeda": "^2.21.3", "typescript": "~5.7.2", "typescript-eslint": "^8.24.1", "vite": "^6.2.0", - "vite-plugin-mdx": "^3.6.1" + "vite-plugin-mdx": "^3.6.1", + "vite-plugin-svgr": "^4.3.0" + }, "packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1abe734..ae2a479 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.1.0(react@19.1.0) + react-router-dom: + specifier: ^7.5.3 + version: 7.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) remeda: specifier: ^2.21.3 version: 2.21.3 @@ -66,12 +69,12 @@ importers: '@eslint/js': specifier: ^9.21.0 version: 9.24.0 + '@types/node': + specifier: ^22.15.3 + version: 22.15.3 '@tanstack/react-query-devtools': specifier: ^5.74.4 version: 5.74.4(@tanstack/react-query@5.72.2(react@19.1.0))(react@19.1.0) - '@types/node': - specifier: ^22.14.1 - version: 22.14.1 '@types/react': specifier: ^19.0.10 version: 19.1.1 @@ -83,10 +86,7 @@ importers: version: 8.29.1(eslint@9.24.0)(typescript@5.7.3) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.2.6(@types/node@22.14.1)) - csstype: - specifier: ^3.1.3 - version: 3.1.3 + version: 4.3.4(vite@6.2.6(@types/node@22.15.3)) eslint: specifier: ^9.21.0 version: 9.24.0 @@ -122,10 +122,13 @@ importers: version: 8.29.1(eslint@9.24.0)(typescript@5.7.3) vite: specifier: ^6.2.0 - version: 6.2.6(@types/node@22.14.1) + version: 6.2.6(@types/node@22.15.3) vite-plugin-mdx: specifier: ^3.6.1 - version: 3.6.1(@mdx-js/mdx@3.1.0(acorn@8.14.1))(vite@6.2.6(@types/node@22.14.1)) + version: 3.6.1(@mdx-js/mdx@3.1.0(acorn@8.14.1))(vite@6.2.6(@types/node@22.15.3)) + vite-plugin-svgr: + specifier: ^4.3.0 + version: 4.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.6(@types/node@22.15.3)) packages: @@ -729,6 +732,73 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@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': '*' '@suspensive/react@2.18.12': resolution: {integrity: sha512-De3sVLxLnMpTSOfW3t3D8uh8+/bK8+L/mV8YRAwjW2PyR8BBe9+nctFRVO+ZCIFKUs7VPtnIXnb+5bKfBQ1vog==} peerDependencies: @@ -790,8 +860,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@22.14.1': - resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + '@types/node@22.15.3': + resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1002,6 +1072,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001713: resolution: {integrity: sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==} @@ -1065,10 +1139,23 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} + 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'} @@ -1144,6 +1231,9 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1157,6 +1247,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1862,6 +1956,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2025,6 +2122,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.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -2184,6 +2284,23 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-router-dom@7.5.3: + resolution: {integrity: sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.5.3: + resolution: {integrity: sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -2277,6 +2394,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2317,6 +2437,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 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'} @@ -2380,6 +2503,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + synckit@0.11.3: resolution: {integrity: sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2413,6 +2539,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2514,6 +2643,11 @@ packages: '@mdx-js/mdx': <2 vite: <3 + vite-plugin-svgr@4.3.0: + resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==} + peerDependencies: + vite: '>=2.6.0' + vite@6.2.6: resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3164,6 +3298,75 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-preset@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10) + + '@svgr/core@8.1.0(typescript@5.7.3)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.7.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.27.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.3))': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.7.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color '@suspensive/react@2.18.12(react@19.1.0)': dependencies: react: 19.1.0 @@ -3230,7 +3433,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@22.14.1': + '@types/node@22.15.3': dependencies: undici-types: 6.21.0 @@ -3333,14 +3536,14 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.3.4(vite@6.2.6(@types/node@22.14.1))': + '@vitejs/plugin-react@4.3.4(vite@6.2.6(@types/node@22.15.3))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.2.6(@types/node@22.14.1) + vite: 6.2.6(@types/node@22.15.3) transitivePeerDependencies: - supports-color @@ -3492,6 +3695,8 @@ snapshots: callsites@3.1.0: {} + camelcase@6.3.0: {} + caniuse-lite@1.0.30001713: {} ccount@2.0.1: {} @@ -3537,6 +3742,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@1.0.2: {} + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -3545,6 +3752,15 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + cosmiconfig@8.3.6(typescript@5.7.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.3 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3620,6 +3836,11 @@ snapshots: '@babel/runtime': 7.27.0 csstype: 3.1.3 + 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 @@ -3632,6 +3853,8 @@ snapshots: emoji-regex@9.2.2: {} + entities@4.5.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -4481,6 +4704,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4827,6 +5054,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.19: {} notistack@3.0.2(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -4986,6 +5218,21 @@ snapshots: react-refresh@0.14.2: {} + react-router-dom@7.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 @@ -5150,6 +5397,8 @@ snapshots: semver@7.7.1: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -5208,6 +5457,11 @@ snapshots: slash@3.0.0: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + source-map-js@1.2.1: {} source-map@0.5.7: {} @@ -5274,6 +5528,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + synckit@0.11.3: dependencies: '@pkgr/core': 0.2.2 @@ -5306,6 +5562,8 @@ snapshots: tslib@2.8.1: {} + turbo-stream@2.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5451,22 +5709,33 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-mdx@3.6.1(@mdx-js/mdx@3.1.0(acorn@8.14.1))(vite@6.2.6(@types/node@22.14.1)): + vite-plugin-mdx@3.6.1(@mdx-js/mdx@3.1.0(acorn@8.14.1))(vite@6.2.6(@types/node@22.15.3)): dependencies: '@alloc/quick-lru': 5.2.0 '@mdx-js/mdx': 3.1.0(acorn@8.14.1) esbuild: 0.13.8 resolve: 1.22.10 unified: 9.2.2 - vite: 6.2.6(@types/node@22.14.1) + vite: 6.2.6(@types/node@22.15.3) + + vite-plugin-svgr@4.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.6(@types/node@22.15.3)): + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.39.0) + '@svgr/core': 8.1.0(typescript@5.7.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.3)) + vite: 6.2.6(@types/node@22.15.3) + transitivePeerDependencies: + - rollup + - supports-color + - typescript - vite@6.2.6(@types/node@22.14.1): + vite@6.2.6(@types/node@22.15.3): dependencies: esbuild: 0.25.2 postcss: 8.5.3 rollup: 4.39.0 optionalDependencies: - '@types/node': 22.14.1 + '@types/node': 22.15.3 fsevents: 2.3.3 which-boxed-primitive@1.1.1: diff --git a/src/App.tsx b/src/App.tsx index b7ec56c..a4ae52e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,17 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import MainLayout from "./components/layout"; import Test from "./components/Test"; function App() { - return ; + return ( + + + }> + } /> + + + + ); } export default App; diff --git a/src/assets/Footer/blog.svg b/src/assets/Footer/blog.svg new file mode 100644 index 0000000..ecb62c2 --- /dev/null +++ b/src/assets/Footer/blog.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/facebook.svg b/src/assets/Footer/facebook.svg new file mode 100644 index 0000000..95bbb43 --- /dev/null +++ b/src/assets/Footer/facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/flickr.svg b/src/assets/Footer/flickr.svg new file mode 100644 index 0000000..cae2f99 --- /dev/null +++ b/src/assets/Footer/flickr.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/github.svg b/src/assets/Footer/github.svg new file mode 100644 index 0000000..d429ea8 --- /dev/null +++ b/src/assets/Footer/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/instagram.svg b/src/assets/Footer/instagram.svg new file mode 100644 index 0000000..dd65c88 --- /dev/null +++ b/src/assets/Footer/instagram.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/linkedin.svg b/src/assets/Footer/linkedin.svg new file mode 100644 index 0000000..7e2ad8e --- /dev/null +++ b/src/assets/Footer/linkedin.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/message.svg b/src/assets/Footer/message.svg new file mode 100644 index 0000000..69646e0 --- /dev/null +++ b/src/assets/Footer/message.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Footer/x.svg b/src/assets/Footer/x.svg new file mode 100644 index 0000000..8d47316 --- /dev/null +++ b/src/assets/Footer/x.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/Footer/youtube.svg b/src/assets/Footer/youtube.svg new file mode 100644 index 0000000..d130f81 --- /dev/null +++ b/src/assets/Footer/youtube.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/langIcon.png b/src/assets/langIcon.png new file mode 100644 index 0000000..1157c55 Binary files /dev/null and b/src/assets/langIcon.png differ diff --git a/src/assets/pyconLogo.png b/src/assets/pyconLogo.png new file mode 100644 index 0000000..b1d5c7b Binary files /dev/null and b/src/assets/pyconLogo.png differ diff --git a/src/components/layout/Footer/index.tsx b/src/components/layout/Footer/index.tsx new file mode 100644 index 0000000..c2f1031 --- /dev/null +++ b/src/components/layout/Footer/index.tsx @@ -0,0 +1,175 @@ +import styled from "@emotion/styled"; +import { useEmail } from "@/hooks/useEmail"; +import MessageIcon from "@/assets/Footer/message.svg?react"; +import FacebookIcon from "@/assets/Footer/facebook.svg?react"; +import YoutubeIcon from "@/assets/Footer/youtube.svg?react"; +import XIcon from "@/assets/Footer/x.svg?react"; +import GithubIcon from "@/assets/Footer/github.svg?react"; +import InstagramIcon from "@/assets/Footer/instagram.svg?react"; +import LinkedinIcon from "@/assets/Footer/linkedin.svg?react"; +import BlogIcon from "@/assets/Footer/blog.svg?react"; +import FlickrIcon from "@/assets/Footer/flickr.svg?react"; + +interface LinkItem { + text: string; + href: string; +} + +interface IconItem { + icon: React.FC>; + alt: string; + href: string; +} + +interface FooterProps { + slogan?: string; + description?: string; + links?: LinkItem[]; + icons?: IconItem[]; +} + +const defaultIcons: IconItem[] = [ + { + icon: FacebookIcon, + alt: "facebook", + href: "https://www.facebook.com/pyconkorea/", + }, + { + icon: YoutubeIcon, + alt: "youtube", + href: "https://www.youtube.com/c/PyConKRtube", + }, + { icon: XIcon, alt: "x", href: "https://x.com/PyConKR" }, + { icon: GithubIcon, alt: "github", href: "https://github.com/pythonkr" }, + { + icon: InstagramIcon, + alt: "instagram", + href: "https://www.instagram.com/pycon_korea/", + }, + { + icon: LinkedinIcon, + alt: "linkedin", + href: "https://www.linkedin.com/company/pyconkorea/", + }, + { icon: BlogIcon, alt: "blog", href: "https://blog.pycon.kr/" }, + { + icon: FlickrIcon, + alt: "flickr", + href: "https://www.flickr.com/photos/126829363@N08/", + }, +]; + +export default function Footer({ + slogan = "Weave with Python, 파이콘 한국 2025", + description = "파이콘 한국 2025는 파이콘 한국 준비위원회가 만들고 있습니다\n파이썬 웹 프레임워크 Django로 만들었습니다", + links = [ + { text: "파이콘 한국 행동 강령(CoC)", href: "#" }, + { text: "서비스 이용 약관", href: "#" }, + { text: "개인 정보 처리 방침", href: "#" }, + ], + icons = defaultIcons, +}: FooterProps) { + const { sendEmail } = useEmail(); + + return ( + + + {slogan} + {description.split("\n").map((line, index) => ( +
{line}
+ ))} + + {links.map((link, index) => ( + <> + + {link.text} + + {index < links.length - 1 && |} + + ))} + + + + + {icons.map((icon) => ( + + + ))} + +
+
+ ); +} + +const FooterContainer = styled.footer` + background-color: ${({ theme }) => theme.palette.primary.main}; + color: ${({ theme }) => theme.palette.common.white}; + font-size: 0.75rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 267px; +`; + +const FooterContent = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.75rem; +`; + +const FooterSlogan = styled.div` + font-weight: 600; +`; + +const FooterLinks = styled.div` + display: flex; + align-items: center; + gap: 0.625rem; +`; +const FooterIcons = styled.div` + display: flex; + align-items: center; + gap: 9px; +`; + +const Link = styled.a` + color: ${({ theme }) => theme.palette.common.white}; + text-decoration: none; + &:hover { + text-decoration: underline; + } +`; + +const Separator = styled.span` + color: ${({ theme }) => theme.palette.common.white}; + opacity: 0.5; +`; + +const IconLink = styled.a` + display: flex; + align-items: center; + justify-content: center; + + cursor: pointer; + + &:hover { + opacity: 0.8; + } + + img { + width: 20px; + height: 20px; + } +`; diff --git a/src/components/layout/Header/index.tsx b/src/components/layout/Header/index.tsx new file mode 100644 index 0000000..7e79de1 --- /dev/null +++ b/src/components/layout/Header/index.tsx @@ -0,0 +1,170 @@ +import styled from "@emotion/styled"; +import { useMenu } from "../../../hooks/useMenu"; +import LanguageSelector from "../LanguageSelector"; +import LoginButton from "../LoginButton"; + +interface SubMenuItem { + text: string; + href: string; +} + +interface MenuItem { + text: string; + href?: string; + subMenu?: SubMenuItem[]; +} + +interface HeaderProps { + menus: MenuItem[]; +} + +export default function Header({ menus }: HeaderProps) { + const { + hoveredMenu, + focusedMenu, + menuRefs, + setHoveredMenu, + setFocusedMenu, + handleKeyDown, + handleBlur, + } = useMenu(); + + return ( + + + pyconLogo + + + + + + + + + ); +} + +const HeaderContainer = styled.header` + background-color: ${({ theme }) => theme.palette.primary.light}; + color: ${({ theme }) => theme.palette.primary.dark}; + font-size: 0.8125rem; + font-weight: 500; + width: 100%; + height: 3.625rem; + padding: 0.5625rem 7.125rem; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; +`; + +const HeaderLogo = styled.div` + display: flex; + align-items: center; + justify-content: center; +`; + +const HeaderNav = styled.ul` + display: flex; + align-items: center; + gap: 2rem; + font-size: 0.875rem; + font-weight: 500; + position: relative; + + li { + position: relative; + cursor: pointer; + outline: none; + + &:focus { + outline: 2px solid ${({ theme }) => theme.palette.primary.main}; + outline-offset: 1px; + } + } +`; + +const HeaderLeft = styled.div` + display: flex; + align-items: center; + gap: 1.125rem; +`; + +// const HeaderItem = styled.div` +// display: flex; +// align-items: center; +// gap: 0.625rem; +// `; + +const SubMenu = styled.ul` + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background-color: white; + border-radius: 5px; + padding: 5px 0; + width: 125px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 1000; +`; + +const SubMenuItem = styled.li` + padding: 5px 0; + text-align: center; + + a { + color: ${({ theme }) => theme.palette.primary.light}; + text-decoration: none; + font-size: 10px; + display: block; + outline: none; + + &:hover, + &:focus { + color: ${({ theme }) => theme.palette.primary.main}; + font-weight: 600; + } + + &:focus { + outline: 2px solid ${({ theme }) => theme.palette.primary.main}; + outline-offset: 0.5px; + } + } +`; diff --git a/src/components/layout/LanguageSelector/index.tsx b/src/components/layout/LanguageSelector/index.tsx new file mode 100644 index 0000000..d0a58e8 --- /dev/null +++ b/src/components/layout/LanguageSelector/index.tsx @@ -0,0 +1,42 @@ +import styled from "@emotion/styled"; +import { useState } from "react"; + +export default function LanguageSelector() { + const [selectedLang, setSelectedLang] = useState<"KO" | "EN">("KO"); + + return ( + + langIcon + setSelectedLang("KO")} + > + KO + + setSelectedLang("EN")} + > + EN + + + ); +} + +const LanguageContainer = styled.div` + display: flex; + align-items: center; + gap: 0.6rem; +`; + +const LanguageItem = styled.div<{ isSelected: boolean }>` + cursor: pointer; + color: ${({ isSelected, theme }) => + isSelected ? theme.palette.primary.dark : theme.palette.primary.nonFocus}; + transition: color 0.2s ease; +`; diff --git a/src/components/layout/LoginButton/index.tsx b/src/components/layout/LoginButton/index.tsx new file mode 100644 index 0000000..7b8b17d --- /dev/null +++ b/src/components/layout/LoginButton/index.tsx @@ -0,0 +1,16 @@ +import styled from "@emotion/styled"; + +export default function LoginButton() { + return 로그인; +} + +const LoginButtonStyled = styled.button` + background: none; + border: none; + color: ${({ theme }) => theme.palette.primary.dark}; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + padding: 0; + transition: color 0.2s ease; +`; diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx new file mode 100644 index 0000000..3af9663 --- /dev/null +++ b/src/components/layout/index.tsx @@ -0,0 +1,69 @@ +import { Outlet } from "react-router-dom"; +import Header from "./Header"; +import Footer from "./Footer"; +import styled from "@emotion/styled"; + +const headerMenus = [ + { + text: "파이콘 한국", + subMenu: [ + { text: "파이콘 한국 2025", href: "/2025" }, + { text: "파이콘 한국 행동강령(CoC)", href: "/coc" }, + { text: "파이썬 사용자 모임", href: "/user-group" }, + { text: "역대 파이콘 행사", href: "/past-events" }, + { text: "파이콘 한국 건강 관련 안내", href: "/health" }, + ], + }, + { + text: "프로그램", + subMenu: [ + { text: "튜토리얼", href: "/tutorial" }, + { text: "스프린트", href: "/sprint" }, + { text: "포스터 세션", href: "/poster" }, + ], + }, + { + text: "세션", + subMenu: [ + { text: "세션 목록", href: "/sessions" }, + { text: "세션 시간표", href: "/schedule" }, + ], + }, + { + text: "구매", + subMenu: [ + { text: "티켓 구매", href: "/tickets" }, + { text: "굿즈 구매", href: "/goods" }, + { text: "결제 내역", href: "/payments" }, + ], + }, + { + text: "후원하기", + subMenu: [ + { text: "후원사 안내", href: "/sponsors" }, + { text: "개인 후원자", href: "/individual-sponsors" }, + ], + }, +]; + +const LayoutContainer = styled.div` + display: flex; + flex-direction: column; + min-height: 100vh; +`; + +const MainContent = styled.main` + flex: 1; +`; + +export default function MainLayout() { + return ( + +
+ + + +