diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 725e9e4..60e839f 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -20,6 +20,10 @@ jobs: echo "API_HOST=${{ vars.API_HOST }}" >> .env echo "NODE_ENV=${{ vars.NODE_ENV }}" >> .env echo "NEXT_PUBLIC_GA_ID=${{ secrets.PUBLIC_GA_ID }}" >> .env + echo "AUTH_KAKAO_ID=${{secrets.AUTH_KAKAO_ID}}" >> .env + echo "AUTH_KAKAO_SECRET=${{secrets.AUTH_KAKAO_SECRET}}" >> .env + echo "AUTH_SECRET=${{secrets.AUTH_SECRET}}" >> .env + echo "NEXTAUTH_URL=${{vars.NEXTAUTH_URL}}" >> .env - name: Build docker image run: docker build -t ${{ vars.DOCKER_IMAGE }} . - name: Login to DockerHub diff --git a/package-lock.json b/package-lock.json index b23b60f..7403e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,14 @@ "name": "polabo-fe", "version": "0.1.0", "dependencies": { + "@jest/globals": "^29.7.0", "@storybook/preview-api": "^8.1.11", "@svgr/webpack": "^8.1.0", "browser-image-compression": "^2.0.2", "eslint-import-resolver-typescript": "^3.6.1", "next": "14.2.4", + "next-auth": "^5.0.0-beta.20", + "path-to-regexp": "^7.1.0", "prettier-plugin-tailwindcss": "^0.6.5", "react": "^18", "react-dom": "^18", @@ -93,6 +96,36 @@ "node": ">=6.0.0" } }, + "node_modules/@auth/core": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.2.tgz", + "integrity": "sha512-KywHKRgLiF3l7PLyL73fjLSIBe1YNcA6sMeew4yMP6cfCWGXZrkkXd32AjRi1hlJ9nvovUBGZHvbn+LijO6ZeQ==", + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -570,7 +603,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -3067,7 +3099,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -3083,7 +3114,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3092,7 +3122,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "engines": { "node": ">=6" } @@ -3101,7 +3130,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3114,7 +3142,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3127,7 +3154,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -3139,7 +3165,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -3154,7 +3179,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -3166,7 +3190,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, "engines": { "node": ">=8" } @@ -3175,7 +3198,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, "engines": { "node": ">=8" } @@ -3396,7 +3418,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -3411,7 +3432,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -3424,7 +3444,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -3436,7 +3455,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -3453,7 +3471,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3618,7 +3635,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -3674,7 +3690,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -3700,7 +3715,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3715,7 +3729,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3731,7 +3744,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3740,7 +3752,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3752,7 +3763,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -3769,7 +3779,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3784,7 +3793,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3800,7 +3808,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3809,7 +3816,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4068,6 +4074,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4167,8 +4181,7 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", @@ -4185,7 +4198,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, "dependencies": { "type-detect": "4.0.8" } @@ -4194,7 +4206,6 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -6031,6 +6042,11 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -6108,7 +6124,6 @@ "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -6136,14 +6151,12 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -6152,7 +6165,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -6320,8 +6332,7 @@ "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/tough-cookie": { "version": "4.0.5", @@ -6345,7 +6356,6 @@ "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -6353,8 +6363,7 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.16.1", @@ -7111,7 +7120,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -7625,7 +7633,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -7641,7 +7648,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -7708,7 +7714,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -8086,7 +8091,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, "dependencies": { "node-int64": "^0.4.0" } @@ -8367,7 +8371,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -9472,7 +9475,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -11222,7 +11224,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -11288,6 +11289,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -11359,7 +11365,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, "dependencies": { "bser": "2.1.1" } @@ -11853,7 +11858,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -11956,7 +11960,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, "engines": { "node": ">=8.0.0" } @@ -13128,7 +13131,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, "engines": { "node": ">=8" } @@ -13812,7 +13814,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -13827,7 +13828,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -13842,7 +13842,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -13858,7 +13857,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -13867,7 +13865,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -13881,7 +13878,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, @@ -13892,14 +13888,12 @@ "node_modules/jest-diff/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/jest-diff/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14067,7 +14061,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -14076,7 +14069,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -14146,7 +14138,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -14161,7 +14152,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14176,7 +14166,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14192,7 +14181,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -14201,7 +14189,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -14215,7 +14202,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, @@ -14226,14 +14212,12 @@ "node_modules/jest-matcher-utils/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/jest-matcher-utils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14245,7 +14229,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -14265,7 +14248,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14280,7 +14262,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14296,7 +14277,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -14305,7 +14285,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -14319,7 +14298,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, @@ -14330,14 +14308,12 @@ "node_modules/jest-message-util/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/jest-message-util/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14349,7 +14325,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -14380,7 +14355,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -14686,7 +14660,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -14717,7 +14690,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14732,7 +14704,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14748,7 +14719,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -14757,7 +14727,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -14771,7 +14740,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, @@ -14782,14 +14750,12 @@ "node_modules/jest-snapshot/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -14801,7 +14767,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14813,7 +14778,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -14830,7 +14794,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14845,7 +14808,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14861,7 +14823,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -14870,7 +14831,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15054,7 +15014,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -15069,7 +15028,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -15078,7 +15036,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15098,6 +15055,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16029,7 +15994,6 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, "dependencies": { "tmpl": "1.0.5" } @@ -16428,6 +16392,32 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.20", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.20.tgz", + "integrity": "sha512-+48SjV9k9AtUU3JbEIa4PXNjKIewfFjVGL7Xs2RKkuQ5QqegDNIQiIG8sLk6/qo7RTScQYIGKgeQ5IuQRtrTQg==", + "dependencies": { + "@auth/core": "0.34.2" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0-0", + "nodemailer": "^6.6.5", + "react": "^18.2.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -16509,8 +16499,7 @@ "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-polyfill-webpack-plugin": { "version": "2.0.1", @@ -16560,7 +16549,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -16725,6 +16713,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oauth4webapi": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.11.1.tgz", + "integrity": "sha512-aNzOnL98bL6izG97zgnZs1PFEyO4WDVRhz2Pd066NPak44w5ESLRCYmJIyey8avSBPOMtBjhF3ZDDm7bIb7UOg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -17215,9 +17211,12 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.1.0.tgz", + "integrity": "sha512-ZToe+MbUF4lBqk6dV8GKot4DKfzrxXsplOddH8zN3YK+qw9/McvP7+4ICjZvOne0jQhN4eJwHsX6tT0Ns19fvw==", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -17673,6 +17672,31 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -19133,7 +19157,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -19266,14 +19289,12 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -19285,7 +19306,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, "engines": { "node": ">=8" } @@ -20373,7 +20393,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -20387,7 +20406,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -20398,7 +20416,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20418,7 +20435,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -20481,8 +20497,7 @@ "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -20871,7 +20886,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } @@ -21333,7 +21347,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, "dependencies": { "makeerror": "1.0.12" } @@ -21805,7 +21818,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -21817,8 +21829,7 @@ "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/ws": { "version": "8.18.0", diff --git a/package.json b/package.json index f9b3a4e..4e1011d 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ ] }, "dependencies": { + "@jest/globals": "^29.7.0", "@storybook/preview-api": "^8.1.11", "@svgr/webpack": "^8.1.0", "browser-image-compression": "^2.0.2", "eslint-import-resolver-typescript": "^3.6.1", "next": "14.2.4", + "next-auth": "^5.0.0-beta.20", "prettier-plugin-tailwindcss": "^0.6.5", "react": "^18", "react-dom": "^18", diff --git a/public/icons/arrow_down.svg b/public/icons/arrow_down.svg new file mode 100644 index 0000000..f1d9ce2 --- /dev/null +++ b/public/icons/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/arrow_right.svg b/public/icons/arrow_right.svg new file mode 100644 index 0000000..c5cb668 --- /dev/null +++ b/public/icons/arrow_right.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/icons/arrow_up.svg b/public/icons/arrow_up.svg new file mode 100644 index 0000000..6e4e3dc --- /dev/null +++ b/public/icons/arrow_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/check.svg b/public/icons/check.svg new file mode 100644 index 0000000..fc2dcf7 --- /dev/null +++ b/public/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/ellipsis.svg b/public/icons/ellipsis.svg new file mode 100644 index 0000000..21eafd8 --- /dev/null +++ b/public/icons/ellipsis.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/empty_board.svg b/public/icons/empty_board.svg new file mode 100644 index 0000000..2d0192d --- /dev/null +++ b/public/icons/empty_board.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/gender_f.svg b/public/icons/gender_f.svg new file mode 100644 index 0000000..acf6736 --- /dev/null +++ b/public/icons/gender_f.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/gender_m.svg b/public/icons/gender_m.svg new file mode 100644 index 0000000..0155767 --- /dev/null +++ b/public/icons/gender_m.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/hamburger.svg b/public/icons/hamburger.svg new file mode 100644 index 0000000..4e3fa93 --- /dev/null +++ b/public/icons/hamburger.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/public/icons/kakao.svg b/public/icons/kakao.svg new file mode 100644 index 0000000..d753209 --- /dev/null +++ b/public/icons/kakao.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/kakaoWbg.svg b/public/icons/kakaoWbg.svg new file mode 100644 index 0000000..88167c4 --- /dev/null +++ b/public/icons/kakaoWbg.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/linkShare.svg b/public/icons/linkShare.svg index 7933dd0..631e959 100644 --- a/public/icons/linkShare.svg +++ b/public/icons/linkShare.svg @@ -1,14 +1,15 @@ - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/public/icons/login_polaroid.png b/public/icons/login_polaroid.png new file mode 100644 index 0000000..a9fd231 Binary files /dev/null and b/public/icons/login_polaroid.png differ diff --git a/public/icons/onboarding-polaroid.svg b/public/icons/onboarding-polaroid.svg new file mode 100644 index 0000000..87096c6 --- /dev/null +++ b/public/icons/onboarding-polaroid.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/onboarding-twopolaroids.svg b/public/icons/onboarding-twopolaroids.svg new file mode 100644 index 0000000..3aa4a9d --- /dev/null +++ b/public/icons/onboarding-twopolaroids.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/pagination_left.svg b/public/icons/pagination_left.svg new file mode 100644 index 0000000..c0b8c2a --- /dev/null +++ b/public/icons/pagination_left.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/pagination_left_disabled.svg b/public/icons/pagination_left_disabled.svg new file mode 100644 index 0000000..7dbffe2 --- /dev/null +++ b/public/icons/pagination_left_disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/pagination_right.svg b/public/icons/pagination_right.svg new file mode 100644 index 0000000..22c3554 --- /dev/null +++ b/public/icons/pagination_right.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/pagination_right_disabled.svg b/public/icons/pagination_right_disabled.svg new file mode 100644 index 0000000..91f5ff4 --- /dev/null +++ b/public/icons/pagination_right_disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/person.svg b/public/icons/person.svg new file mode 100644 index 0000000..a45e686 --- /dev/null +++ b/public/icons/person.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/sketch_check.svg b/public/icons/sketch_check.svg new file mode 100644 index 0000000..7d312c6 --- /dev/null +++ b/public/icons/sketch_check.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/sticker_polaroid.svg b/public/icons/sticker_polaroid.svg new file mode 100644 index 0000000..dbfacb2 --- /dev/null +++ b/public/icons/sticker_polaroid.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/threePolaroids.png b/public/icons/threePolaroids.png new file mode 100644 index 0000000..c1f25a9 Binary files /dev/null and b/public/icons/threePolaroids.png differ diff --git a/src/__tests__/validateBirthDt.test.ts b/src/__tests__/validateBirthDt.test.ts new file mode 100644 index 0000000..8c9c081 --- /dev/null +++ b/src/__tests__/validateBirthDt.test.ts @@ -0,0 +1,45 @@ +import { validateBirthDt } from '@/lib/utils/validation' +import { describe, expect, it } from '@jest/globals' + +describe('validateBirthDt', () => { + it('before 1900 -> false', () => { + expect(validateBirthDt('1899-12-31')).toBe(false) + }) + + it('year after the current year -> false', () => { + const nextYear = (new Date().getFullYear() + 1).toString() + expect(validateBirthDt(`${nextYear}-01-01`)).toBe(false) + }) + + it('invalid month -> false', () => { + expect(validateBirthDt('2020-13-01')).toBe(false) + expect(validateBirthDt('2020-00-01')).toBe(false) + }) + + it('invalid day -> false', () => { + expect(validateBirthDt('2024-01-32')).toBe(false) + expect(validateBirthDt('2024-02-30')).toBe(false) + }) + + it('valid date -> true', () => { + expect(validateBirthDt('2024-01-31')).toBe(true) + expect(validateBirthDt('2020-02-29')).toBe(true) // Leap year + }) + + it('non-leap year February 29 -> false', () => { + expect(validateBirthDt('2019-02-29')).toBe(false) // non-leap year + }) + + it('at the edge of the year range -> true', () => { + expect(validateBirthDt('1900-01-01')).toBe(true) + }) + + it('future date -> false', () => { + const tomorrow = new Date() + tomorrow.setDate(tomorrow.getDate() + 1) + const futureDate = tomorrow + .toISOString() + .split('T')[0] as `${string}-${string}-${string}` + expect(validateBirthDt(futureDate)).toBe(false) + }) +}) diff --git a/src/app/(board)/board/[boardId]/actions/uploadAction.ts b/src/app/(board)/board/[boardId]/_actions/uploadAction.ts similarity index 100% rename from src/app/(board)/board/[boardId]/actions/uploadAction.ts rename to src/app/(board)/board/[boardId]/_actions/uploadAction.ts diff --git a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/ArrowBack.tsx b/src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/ArrowBack.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/CreatePolaroidModal/ArrowBack.tsx rename to src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/ArrowBack.tsx diff --git a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/ModalContext.tsx b/src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/ModalContext.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/CreatePolaroidModal/ModalContext.tsx rename to src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/ModalContext.tsx diff --git a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/UploadBtn.tsx b/src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/UploadBtn.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/CreatePolaroidModal/UploadBtn.tsx rename to src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/UploadBtn.tsx diff --git a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx b/src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/index.tsx similarity index 95% rename from src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx rename to src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/index.tsx index bd96150..cd59ebe 100644 --- a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx +++ b/src/app/(board)/board/[boardId]/_components/CreatePolaroidModal/index.tsx @@ -2,7 +2,7 @@ import PolaroidMaker from '@/components/Polaroid/PolaroidMaker' import { useRef, useState } from 'react' -import { uploadAction } from '../../actions/uploadAction' +import { uploadAction } from '../../_actions/uploadAction' import ArrowBack from './ArrowBack' import { useModal } from './ModalContext' import UploadBtn from './UploadBtn' diff --git a/src/app/(board)/board/[boardId]/components/Empty.tsx b/src/app/(board)/board/[boardId]/_components/Empty.tsx similarity index 64% rename from src/app/(board)/board/[boardId]/components/Empty.tsx rename to src/app/(board)/board/[boardId]/_components/Empty.tsx index 504c9cd..f936c16 100644 --- a/src/app/(board)/board/[boardId]/components/Empty.tsx +++ b/src/app/(board)/board/[boardId]/_components/Empty.tsx @@ -1,8 +1,8 @@ import Icon from 'public/icons/pinned.svg' const Empty = () => ( -
- +
+ 보드를 꾸며주세요!
) diff --git a/src/app/(board)/board/[boardId]/components/OpenModalBtn.tsx b/src/app/(board)/board/[boardId]/_components/OpenModalBtn.tsx similarity index 66% rename from src/app/(board)/board/[boardId]/components/OpenModalBtn.tsx rename to src/app/(board)/board/[boardId]/_components/OpenModalBtn.tsx index 2c66ad5..6f9bef7 100644 --- a/src/app/(board)/board/[boardId]/components/OpenModalBtn.tsx +++ b/src/app/(board)/board/[boardId]/_components/OpenModalBtn.tsx @@ -1,10 +1,13 @@ 'use client' import Modal from '@/components/Modal' +import { useSession } from 'next-auth/react' import AddPolaroid from 'public/icons/add_polaroid.svg' import { ReactNode } from 'react' import { useModal } from './CreatePolaroidModal/ModalContext' import CannotUploadModal from './modals/CannotUploadModal' +import Tutorial from './Tutorial' +import { Step2Tooltip } from './Tutorial/Tooltips' interface OpenModalBtnProps { polaroidNum: number @@ -12,6 +15,7 @@ interface OpenModalBtnProps { } const OpenModalBtn = ({ polaroidNum, children }: OpenModalBtnProps) => { + const { data: session } = useSession() const { isOpen, openModal, closeModal } = useModal() const renderModalContent = () => { @@ -28,7 +32,15 @@ const OpenModalBtn = ({ polaroidNum, children }: OpenModalBtnProps) => { return (
{isOpen && renderModalContent()} - +
+ } + hasNext={false} + > + + +
) } diff --git a/src/app/(board)/board/[boardId]/components/ShareBtn.tsx b/src/app/(board)/board/[boardId]/_components/ShareBtn.tsx similarity index 82% rename from src/app/(board)/board/[boardId]/components/ShareBtn.tsx rename to src/app/(board)/board/[boardId]/_components/ShareBtn.tsx index 754e664..8c78234 100644 --- a/src/app/(board)/board/[boardId]/components/ShareBtn.tsx +++ b/src/app/(board)/board/[boardId]/_components/ShareBtn.tsx @@ -5,6 +5,7 @@ import CopyIcon from 'public/icons/copy.svg' import Share from 'public/icons/ios_share.svg' import TwoPolaroidsIcon from 'public/icons/twopolaroids.svg' import { useEffect, useState } from 'react' +import { useTutorial } from './Tutorial/TutorialContext' const ShareBtn = () => { const [showShareModal, setShowShareModal] = useState(false) @@ -18,11 +19,19 @@ const ShareBtn = () => { return navigator.clipboard.writeText(currentURL) } + const { run, nextStep } = useTutorial() + const handleClose = () => { + setShowShareModal(false) + if (run) { + nextStep() + } + } + return ( <> setShowShareModal(true)} className="w-6" /> - setShowShareModal(false)}> + }> 보드를 친구에게 공유해보세요! diff --git a/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltip.tsx b/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltip.tsx new file mode 100644 index 0000000..b1c1252 --- /dev/null +++ b/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltip.tsx @@ -0,0 +1,106 @@ +'use client' + +import { ReactNode } from 'react' +import { twMerge } from 'tailwind-merge' +import { useTutorial } from './TutorialContext' + +const Tooltip = ({ + children, + className = '', +}: { + children: ReactNode + className?: React.ComponentProps<'div'>['className'] +}) => { + return
{children}
+} + +const Triangle = ({ + className = '', +}: { + className?: React.ComponentProps<'div'>['className'] +}) => ( +
+
+
+) + +const Box = ({ + children, + className = '', + trianglePos = '', +}: { + children: ReactNode + className?: React.ComponentProps<'div'>['className'] + trianglePos?: React.ComponentProps<'div'>['className'] +}) => { + return ( +
+ + {children} +
+ ) +} + +const Content = ({ + children, + className = '', +}: { + children: ReactNode + className?: React.ComponentProps<'div'>['className'] +}) => { + return ( +
+ {children} +
+ ) +} + +const Icon = ({ + icon, + sendToBack = false, + className = '', +}: { + icon: ReactNode + sendToBack?: boolean + className?: React.ComponentProps<'div'>['className'] +}) => { + return ( +
+ {icon} +
+ ) +} + +const NextBtn = ({ hasNext }: { hasNext: boolean }) => { + const { nextStep, endTutorial } = useTutorial() + return ( +
+ {hasNext ? '다음' : '확인'} +
+ ) +} + +Tooltip.Box = Box +Tooltip.Icon = Icon +Tooltip.Content = Content +Tooltip.NextBtn = NextBtn + +export default Tooltip diff --git a/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltips.tsx b/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltips.tsx new file mode 100644 index 0000000..0c7cdb8 --- /dev/null +++ b/src/app/(board)/board/[boardId]/_components/Tutorial/Tooltips.tsx @@ -0,0 +1,43 @@ +'use client' + +import Step1Icon from 'public/icons/onboarding-twopolaroids.svg' +import Step2Icon from 'public/icons/onboarding-polaroid.svg' +import Tooltip from './Tooltip' + +export const Step1Tooltip = () => { + return ( + + } sendToBack className="-left-[230px]" /> + + + 친구들에게 보드를 공유해보세요! + + + + + ) +} + +export const Step2Tooltip = () => { + return ( + + } + className="-left-[270px] -translate-y-1/4" + /> + + + 보드 주제와 맞는 사진 + {`을 올려 \n 보드를 꾸며주세요!`} + + + + + ) +} diff --git a/src/app/(board)/board/[boardId]/_components/Tutorial/TutorialContext.tsx b/src/app/(board)/board/[boardId]/_components/Tutorial/TutorialContext.tsx new file mode 100644 index 0000000..49d003a --- /dev/null +++ b/src/app/(board)/board/[boardId]/_components/Tutorial/TutorialContext.tsx @@ -0,0 +1,66 @@ +'use client' + +import { + ReactNode, + createContext, + useContext, + useEffect, + useMemo, + useState, +} from 'react' + +interface TutorialContextProps { + run: boolean + currentStep: number + nextStep: () => void + startTutorial: () => void + endTutorial: () => void +} + +const TutorialContext = createContext( + undefined, +) + +export const TutorialProvider = ({ children }: { children: ReactNode }) => { + const [run, setRun] = useState(false) + const [currentStep, setCurrentStep] = useState(1) + + const nextStep = () => setCurrentStep((prev) => prev + 1) + + const startTutorial = () => setRun(true) + const endTutorial = () => { + setRun(false) + localStorage.setItem('needTutorial', 'false') + } + + useEffect(() => { + if (localStorage.getItem('needTutorial') === 'true') { + startTutorial() + } + }, []) + + const value = useMemo( + () => ({ + run, + currentStep, + nextStep, + startTutorial, + endTutorial, + }), + [currentStep, run], + ) + + return ( + + {children} + + ) +} + +export const useTutorial = () => { + const context = useContext(TutorialContext) + if (context === undefined) { + throw new Error('Error at useTutorial') + } + return context +} diff --git a/src/app/(board)/board/[boardId]/_components/Tutorial/index.tsx b/src/app/(board)/board/[boardId]/_components/Tutorial/index.tsx new file mode 100644 index 0000000..9850716 --- /dev/null +++ b/src/app/(board)/board/[boardId]/_components/Tutorial/index.tsx @@ -0,0 +1,116 @@ +'use client' + +import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react' +import { useTutorial } from './TutorialContext' + +interface TutorialProps { + children: ReactNode + step: number + tooltip: ReactNode + hasNext?: boolean +} + +const Tutorial = ({ + step, + tooltip, + hasNext = true, + children, +}: TutorialProps) => { + const targetRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const [overlayStyle, setOverlayStyle] = useState({}) + const { run, currentStep, endTutorial } = useTutorial() + + // overlay 클릭 방지 + const [topBox, setTopBox] = useState({}) + const [bottomBox, setBottomBox] = useState({}) + const [leftBox, setLeftBox] = useState({}) + const [rightBox, setRightBox] = useState({}) + + useEffect(() => { + if (run && step === currentStep) { + setIsOpen(true) + } else { + setIsOpen(false) + } + }, [run, step, currentStep]) + + useEffect(() => { + if (isOpen && targetRef.current) { + const targetRect = targetRef.current.getBoundingClientRect() + + setOverlayStyle({ + position: 'fixed', + top: targetRect.top - 2, + left: targetRect.left - 3, + width: targetRect.width + 6, + height: targetRect.height + 7, + borderRadius: '50%', + zIndex: 10, + boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.6)', + pointerEvents: 'none', + // border: '2px dotted red', + }) + + setTopBox({ + bottom: `calc(100% - ${targetRect.top}px + 2px)`, + }) + setBottomBox({ + top: targetRect.bottom + 5, + }) + setLeftBox({ + right: `calc(100% - ${targetRect.left}px + 3px)`, + }) + setRightBox({ + left: targetRect.right + 3, + }) + } + }, [isOpen]) + + const handleTargetClick = () => { + setIsOpen(false) + if (!hasNext) { + endTutorial() + } + } + + return ( + <> +
+ {children} +
+ {isOpen && ( + <> +
+
+
+
+
+
+
+ {tooltip} + + )} + + ) +} + +export default Tutorial diff --git a/src/app/(board)/board/[boardId]/components/modals/AskBfCloseModal.tsx b/src/app/(board)/board/[boardId]/_components/modals/AskBfCloseModal.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/modals/AskBfCloseModal.tsx rename to src/app/(board)/board/[boardId]/_components/modals/AskBfCloseModal.tsx diff --git a/src/app/(board)/board/[boardId]/components/modals/CannotUploadModal.tsx b/src/app/(board)/board/[boardId]/_components/modals/CannotUploadModal.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/modals/CannotUploadModal.tsx rename to src/app/(board)/board/[boardId]/_components/modals/CannotUploadModal.tsx diff --git a/src/app/(board)/board/[boardId]/components/modals/FinalModal.tsx b/src/app/(board)/board/[boardId]/_components/modals/FinalModal.tsx similarity index 100% rename from src/app/(board)/board/[boardId]/components/modals/FinalModal.tsx rename to src/app/(board)/board/[boardId]/_components/modals/FinalModal.tsx diff --git a/src/app/(board)/board/[boardId]/components/Header.tsx b/src/app/(board)/board/[boardId]/components/Header.tsx deleted file mode 100644 index 5eaebeb..0000000 --- a/src/app/(board)/board/[boardId]/components/Header.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import PinIcon from 'public/icons/pinFilled.svg' -import ShareBtn from './ShareBtn' - -interface BoardHeaderProps { - name: string -} - -const BoardHeader = ({ name }: BoardHeaderProps) => { - return ( -
-
-
- -

{name}

-
- -
- ) -} - -export default BoardHeader diff --git a/src/app/(board)/board/[boardId]/loading.tsx b/src/app/(board)/board/[boardId]/loading.tsx deleted file mode 100644 index 4595954..0000000 --- a/src/app/(board)/board/[boardId]/loading.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import Loader from '@/components/Loading' - -const Loading = () => { - return ( -
- -
- ) -} - -export default Loading diff --git a/src/app/(board)/board/[boardId]/page.tsx b/src/app/(board)/board/[boardId]/page.tsx index 30b6b2b..4360153 100644 --- a/src/app/(board)/board/[boardId]/page.tsx +++ b/src/app/(board)/board/[boardId]/page.tsx @@ -1,11 +1,18 @@ +import { auth } from '@/auth' +import Hamburger from '@/components/HamburgerMenu' +import Header from '@/components/Header' import PolaroidCard from '@/components/Polaroid/PolaroidCard' import { getBoard } from '@/lib' import { Metadata } from 'next' -import CreatePolaroid from './components/CreatePolaroidModal' -import { ModalProvider } from './components/CreatePolaroidModal/ModalContext' -import Empty from './components/Empty' -import BoardHeader from './components/Header' -import OpenModalBtn from './components/OpenModalBtn' +import PinIcon from 'public/icons/pinFilled.svg' +import CreatePolaroid from './_components/CreatePolaroidModal' +import { ModalProvider } from './_components/CreatePolaroidModal/ModalContext' +import Empty from './_components/Empty' +import OpenModalBtn from './_components/OpenModalBtn' +import ShareBtn from './_components/ShareBtn' +import Tutorial from './_components/Tutorial' +import { Step1Tooltip } from './_components/Tutorial/Tooltips' +import { TutorialProvider } from './_components/Tutorial/TutorialContext' export async function generateMetadata({ params, @@ -47,31 +54,52 @@ const BoardPage = async ({ params }: BoardPageProps) => { const { boardId } = params const board = await getBoard(boardId) + const session = await auth() + return ( -
- - {board.items.length === 0 ? ( - - ) : ( -
-
- {board.items.map((item) => ( - - ))} + +
+
+ +

{board.title}

+
+ } + leftButton={} + rightButton={ + session ? ( + } hasNext> + + + ) : ( + + ) + } + /> + {board.items.length === 0 ? ( + + ) : ( +
+
+ {board.items.map((item) => ( + + ))} +
-
- )} + )} - - - - - -
+ + + + + +
+ ) } diff --git a/src/app/(board)/board/create/components/BoardAvailabilityCheckModal.tsx b/src/app/(board)/board/create/_components/BoardAvailabilityCheckModal.tsx similarity index 100% rename from src/app/(board)/board/create/components/BoardAvailabilityCheckModal.tsx rename to src/app/(board)/board/create/_components/BoardAvailabilityCheckModal.tsx diff --git a/src/app/(board)/board/create/components/BoardNameForm.tsx b/src/app/(board)/board/create/_components/BoardNameForm.tsx similarity index 76% rename from src/app/(board)/board/create/components/BoardNameForm.tsx rename to src/app/(board)/board/create/_components/BoardNameForm.tsx index 7eaa32e..af60894 100644 --- a/src/app/(board)/board/create/components/BoardNameForm.tsx +++ b/src/app/(board)/board/create/_components/BoardNameForm.tsx @@ -1,18 +1,20 @@ 'use client' +import Button from '@/components/Button' import TextInput from '@/components/TextInput' import { ReactNode, useState } from 'react' -import Button from '@/components/Button' -import { postBoard } from '@/lib' -import { useRouter } from 'next/navigation' const MAX_BOARD_NAME_LENGTH = 15 -const BoardNameForm = ({ children }: { children: ReactNode }) => { +interface BoardNameFormProps { + children: ReactNode + createBoard: (title: string) => void +} + +const BoardNameForm = ({ children, createBoard }: BoardNameFormProps) => { const [title, setTitle] = useState('') const [hasError, setHasError] = useState(false) const isEmpty = title.length === 0 - const router = useRouter() const onInput = (value: string) => { setTitle(value) @@ -23,15 +25,6 @@ const BoardNameForm = ({ children }: { children: ReactNode }) => { } } - const createBoard = async () => { - const boardId = await postBoard({ - title, - userId: null, - }) - - router.push(`/board/${boardId}`) - } - return ( <>
@@ -52,7 +45,7 @@ const BoardNameForm = ({ children }: { children: ReactNode }) => { size="lg" className="mb-12" disabled={hasError || isEmpty} - onClick={createBoard} + onClick={() => createBoard(title)} > 완료 diff --git a/src/app/(board)/board/create/components/BoardNameRecommendations.tsx b/src/app/(board)/board/create/_components/BoardNameRecommendations.tsx similarity index 100% rename from src/app/(board)/board/create/components/BoardNameRecommendations.tsx rename to src/app/(board)/board/create/_components/BoardNameRecommendations.tsx diff --git a/src/app/(board)/board/create/page.tsx b/src/app/(board)/board/create/page.tsx index 4fb6911..eb0169a 100644 --- a/src/app/(board)/board/create/page.tsx +++ b/src/app/(board)/board/create/page.tsx @@ -1,10 +1,24 @@ -import PolaboLogo from 'public/images/polabo_logo.png' import Image from 'next/image' -import BoardNameForm from './components/BoardNameForm' -import BoardNameRecommendations from './components/BoardNameRecommendations' -import BoardAvailabilityCheckModal from './components/BoardAvailabilityCheckModal' +import PolaboLogo from 'public/images/polabo_logo.png' +import { postBoard } from '@/lib' +import { revalidateTag } from 'next/cache' +import { redirect } from 'next/navigation' +import BoardAvailabilityCheckModal from './_components/BoardAvailabilityCheckModal' +import BoardNameForm from './_components/BoardNameForm' +import BoardNameRecommendations from './_components/BoardNameRecommendations' const CreateBoardPage = () => { + const createBoard = async (title: string) => { + 'use server' + + const boardId = await postBoard({ + title, + userId: null, + }) + + revalidateTag('myBoard') + redirect(`/board/${boardId}`) + } return (
{ className="object-contain px-20 pt-6" /> - +
diff --git a/src/app/(home)/components/CopyLinkBtn.tsx b/src/app/(home)/_components/CopyLinkBtn.tsx similarity index 89% rename from src/app/(home)/components/CopyLinkBtn.tsx rename to src/app/(home)/_components/CopyLinkBtn.tsx index 1d72033..2a72ac4 100644 --- a/src/app/(home)/components/CopyLinkBtn.tsx +++ b/src/app/(home)/_components/CopyLinkBtn.tsx @@ -19,12 +19,12 @@ const CopyLinkBtn = () => { return ( <> -
+
copy link!
+ setLoginModalOpen(false)} + /> + + ) +} + +export default CreateBoardBtn diff --git a/src/app/(home)/_components/GoToLoginModal.tsx b/src/app/(home)/_components/GoToLoginModal.tsx new file mode 100644 index 0000000..1264a42 --- /dev/null +++ b/src/app/(home)/_components/GoToLoginModal.tsx @@ -0,0 +1,26 @@ +import Modal from '@/components/Modal' +import { useRouter } from 'next/navigation' +import SurprisedIcon from 'public/icons/surprised.svg' + +interface ModalProps { + isOpen: boolean + onClose: () => void +} + +const GoToLoginModal = ({ isOpen, onClose }: ModalProps) => { + const router = useRouter() + return ( + + }> + 로그인 후 이용 가능합니다. + 지금 폴라보와 함께 추억을 공유해보세요! + router.push('/login')} + /> + + + ) +} + +export default GoToLoginModal diff --git a/src/app/(home)/components/TotalCount.tsx b/src/app/(home)/_components/TotalCount.tsx similarity index 89% rename from src/app/(home)/components/TotalCount.tsx rename to src/app/(home)/_components/TotalCount.tsx index 0f37ab0..13e489b 100644 --- a/src/app/(home)/components/TotalCount.tsx +++ b/src/app/(home)/_components/TotalCount.tsx @@ -6,7 +6,7 @@ const TotalCount = async () => { if (!sharedCount) return null return ( -
+
지금까지 {sharedCount.toLocaleString()}개의 보드가 만들어졌어요! diff --git a/src/app/(home)/components/CreateBoardBtn.tsx b/src/app/(home)/components/CreateBoardBtn.tsx deleted file mode 100644 index 280110c..0000000 --- a/src/app/(home)/components/CreateBoardBtn.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client' - -import Button from '@/components/Button' -import { useRouter } from 'next/navigation' - -const CreateBoardBtn = () => { - const router = useRouter() - - return ( - - ) -} - -export default CreateBoardBtn diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 1bb1139..bb01f79 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,13 +1,15 @@ +import Image from 'next/image' import PolaroidsIcon from 'public/icons/home_polaroids.svg' import PolaboLogo from 'public/images/polabo_logo.png' -import Image from 'next/image' -import CreateBoardBtn from './components/CreateBoardBtn' -import CopyLinkBtn from './components/CopyLinkBtn' -import TotalCount from './components/TotalCount' +import Hamburger from '@/components/HamburgerMenu' +import CopyLinkBtn from './_components/CopyLinkBtn' +import CreateBoardBtn from './_components/CreateBoardBtn' +import TotalCount from './_components/TotalCount' const HomePage = () => { return (
+
diff --git a/src/app/(onboarding)/login/_components/KakaoLogin.tsx b/src/app/(onboarding)/login/_components/KakaoLogin.tsx new file mode 100644 index 0000000..e1d8df5 --- /dev/null +++ b/src/app/(onboarding)/login/_components/KakaoLogin.tsx @@ -0,0 +1,22 @@ +'use client' + +import Button from '@/components/Button' +import { signIn } from 'next-auth/react' +import KakaoIcon from 'public/icons/kakao.svg' + +const KakaoLogin = () => { + return ( + + ) +} + +export default KakaoLogin diff --git a/src/app/(onboarding)/login/_components/Policy.tsx b/src/app/(onboarding)/login/_components/Policy.tsx new file mode 100644 index 0000000..5950c3d --- /dev/null +++ b/src/app/(onboarding)/login/_components/Policy.tsx @@ -0,0 +1,31 @@ +import Link from 'next/link' + +const LinkTo = ({ text, link }: { text: string; link: string }) => ( + + {text} + +) + +const Policy = () => { + return ( +
+
+ 시작하기 버튼을 누르시면 POLABO의{' '} + + 과 +
+
+ + 에 동의하게 됩니다. +
+
+ ) +} + +export default Policy diff --git a/src/app/(onboarding)/login/page.tsx b/src/app/(onboarding)/login/page.tsx new file mode 100644 index 0000000..2b62413 --- /dev/null +++ b/src/app/(onboarding)/login/page.tsx @@ -0,0 +1,35 @@ +import Image from 'next/image' +import PolaroidsIcon from 'public/icons/home_polaroids.svg' +import LoginPolaroid from 'public/icons/login_polaroid.png' +import PolaboLogo from 'public/images/polabo_logo.png' +import KakaoLogin from './_components/KakaoLogin' +import Policy from './_components/Policy' + +const LoginPage = () => { + return ( +
+
+ + + 함께 꾸미는 폴라로이드 보드 + + logo +
+ polaroids icon +
+
+ 지금 폴라보와 함께 추억을 공유해보세요! +
+ + +
+
+ ) +} + +export default LoginPage diff --git a/src/app/(onboarding)/signup/_components/Header.tsx b/src/app/(onboarding)/signup/_components/Header.tsx new file mode 100644 index 0000000..5a76234 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/Header.tsx @@ -0,0 +1,19 @@ +'use client' + +import BackIcon from 'public/icons/arrow_back_ios.svg' +import { useStep } from './contexts/StepContext' + +const Header = () => { + const { step, prevStep } = useStep() + return ( +
+ {step !== 1 && ( +
+ prevStep()} /> +
+ )} +
+ ) +} + +export default Header diff --git a/src/app/(onboarding)/signup/_components/Indicator.tsx b/src/app/(onboarding)/signup/_components/Indicator.tsx new file mode 100644 index 0000000..f31b7d4 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/Indicator.tsx @@ -0,0 +1,26 @@ +import { useStep } from './contexts/StepContext' + +const Circle = ({ step, active }: { step: number; active: boolean }) => ( +
+ {active ? step : ''} +
+) + +const Indicator = () => { + const { step } = useStep() + return ( +
+ + + +
+ ) +} + +export default Indicator diff --git a/src/app/(onboarding)/signup/_components/ProfileForm.tsx b/src/app/(onboarding)/signup/_components/ProfileForm.tsx new file mode 100644 index 0000000..df6663e --- /dev/null +++ b/src/app/(onboarding)/signup/_components/ProfileForm.tsx @@ -0,0 +1,42 @@ +'use client' + +import { updateProfile } from '@/lib' +import { UserProfile } from '@/types' +import { useSession } from 'next-auth/react' +import Indicator from './Indicator' +import { ProfileProvider } from './contexts/ProfileContext' +import { useStep } from './contexts/StepContext' +import BirthDtForm from './steps/BirthDtForm' +import GenderForm from './steps/GenderForm' +import NicknameForm from './steps/NicknameForm' + +const ProfileForm = () => { + const { step } = useStep() + const { data: session, update } = useSession() + + const handleSubmit = async (newProfile: UserProfile): Promise => { + if (session?.profile !== newProfile) { + const serverRes = await updateProfile(newProfile) + + if (serverRes.code === 'SUCCESS') { + update({ profile: newProfile }) + return true + } + return false + } + return true + } + + return ( +
+ + + {step === 1 && } + {step === 2 && } + {step === 3 && } + +
+ ) +} + +export default ProfileForm diff --git a/src/app/(onboarding)/signup/_components/contexts/ProfileContext.tsx b/src/app/(onboarding)/signup/_components/contexts/ProfileContext.tsx new file mode 100644 index 0000000..3433644 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/contexts/ProfileContext.tsx @@ -0,0 +1,65 @@ +'use client' + +import { UserProfile } from '@/types' +import { useSession } from 'next-auth/react' +import { + Dispatch, + SetStateAction, + createContext, + useContext, + useMemo, + useState, + useEffect, +} from 'react' + +interface ProfileContextProps { + newName: UserProfile['nickName'] + newBirthDt: UserProfile['birthDt'] + newGender: UserProfile['gender'] + setNewName: Dispatch> + setNewBirthDt: Dispatch> + setNewGender: Dispatch> +} + +const ProfileContext = createContext(undefined) + +export const ProfileProvider = ({ + children, +}: { + children: React.ReactNode +}) => { + const { data: session } = useSession() + const [newName, setNewName] = useState('') + const [newBirthDt, setNewBirthDt] = + useState(undefined) + const [newGender, setNewGender] = useState('NONE') + + useEffect(() => { + setNewName(session?.profile.nickName ?? '') + setNewBirthDt(session?.profile.birthDt) + }, [session]) + + const value = useMemo( + () => ({ + newName, + newBirthDt, + newGender, + setNewName, + setNewBirthDt, + setNewGender, + }), + [newName, newBirthDt, newGender], + ) + + return ( + {children} + ) +} + +export const useProfile = () => { + const context = useContext(ProfileContext) + if (context === undefined) { + throw new Error('Error at useProfile') + } + return context +} diff --git a/src/app/(onboarding)/signup/_components/contexts/StepContext.tsx b/src/app/(onboarding)/signup/_components/contexts/StepContext.tsx new file mode 100644 index 0000000..b4ce3c6 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/contexts/StepContext.tsx @@ -0,0 +1,40 @@ +'use client' + +import { ReactNode, createContext, useContext, useMemo, useState } from 'react' + +interface StepContextProps { + step: 1 | 2 | 3 + nextStep: () => void + prevStep: () => void +} + +const StepContext = createContext(undefined) + +export const StepProvider = ({ children }: { children: ReactNode }) => { + const [step, setStep] = useState<1 | 2 | 3>(1) + + const nextStep = () => + setStep((prev) => (prev !== 3 ? ((prev + 1) as 1 | 2 | 3) : prev)) + + const prevStep = () => + setStep((prev) => (prev !== 1 ? ((prev - 1) as 1 | 2 | 3) : prev)) + + const value = useMemo( + () => ({ + step, + nextStep, + prevStep, + }), + [step], + ) + + return {children} +} + +export const useStep = () => { + const context = useContext(StepContext) + if (context === undefined) { + throw new Error('Error at useStep') + } + return context +} diff --git a/src/app/(onboarding)/signup/_components/steps/BirthDtForm.tsx b/src/app/(onboarding)/signup/_components/steps/BirthDtForm.tsx new file mode 100644 index 0000000..25a70d2 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/steps/BirthDtForm.tsx @@ -0,0 +1,79 @@ +import BirthDateInput from '@/components/BirthDateInput' +import Button from '@/components/Button' +import BirthInvalidModal from '@/components/Modal/BirthInvalidModal' +import { validateBirthDt } from '@/lib/utils/validation' +import { useState } from 'react' +import { UserProfile } from '@/types' +import { useProfile } from '../contexts/ProfileContext' +import { useStep } from '../contexts/StepContext' + +const BirthDtForm = ({ + handleSubmit, +}: { + handleSubmit: (profile: UserProfile) => Promise +}) => { + const { newName, newBirthDt, setNewBirthDt, newGender } = useProfile() + const { nextStep } = useStep() + const [hasError, setHasError] = useState(false) + const [modalOpen, setModalOpen] = useState(false) + + const onSubmit = async () => { + if (newBirthDt) { + if (!validateBirthDt(newBirthDt)) { + setModalOpen(true) + return + } + } + + await handleSubmit({ + nickName: newName, + birthDt: newBirthDt, + gender: newGender, + }) + nextStep() + } + + return ( + <> +
+
+ {newName} + {'님의\n생일을 입력해주세요!'} +
+

+ 추가 정보를 입력하시면 나에게 딱 맞는 보드 주제를 추천해드려요 :) +

+ +
+ +
+ + +
+ setModalOpen(false)} + /> + + ) +} + +export default BirthDtForm diff --git a/src/app/(onboarding)/signup/_components/steps/GenderBtn.tsx b/src/app/(onboarding)/signup/_components/steps/GenderBtn.tsx new file mode 100644 index 0000000..94ffef9 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/steps/GenderBtn.tsx @@ -0,0 +1,30 @@ +import { UserProfile } from '@/types' +import FemaleIcon from 'public/icons/gender_f.svg' +import MaleIcon from 'public/icons/gender_m.svg' +import { useProfile } from '../contexts/ProfileContext' + +interface GenderBtnProps extends React.ComponentProps<'button'> { + gender: Exclude +} + +const GenderBtn = ({ gender, onClick }: GenderBtnProps) => { + const { newGender } = useProfile() + return ( + + ) +} + +export default GenderBtn diff --git a/src/app/(onboarding)/signup/_components/steps/GenderForm.tsx b/src/app/(onboarding)/signup/_components/steps/GenderForm.tsx new file mode 100644 index 0000000..b7c9b76 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/steps/GenderForm.tsx @@ -0,0 +1,66 @@ +import Button from '@/components/Button' +import { UserProfile } from '@/types' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' +import { useProfile } from '../contexts/ProfileContext' +import GenderBtn from './GenderBtn' + +const GenderForm = ({ + handleSubmit, +}: { + handleSubmit: (profile: UserProfile) => Promise +}) => { + const { newName, newBirthDt, newGender, setNewGender } = useProfile() + const [hasError, setHasError] = useState(false) + const router = useRouter() + + useEffect(() => { + if (newGender === 'NONE') { + setHasError(true) + } else setHasError(false) + }, [newGender]) + + const onSubmit = async () => { + const res = await handleSubmit({ + nickName: newName, + birthDt: newBirthDt, + gender: newGender, + }) + if (res) router.push('/signup/complete') + } + + return ( +
+
+ 성별 + {'을 \n 입력해주세요!'} +
+

+ 추가 정보를 입력하시면 나에게 딱 맞는 보드 주제를 추천해드려요 :) +

+ +
+ setNewGender('M')} /> + setNewGender('F')} /> +
+ + + + 다음에 할게요 + +
+ ) +} + +export default GenderForm diff --git a/src/app/(onboarding)/signup/_components/steps/NicknameForm.tsx b/src/app/(onboarding)/signup/_components/steps/NicknameForm.tsx new file mode 100644 index 0000000..a78af24 --- /dev/null +++ b/src/app/(onboarding)/signup/_components/steps/NicknameForm.tsx @@ -0,0 +1,56 @@ +'use client' + +import Button from '@/components/Button' +import NicknameInput from '@/components/TextInput/NicknameInput' +import SketchIcon from 'public/icons/sketchIcons-1.svg' +import { useState } from 'react' +import { UserProfile } from '@/types' +import { useProfile } from '../contexts/ProfileContext' +import { useStep } from '../contexts/StepContext' + +const NicknameForm = ({ + handleSubmit, +}: { + handleSubmit: (profile: UserProfile) => Promise +}) => { + const { newName, setNewName, newBirthDt, newGender } = useProfile() + const [hasError, setHasError] = useState(false) + const isEmpty = newName.length === 0 + + const { nextStep } = useStep() + + const createNickname = async () => { + await handleSubmit({ + nickName: newName, + birthDt: newBirthDt, + gender: newGender, + }) + nextStep() + } + + return ( +
+
+ 닉네임을 정해주세요! +
+
+ } + /> +
+ +
+ ) +} + +export default NicknameForm diff --git a/src/app/(onboarding)/signup/complete/_components/Buttons.tsx b/src/app/(onboarding)/signup/complete/_components/Buttons.tsx new file mode 100644 index 0000000..9b010e2 --- /dev/null +++ b/src/app/(onboarding)/signup/complete/_components/Buttons.tsx @@ -0,0 +1,26 @@ +'use client' + +import Button from '@/components/Button' +import Link from 'next/link' +import { useRouter } from 'next/navigation' + +export const GoToCreateBoard = () => { + const router = useRouter() + return ( + + ) +} + +export const GoToMain = () => { + return ( + + 메인으로 가기 + + ) +} diff --git a/src/app/(onboarding)/signup/complete/page.tsx b/src/app/(onboarding)/signup/complete/page.tsx new file mode 100644 index 0000000..32fd0e8 --- /dev/null +++ b/src/app/(onboarding)/signup/complete/page.tsx @@ -0,0 +1,30 @@ +import Image from 'next/image' +import ThreePolaroids from 'public/icons/threePolaroids.png' +import CheckIcon from 'public/icons/sketch_check.svg' +import { GoToCreateBoard, GoToMain } from './_components/Buttons' + +const SignUpCompletePage = () => { + return ( +
+ +

+ {'회원가입이\n 완료되었습니다!'} +

+

+ 보드를 만들어 친구들에게 공유해보세요! +

+
+ polaroids icon +
+ + +
+ ) +} + +export default SignUpCompletePage diff --git a/src/app/(onboarding)/signup/page.tsx b/src/app/(onboarding)/signup/page.tsx new file mode 100644 index 0000000..b236852 --- /dev/null +++ b/src/app/(onboarding)/signup/page.tsx @@ -0,0 +1,23 @@ +import { auth } from '@/auth' +import { redirect } from 'next/navigation' +import ProfileForm from './_components/ProfileForm' +import { StepProvider } from './_components/contexts/StepContext' +import Header from './_components/Header' + +const SignUpPage = async () => { + const session = await auth() + + if (session && !session.newUser) { + redirect('/') + } + return ( +
+ +
+ + +
+ ) +} + +export default SignUpPage diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..c4ea295 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from '@/auth' + +export const { GET, POST } = handlers diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..643b23b --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,11 @@ +'use client' + +const ErrorPage = () => { + return ( +
+

오류가 발생했어요.

+
+ ) +} + +export default ErrorPage diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c7f2731..76aaf4f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,5 @@ +import AuthSession from '@/components/AuthSession' +import CheckNewUser from '@/components/CheckNewUser' import GoogleAnalytics from '@/components/GoogleAnalytics' import '@/styles/globals.css' import type { Metadata } from 'next' @@ -56,9 +58,12 @@ export default function RootLayout({ -
- {children} -
+ + +
+ {children} +
+