diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..31e0979 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,50 @@ +name: MAIN CI + +on: + push: + branches: + - main + +jobs: + Deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Get pnpm store path + id: pnpm-cache-path + run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_ENV + + - name: Cache pnpm modules + id: cache + uses: actions/cache@v3 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + ${{ runner.os }}- + + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + pnpm install --frozen-lockfile + pnpm store prune + + - name: Build + run: pnpm run build + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Deploy to S3 + run: aws s3 sync ./build s3://${{ secrets.AWS_BUCKET_NAME }} --delete + + - name: Invalidate CloudFront Cache + run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/*" diff --git a/package.json b/package.json index c17075f..9514795 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,26 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host 0.0.0.0", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "@egjs/react-infinitegrid": "^4.12.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^6.4.4", + "@mui/material": "^6.4.4", "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "axios": "^1.7.9", + "gsap": "^3.12.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-intersection-observer": "^9.15.1", "react-router": "^7.1.5", + "swiper": "^11.2.4", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.6", "zustand": "^5.0.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fd31b7..dac4254 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,21 @@ importers: .: dependencies: + '@egjs/react-infinitegrid': + specifier: ^4.12.0 + version: 4.12.0 + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.0.8)(react@18.3.1) + '@emotion/styled': + specifier: ^11.14.0 + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + '@mui/icons-material': + specifier: ^6.4.4 + version: 6.4.4(@mui/material@6.4.4(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + '@mui/material': + specifier: ^6.4.4 + version: 6.4.4(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tailwindcss/vite': specifier: ^4.0.6 version: 4.0.6(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)) @@ -17,15 +32,24 @@ importers: axios: specifier: ^1.7.9 version: 1.7.9 + gsap: + specifier: ^3.12.7 + version: 3.12.7 react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-intersection-observer: + specifier: ^9.15.1 + version: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router: specifier: ^7.1.5 version: 7.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + swiper: + specifier: ^11.2.4 + version: 11.2.4 tailwind-merge: specifier: ^3.0.1 version: 3.0.1 @@ -34,7 +58,7 @@ importers: version: 4.0.6 zustand: specifier: ^5.0.3 - version: 5.0.3(@types/react@19.0.8)(react@18.3.1) + version: 5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@18.3.1) devDependencies: '@eslint/js': specifier: ^9.19.0 @@ -57,6 +81,9 @@ importers: eslint-config-prettier: specifier: ^10.0.1 version: 10.0.1(eslint@9.20.1(jiti@2.4.2)) + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.0.0 version: 5.1.0(eslint@9.20.1(jiti@2.4.2)) @@ -145,6 +172,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.26.9': + resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} + engines: {node: '>=6.9.0'} + '@babel/template@7.26.9': resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} @@ -157,6 +188,87 @@ packages: resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} engines: {node: '>=6.9.0'} + '@cfcs/core@0.0.24': + resolution: {integrity: sha512-feB38qu+eDk0Pggh/yR7gjaNmvUYA2uCxHP3Pz2MLE4LZ/9jPdtu8bzCSI47yTEhWyZCF5Pk698hdz8IN2mTjA==} + + '@cfcs/core@0.0.5': + resolution: {integrity: sha512-TZ/RXKV7MUrqFpiX+16uaaptv5jgChWNsuE6w8Rn3eJbO1PFd4Wpn25zeJbii7ch46ck8/ZIIYCcW3pHiYtm4Q==} + + '@egjs/children-differ@1.0.1': + resolution: {integrity: sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==} + + '@egjs/component@3.0.5': + resolution: {integrity: sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==} + + '@egjs/grid@1.16.0': + resolution: {integrity: sha512-w344hL2HNwmOHu379EG9I8ULkWF4A0cVf1XDn5nGlLXc+8c/rMvQadMBkGPOcdWMEKvFZpEiOPI7tWlXLOCloQ==} + + '@egjs/imready@1.4.1': + resolution: {integrity: sha512-JIOBs4lB7FYdsKi5uvz2j3SObX8eShtZjtqlOH41tm185aJOQZwiKBK8+V4MxzG4X6DqVhpdN8UcuVwBbElfsg==} + + '@egjs/infinitegrid@4.12.0': + resolution: {integrity: sha512-zoDz+mag7DAropcgmxW0Bx3JQ5ID19lk+AUN9rf9U7diwmO8IBfddX44jOVc9+ya5yZ+Goj95Fkm5S3GqCOrZQ==} + + '@egjs/list-differ@1.0.1': + resolution: {integrity: sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==} + + '@egjs/react-infinitegrid@4.12.0': + resolution: {integrity: sha512-V661KH1nka13wgy73N0IGBazZGe7LiECml1JDqB5DP6axTERUVCs8KRHadHu6oQihDeXwJIHe1YRhhMN9eAnUw==} + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.3.1': + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.0': + resolution: {integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -383,6 +495,97 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@mui/core-downloads-tracker@6.4.4': + resolution: {integrity: sha512-r+J0EditrekkTtO2CnCBCOGpNaDYwJqz8lH4rj6o/anDcskZFJodBlG8aCJkS8DL/CF/9EHS+Gz53EbmYEnQbw==} + + '@mui/icons-material@6.4.4': + resolution: {integrity: sha512-uF1chGaoFmYdRUomK6f8kgJfWosk9A3HXWiVD0vQm+2mE7f25eTQ1E8RRO11LXpnUBqu8Rbv/uGlpnjT/u1Ksg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^6.4.4 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@6.4.4': + resolution: {integrity: sha512-ISVPrIsPQsxnwvS40C4u03AuNSPigFeS2+n1qpuEZ94hDsdMi19dQM2JcC9CHEhXecSIQjP1RTyY0mPiSpSrFQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^6.4.3 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@6.4.3': + resolution: {integrity: sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@6.4.3': + resolution: {integrity: sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@6.4.3': + resolution: {integrity: sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.21': + resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@6.4.3': + resolution: {integrity: sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -395,6 +598,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} @@ -499,6 +705,9 @@ packages: cpu: [x64] os: [win32] + '@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'} @@ -742,11 +951,25 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/react-dom@19.0.3': resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} peerDependencies: '@types/react': ^19.0.0 + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + '@types/react@19.0.8': resolution: {integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==} @@ -822,12 +1045,48 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + axios@1.7.9: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -850,6 +1109,14 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -865,6 +1132,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -879,6 +1150,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -886,6 +1160,10 @@ packages: 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'} @@ -902,6 +1180,26 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -914,6 +1212,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -923,6 +1229,13 @@ packages: engines: {node: '>=0.10'} hasBin: true + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -944,6 +1257,10 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -960,6 +1277,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -979,6 +1304,40 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint-plugin-react-hooks@5.1.0: resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} engines: {node: '>=10'} @@ -1059,6 +1418,9 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1079,6 +1441,10 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + form-data@4.0.2: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} @@ -1091,6 +1457,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1103,6 +1476,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1123,6 +1500,10 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1136,10 +1517,24 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gsap@3.12.7: + resolution: {integrity: sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1152,10 +1547,16 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1164,21 +1565,112 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1210,6 +1702,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1333,6 +1829,9 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1350,10 +1849,42 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1378,6 +1909,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1393,6 +1927,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.2: resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} engines: {node: ^10 || ^12 || >=14} @@ -1461,6 +1999,9 @@ packages: engines: {node: '>=14'} hasBin: true + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -1476,6 +2017,21 @@ packages: peerDependencies: react: ^18.3.1 + react-intersection-observer@9.15.1: + resolution: {integrity: sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@19.0.0: + resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-router@7.1.5: resolution: {integrity: sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==} engines: {node: '>=20.0.0'} @@ -1486,16 +2042,38 @@ packages: react-dom: optional: true + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rollup@4.34.7: @@ -1506,6 +2084,18 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1521,6 +2111,18 @@ packages: 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'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1529,6 +2131,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -1536,17 +2154,48 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + swiper@11.2.4: + resolution: {integrity: sha512-DTtglrsFfMYytid+oNy4QI3t2N2+XhhwSYbnyOhlwBmvY8Bkoj3ombK1/b80w8vDpQ+Lqlnbm+0737+i32MrcA==} + engines: {node: '>= 4.7.0'} + tailwind-merge@3.0.1: resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==} @@ -1577,6 +2226,9 @@ packages: typescript: optional: true + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1587,6 +2239,22 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + typescript-eslint@8.24.0: resolution: {integrity: sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1599,6 +2267,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -1661,6 +2333,22 @@ packages: yaml: optional: true + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1673,6 +2361,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1777,6 +2469,10 @@ snapshots: dependencies: '@babel/types': 7.26.9 + '@babel/runtime@7.26.9': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 @@ -1800,6 +2496,128 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@cfcs/core@0.0.24': + dependencies: + '@egjs/component': 3.0.5 + + '@cfcs/core@0.0.5': + dependencies: + '@egjs/component': 3.0.5 + + '@egjs/children-differ@1.0.1': + dependencies: + '@egjs/list-differ': 1.0.1 + + '@egjs/component@3.0.5': {} + + '@egjs/grid@1.16.0': + dependencies: + '@egjs/children-differ': 1.0.1 + '@egjs/component': 3.0.5 + '@egjs/imready': 1.4.1 + + '@egjs/imready@1.4.1': + dependencies: + '@cfcs/core': 0.0.24 + '@egjs/component': 3.0.5 + + '@egjs/infinitegrid@4.12.0': + dependencies: + '@cfcs/core': 0.0.5 + '@egjs/children-differ': 1.0.1 + '@egjs/component': 3.0.5 + '@egjs/grid': 1.16.0 + '@egjs/list-differ': 1.0.1 + + '@egjs/list-differ@1.0.1': {} + + '@egjs/react-infinitegrid@4.12.0': + dependencies: + '@egjs/infinitegrid': 4.12.0 + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.9 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.0.8 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.14.0(@types/react@19.0.8)(react@18.3.1) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.0.8 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -1951,6 +2769,91 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mui/core-downloads-tracker@6.4.4': {} + + '@mui/icons-material@6.4.4(@mui/material@6.4.4(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@mui/material': 6.4.4(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 19.0.8 + + '@mui/material@6.4.4(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@mui/core-downloads-tracker': 6.4.4 + '@mui/system': 6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + '@mui/types': 7.2.21(@types/react@19.0.8) + '@mui/utils': 6.4.3(@types/react@19.0.8)(react@18.3.1) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.0.8) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.0.0 + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + '@types/react': 19.0.8 + + '@mui/private-theming@6.4.3(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@mui/utils': 6.4.3(@types/react@19.0.8)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.0.8 + + '@mui/styled-engine@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + + '@mui/system@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@mui/private-theming': 6.4.3(@types/react@19.0.8)(react@18.3.1) + '@mui/styled-engine': 6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.21(@types/react@19.0.8) + '@mui/utils': 6.4.3(@types/react@19.0.8)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@18.3.1))(@types/react@19.0.8)(react@18.3.1) + '@types/react': 19.0.8 + + '@mui/types@7.2.21(@types/react@19.0.8)': + optionalDependencies: + '@types/react': 19.0.8 + + '@mui/utils@6.4.3(@types/react@19.0.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@mui/types': 7.2.21(@types/react@19.0.8) + '@types/prop-types': 15.7.14 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 19.0.0 + optionalDependencies: + '@types/react': 19.0.8 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1963,6 +2866,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.0 + '@popperjs/core@2.11.8': {} + '@rollup/pluginutils@5.1.4(rollup@4.34.7)': dependencies: '@types/estree': 1.0.6 @@ -2028,6 +2933,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.34.7': optional: true + '@rtsao/scc@1.1.0': {} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -2232,10 +3139,20 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': {} + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.14': {} + '@types/react-dom@19.0.3(@types/react@19.0.8)': dependencies: '@types/react': 19.0.8 + '@types/react-transition-group@4.4.12(@types/react@19.0.8)': + dependencies: + '@types/react': 19.0.8 + '@types/react@19.0.8': dependencies: csstype: 3.1.3 @@ -2343,8 +3260,61 @@ snapshots: argparse@2.0.1: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.3 + is-array-buffer: 3.0.5 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + is-string: 1.1.1 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + axios@1.7.9: dependencies: follow-redirects: 1.15.9 @@ -2353,6 +3323,12 @@ snapshots: transitivePeerDependencies: - debug + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.26.9 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -2380,6 +3356,18 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.2.7 + set-function-length: 1.2.2 + + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.2.7 + callsites@3.1.0: {} camelcase@6.3.0: {} @@ -2391,6 +3379,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2403,10 +3393,20 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} cookie@1.0.2: {} + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + cosmiconfig@8.3.6(typescript@5.7.3): dependencies: import-fresh: 3.3.1 @@ -2424,16 +3424,59 @@ snapshots: csstype@3.1.3: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.0: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delayed-stream@1.0.0: {} detect-libc@1.0.3: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.26.9 + csstype: 3.1.3 + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -2458,6 +3501,60 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.18 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2473,6 +3570,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -2509,6 +3616,53 @@ snapshots: dependencies: eslint: 9.20.1(jiti@2.4.2) + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.20.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.20.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-react-hooks@5.1.0(eslint@9.20.1(jiti@2.4.2)): dependencies: eslint: 9.20.1(jiti@2.4.2) @@ -2613,6 +3767,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-root@1.1.0: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2627,6 +3783,10 @@ snapshots: follow-redirects@1.15.9: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + form-data@4.0.2: dependencies: asynckit: 0.4.0 @@ -2639,6 +3799,17 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + gensync@1.0.0-beta.2: {} get-intrinsic@1.2.7: @@ -2659,6 +3830,12 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2673,6 +3850,11 @@ snapshots: globals@15.15.0: {} + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globrex@0.1.2: {} gopd@1.2.0: {} @@ -2681,8 +3863,20 @@ snapshots: graphemer@1.4.0: {} + gsap@3.12.7: {} + + has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -2693,8 +3887,15 @@ snapshots: dependencies: function-bind: 1.1.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + ignore@5.3.2: {} + immer@10.1.1: + optional: true + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -2702,16 +3903,121 @@ snapshots: imurmurhash@0.1.4: {} + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.3 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.3 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.3 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.3 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.18 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.3 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + + isarray@2.0.5: {} + isexe@2.0.0: {} jiti@2.4.2: {} @@ -2732,6 +4038,10 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@1.0.2: + dependencies: + minimist: 1.2.8 + json5@2.2.3: {} keyv@4.5.4: @@ -2831,6 +4141,8 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimist@1.2.8: {} + ms@2.1.3: {} nanoid@3.3.8: {} @@ -2844,6 +4156,41 @@ snapshots: node-releases@2.0.19: {} + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2853,6 +4200,12 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.2.7 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -2876,6 +4229,8 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + path-type@4.0.0: {} picocolors@1.1.1: {} @@ -2884,6 +4239,8 @@ snapshots: picomatch@4.0.2: {} + possible-typed-array-names@1.1.0: {} + postcss@8.5.2: dependencies: nanoid: 3.3.8 @@ -2898,6 +4255,12 @@ snapshots: prettier@3.5.1: {} + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + proxy-from-env@1.1.0: {} punycode@2.3.1: {} @@ -2910,6 +4273,16 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-intersection-observer@9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react-is@16.13.1: {} + + react-is@19.0.0: {} + react-router@7.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@types/cookie': 0.6.0 @@ -2920,12 +4293,49 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + resolve-from@4.0.0: {} + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.0.4: {} rollup@4.34.7: @@ -2957,6 +4367,25 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -2967,12 +4396,62 @@ snapshots: set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.7 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -2980,14 +4459,47 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.5.7: {} + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-bom@3.0.0: {} + strip-json-comments@3.1.1: {} + stylis@4.2.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + swiper@11.2.4: {} + tailwind-merge@3.0.1: {} tailwindcss@4.0.6: {} @@ -3006,6 +4518,13 @@ snapshots: optionalDependencies: typescript: 5.7.3 + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} turbo-stream@2.4.0: {} @@ -3014,6 +4533,39 @@ snapshots: dependencies: prelude-ls: 1.2.1 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typescript-eslint@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3): dependencies: '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) @@ -3026,6 +4578,13 @@ snapshots: typescript@5.7.3: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.3 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -3068,6 +4627,46 @@ snapshots: jiti: 2.4.2 lightningcss: 1.29.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.3 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.18 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.18: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + for-each: 0.3.5 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3076,9 +4675,12 @@ snapshots: yallist@3.1.1: {} + yaml@1.10.2: {} + yocto-queue@0.1.0: {} - zustand@5.0.3(@types/react@19.0.8)(react@18.3.1): + zustand@5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@18.3.1): optionalDependencies: '@types/react': 19.0.8 + immer: 10.1.1 react: 18.3.1 diff --git a/src/App.tsx b/src/App.tsx index a0c8e95..29e7dfa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,25 @@ import { Route, Routes } from 'react-router'; +import useViewport from './hooks/useViewport'; +import Layout from './layouts/Layout'; +import MobileLayout from './layouts/MobileLayout'; +import PrivateRoute from './layouts/PrivateRoute'; +import AdminPage from './pages/Admin'; +import FilteredLetterManage from './pages/Admin/FilteredLetter'; +import FilteringManage from './pages/Admin/Filtering'; +import ReportManage from './pages/Admin/Report'; +import AuthCallbackPage from './pages/Auth'; import Home from './pages/Home'; +import Landing from './pages/Landing'; import LetterBoardPage from './pages/LetterBoard'; import LetterBoardDetailPage from './pages/LetterBoardDetail'; import LetterBoxPage from './pages/LetterBox'; +import LetterBoxDetailPage from './pages/LetterBoxDetail'; import LetterDetailPage from './pages/LetterDetail'; import LoginPage from './pages/Login'; import MyPage from './pages/MyPage'; +import MyBoardPage from './pages/MyPage/components/MyBoardPage'; +import NotFoundPage from './pages/NotFound'; import NotificationsPage from './pages/Notifications'; import OnboardingPage from './pages/Onboarding'; import RandomLettersPage from './pages/RandomLetters'; @@ -14,26 +27,48 @@ import RollingPaperPage from './pages/RollingPaper'; import WritePage from './pages/Write'; const App = () => { + useViewport(); + return ( - - } /> + }> } /> + } /> + } /> + } /> + } /> } /> - - } /> - } /> - } /> - } /> - - - } /> - } /> - } /> + + }> + + }> + } /> + } /> + } /> + + } /> + } /> + + + }> + } /> + } /> + + } /> + + }> + } /> + } /> + } /> + - - } /> - } /> + + + }> + }> + } /> + } /> + } /> diff --git a/src/apis/admin.ts b/src/apis/admin.ts new file mode 100644 index 0000000..c5152e7 --- /dev/null +++ b/src/apis/admin.ts @@ -0,0 +1,79 @@ +import client from './client'; + +const postReports = async (postReportRequest: PostReportRequest) => { + try { + const res = await client.post(`/api/reports`, postReportRequest); + if (res.status === 200) { + return res; + } + } catch (error) { + console.error(error); + } +}; + +const getReports = async (reportQueryString: ReportQueryString) => { + try { + const queryParams = new URLSearchParams(); + if (reportQueryString.reportType !== null) + queryParams.append('reportType', reportQueryString.reportType); + if (reportQueryString.status !== null) queryParams.append('status', reportQueryString.status); + if (reportQueryString.page !== null) queryParams.append('page', reportQueryString.page); + if (reportQueryString.size !== null) queryParams.append('size', reportQueryString.size); + + const queryStrings = queryParams.toString(); + const res = await client.get(`/api/reports?${queryStrings}`); + if (!res) throw new Error('신고 목록 데이터 조회 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const patchReport = async (reportId: number, patchReportRequest: PatchReportRequest) => { + try { + console.log(`/api/reports/${reportId}`, patchReportRequest); + const res = await client.patch(`/api/reports/${reportId}`, patchReportRequest); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +// badwords +const getBadWords = async (setBadWords: React.Dispatch>) => { + try { + const res = await client.get('/api/bad-words'); + setBadWords(res.data.data); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +const postBadWords = async (badWordsRequest: BadWords, callBack?: () => void) => { + try { + const res = await client.post('/api/bad-words', badWordsRequest); + if (callBack) callBack(); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +// 내 상상대로 만든 필터링 단어 취소 버튼 +const patchBadWords = async ( + badWordId: number, + badWordsRequest: BadWords, + callBack?: () => void, +) => { + try { + const res = await client.patch(`/api/bad-words/${badWordId}/status`, badWordsRequest); + if (callBack) callBack(); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +export { postReports, getReports, patchReport, getBadWords, postBadWords, patchBadWords }; diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 0000000..064fa5d --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,77 @@ +import client from './client'; + +export const socialLogin = (loginType: LoginType) => { + window.location.href = `${import.meta.env.VITE_API_URL}/oauth2/authorization/${loginType}`; +}; + +export const getUserToken = async (stateToken: string) => { + try { + const response = await client.get(`/api/auth/token?state=${stateToken}`); + if (!response) throw new Error('getUserToken: Error while fetching user token'); + const userInfo = response.data; + if (userInfo) { + return userInfo; + } + return response; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const postZipCode = async () => { + try { + const response = await client.post(`/api/members/zipCode`); + if (!response) throw new Error('fail to post ZipCode'); + return response; + } catch (error) { + console.error(error); + } +}; + +export const getNewToken = async () => { + try { + const response = await client.post('/api/reissue', {}, { withCredentials: true }); + if (!response) throw new Error('getNewToken: no response data'); + return response; + } catch (error) { + console.error(error); + } +}; + +export const getMydata = async () => { + try { + const response = await client.get('/api/members/me'); + if (!response) throw new Error('getNewToken: no response data'); + return response; + } catch (error) { + console.error(error); + } +}; + +export const deleteUserInfo = async () => { + try { + const response = await client.delete('/api/members/me', { + withCredentials: true, + }); + if (!response) throw new Error('deleteUserInfo: no response'); + return response; + } catch (error) { + console.error(error); + } +}; + +export const postLogout = async () => { + try { + console.log(' before logout'); + + const response = await client.post('/api/logout', { withCredentials: true }); + console.log('logout', response); + if (!response) throw new Error('postLogout: failed to logout'); + return response; + } catch (error) { + console.log('logout error'); + + console.error(error); + } +}; diff --git a/src/apis/client.ts b/src/apis/client.ts index c2b5f85..df8b3b3 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -1,5 +1,113 @@ import axios from 'axios'; -export const client = axios.create({ +import useAuthStore from '@/stores/authStore'; + +import { getNewToken } from './auth'; + +const client = axios.create({ baseURL: import.meta.env.VITE_API_URL, + headers: { 'Content-Type': 'application/json' }, }); + +// type FailedRequest = { +// resolve: (token: string) => void; +// reject: (error: unknown) => void; +// }; + +let isRefreshing = false; +// let failedQueue: FailedRequest[] = []; + +// const processQueue = (error: unknown, token: string | null = null) => { +// failedQueue.forEach((prom) => { +// if (error) { +// prom.reject(error); +// } else { +// if (token) { +// prom.resolve(token); +// } +// } +// }); + +// failedQueue = []; +// }; + +const callReissue = async () => { + try { + const response = await getNewToken(); + const newToken = response?.data.accessToken; + return newToken; + } catch (e) { + return Promise.reject(e); + } +}; + +let retry = false; + +client.interceptors.request.use( + (config) => { + console.log('response again', config); + + const accessToken = useAuthStore.getState().accessToken; + if (config.url !== '/auth/reissue' && accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + return config; + }, + (error) => Promise.reject(error), +); + +client.interceptors.response.use( + (response) => response, + async (error) => { + const setAccessToken = useAuthStore.getState().setAccessToken; + const logout = useAuthStore.getState().logout; + const isLoggedIn = useAuthStore.getState().isLoggedIn; + + const originalRequest = error.config; + + if (!originalRequest || originalRequest.url === '/auth/reissue') { + if (isLoggedIn) logout(); + return Promise.reject(error); + } + + if ((error.response?.status === 401 || error.response?.status === 403) && !retry) { + retry = true; + if (isRefreshing) { + if (isLoggedIn) logout(); + // try { + // return new Promise((resolve, reject) => { + // failedQueue.push({ + // resolve: (token: string) => { + // originalRequest.headers.Authorization = `Bearer ${token}`; + // resolve(client(originalRequest)); + // }, + // reject: (err: unknown) => reject(err), + // }); + // }); + // } catch (e) { + // return Promise.reject(e); + // } + } else { + isRefreshing = true; + try { + const newToken = await callReissue(); + setAccessToken(newToken); + // processQueue(null, newToken); + isRefreshing = false; + originalRequest.headers.Authorization = `Bearer ${newToken}`; + return client(originalRequest); + } catch (e) { + // processQueue(e, null); + isRefreshing = false; + if (isLoggedIn) logout(); + return Promise.reject(e); + } + } + } + if (isLoggedIn) logout(); + console.error('Failed to refresh token', error); + return Promise.reject(error); + }, +); + +export default client; diff --git a/src/apis/draftLetters.ts b/src/apis/draftLetters.ts new file mode 100644 index 0000000..424853e --- /dev/null +++ b/src/apis/draftLetters.ts @@ -0,0 +1,33 @@ +import client from './client'; + +export interface DraftLetter { + letterId: number; + writerId: number; + receiverId: number; + parentLetterId: number; + zipCode: string; + title: string; + content: string; + category: string; + paperType: string; + fontType: string; + deliveryStartedAt: string; + deliveryCompletedAt: string; + matched: boolean; +} + +export const getDraftLetters = async () // token: string +: Promise => { + try { + const { data } = await client.get('/api/letters?status=draft', { + // headers: { + // Authorization: `Bearer ${token}`, + // }, + }); + console.log('임시저장된 편지 데이터', data); + return data.data; + } catch (error) { + console.error(`❌임시저장된 편지를 불러오던 중 에러가 발생했습니다`, error); + throw new Error('임시저장된 편지 불러오기 실패'); + } +}; diff --git a/src/apis/incomingLetters.ts b/src/apis/incomingLetters.ts new file mode 100644 index 0000000..adde539 --- /dev/null +++ b/src/apis/incomingLetters.ts @@ -0,0 +1,16 @@ +import client from './client'; + +export const getIncomingLetters = async (token: string) => { + try { + const { data } = await client.get('/api/letters?status=delivery', { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + console.log('불러온 데이터', data); + return data; + } catch (error) { + console.error('❌오고 있는 편지 목록을 불러오던 중 에러 발생', error); + throw error; + } +}; diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts new file mode 100644 index 0000000..d321438 --- /dev/null +++ b/src/apis/letterDetail.ts @@ -0,0 +1,26 @@ +import client from './client'; + +const getLetter = async (letterId: string) => { + try { + const res = await client.get(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 데이터를 가져오는 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const deleteLetter = async (letterId: string) => { + try { + console.log(`/api/letters/${letterId}`); + const res = await client.delete(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +export { getLetter, deleteLetter }; diff --git a/src/apis/mailBox.ts b/src/apis/mailBox.ts new file mode 100644 index 0000000..722e248 --- /dev/null +++ b/src/apis/mailBox.ts @@ -0,0 +1,32 @@ +import client from './client'; + +export const getMailbox = async () => { + try { + const response = await client.get('/api/mailbox'); + if (!response) throw new Error('error while fetching mailbox data'); + return response.data; + } catch (error) { + console.error(error); + } +}; + +export const getMailboxDetail = async (id: number, pageParam: number) => { + try { + const response = await client.get(`/api/mailbox/${id}/detail?page=${pageParam}&size=20`); + + if (!response) throw new Error('error while fetching mailbox detail data'); + return response.data; + } catch (error) { + console.error(error); + } +}; + +export const postMailboxDisconnect = async (id: number) => { + try { + const response = await client.post(`/api/mailbox/${id}/disconnect`); + if (!response) throw new Error('error while disconnecting mailbox'); + return response; + } catch (error) { + console.error(error); + } +}; diff --git a/src/apis/myPage.ts b/src/apis/myPage.ts new file mode 100644 index 0000000..7d64cc9 --- /dev/null +++ b/src/apis/myPage.ts @@ -0,0 +1,21 @@ +import client from './client'; + +export const fetchMyPageInfo = async () => { + try { + const response = await client.get('/api/members/me'); + if (!response) throw new Error('❌데이터를 불러오던 중 오류가 발생했습니다'); + return response.data; + } catch (error) { + console.error(error); + } +}; + +export const getMySharePostList = async () => { + try { + const response = await client.get('/api/share-proposals/inbox'); + if (!response) throw new Error('error while fetching my share post list'); + return response.data; + } catch (error) { + console.error(error); + } +}; diff --git a/src/apis/randomLetter.ts b/src/apis/randomLetter.ts new file mode 100644 index 0000000..994aff6 --- /dev/null +++ b/src/apis/randomLetter.ts @@ -0,0 +1,70 @@ +import client from './client'; + +const getRandomLetters = async (category: string | null) => { + try { + const res = await client.get(`/api/random-letters/${category}`); + if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const postRandomLettersApprove = async (approveRequest: ApproveRequest, callBack?: () => void) => { + try { + console.log('엔드포인트 : /api/random-letters/approve'); + console.log('request', approveRequest); + const res = await client.post('/api/random-letters/approve', approveRequest); + if (!res) throw new Error('랜덤편지 매칭수락 도중 에러가 발생했습니다.'); + if (callBack) callBack(); + return res; + } catch (error) { + console.error(error); + } +}; + +const getRandomLetterMatched = async (callBack?: () => void) => { + try { + const res = await client.post('/api/random-letters/valid-table'); + if (!res) + throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); + if (callBack) callBack(); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const getRandomLetterCoolTime = async (callBack?: () => void) => { + try { + const res = await client.post('/api/random-letters/valid'); + if (!res) + throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); + if (callBack) callBack(); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const deleteRandomLetterMatching = async () => { + try { + const res = await client.delete('/api/random-letters/matching/cancel'); + if (!res) throw new Error('매칭 취소 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.log(error); + } +}; + +export { + getRandomLetters, + postRandomLettersApprove, + getRandomLetterCoolTime, + getRandomLetterMatched, + deleteRandomLetterMatching, +}; diff --git a/src/apis/rolling.ts b/src/apis/rolling.ts new file mode 100644 index 0000000..8930c0d --- /dev/null +++ b/src/apis/rolling.ts @@ -0,0 +1,38 @@ +import client from './client'; + +export const getCurrentRollingPaper = async (): Promise => { + const { + data: { data }, + } = await client.get('/api/event-posts'); + return data; +}; + +export const getRollingPaperDetail = async ( + rollingPaperId: string | number, +): Promise => { + const { + data: { data }, + } = await client.get(`/api/event-posts/${rollingPaperId}`); + return data; +}; + +export const postRollingPaperComment = async (rollingPaperId: string | number, content: string) => { + const { + data: { data }, + } = await client.post(`/api/event-posts/${rollingPaperId}/comments`, { + content, + }); + return data; +}; + +export const deleteRollingPaperComment = async (commentId: string | number) => { + try { + const { + data: { data }, + } = await client.delete(`/api/event-posts/comments/${commentId}`); + return data; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/src/apis/share.ts b/src/apis/share.ts new file mode 100644 index 0000000..767b97e --- /dev/null +++ b/src/apis/share.ts @@ -0,0 +1,126 @@ +import client from './client'; + +//공유 게시글 상세 페이지 편지 +interface ShareLetter { + id: number; + content: string; + writerZipCode: string; + receiverZipCode: string; +} + +// 공유 게시글 목록 조회 타입 +export interface SharePost { + writerZipCode: string; + receiverZipCode: string; + content: string; + createdAt: string; + active: boolean; + sharePostId: number; + sharePostContent: string; + zipCode: string; + letters: ShareLetter[]; +} + +// 페이징 포함 +export interface SharePostResponse { + content: SharePost[]; + currentPage: number; + size: number; + totalElements: number; + totalPages: number; +} + +// 편지 공유 수락 / 거절 +export interface SharePostApproval { + shareProposalId: number; + status: 'APPROVED' | 'REJECTED'; + sharePostId: number; +} + +// 공유 게시글 목록 조회 +export const getSharePostList = async (page: number = 1, size: number = 10) => { + try { + const response = await client.get('/api/share-posts', { + params: { page, size }, + }); + console.log(`🌟공유 게시글 목록`, response.data.data); + + return response.data.data; + } catch (error) { + console.error('❌ 편지 공유 게시글 목록을 조회하던 중 에러가 발생했습니다', error); + throw new Error('편지 공유 게시글 목록 조회 실패'); + } +}; + +// 공유 게시글 상세 조회 +export const getSharePostDetail = async (sharePostId: number): Promise => { + try { + const response = await client.get(`/api/share-posts/${sharePostId}`); + console.log(`🔥공유 게시글 상세 데이터`, response.data); + return response.data.data; + } catch (error) { + console.error('❌ 편지 공유 게시글을 상세 조회하던 중 에러가 발생했습니다', error); + throw new Error('편지 공유 게시글 상세 조회 실패'); + } +}; + +// 공유 요청 보내기 +export const postShareProposals = async ( + letterIds: number[], + recipientId: number, + message: string, +) => { + try { + const response = await client.post('/api/share-proposals', { + letterIds: letterIds, + recipientId, + message, + }); + if (!response) throw new Error('error while fetching mailbox data'); + return response.data; + } catch (error) { + console.error('❌ 공유 요청 보내기 중 에러가 발생했습니다', error); + throw new Error('공유 요청 실패'); + } +}; + +// 편지 공유 수락 / 거절 +export const postShareProposalApproval = async ( + shareProposalId: number, + action: 'approve' | 'reject', +): Promise => { + try { + const response = await client.patch(`/api/share-proposal/${shareProposalId}/${action}`); + return response.data; + } catch (error) { + console.error( + `❌ 편지 공유 ${action === 'approve' ? '수락' : '거절'} 중 에러가 발생했습니다`, + error, + ); + throw new Error(`편지 공유 ${action === 'approve' ? '수락' : '거부'} 실패`); + } +}; + +// 편지 좋아요 추가, 취소 +export const postSharePostLike = async (sharePostId: number) => { + try { + const response = await client.post(`/api/share-posts/${sharePostId}/likes`); + if (!response) throw new Error('error while posting like'); + return response.data; + } catch (error) { + console.error('❌ 편지 좋아요 중 에러가 발생했습니다', error); + throw new Error('편지 좋아요 실패'); + } +}; + +// 편지 좋아요 갯수 +export const getSharePostLikeCount = async (sharePostId: number) => { + try { + const response = await client.get(`/api/share-posts/${sharePostId}/likes`); + if (!response) throw new Error('error while fetching likes'); + return response.data; + } catch (error) { + console.error('❌ 편지 좋아요 갯수 조회 중 에러가 발생했습니다', error); + throw new Error('편지 좋아요 갯수 조회 실패'); + } +}; diff --git a/src/apis/unreadLetters.ts b/src/apis/unreadLetters.ts new file mode 100644 index 0000000..d7bccc9 --- /dev/null +++ b/src/apis/unreadLetters.ts @@ -0,0 +1,12 @@ +import client from './client'; + +export const getUnreadLettersCount = async (): Promise => { + try { + const response = await client.get('/api/letters/unread/count'); + console.log('📩 안 읽은 편지 개수 데이터', response); + return response.data; + } catch (error) { + console.error('❌안 읽은 편지 개수를 받아오던 중 에러가 발생했습니다', error); + throw new Error('안 읽은 편지 개수 조회 실패'); + } +}; diff --git a/src/apis/write.ts b/src/apis/write.ts new file mode 100644 index 0000000..7046e1f --- /dev/null +++ b/src/apis/write.ts @@ -0,0 +1,37 @@ +// import { AxiosResponse } from 'axios'; +import client from './client'; + +const postLetter = async (data: LetterRequest) => { + try { + const res = await client.post('/api/letters', data); + if (!res) throw new Error('편지 전송과정중에서 오류가 발생했습니다.'); + console.log(`api 주소 : /api/letters, 전송타입 : post`); + return res; + } catch (error) { + console.error(error); + } +}; + +const postFirstReply = async (data: FirstReplyRequest) => { + try { + const res = await client.post('/api/random-letters/matching', data); + if (!res) throw new Error('최초 답장 전송과정중에서 오류가 발생했습니다.'); + console.log(`api 주소 : /api/random-letters/matching, 전송타입 : post`); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const getPrevLetter = async (letterId: string) => { + try { + const res = await client.get(`/api/letters/${letterId}/previous`); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +export { postLetter, postFirstReply, getPrevLetter }; diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg new file mode 100644 index 0000000..db9adc1 --- /dev/null +++ b/src/assets/icons/add.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/alarm.svg b/src/assets/icons/alarm.svg new file mode 100644 index 0000000..08ac647 --- /dev/null +++ b/src/assets/icons/alarm.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/arrow-down.svg b/src/assets/icons/arrow-down.svg new file mode 100644 index 0000000..e00fa93 --- /dev/null +++ b/src/assets/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/arrow-left.svg b/src/assets/icons/arrow-left.svg new file mode 100644 index 0000000..e07f071 --- /dev/null +++ b/src/assets/icons/arrow-left.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/board.svg b/src/assets/icons/board.svg new file mode 100644 index 0000000..11c2560 --- /dev/null +++ b/src/assets/icons/board.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/cancel.svg b/src/assets/icons/cancel.svg new file mode 100644 index 0000000..b103417 --- /dev/null +++ b/src/assets/icons/cancel.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 0000000..d7b785a --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/cloud.svg b/src/assets/icons/cloud.svg new file mode 100644 index 0000000..ac0df8d --- /dev/null +++ b/src/assets/icons/cloud.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/color-siren.svg b/src/assets/icons/color-siren.svg new file mode 100644 index 0000000..139be74 --- /dev/null +++ b/src/assets/icons/color-siren.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/delete.svg b/src/assets/icons/delete.svg new file mode 100644 index 0000000..86a8132 --- /dev/null +++ b/src/assets/icons/delete.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/envelope.svg b/src/assets/icons/envelope.svg new file mode 100644 index 0000000..60281da --- /dev/null +++ b/src/assets/icons/envelope.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/google.svg b/src/assets/icons/google.svg new file mode 100644 index 0000000..454d2c2 --- /dev/null +++ b/src/assets/icons/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts new file mode 100644 index 0000000..42da171 --- /dev/null +++ b/src/assets/icons/index.ts @@ -0,0 +1,57 @@ +import AddIcon from './add.svg?react'; +import AlarmIcon from './alarm.svg?react'; +import ArrowDownIcon from './arrow-down.svg?react'; +import ArrowLeftIcon from './arrow-left.svg?react'; +import BoardIcon from './board.svg?react'; +import CancelIcon from './cancel.svg?react'; +import CheckIcon from './check.svg?react'; +import CloudIcon from './cloud.svg?react'; +import DeleteIcon from './delete.svg?react'; +import EnvelopeIcon from './envelope.svg?react'; +import GoogleIcon from './google.svg?react'; +import InformationIcon from './information.svg?react'; +import KakaoIcon from './kakao.svg?react'; +import KebobMenuIcon from './kebob-menu.svg?react'; +import LikeFilledIcon from './like-filled.svg?react'; +import LikeOutlinedIcon from './like-outlined.svg?react'; +import NaverIcon from './naver.svg?react'; +import NoticeIcon from './notice.svg?react'; +import PencilIcon from './pencil.svg?react'; +import PersonIcon from './person.svg?react'; +import RestartIcon from './restart.svg'; +import SirenFilledIcon from './siren-filled.svg?react'; +import SirenOutlinedIcon from './siren-outlined.svg?react'; +import SnowIcon from './snow.svg?react'; +import StampIcon from './stamp.svg?react'; +import ThermostatIcon from './thermostat.svg?react'; +import WarmIcon from './warm.svg?react'; + +export { + AddIcon, + NaverIcon, + KakaoIcon, + GoogleIcon, + StampIcon, + AlarmIcon, + CheckIcon, + ArrowDownIcon, + PersonIcon, + ArrowLeftIcon, + InformationIcon, + KebobMenuIcon, + EnvelopeIcon, + BoardIcon, + RestartIcon, + CloudIcon, + SnowIcon, + ThermostatIcon, + WarmIcon, + SirenFilledIcon, + SirenOutlinedIcon, + NoticeIcon, + LikeFilledIcon, + LikeOutlinedIcon, + DeleteIcon, + CancelIcon, + PencilIcon, +}; diff --git a/src/assets/icons/information.svg b/src/assets/icons/information.svg new file mode 100644 index 0000000..25935ef --- /dev/null +++ b/src/assets/icons/information.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/kakao.svg b/src/assets/icons/kakao.svg new file mode 100644 index 0000000..37f35ef --- /dev/null +++ b/src/assets/icons/kakao.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/kebob-menu.svg b/src/assets/icons/kebob-menu.svg new file mode 100644 index 0000000..32857ac --- /dev/null +++ b/src/assets/icons/kebob-menu.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/like-filled.svg b/src/assets/icons/like-filled.svg new file mode 100644 index 0000000..6238ba6 --- /dev/null +++ b/src/assets/icons/like-filled.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/like-outlined.svg b/src/assets/icons/like-outlined.svg new file mode 100644 index 0000000..a210945 --- /dev/null +++ b/src/assets/icons/like-outlined.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/message.svg b/src/assets/icons/message.svg new file mode 100644 index 0000000..dd01d4e --- /dev/null +++ b/src/assets/icons/message.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/naver.svg b/src/assets/icons/naver.svg new file mode 100644 index 0000000..1c90751 --- /dev/null +++ b/src/assets/icons/naver.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/notice.svg b/src/assets/icons/notice.svg new file mode 100644 index 0000000..41d2136 --- /dev/null +++ b/src/assets/icons/notice.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/pencil.svg b/src/assets/icons/pencil.svg new file mode 100644 index 0000000..3e22a81 --- /dev/null +++ b/src/assets/icons/pencil.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/person.svg b/src/assets/icons/person.svg new file mode 100644 index 0000000..aca27fb --- /dev/null +++ b/src/assets/icons/person.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/restart.svg b/src/assets/icons/restart.svg new file mode 100644 index 0000000..b228ae2 --- /dev/null +++ b/src/assets/icons/restart.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/siren-filled.svg b/src/assets/icons/siren-filled.svg new file mode 100644 index 0000000..f95ec1b --- /dev/null +++ b/src/assets/icons/siren-filled.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/siren-outlined.svg b/src/assets/icons/siren-outlined.svg new file mode 100644 index 0000000..e0555be --- /dev/null +++ b/src/assets/icons/siren-outlined.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/snow.svg b/src/assets/icons/snow.svg new file mode 100644 index 0000000..fe6746b --- /dev/null +++ b/src/assets/icons/snow.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/stamp.svg b/src/assets/icons/stamp.svg new file mode 100644 index 0000000..d78ecf1 --- /dev/null +++ b/src/assets/icons/stamp.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/thermostat.svg b/src/assets/icons/thermostat.svg new file mode 100644 index 0000000..89d2b9e --- /dev/null +++ b/src/assets/icons/thermostat.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/warm.svg b/src/assets/icons/warm.svg new file mode 100644 index 0000000..5070cc4 --- /dev/null +++ b/src/assets/icons/warm.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/background-overlay.png b/src/assets/images/background-overlay.png new file mode 100644 index 0000000..e492dbe Binary files /dev/null and b/src/assets/images/background-overlay.png differ diff --git a/src/assets/images/basic-theme.png b/src/assets/images/basic-theme.png new file mode 100644 index 0000000..f99e6fe Binary files /dev/null and b/src/assets/images/basic-theme.png differ diff --git a/src/assets/images/celebration-stamp.png b/src/assets/images/celebration-stamp.png new file mode 100644 index 0000000..f80d31d Binary files /dev/null and b/src/assets/images/celebration-stamp.png differ diff --git a/src/assets/images/celebration-theme-asset-bottom.png b/src/assets/images/celebration-theme-asset-bottom.png new file mode 100644 index 0000000..98a8fd2 Binary files /dev/null and b/src/assets/images/celebration-theme-asset-bottom.png differ diff --git a/src/assets/images/celebration-theme-asset-side.png b/src/assets/images/celebration-theme-asset-side.png new file mode 100644 index 0000000..28c04ee Binary files /dev/null and b/src/assets/images/celebration-theme-asset-side.png differ diff --git a/src/assets/images/celebration.png b/src/assets/images/celebration.png new file mode 100644 index 0000000..cfffc10 Binary files /dev/null and b/src/assets/images/celebration.png differ diff --git a/src/assets/images/closed-letter.png b/src/assets/images/closed-letter.png new file mode 100644 index 0000000..d787dca Binary files /dev/null and b/src/assets/images/closed-letter.png differ diff --git a/src/assets/images/congrat-theme.png b/src/assets/images/congrat-theme.png new file mode 100644 index 0000000..286d532 Binary files /dev/null and b/src/assets/images/congrat-theme.png differ diff --git a/src/assets/images/consolation-stamp.png b/src/assets/images/consolation-stamp.png new file mode 100644 index 0000000..a4c7090 Binary files /dev/null and b/src/assets/images/consolation-stamp.png differ diff --git a/src/assets/images/consolation.png b/src/assets/images/consolation.png new file mode 100644 index 0000000..e44a027 Binary files /dev/null and b/src/assets/images/consolation.png differ diff --git a/src/assets/images/consult-stamp.png b/src/assets/images/consult-stamp.png new file mode 100644 index 0000000..094d616 Binary files /dev/null and b/src/assets/images/consult-stamp.png differ diff --git a/src/assets/images/consult.png b/src/assets/images/consult.png new file mode 100644 index 0000000..e719e2b Binary files /dev/null and b/src/assets/images/consult.png differ diff --git a/src/assets/images/door.png b/src/assets/images/door.png new file mode 100644 index 0000000..50a5056 Binary files /dev/null and b/src/assets/images/door.png differ diff --git a/src/assets/images/envelope-pink-back-top.png b/src/assets/images/envelope-pink-back-top.png new file mode 100644 index 0000000..1188f10 Binary files /dev/null and b/src/assets/images/envelope-pink-back-top.png differ diff --git a/src/assets/images/etc-stamp.png b/src/assets/images/etc-stamp.png new file mode 100644 index 0000000..cc3f4dd Binary files /dev/null and b/src/assets/images/etc-stamp.png differ diff --git a/src/assets/images/etc.png b/src/assets/images/etc.png new file mode 100644 index 0000000..76174c2 Binary files /dev/null and b/src/assets/images/etc.png differ diff --git a/src/assets/images/field-4.png b/src/assets/images/field-4.png new file mode 100644 index 0000000..d920f40 Binary files /dev/null and b/src/assets/images/field-4.png differ diff --git a/src/assets/images/field-theme-asset-bird.png b/src/assets/images/field-theme-asset-bird.png new file mode 100644 index 0000000..919e4dd Binary files /dev/null and b/src/assets/images/field-theme-asset-bird.png differ diff --git a/src/assets/images/field-theme-asset-bottom.png b/src/assets/images/field-theme-asset-bottom.png new file mode 100644 index 0000000..128e8c2 Binary files /dev/null and b/src/assets/images/field-theme-asset-bottom.png differ diff --git a/src/assets/images/field-theme.png b/src/assets/images/field-theme.png new file mode 100644 index 0000000..09978f8 Binary files /dev/null and b/src/assets/images/field-theme.png differ diff --git a/src/assets/images/go-to-letter-board.png b/src/assets/images/go-to-letter-board.png new file mode 100644 index 0000000..a97bb28 Binary files /dev/null and b/src/assets/images/go-to-letter-board.png differ diff --git a/src/assets/images/go-to-letter-box-new-letters.png b/src/assets/images/go-to-letter-box-new-letters.png new file mode 100644 index 0000000..0cc116a Binary files /dev/null and b/src/assets/images/go-to-letter-box-new-letters.png differ diff --git a/src/assets/images/go-to-letter-box.png b/src/assets/images/go-to-letter-box.png new file mode 100644 index 0000000..0ce80e1 Binary files /dev/null and b/src/assets/images/go-to-letter-box.png differ diff --git a/src/assets/images/go-to-random-letter.png b/src/assets/images/go-to-random-letter.png new file mode 100644 index 0000000..9e0d54f Binary files /dev/null and b/src/assets/images/go-to-random-letter.png differ diff --git a/src/assets/images/home-left-mountain.png b/src/assets/images/home-left-mountain.png new file mode 100644 index 0000000..30e99bf Binary files /dev/null and b/src/assets/images/home-left-mountain.png differ diff --git a/src/assets/images/home-right-mountain-bottom.png b/src/assets/images/home-right-mountain-bottom.png new file mode 100644 index 0000000..05ab386 Binary files /dev/null and b/src/assets/images/home-right-mountain-bottom.png differ diff --git a/src/assets/images/home-right-mountain-top.png b/src/assets/images/home-right-mountain-top.png new file mode 100644 index 0000000..d66d971 Binary files /dev/null and b/src/assets/images/home-right-mountain-top.png differ diff --git a/src/assets/images/landing-blur.png b/src/assets/images/landing-blur.png new file mode 100644 index 0000000..b9f95c1 Binary files /dev/null and b/src/assets/images/landing-blur.png differ diff --git a/src/assets/images/landing.png b/src/assets/images/landing.png new file mode 100644 index 0000000..950916b Binary files /dev/null and b/src/assets/images/landing.png differ diff --git a/src/assets/images/letter-1.png b/src/assets/images/letter-1.png new file mode 100644 index 0000000..adb4613 Binary files /dev/null and b/src/assets/images/letter-1.png differ diff --git a/src/assets/images/letter-2.png b/src/assets/images/letter-2.png new file mode 100644 index 0000000..df34f4d Binary files /dev/null and b/src/assets/images/letter-2.png differ diff --git a/src/assets/images/letter-3.png b/src/assets/images/letter-3.png new file mode 100644 index 0000000..31d9cb0 Binary files /dev/null and b/src/assets/images/letter-3.png differ diff --git a/src/assets/images/letter-4.png b/src/assets/images/letter-4.png new file mode 100644 index 0000000..6b4d82c Binary files /dev/null and b/src/assets/images/letter-4.png differ diff --git a/src/assets/images/opened-letter-front.png b/src/assets/images/opened-letter-front.png new file mode 100644 index 0000000..c326013 Binary files /dev/null and b/src/assets/images/opened-letter-front.png differ diff --git a/src/assets/images/opened-letter.png b/src/assets/images/opened-letter.png new file mode 100644 index 0000000..5797b4a Binary files /dev/null and b/src/assets/images/opened-letter.png differ diff --git a/src/assets/images/postoffice-letter.png b/src/assets/images/postoffice-letter.png new file mode 100644 index 0000000..39d27c9 Binary files /dev/null and b/src/assets/images/postoffice-letter.png differ diff --git a/src/assets/images/postoffice.png b/src/assets/images/postoffice.png new file mode 100644 index 0000000..02dad3a Binary files /dev/null and b/src/assets/images/postoffice.png differ diff --git a/src/assets/images/respondent-stamp.png b/src/assets/images/respondent-stamp.png new file mode 100644 index 0000000..2d58c20 Binary files /dev/null and b/src/assets/images/respondent-stamp.png differ diff --git a/src/assets/images/sky-theme-asset-clouds.png b/src/assets/images/sky-theme-asset-clouds.png new file mode 100644 index 0000000..98187b9 Binary files /dev/null and b/src/assets/images/sky-theme-asset-clouds.png differ diff --git a/src/assets/images/sky-theme.png b/src/assets/images/sky-theme.png new file mode 100644 index 0000000..14ba42f Binary files /dev/null and b/src/assets/images/sky-theme.png differ diff --git a/src/assets/images/vintage-theme-asset-bg.png b/src/assets/images/vintage-theme-asset-bg.png new file mode 100644 index 0000000..b67338f Binary files /dev/null and b/src/assets/images/vintage-theme-asset-bg.png differ diff --git a/src/assets/images/vintage-theme.png b/src/assets/images/vintage-theme.png new file mode 100644 index 0000000..c27e5c3 Binary files /dev/null and b/src/assets/images/vintage-theme.png differ diff --git a/src/assets/images/window-disabled.png b/src/assets/images/window-disabled.png new file mode 100644 index 0000000..b3d2ca6 Binary files /dev/null and b/src/assets/images/window-disabled.png differ diff --git a/src/components/.gitkeep b/src/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/BackButton.tsx b/src/components/BackButton.tsx new file mode 100644 index 0000000..4a9815a --- /dev/null +++ b/src/components/BackButton.tsx @@ -0,0 +1,12 @@ +import { useNavigate } from 'react-router'; + +import { ArrowLeftIcon } from '@/assets/icons'; + +export default function BackButton() { + const navigate = useNavigate(); + return ( + + ); +} diff --git a/src/components/BackgroundBottom.tsx b/src/components/BackgroundBottom.tsx new file mode 100644 index 0000000..88389fd --- /dev/null +++ b/src/components/BackgroundBottom.tsx @@ -0,0 +1,15 @@ +import BgItem from '@/assets/images/field-4.png'; + +import BackgroundImageWrapper from './BackgroundImageWrapper'; + +const BackgroundBottom = () => { + return ( + + ); +}; + +export default BackgroundBottom; diff --git a/src/components/BackgroundImageWrapper.tsx b/src/components/BackgroundImageWrapper.tsx new file mode 100644 index 0000000..330150c --- /dev/null +++ b/src/components/BackgroundImageWrapper.tsx @@ -0,0 +1,29 @@ +import { twMerge } from 'tailwind-merge'; + +interface BackgroundImageWrapperProps { + as?: React.ElementType; + imageUrl: string; + className?: string; + children?: React.ReactNode; + onClick?: () => void; +} + +const BackgroundImageWrapper = ({ + as: Component = 'div', + imageUrl, + className, + children, + onClick, +}: BackgroundImageWrapperProps) => { + return ( + + {children} + + ); +}; + +export default BackgroundImageWrapper; diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx new file mode 100644 index 0000000..2b4f8aa --- /dev/null +++ b/src/components/ConfirmModal.tsx @@ -0,0 +1,58 @@ +import ModalBackgroundWrapper from './ModalBackgroundWrapper'; +import ModalOverlay from './ModalOverlay'; + +interface ConfirmModalProps { + title: string; + description: string; + cancelText: string; + confirmText: string; + confirmDisabled?: boolean; + children?: React.ReactNode; + onCancel: () => void; + onConfirm: () => void; +} + +const ConfirmModal = ({ + title, + description, + cancelText, + confirmText, + confirmDisabled, + children, + onCancel, + onConfirm, +}: ConfirmModalProps) => { + // TODO: 전역 상태로 관리해야하는지 고민 + return ( + +
+ +
+

{title}

+

{description}

+
+ {children} +
+
+ + +
+
+
+ ); +}; + +export default ConfirmModal; diff --git a/src/components/HomeButton.tsx b/src/components/HomeButton.tsx new file mode 100644 index 0000000..cc185e2 --- /dev/null +++ b/src/components/HomeButton.tsx @@ -0,0 +1,17 @@ +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; +import { Link } from 'react-router'; + +export default function HomeButton() { + return ( + <> +
+ + + +
+ + ); +} diff --git a/src/components/LetterWrapper.tsx b/src/components/LetterWrapper.tsx new file mode 100644 index 0000000..7373ea2 --- /dev/null +++ b/src/components/LetterWrapper.tsx @@ -0,0 +1,30 @@ +import { forwardRef } from 'react'; +import { twMerge } from 'tailwind-merge'; + +interface LetterWrapperProps { + isSender?: boolean; + className?: string; + children: React.ReactNode; + onClick?: (e: React.MouseEvent) => void; +} + +const LetterWrapper = forwardRef( + ({ isSender = false, className, children, onClick }, ref) => { + return ( +
+
{children}
+
+
+ ); + }, +); + +export default LetterWrapper; diff --git a/src/components/MemoWrapper.tsx b/src/components/MemoWrapper.tsx new file mode 100644 index 0000000..2179251 --- /dev/null +++ b/src/components/MemoWrapper.tsx @@ -0,0 +1,31 @@ +import { twMerge } from 'tailwind-merge'; + +interface MemoWrapperProps { + isSender?: boolean; + className?: string; + children: React.ReactNode; + onClick?: (e: React.MouseEvent) => void; +} + +const MemoWrapper = ({ isSender = false, className, children, onClick }: MemoWrapperProps) => { + return ( +
+
{children}
+
+
+ ); +}; + +export default MemoWrapper; diff --git a/src/components/MessageModal.tsx b/src/components/MessageModal.tsx new file mode 100644 index 0000000..05e9395 --- /dev/null +++ b/src/components/MessageModal.tsx @@ -0,0 +1,62 @@ +import { ChangeEvent } from 'react'; + +import ModalOverlay from './ModalOverlay'; +import TextareaField from './TextareaField'; + +interface MessageModalProps { + description?: string; + inputValue: string; + placeholder?: string; + cancelText: string; + completeText: string; + children?: React.ReactNode; + onInputChange: (e: ChangeEvent) => void; + onCancel: () => void; + onComplete: () => void; +} + +const MessageModal = ({ + description, + inputValue, + placeholder, + cancelText, + completeText, + children, + onInputChange, + onCancel, + onComplete, +}: MessageModalProps) => { + return ( + +

{description}

+
+ +
{children}
+
+
+
+ + +
+
+ ); +}; + +export default MessageModal; diff --git a/src/components/ModalBackgroundWrapper.tsx b/src/components/ModalBackgroundWrapper.tsx new file mode 100644 index 0000000..0982a97 --- /dev/null +++ b/src/components/ModalBackgroundWrapper.tsx @@ -0,0 +1,17 @@ +import { twMerge } from 'tailwind-merge'; + +interface ModalBackgroundWrapperProps { + className?: string; + children: React.ReactNode; +} + +const ModalBackgroundWrapper = ({ className, children }: ModalBackgroundWrapperProps) => { + return ( +
+
{children}
+
+
+ ); +}; + +export default ModalBackgroundWrapper; diff --git a/src/components/ModalOverlay.tsx b/src/components/ModalOverlay.tsx new file mode 100644 index 0000000..95104ef --- /dev/null +++ b/src/components/ModalOverlay.tsx @@ -0,0 +1,46 @@ +import { useEffect } from 'react'; + +import { getScrollbarWidth } from '@/utils/getScrollbarWidth'; + +interface ModalOverlayProps { + closeOnOutsideClick?: boolean; + children: React.ReactNode; + onClose?: () => void; +} + +const ModalOverlay = ({ closeOnOutsideClick = false, children, onClose }: ModalOverlayProps) => { + useEffect(() => { + const scrollbarWidth = getScrollbarWidth(); + + document.documentElement.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`); + document.body.classList.add('modal-open'); + + return () => { + document.documentElement.style.setProperty('--scrollbar-width', '0px'); + document.body.classList.remove('modal-open'); + }; + }, []); + + const handleClickOutside = () => { + if (closeOnOutsideClick && onClose) { + onClose(); + } + }; + + const handleClickInside = (event: React.MouseEvent) => { + event.stopPropagation(); + }; + + return ( +
+
+ {children} +
+
+ ); +}; + +export default ModalOverlay; diff --git a/src/components/NoticeRollingPaper.tsx b/src/components/NoticeRollingPaper.tsx new file mode 100644 index 0000000..b495b0e --- /dev/null +++ b/src/components/NoticeRollingPaper.tsx @@ -0,0 +1,34 @@ +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import { getCurrentRollingPaper } from '@/apis/rolling'; +import { NoticeIcon } from '@/assets/icons'; + +const NoticeRollingPaper = () => { + const { data } = useQuery({ + queryKey: ['notice-rolling-paper'], + queryFn: () => getCurrentRollingPaper(), + }); + + const noticeText = data?.title; + + return ( + +
+ +
+

{noticeText}

+
+
+ + ); +}; + +export default NoticeRollingPaper; diff --git a/src/components/PageTitle.tsx b/src/components/PageTitle.tsx new file mode 100644 index 0000000..ffdf2be --- /dev/null +++ b/src/components/PageTitle.tsx @@ -0,0 +1,16 @@ +import { twMerge } from 'tailwind-merge'; + +interface PageTitleProps { + className?: string; + children: React.ReactNode; +} + +const PageTitle = ({ className, children }: PageTitleProps) => { + return ( +

+ {children} +

+ ); +}; + +export default PageTitle; diff --git a/src/components/ReportModal.tsx b/src/components/ReportModal.tsx new file mode 100644 index 0000000..be47d81 --- /dev/null +++ b/src/components/ReportModal.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { postReports } from '@/apis/admin'; + +import ConfirmModal from './ConfirmModal'; +import TextareaField from './TextareaField'; + +interface ReportModalProps { + reportType: ReportType; + letterId: number | null; + onClose: () => void; +} + +interface ReportReason { + name: string; + type: Reason; +} +const REPORT_REASON: ReportReason[] = [ + { name: '욕설', type: 'ABUSE' }, + { name: '비방', type: 'DEFAMATION' }, + { name: '폭언', type: 'THREATS' }, + { name: '성희롱', type: 'HARASSMENT' }, + { name: '기타', type: 'ETC' }, +]; + +const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => { + const [postReportRequest, setPostReportRequest] = useState({ + reportType: reportType, + reasonType: '', + reason: '', + letterId: letterId, + }); + + const handleReasonClick = (reason: Reason) => { + if (postReportRequest.reasonType === reason) + setPostReportRequest((cur) => ({ ...cur, reasonType: '' })); + else setPostReportRequest((cur) => ({ ...cur, reasonType: reason })); + }; + + const handleSubmit = async () => { + const res = await postReports(postReportRequest); + if (res?.status === 200) { + alert('신고 처리되었습니다.'); + onClose(); + } else if (res?.status === 409) { + alert('신고한 이력이 있습니다.'); + onClose(); + } + }; + + useEffect(() => { + if (!postReportRequest.letterId) { + alert('신고 모달을 여는 과정에서 오류가 발생했습니다. 새로고침을 눌러주세요'); + onClose(); + } + }); + + return ( + +
+ {REPORT_REASON.map((reason, idx) => ( + + ))} +
+ setPostReportRequest((cur) => ({ ...cur, reason: e.target.value }))} + /> +
+ ); +}; + +export default ReportModal; diff --git a/src/components/ResultLetter.tsx b/src/components/ResultLetter.tsx new file mode 100644 index 0000000..36980d5 --- /dev/null +++ b/src/components/ResultLetter.tsx @@ -0,0 +1,45 @@ +import { CATEGORYS } from '../pages/Write/constants'; + +import LetterWrapper from './LetterWrapper'; + +export default function ResultLetter({ + categoryName = 'CONSOLATION', + title, + zipCode = 'ERROR', +}: { + categoryName: Category; + title: string; + zipCode: string; +}) { + const date = new Date(); + const today = `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`; + + return ( + +
+
+
+ 따숨이님께 + {title} +
+ 우표 +
+
+ {today} +
+ {zipCode.split('').map((spell, idx) => { + return ( + + {spell} + + ); + })} +
+
+
+
+ ); +} diff --git a/src/components/TextareaField.tsx b/src/components/TextareaField.tsx new file mode 100644 index 0000000..cd21291 --- /dev/null +++ b/src/components/TextareaField.tsx @@ -0,0 +1,14 @@ +import { ComponentPropsWithoutRef } from 'react'; + +const TextareaField = ({ ...props }: ComponentPropsWithoutRef<'textarea'>) => { + return ( +
+ +
+ + +
+
+ + ); +} diff --git a/src/pages/Admin/components/ReportListItem.tsx b/src/pages/Admin/components/ReportListItem.tsx new file mode 100644 index 0000000..fec8aab --- /dev/null +++ b/src/pages/Admin/components/ReportListItem.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; + +import { KebobMenuIcon } from '@/assets/icons'; + +import MenuModal from './MenuModal'; + +export default function ReportListItem({ + report, + setDetailModalOpen, + setSelectedReportId, + setHandleModalOpen, + setSelectReport, +}: { + report: Report; + setDetailModalOpen: React.Dispatch>; + setSelectedReportId: React.Dispatch>; + setHandleModalOpen: React.Dispatch>; + setSelectReport: React.Dispatch>; +}) { + const [modalOpen, setModalOpen] = useState(false); + + const dateString = report.reportedAt; + const date = new Date(dateString); + const formattedDate = date.toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }); + const formattedTime = date.toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + + const modalContents: ModalContents[] = [ + { + title: '신고 처리', + onClick: () => { + setSelectedReportId(report.id); + setHandleModalOpen(true); + }, + }, + ]; + + const reasonList = { + ABUSE: '욕설', + DEFAMATION: '비방', + HARASSMENT: '성희롱', + THREATS: '폭언', + ETC: '기타', + }; + return ( +
{ + setSelectReport(report); + setDetailModalOpen(true); + }} + > +
+ {report.id} + {report.reporterEmail} + {report.targetEmail} + {`${formattedDate} ${formattedTime}`} + {reasonList[report.reasonType]} +
+ + {modalOpen && } +
+ ); +} diff --git a/src/pages/Admin/components/Sidebar.tsx b/src/pages/Admin/components/Sidebar.tsx new file mode 100644 index 0000000..6de7cc6 --- /dev/null +++ b/src/pages/Admin/components/Sidebar.tsx @@ -0,0 +1,61 @@ +import { NavLink, useLocation } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import { AlarmIcon, ArrowDownIcon } from '@/assets/icons'; + +import { ADMIN_MENU_LIST } from '../constants'; + +export default function Sidebar() { + const location = useLocation(); + + return ( +
+

+ 마음이 맞닿는 온도 + 36.5 +

+
+

현재 로그인 계정

+

{'admin123@test.com'}

+
+
+
+

사이트 관리

+ {ADMIN_MENU_LIST.map((menu) => ( +
+ + + {menu.title} + {!menu.path && } + +
+ {menu.subMenu && + menu.subMenu.map((subMenu) => ( + + {subMenu.title} + + ))} +
+
+ ))} +
+ +
+ ); +} diff --git a/src/pages/Admin/components/WrapperFrame.tsx b/src/pages/Admin/components/WrapperFrame.tsx new file mode 100644 index 0000000..e093c1b --- /dev/null +++ b/src/pages/Admin/components/WrapperFrame.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; +import { twMerge } from 'tailwind-merge'; + +export default function WrapperFrame({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/pages/Admin/components/WrapperTitle.tsx b/src/pages/Admin/components/WrapperTitle.tsx new file mode 100644 index 0000000..22b076e --- /dev/null +++ b/src/pages/Admin/components/WrapperTitle.tsx @@ -0,0 +1,13 @@ +export default function WrapperTitle({ + title, + Icon, +}: { + title: string; + Icon: React.FunctionComponent>; +}) { + return ( + + {title} + + ); +} diff --git a/src/pages/Admin/constants/index.ts b/src/pages/Admin/constants/index.ts new file mode 100644 index 0000000..d5e59b7 --- /dev/null +++ b/src/pages/Admin/constants/index.ts @@ -0,0 +1,34 @@ +export const ADMIN_MENU_LIST = [ + { title: '대시보드', path: undefined }, + { title: '사용자 목록', path: undefined }, + { + title: '게시판 관리', + subMenu: [ + { + title: '공개 편지 설정', + path: undefined, + }, + { + title: '롤링 페이퍼 설정', + path: '/admin/rolling-paper', + }, + ], + }, + { + title: '검열 관리', + subMenu: [ + { + title: '신고 편지 목록', + path: '/admin/report', + }, + { + title: '필터링 단어 설정', + path: '/admin/badwords', + }, + { + title: '차단된 편지 목록', + path: '/admin/filtered-letter', + }, + ], + }, +]; diff --git a/src/pages/Admin/index.tsx b/src/pages/Admin/index.tsx new file mode 100644 index 0000000..b956170 --- /dev/null +++ b/src/pages/Admin/index.tsx @@ -0,0 +1,15 @@ +import { Outlet } from 'react-router'; + +import Sidebar from './components/Sidebar'; + +const AdminPage = () => { + return ( +
+ +
+ +
+
+ ); +}; +export default AdminPage; diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx new file mode 100644 index 0000000..0596a77 --- /dev/null +++ b/src/pages/Auth/index.tsx @@ -0,0 +1,83 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router'; + +import { getUserToken, getMydata, postZipCode } from '@/apis/auth'; +import useAuthStore from '@/stores/authStore'; + +const AuthCallbackPage = () => { + const stateToken = new URLSearchParams(window.location.search).get('state'); + const redirectURL = new URLSearchParams(window.location.search).get('redirect'); + + const login = useAuthStore((state) => state.login); + const logout = useAuthStore((state) => state.logout); + const setAccessToken = useAuthStore((state) => state.setAccessToken); + const setZipCode = useAuthStore((state) => state.setZipCode); + const navigate = useNavigate(); + + const handleError = (error: unknown) => { + console.error('AuthCallback Error:', error); + logout(); + navigate('/login', { replace: true }); + }; + + const setUserInfo = async (stateToken: string) => { + try { + const response = await getUserToken(stateToken); + console.log(response); + if (!response) throw new Error('Error fetching user token'); + + const userInfo = response.data; + if (!userInfo) throw new Error('Invalid user info'); + + login(); + if (userInfo.accessToken) setAccessToken(userInfo.accessToken); + + switch (redirectURL) { + case 'home': + { + const zipCodeResponse = await getMydata(); + if (!zipCodeResponse) throw new Error('Error fetching user data'); + setZipCode(zipCodeResponse.data.data.zipCode); + } + break; + + case 'onboarding': + { + const createZipCodeResponse = await postZipCode(); + if (!createZipCodeResponse) throw new Error('Error creating ZipCode'); + + setZipCode(createZipCodeResponse.data.data.zipCode); + const newAccessToken = createZipCodeResponse.headers['authorization']?.split(' ')[1]; + if (!newAccessToken) throw new Error('Missing new access token'); + + setAccessToken(newAccessToken); + } + break; + + default: + navigate('/notFound'); + return; + } + navigate(redirectURL === 'onboarding' ? '/onboarding' : '/'); + } catch (error) { + handleError(error); + } + }; + + useEffect(() => { + if (!stateToken) { + navigate('/notFound'); + return; + } + + const fetchData = async () => { + await setUserInfo(stateToken as string); + }; + + fetchData(); + }, [stateToken, navigate]); + + return null; +}; + +export default AuthCallbackPage; diff --git a/src/pages/Home/components/FloatingLetters.tsx b/src/pages/Home/components/FloatingLetters.tsx new file mode 100644 index 0000000..3403883 --- /dev/null +++ b/src/pages/Home/components/FloatingLetters.tsx @@ -0,0 +1,49 @@ +import gsap from 'gsap'; +import { useEffect, useRef } from 'react'; + +import letter1 from '@/assets/images/letter-1.png'; +import letter2 from '@/assets/images/letter-2.png'; +import letter3 from '@/assets/images/letter-3.png'; +import letter4 from '@/assets/images/letter-4.png'; + +const images = [letter1, letter2, letter3, letter4]; + +const FloatingLetters = () => { + const lettersRef = useRef([]); + useEffect(() => { + if (!lettersRef.current) return; + + lettersRef.current.forEach((letter, index) => { + gsap.to(letter, { + // x: Math.random() * 50 - 40, + y: Math.random() * 20 - 40 + 'vh', // 위아래 이동 + rotation: Math.random() * 50 - 25, // 회전 + duration: Math.random() * 3 + 2, // 지속 시간 + repeat: -1, // 무한 반복 + yoyo: true, // 왕복 + ease: 'power1.inOut', + delay: index * 1, // 편지마다 시차 + }); + }); + }, []); + return ( + <> + {images.map((src, index) => ( + { + if (el) lettersRef.current[index] = el; + }} + className="absolute w-20 opacity-90" + style={{ + left: `${index * 30 + 30}px`, // 편지지 간격 + top: '60vh', + }} + /> + ))} + + ); +}; + +export default FloatingLetters; diff --git a/src/pages/Home/components/GoToLetterBoard.tsx b/src/pages/Home/components/GoToLetterBoard.tsx new file mode 100644 index 0000000..2f86778 --- /dev/null +++ b/src/pages/Home/components/GoToLetterBoard.tsx @@ -0,0 +1,18 @@ +import { Link } from 'react-router'; + +import goToLetterBoard from '@/assets/images/go-to-letter-board.png'; + +const GoToLetterBoard = () => { + return ( +
+
+

게시판

+ + go to letter board + +
+
+ ); +}; + +export default GoToLetterBoard; diff --git a/src/pages/Home/components/GoToLetterBox.tsx b/src/pages/Home/components/GoToLetterBox.tsx new file mode 100644 index 0000000..ca8b3fe --- /dev/null +++ b/src/pages/Home/components/GoToLetterBox.tsx @@ -0,0 +1,39 @@ +import { useState, useEffect } from 'react'; +import { Link } from 'react-router'; + +import { getUnreadLettersCount } from '@/apis/unreadLetters'; +import goToLetterBoxNewLetters from '@/assets/images/go-to-letter-box-new-letters.png'; +import goToLetterBox from '@/assets/images/go-to-letter-box.png'; + +const GoToLetterBox = () => { + const [arrivedCount, setArrivedCount] = useState(0); + + useEffect(() => { + const fetchUnreadCount = async () => { + try { + const result = await getUnreadLettersCount(); + setArrivedCount(result.data); + } catch (error) { + console.error('❌ 안 읽은 편지 개수를 불러오는 데 실패했습니다:', error); + } + }; + fetchUnreadCount(); + }, []); + + return ( +
+
+

내 편지함

+ + go to letter box + +
+
+ ); +}; + +export default GoToLetterBox; diff --git a/src/pages/Home/components/GoToRandomLetter.tsx b/src/pages/Home/components/GoToRandomLetter.tsx new file mode 100644 index 0000000..2154e7e --- /dev/null +++ b/src/pages/Home/components/GoToRandomLetter.tsx @@ -0,0 +1,18 @@ +import { Link } from 'react-router'; + +import goToRandomLetter from '@/assets/images/go-to-random-letter.png'; + +const GoToRandomLetter = () => { + return ( + <> +
+

고민편지 보러가기

+ + go to random letter + +
+ + ); +}; + +export default GoToRandomLetter; diff --git a/src/pages/Home/components/GoToWrite.tsx b/src/pages/Home/components/GoToWrite.tsx new file mode 100644 index 0000000..9c4d589 --- /dev/null +++ b/src/pages/Home/components/GoToWrite.tsx @@ -0,0 +1,16 @@ +import { Link } from 'react-router'; + +import goToWrite from '@/assets/images/postoffice-letter.png'; + +const GoToWrite = () => { + return ( +
+

속마음 나누기

+ + go to write + +
+ ); +}; + +export default GoToWrite; diff --git a/src/pages/Home/components/HomeBackgroundLeft.tsx b/src/pages/Home/components/HomeBackgroundLeft.tsx new file mode 100644 index 0000000..43b9f91 --- /dev/null +++ b/src/pages/Home/components/HomeBackgroundLeft.tsx @@ -0,0 +1,14 @@ +import homeLeftMountain from '@/assets/images/home-left-mountain.png'; +import BackgroundImageWrapper from '@/components/BackgroundImageWrapper'; + +const HomeBackgroundLeft = () => { + return ( + + ); +}; + +export default HomeBackgroundLeft; diff --git a/src/pages/Home/components/HomeBackgroundRightBottom.tsx b/src/pages/Home/components/HomeBackgroundRightBottom.tsx new file mode 100644 index 0000000..6b55d0c --- /dev/null +++ b/src/pages/Home/components/HomeBackgroundRightBottom.tsx @@ -0,0 +1,14 @@ +import homeRightMountainBottom from '@/assets/images/home-right-mountain-bottom.png'; +import BackgroundImageWrapper from '@/components/BackgroundImageWrapper'; + +const HomeBackgroundRightBottom = () => { + return ( + + ); +}; + +export default HomeBackgroundRightBottom; diff --git a/src/pages/Home/components/HomeBackgroundRightTop.tsx b/src/pages/Home/components/HomeBackgroundRightTop.tsx new file mode 100644 index 0000000..966424e --- /dev/null +++ b/src/pages/Home/components/HomeBackgroundRightTop.tsx @@ -0,0 +1,13 @@ +import homeRightMountainTop from '@/assets/images/home-right-mountain-top.png'; +import BackgroundImageWrapper from '@/components/BackgroundImageWrapper'; +const HomeBackgroundRightTop = () => { + return ( + + ); +}; + +export default HomeBackgroundRightTop; diff --git a/src/pages/Home/components/HomeHeader.tsx b/src/pages/Home/components/HomeHeader.tsx new file mode 100644 index 0000000..3c1db95 --- /dev/null +++ b/src/pages/Home/components/HomeHeader.tsx @@ -0,0 +1,20 @@ +import { Link } from 'react-router'; + +import { AlarmIcon, PersonIcon } from '@/assets/icons'; + +const HomeHeader = () => { + return ( +
+
+ + + + + + +
+
+ ); +}; + +export default HomeHeader; diff --git a/src/pages/Home/components/HomeLeft.tsx b/src/pages/Home/components/HomeLeft.tsx new file mode 100644 index 0000000..f54038e --- /dev/null +++ b/src/pages/Home/components/HomeLeft.tsx @@ -0,0 +1,18 @@ +import GoToRandomLetter from './GoToRandomLetter'; +import GoToWrite from './GoToWrite'; +import RandomCheer from './RandomCheer'; + +const HomeLeft = () => { + return ( +
+ + + +
+ ); +}; + +export default HomeLeft; diff --git a/src/pages/Home/components/HomeRight.tsx b/src/pages/Home/components/HomeRight.tsx new file mode 100644 index 0000000..8182517 --- /dev/null +++ b/src/pages/Home/components/HomeRight.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; + +import { getUnreadLettersCount } from '@/apis/unreadLetters'; + +import FloatingLetters from './FloatingLetters'; +import GoToLetterBoard from './GoToLetterBoard'; +import GoToLetterBox from './GoToLetterBox'; +import UnreadLetterModal from './UnreadLetterModal'; + +const HomeRight = () => { + const [arrivedCount, setArrivedCount] = useState(0); + + useEffect(() => { + const fetchUnreadCount = async () => { + try { + const result = await getUnreadLettersCount(); + setArrivedCount(result.data); + } catch (error) { + console.error('❌ 안 읽은 편지 개수를 불러오는 데 실패했습니다:', error); + } + }; + fetchUnreadCount(); + }, []); + + return ( +
+ {arrivedCount !== 0 && } + + + {arrivedCount !== 0 && } +
+ ); +}; + +export default HomeRight; diff --git a/src/pages/Home/components/LetterActions.tsx b/src/pages/Home/components/LetterActions.tsx new file mode 100644 index 0000000..a7259cd --- /dev/null +++ b/src/pages/Home/components/LetterActions.tsx @@ -0,0 +1,55 @@ +import DriveFileRenameOutlineOutlinedIcon from '@mui/icons-material/DriveFileRenameOutlineOutlined'; +import SendOutlinedIcon from '@mui/icons-material/SendOutlined'; +import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined'; +import { useState } from 'react'; + +import ShowDraftModal from './ShowDraftModal'; +import ShowIncomingLettersModal from './ShowIncomingLettersModal'; +import ShowShareAccessModal from './ShowShareAccessModal'; + +const LetterActions = () => { + const [activeModal, setActiveModal] = useState< + null | 'incomingLetters' | 'draft' | 'shareAccess' + >(null); + + const arr: { title: 'incomingLetters' | 'draft' | 'shareAccess'; icon: React.ReactNode }[] = [ + { + title: 'incomingLetters', + icon: , + }, + { + title: 'draft', + icon: , + }, + { + title: 'shareAccess', + icon: , + }, + ]; + return ( + <> +
+
+ {arr.map((item, index) => ( + + ))} +
+
+ {activeModal === 'incomingLetters' && ( + setActiveModal(null)} /> + )} + {activeModal === 'draft' && setActiveModal(null)} />} + {activeModal === 'shareAccess' && ( + setActiveModal(null)} /> + )} + + ); +}; + +export default LetterActions; diff --git a/src/pages/Home/components/RandomCheer.tsx b/src/pages/Home/components/RandomCheer.tsx new file mode 100644 index 0000000..83e96c6 --- /dev/null +++ b/src/pages/Home/components/RandomCheer.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react'; + +import randomCheerBird from '@/assets/images/field-theme-asset-bird.png'; + +import { RANDOM_CHEER_LIST } from '../constants'; + +const RandomCheer = () => { + const getRandomCheer = (): string => { + const randomIndex = Math.floor(Math.random() * RANDOM_CHEER_LIST.length); + return RANDOM_CHEER_LIST[randomIndex]; + }; + + const [randomCheer, setRandomCheer] = useState(getRandomCheer()); + + return ( +
+
setRandomCheer(getRandomCheer())} + > +

{randomCheer}

+
+
+ random cheer bird +
+ ); +}; + +export default RandomCheer; diff --git a/src/pages/Home/components/ShowDraftModal.tsx b/src/pages/Home/components/ShowDraftModal.tsx new file mode 100644 index 0000000..3cb9060 --- /dev/null +++ b/src/pages/Home/components/ShowDraftModal.tsx @@ -0,0 +1,68 @@ +import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded'; +import React, { useEffect, useState } from 'react'; +// import { useNavigate } from 'react-router'; + +import { DraftLetter, getDraftLetters } from '@/apis/draftLetters'; +import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper'; +import ModalOverlay from '@/components/ModalOverlay'; + +interface ShowDraftModalProps { + children?: React.ReactNode; + onClose: () => void; +} + +const ShowDraftModal = ({ onClose }: ShowDraftModalProps) => { + const [draftLetters, setDraftLetters] = useState([]); + + // const navigate = useNavigate(); + + // const handleNavigation = (incomingId: number) => { + // navigate(`/board/letter/${incomingId}`, { + // state: { isShareLetterPreview: false }, + // }); + // }; + + useEffect(() => { + getDraftLetters() + .then((data) => { + setDraftLetters(data || []); + }) + .catch((error) => { + console.error('❌ 임시저장된 편지를 불러오는데 실패했습니다', error); + }); + }, [onClose]); + + return ( + +
+

+ 임시저장된 편지가 있어요! +

+
+ +
+

임시저장 편지

+

로그아웃 시 임시 저장된 편지는 사라집니다

+
+
+ {draftLetters.map((draft) => ( +
handleNavigation(draft.letterId)} + > +

{draft.title}

+
+ +
+
+ ))} +
+
+
+
+
+ ); +}; + +export default ShowDraftModal; diff --git a/src/pages/Home/components/ShowIncomingLettersModal.tsx b/src/pages/Home/components/ShowIncomingLettersModal.tsx new file mode 100644 index 0000000..219015b --- /dev/null +++ b/src/pages/Home/components/ShowIncomingLettersModal.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react'; + +import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper'; +import ModalOverlay from '@/components/ModalOverlay'; +import { useIncomingLettersStore } from '@/stores/incomingLettersStore'; + +interface ShowIncomingLettersModalProps { + children?: React.ReactNode; + onClose: () => void; +} + +const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) => { + const { data, fetchIncomingLetters } = useIncomingLettersStore(); + + useEffect(() => { + fetchIncomingLetters(); + }, [fetchIncomingLetters]); + + return ( + +
+

+ 따숨 배달부가 따숨이의 답장을 배달 중이에요! +

+
+ +
+

오고 있는 편지

+

시간은 실제 시간을 기반으로 책정됩니다.

+
+
+ {data.map((letter) => ( +
+

{letter.title}

+

{letter.remainingTime}

+
+ ))} +
+
+
+
+
+ ); +}; + +export default ShowIncomingLettersModal; diff --git a/src/pages/Home/components/ShowShareAccessModal.tsx b/src/pages/Home/components/ShowShareAccessModal.tsx new file mode 100644 index 0000000..e1e40df --- /dev/null +++ b/src/pages/Home/components/ShowShareAccessModal.tsx @@ -0,0 +1,76 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + +import { getSharePostDetail, getSharePostList } from '@/apis/share'; +import { SharePostResponse } from '@/apis/share'; +import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper'; +import ModalOverlay from '@/components/ModalOverlay'; + +interface ShowShareAccessModalProps { + children?: React.ReactNode; + onClose: () => void; +} + +const ShowShareAccessModal = ({ onClose }: ShowShareAccessModalProps) => { + const navigate = useNavigate(); + + const [sharePosts, setSharePosts] = useState(); + + useEffect(() => { + const fetchPosts = async () => { + try { + const data = await getSharePostList(1, 10); + setSharePosts(data); + } catch (error) { + console.error('❌ 게시글 목록을 불러오는 데 실패했습니다.', error); + } + }; + + fetchPosts(); + }, []); + + const handleNavigation = async (sharePostId: number) => { + try { + const postDetail = await getSharePostDetail(sharePostId); + navigate(`/board/letter/${sharePostId}`, { + state: { postDetail, isShareLetterPreview: true }, + }); + } catch (error) { + console.error('❌ 게시글 상세 페이지로 이동하는 데에 실패했습니다.', error); + } + }; + + return ( + +
+

+ 공유 요청이 왔어요! +

+
+ +
+

게시판 공유 승인하기

+

+ 따숨님과 주고받은 추억을 게시판에 공유하고 싶으신 분이 있어요. 클릭해서 확인하고, + 허락 여부를 체크해주세요! +

+
+
+ {sharePosts?.content.map((post) => ( + + ))} +
+
+
+
+
+ ); +}; + +export default ShowShareAccessModal; diff --git a/src/pages/Home/components/UnreadLetterModal.tsx b/src/pages/Home/components/UnreadLetterModal.tsx new file mode 100644 index 0000000..c3491a3 --- /dev/null +++ b/src/pages/Home/components/UnreadLetterModal.tsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; + +import { getUnreadLettersCount } from '@/apis/unreadLetters'; + +const UnreadLetterModal = () => { + const [arrivedCount, setArrivedCount] = useState(0); + + useEffect(() => { + const fetchUnreadCount = async () => { + try { + const result = await getUnreadLettersCount(); + setArrivedCount(result.data); + } catch (error) { + console.error('❌ 안 읽은 편지 개수를 불러오는 데 실패했습니다:', error); + } + }; + fetchUnreadCount(); + }, []); + + return ( +

+ {arrivedCount}통의 편지가 도착했어요! +

+ ); +}; + +export default UnreadLetterModal; diff --git a/src/pages/Home/constants/index.ts b/src/pages/Home/constants/index.ts new file mode 100644 index 0000000..a2e0fd8 --- /dev/null +++ b/src/pages/Home/constants/index.ts @@ -0,0 +1,45 @@ +export const RANDOM_CHEER_LIST: string[] = [ + '오늘도 화이팅!☀️', + '오늘도 수고 많았어요. ☕', + '괜찮아요, 다 잘 될 거예요. 💙', + '혼자가 아니에요. 🤗', + '잠시 쉬어가도 괜찮아요. 🍃', + '있는 그대로도 충분해요. 🫂', + '분명 나아질 거예요. 🌈', + '천천히 가도 돼요. 👣', + '지금도 충분히 잘하고 있어요. 💕', + '항상 응원하고 있어요! 😊', + '조금만 더 힘내봐요. 🌟', + '기대도 괜찮아요. 🎈', + '좋은 날이 올 거예요. ✨', + '너무 잘하고 있어요! 💪', + '따숨님의 노력은 언젠가 빛날 거예요. 🌟', + '하루하루 조금씩 나아지고 있어요. ⏳', + '매일 조금씩 성장하고 있어요. 🌱', + '세상은 따숨님을 기다리고 있어요. 🌍', + '힘들 땐 잠시 멈추어도 괜찮아요. ⏸️', + '오늘도 멋진 하루가 될 거예요. 🌅', + '모든 순간이 소중해요. 🌸', + '이 순간이 따숨님의 노력의 결과예요. 🎯', + '항상 자기 자신을 사랑해주세요. ❤️', + '모든 일은 시간이 해결해줄 거예요. ⏳', + '따숨님은 이미 충분히 잘하고 있어요. 🏅', + '끝까지 가면 꿈이 이루어져요. 🌠', + '오늘도 행복한 하루 되세요. 🌞', + '따숨님의 열정이 빛나고 있어요. 🔥', + '내일은 오늘보다 더 나을 거예요. 🌟', + '모든 것이 끝난 후 웃을 수 있을 거예요. 😁', + '자신감을 가지고 계속 나아가세요. 🚀', + '세상에서 가장 중요한 건 따숨님이에요. 💖', + '따숨님의 미소가 세상을 밝게 해요. 😄', + '한 걸음씩, 천천히 가도 괜찮아요. 👟', + '지금 그 길이 바로 올바른 길이에요. 🛣️', + '작은 변화가 큰 차이를 만들어낼 거예요. 🔄', + '오늘도 모든 것이 가능해요. 💫', + '따숨님의 열정이 승리로 이어질 거예요. 🏆', + '힘들 때일수록 더 강해지는 거예요. 💥', + '따숨님은 강하고 대단한 사람이에요! 💪', + '따숨님은 충분히 잘하고 있어요. 🥇', + '모든 것은 시간이 지나면 잘 될 거예요. ⏳', + '도전이 결국 성공으로 이어질 거예요! 🏅', +]; diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index b2f0bf4..68f528f 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,5 +1,54 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router'; + +import HomeButton from '@/components/HomeButton'; +import NoticeRollingPaper from '@/components/NoticeRollingPaper'; +import useViewport from '@/hooks/useViewport'; +import useAuthStore from '@/stores/authStore'; + +import HomeBackgroundLeft from './components/HomeBackgroundLeft'; +import HomeBackgroundRightBottom from './components/HomeBackgroundRightBottom'; +import HomeBackgroundRightTop from './components/HomeBackgroundRightTop'; +import HomeHeader from './components/HomeHeader'; +import HomeLeft from './components/HomeLeft'; +import HomeRight from './components/HomeRight'; +import LetterActions from './components/LetterActions'; + const HomePage = () => { - return
HomePage
; + const isLoggedIn = useAuthStore.getState().isLoggedIn; + const navigate = useNavigate(); + + useViewport(); + useEffect(() => { + if (!isLoggedIn) { + navigate('/login'); + } + }, []); + + return ( +
+ +
+ +
+ + +
+
+ + +
+ +
+ + + +
+
+ + +
+ ); }; export default HomePage; diff --git a/src/pages/Landing/constants/index.ts b/src/pages/Landing/constants/index.ts new file mode 100644 index 0000000..da27eda --- /dev/null +++ b/src/pages/Landing/constants/index.ts @@ -0,0 +1,25 @@ +export const STYLE_CLASS = [ + { + imagePosition: 'left-[calc(50%-200px)]', + mask: 'bottom-26 left-[calc(50%+65px)]', + circle: 'h-41 w-41', + textPosition: '-top-20 left-[calc(50%-30px)] text-right', + description: + '따뜻한 편지 사례들을 모아 볼 수 있어요.\n응원이 필요한 사람들에게\n단체로 롤링페이퍼를 쓸 수도 있습니다.', + }, + { + imagePosition: 'left-[calc(50%-200px)]', + mask: 'bottom-5 left-[calc(50%-75px)]', + circle: 'h-42 w-42', + textPosition: '-top-14 left-[calc(50%+40px)] text-left', + description: '주고받은 편지 내역을 확인해 보세요.\n보는 것 만으로도 마음이 따뜻해집니다.', + }, + { + imagePosition: 'left-[calc(50%+130px)]', + mask: 'bottom-8 left-[calc(50%)]', + circle: 'h-65 w-65', + textPosition: '-top-14 left-1/2 text-center', + description: + '모르는 사람에게 고민을 털어 놓아 보세요.\n다른 사람에게도 위로, 응원, 축하를 보낼 수 있어요.', + }, +]; diff --git a/src/pages/Landing/index.tsx b/src/pages/Landing/index.tsx new file mode 100644 index 0000000..b89bfda --- /dev/null +++ b/src/pages/Landing/index.tsx @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react'; +import { Navigate, useNavigate } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import LandingImg from '@/assets/images/landing.png'; +import useAuthStore from '@/stores/authStore'; + +import { STYLE_CLASS } from './constants'; + +const Landing = () => { + const [step, setStep] = useState(0); + const isLoggedIn = useAuthStore((state) => state.isLoggedIn); + const navigate = useNavigate(); + + useEffect(() => { + if (isLoggedIn) navigate('/'); + }, [isLoggedIn, navigate]); + + if (step === 3) return ; + + return ( +
setStep((prev) => prev + 1)}> + 서비스 소개 이미지 +
+

+ {STYLE_CLASS[step].description} +

+
+
+
+ ); +}; + +export default Landing; diff --git a/src/pages/LetterBoard/components/LetterPreview.tsx b/src/pages/LetterBoard/components/LetterPreview.tsx new file mode 100644 index 0000000..2546caa --- /dev/null +++ b/src/pages/LetterBoard/components/LetterPreview.tsx @@ -0,0 +1,27 @@ +import { forwardRef } from 'react'; +import { useNavigate } from 'react-router'; + +import LetterWrapper from '@/components/LetterWrapper'; + +interface LetterPreviewProps { + id: number; + to: string; + from: string; + content: string; +} + +const LetterPreview = forwardRef((props, ref) => { + const { id, to, from, content }: LetterPreviewProps = props; + const navigate = useNavigate(); + return ( + navigate(`/board/letter/${id}`)}> +
+

From.{from}

+

{content}

+

To.{to}

+
+
+ ); +}); + +export default LetterPreview; diff --git a/src/pages/LetterBoard/index.tsx b/src/pages/LetterBoard/index.tsx index bd658c3..80d26a7 100644 --- a/src/pages/LetterBoard/index.tsx +++ b/src/pages/LetterBoard/index.tsx @@ -1,5 +1,91 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { useInView } from 'react-intersection-observer'; +import { useNavigate } from 'react-router'; + +import { getSharePostList } from '@/apis/share'; +import BackgroundBottom from '@/components/BackgroundBottom'; +import NoticeRollingPaper from '@/components/NoticeRollingPaper'; +import PageTitle from '@/components/PageTitle'; + +import LetterPreview from './components/LetterPreview'; + const LetterBoardPage = () => { - return
LetterBoardPage
; + const navigate = useNavigate(); + const { ref, inView } = useInView(); + + const fetchPostList = async (page: number = 1) => { + try { + const response = await getSharePostList(page); + if (!response) throw new Error('게시글 목록을 불러오는데 실패했습니다.'); + console.log('page', response); + return response as SharePostResponse; + } catch (e) { + console.error(e); + } + }; + + const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteQuery({ + queryKey: ['sharePostList'], + queryFn: ({ pageParam = 1 }) => fetchPostList(pageParam), + enabled: true, + initialPageParam: 1, + getNextPageParam: (res) => { + if (!res || res.currentPage >= res.totalPages) { + return undefined; + } + return res.currentPage + 1; + }, + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 10, + }); + + const postLists = data?.pages.flatMap((page) => page?.content) || []; + + useEffect(() => { + if (!hasNextPage) return; + if (inView && !isFetchingNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); + + if (isError) { + navigate('/notFound'); + } + + return ( + <> +
+ <> + + 게시판 +

+ 따숨이에게 힘이 되었던 다양한 편지들을 모아두었어요 +

+ + {isLoading ? ( +

loading

+ ) : ( +
+ {postLists.map((item, index) => { + return ( + + ); + })} +
+ )} +
+ + + ); }; export default LetterBoardPage; diff --git a/src/pages/LetterBoardDetail/components/Header.tsx b/src/pages/LetterBoardDetail/components/Header.tsx new file mode 100644 index 0000000..f1e15f9 --- /dev/null +++ b/src/pages/LetterBoardDetail/components/Header.tsx @@ -0,0 +1,70 @@ +import { Link, useNavigate } from 'react-router'; + +import { + ArrowLeftIcon, + DeleteIcon, + LikeFilledIcon, + LikeOutlinedIcon, + SirenOutlinedIcon, +} from '@/assets/icons'; + +interface HeaderProps { + likeCount: number; + isLike: boolean; + isWriter: boolean; + onToggleLike: () => void; + onOpenReportModal: () => void; + isShareLetterPreview?: boolean; +} + +const Header = ({ + likeCount, + isLike, + isWriter, + onToggleLike, + onOpenReportModal, + isShareLetterPreview = false, +}: HeaderProps) => { + const navigate = useNavigate(); + + return ( +
+
+ {isShareLetterPreview ? ( + + ) : ( + + + + )} + + {!isShareLetterPreview && ( +
+
+ +

{likeCount}

+
+ {isWriter ? ( + + ) : ( + + )} +
+ )} +
+
+
+ ); +}; + +export default Header; diff --git a/src/pages/LetterBoardDetail/components/Letter.tsx b/src/pages/LetterBoardDetail/components/Letter.tsx new file mode 100644 index 0000000..58c80a2 --- /dev/null +++ b/src/pages/LetterBoardDetail/components/Letter.tsx @@ -0,0 +1,24 @@ +import MemoWrapper from '@/components/MemoWrapper'; + +interface LetterProps { + letter: { + receiverZipCode: string; + content: string; + writerZipCode: string; + }; + isWriter?: boolean; +} + +const Letter = ({ letter, isWriter = false }: LetterProps) => { + return ( + +
+

To. {letter.receiverZipCode}

+

{letter.content}

+

From. {letter.writerZipCode}

+
+
+ ); +}; + +export default Letter; diff --git a/src/pages/LetterBoardDetail/index.tsx b/src/pages/LetterBoardDetail/index.tsx index 2efa63d..3f8292d 100644 --- a/src/pages/LetterBoardDetail/index.tsx +++ b/src/pages/LetterBoardDetail/index.tsx @@ -1,5 +1,153 @@ -const LetterBoardDetailPage = () => { - return
LetterBoardDetailPage
; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import { + getSharePostDetail, + postShareProposalApproval, + SharePost, + postSharePostLike, + getSharePostLikeCount, +} from '@/apis/share'; +import BlurImg from '@/assets/images/landing-blur.png'; +import ReportModal from '@/components/ReportModal'; + +import Header from './components/Header'; +import Letter from './components/Letter'; + +interface ShareLetterPreviewProps { + confirmDisabled?: boolean; + children?: React.ReactNode; +} + +const LetterBoardDetailPage = ({ confirmDisabled }: ShareLetterPreviewProps) => { + const [likeCount, setLikeCount] = useState(122); + const [isLike, setIsLike] = useState(false); + const isWriter = false; + const [activeReportModal, setActiveReportModal] = useState(false); + + const handleToggleLike = () => { + setLikeCount((prev) => prev + (isLike ? -1 : 1)); + setIsLike((prev) => !prev); + }; + + const location = useLocation(); + const navigate = useNavigate(); + + const isShareLetterPreview = location.state?.isShareLetterPreview || false; + const [postDetail, setPostDetail] = useState(); + + useEffect(() => { + const { sharePostId } = location.state.postDetail; + const fetchPostDetail = async (postId: number) => { + try { + console.log('sharePostId:', postId); + + const data = await getSharePostDetail(postId); + + setPostDetail(data); + } catch (error) { + console.error('❌ 공유 게시글 상세 조회에 실패했습니다.', error); + } + }; + + const fetchLikeCounts = async (postId: number) => { + try { + const response = await getSharePostLikeCount(postId); + if (!response) throw new Error('error while fetching like count'); + console.log(response); + setLikeCount(response.data.likeCount); + } catch (error) { + console.error('❌ 편지 좋아요 갯수를 가져오는 중 에러가 발생했습니다', error); + throw new Error('편지 좋아요 갯수 가져오기 실패'); + } + }; + + if (location.state?.postDetail) { + fetchPostDetail(sharePostId); + fetchLikeCounts(sharePostId); + } else { + console.warn('postDetail not found in location.state'); + } + }, [location.state]); + + const handleProposalApproval = async ( + action: 'approve' | 'reject', + shareProposalId: number = location.state?.postDetail?.sharePostId, + ) => { + try { + const result = await postShareProposalApproval(shareProposalId, action); + console.log(`✅ 편지 공유 ${action === 'approve' ? '수락' : '거절'}됨:`, result); + + navigate('/'); + } catch (error) { + console.error(error); + } + }; + + return ( + <> + {activeReportModal && setActiveReportModal(false)} />} +
+
setActiveReportModal(true)} + isShareLetterPreview={isShareLetterPreview} + /> +
+

FROM. {postDetail?.zipCode}

+

+ {postDetail?.sharePostContent} +

+
+ {postDetail?.letters.map((letter, index) => ( + + ))} +
+ + {isShareLetterPreview && ( + <> + landing blur +
+ + + +
+ + )} +
+
+ + ); }; export default LetterBoardDetailPage; diff --git a/src/pages/LetterBox/components/LetterBoxItem.tsx b/src/pages/LetterBox/components/LetterBoxItem.tsx new file mode 100644 index 0000000..f493ffb --- /dev/null +++ b/src/pages/LetterBox/components/LetterBoxItem.tsx @@ -0,0 +1,68 @@ +import { useNavigate } from 'react-router'; +import { twMerge } from 'tailwind-merge'; +interface LetterBoxItemProps { + boxId: number; + zipCode: string; + letterCount: number; + isChecked?: boolean; + isClosed?: boolean; +} + +const LetterBoxItem = ({ + boxId, + zipCode, + letterCount, + isChecked = false, + isClosed = false, +}: LetterBoxItemProps) => { + const navigate = useNavigate(); + const handleClickItem = (id: number) => { + navigate(`${id}`, { + state: { + id, + zipCode, + isClosed, + }, + }); + }; + return ( +
handleClickItem(boxId)} + > +
+

+ {zipCode} +

+ {isClosed ? ( +
+ ) : ( +
+

{letterCount}통

+
+ )} +
+
+
+
+
+
+
+ ); +}; + +export default LetterBoxItem; diff --git a/src/pages/LetterBox/index.tsx b/src/pages/LetterBox/index.tsx index 2244eb0..c8010c9 100644 --- a/src/pages/LetterBox/index.tsx +++ b/src/pages/LetterBox/index.tsx @@ -1,5 +1,100 @@ +import { useQuery } from '@tanstack/react-query'; +import { useNavigate } from 'react-router'; + +import { getMailbox } from '@/apis/mailBox'; +import DoorImg from '@/assets/images/door.png'; +import ClosedWindowImg from '@/assets/images/window-disabled.png'; +import PageTitle from '@/components/PageTitle'; +import { chunkBox } from '@/utils/chunkBox'; + +import LetterBoxItem from './components/LetterBoxItem'; + +interface LetterBoxData { + letterMatchingId: number; + oppositeZipCode: string; + active: boolean; + oppositeRead: boolean; + letterCount: number; +} + +const fetchMailLists = async () => { + const response = await getMailbox(); + if (!response) throw new Error(); + const data: LetterBoxData[] = response.data; + console.log(data); + // 정렬? + return data; +}; + const LetterBoxPage = () => { - return
LetterBoxPage
; + const { + data: letterBox = [], + isLoading, + isError, + } = useQuery({ + queryKey: ['mailbox'], + queryFn: fetchMailLists, + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 10, + }); + + const navigate = useNavigate(); + + if (isError) { + navigate('/NotFound'); + } + + return ( +
+ 내 편지함 +
+

+ 나와 연락한 사람들 {letterBox?.length} +

+
+
+ {isLoading ? ( +

로딩중..

+ ) : letterBox.length > 0 ? ( + chunkBox( + letterBox.map((data: LetterBoxData, index) => ( + + )), + ).map((row, index) => + row.length === 3 ? ( +
+ {row} +
+ ) : ( +
+ {row} + 닫힌 문 이미지 + {row.length === 1 && ( + 닫힌 문 이미지 + )} +
+ ), + ) + ) : ( +

아직 주고 받은 편지가 없어요

+ )} +
+ 닫힌 문 이미지 + 출입문 이미지 + 닫힌 문 이미지 +
+
+
+
+
+ ); }; export default LetterBoxPage; diff --git a/src/pages/LetterBoxDetail/components/InformationTooltip.tsx b/src/pages/LetterBoxDetail/components/InformationTooltip.tsx new file mode 100644 index 0000000..820652f --- /dev/null +++ b/src/pages/LetterBoxDetail/components/InformationTooltip.tsx @@ -0,0 +1,42 @@ +import { useEffect, useRef, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { InformationIcon } from '@/assets/icons'; +const InformationTooltip = () => { + const [isShow, setIsShow] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + setIsShow(false); + } + }; + + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }, []); + + return ( +
+ +
+

나만 보기 아까운 편지, 함께 나눠요!

+
+

+ 주고받은 편지 중 감동적이거나 도움이 된 편지를 공유 게시판에 등록해 더 많은 사람들과 + 따뜻한 마음을 나눌 수 있어요. +

+
+
+ ); +}; + +export default InformationTooltip; diff --git a/src/pages/LetterBoxDetail/components/LetterPreview.tsx b/src/pages/LetterBoxDetail/components/LetterPreview.tsx new file mode 100644 index 0000000..ac4f843 --- /dev/null +++ b/src/pages/LetterBoxDetail/components/LetterPreview.tsx @@ -0,0 +1,70 @@ +import { forwardRef } from 'react'; +import { useNavigate } from 'react-router'; + +import LetterWrapper from '@/components/LetterWrapper'; +import formatDate from '@/utils/formatDate'; + +interface LetterPreviewProps { + id: number; + date: string; + title: string; + isSend: boolean; + checked: boolean; + isShareMode?: boolean; + onToggle: () => void; + isClosed: boolean; + zipCode: string; +} +const LetterPreview = forwardRef((props, ref) => { + const { + id, + date, + title, + isSend, + checked, + isShareMode = false, + onToggle, + isClosed, + zipCode, + } = props; + const navigate = useNavigate(); + + const handleItemClick = (id: number) => { + navigate(`/letter/${id}`, { + state: { + id, + isClosed, + zipCode, + }, + }); + }; + + if (isShareMode) + return ( + +
+

{formatDate(date)}

+