diff --git a/.eslintrc.js b/.eslintrc.js index cb854af6..8ba45437 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,8 @@ module.exports = { 'react/jsx-props-no-spreading': 'off', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': ['off'], + 'react/require-default-props': 'off', + 'react/self-closing-comp': 'off', }, settings: { react: { diff --git a/package-lock.json b/package-lock.json index 1df42c85..9fb61474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,12 +23,14 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "lucide-react": "^0.407.0", + "lucide-react": "^0.402.0", "next": "14.2.4", "qs": "^6.12.2", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.1", + "react-toastify": "^10.0.5", + "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" @@ -75,6 +77,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -252,6 +263,437 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz", + "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz", + "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz", + "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz", + "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", + "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.31", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", + "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz", + "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz", + "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", + "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", + "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz", + "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2302,6 +2744,18 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2318,6 +2772,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2541,6 +3004,14 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4156,6 +4627,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -4686,7 +5162,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -4801,9 +5278,9 @@ } }, "node_modules/lucide-react": { - "version": "0.407.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.407.0.tgz", - "integrity": "sha512-+dRIu9Sry+E8wPF9+sY5eKld2omrU4X5IKXxrgqBt+o11IIHVU0QOfNoVWFuj0ZRDrxr4Wci26o2mKZqLGE0lA==", + "version": "0.402.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.402.0.tgz", + "integrity": "sha512-V2ModWMXzoZbQy4dhVUY4snrOplw+DwH0tsr8SLf8N0+irwO5okFcMS4eFHrXHttt24sLFZP3IJQo832o3fUWQ==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -5861,6 +6338,18 @@ } } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6096,7 +6585,6 @@ "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" }, @@ -6135,6 +6623,45 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", + "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.0" + }, + "engines": { + "libvips": ">=8.15.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.4", + "@img/sharp-darwin-x64": "0.33.4", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-s390x": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-linux-arm": "0.33.4", + "@img/sharp-linux-arm64": "0.33.4", + "@img/sharp-linux-s390x": "0.33.4", + "@img/sharp-linux-x64": "0.33.4", + "@img/sharp-linuxmusl-arm64": "0.33.4", + "@img/sharp-linuxmusl-x64": "0.33.4", + "@img/sharp-wasm32": "0.33.4", + "@img/sharp-win32-ia32": "0.33.4", + "@img/sharp-win32-x64": "0.33.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6182,6 +6709,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index fd2f3859..a84b4b59 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,14 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "lucide-react": "^0.407.0", + "lucide-react": "^0.402.0", "next": "14.2.4", "qs": "^6.12.2", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.1", + "react-toastify": "^10.0.5", + "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" diff --git a/public/ProfileTestImage.jpg b/public/ProfileTestImage.jpg new file mode 100644 index 00000000..3d7e85d8 Binary files /dev/null and b/public/ProfileTestImage.jpg differ diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 718d6fea..00000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/icon/BW/AngryFaceBWIcon.svg b/public/icon/BW/AngryFaceBWIcon.svg new file mode 100644 index 00000000..c499add3 --- /dev/null +++ b/public/icon/BW/AngryFaceBWIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icon/BW/HeartFaceBWIcon.svg b/public/icon/BW/HeartFaceBWIcon.svg new file mode 100644 index 00000000..5702c0f9 --- /dev/null +++ b/public/icon/BW/HeartFaceBWIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/icon/BW/SadFaceBWIcon.svg b/public/icon/BW/SadFaceBWIcon.svg new file mode 100644 index 00000000..90df96bd --- /dev/null +++ b/public/icon/BW/SadFaceBWIcon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/icon/BW/SmileFaceBWIcon.svg b/public/icon/BW/SmileFaceBWIcon.svg new file mode 100644 index 00000000..58be6715 --- /dev/null +++ b/public/icon/BW/SmileFaceBWIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/icon/BW/ThinkFaceBWIcon.svg b/public/icon/BW/ThinkFaceBWIcon.svg new file mode 100644 index 00000000..2ac21adc --- /dev/null +++ b/public/icon/BW/ThinkFaceBWIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/icon/Color/AngryFaceColorIcon.svg b/public/icon/Color/AngryFaceColorIcon.svg new file mode 100644 index 00000000..ca6e754a --- /dev/null +++ b/public/icon/Color/AngryFaceColorIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icon/Color/HeartFaceColorIcon.svg b/public/icon/Color/HeartFaceColorIcon.svg new file mode 100644 index 00000000..9db0bbd0 --- /dev/null +++ b/public/icon/Color/HeartFaceColorIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/icon/Color/SadFaceColorIcon.svg b/public/icon/Color/SadFaceColorIcon.svg new file mode 100644 index 00000000..58e96e31 --- /dev/null +++ b/public/icon/Color/SadFaceColorIcon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/icon/Color/SmileFaceColorIcon.svg b/public/icon/Color/SmileFaceColorIcon.svg new file mode 100644 index 00000000..3e66c738 --- /dev/null +++ b/public/icon/Color/SmileFaceColorIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/icon/Color/ThinkFaceColorIcon.svg b/public/icon/Color/ThinkFaceColorIcon.svg new file mode 100644 index 00000000..0e2bab7d --- /dev/null +++ b/public/icon/Color/ThinkFaceColorIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/icon/arrow-left-icon.svg b/public/icon/arrow-left-icon.svg new file mode 100644 index 00000000..a54a7cb4 --- /dev/null +++ b/public/icon/arrow-left-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icon/profile-icon.svg b/public/icon/profile-icon.svg new file mode 100644 index 00000000..e7109ea6 --- /dev/null +++ b/public/icon/profile-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icon/search-icon.svg b/public/icon/search-icon.svg new file mode 100644 index 00000000..02de48b2 --- /dev/null +++ b/public/icon/search-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icon/share-icon.svg b/public/icon/share-icon.svg new file mode 100644 index 00000000..4d1c6d03 --- /dev/null +++ b/public/icon/share-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/lg.svg b/public/lg.svg new file mode 100644 index 00000000..a4d3364f --- /dev/null +++ b/public/lg.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/logo-google.svg b/public/logo-google.svg new file mode 100644 index 00000000..5b169484 --- /dev/null +++ b/public/logo-google.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/logo-kakao.svg b/public/logo-kakao.svg new file mode 100644 index 00000000..f546e64d --- /dev/null +++ b/public/logo-kakao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/logo-naver.svg b/public/logo-naver.svg new file mode 100644 index 00000000..dbec93dd --- /dev/null +++ b/public/logo-naver.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f84222..00000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 00000000..8244a9b4 --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,9 @@ +import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; +import httpClient from '.'; + +const postSignin = async (request: PostSigninRequestType): Promise => { + const response = await httpClient.post('/auth/signIn', request); + return response.data; +}; + +export default postSignin; diff --git a/src/apis/index.ts b/src/apis/index.ts index 29949fc2..4167407d 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -8,3 +8,45 @@ const httpClient = axios.create({ }); export default httpClient; + +// NOTE: eslint-disable no-param-reassign 미해결로 인한 설정 +httpClient.interceptors.request.use((config) => { + const accessToken = localStorage.getItem('accessToken'); + /* eslint-disable no-param-reassign */ + if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`; + /* eslint-enable no-param-reassign */ + return config; +}); + +httpClient.interceptors.response.use( + (response) => response, + + (error) => { + if (error.response && error.response.status === 401) { + const refreshToken = localStorage.getItem('refreshToken'); + + if (!refreshToken) { + window.location.href = '/auth/SignIn'; + return Promise.reject(error); + } + + return httpClient + .post('/auth/refresh-token', null, { + headers: { Authorization: `Bearer ${refreshToken}` }, + }) + .then((response) => { + const { accessToken, refreshToken: newRefreshToken } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefreshToken); + + const originalRequest = error.config; + return httpClient(originalRequest); + }) + .catch(() => { + window.location.href = '/auth/SignIn'; + return Promise.reject(error); + }); + } + return Promise.reject(error); + }, +); diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx new file mode 100644 index 00000000..a58f0e6f --- /dev/null +++ b/src/components/Card/CommentCard.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import Image from 'next/image'; +import { sizeStyles, textSizeStyles, gapStyles, paddingStyles, contentWidthStyles } from '@/styles/CommentCardStyles'; + +export interface CommentCardProps { + status: 'edit' | 'complete'; +} + +function CommentCard({ status }: CommentCardProps) { + return ( + + + + + {' '} + + + + + + + {/* 테스트 텍스트입니다. */} + 지킬과 하이드 + + + {/* 테스트 텍스트입니다. */} + 1시간 전 + + + {status === 'edit' && ( + + 수정 + 삭제 + + )} + + + {/* 테스트 텍스트입니다. */} + 오늘 하루 우울했었는데 덕분에 많은 힘 얻고 갑니다. 연금술사 책 다시 사서 오랜만에 읽어봐야겠어요! + + + + + ); +} + +export default CommentCard; diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx new file mode 100644 index 00000000..07ceb392 --- /dev/null +++ b/src/components/Card/EpigramCard.tsx @@ -0,0 +1,63 @@ +import React from 'react'; + +// figma 상으로는 sm ~ 3xl 사이즈로 구현되어 있는데, tailwind 환경을 반영해 +// xs ~ 2xl 으로 정의했습니다. +const sizeStyles = { + xs: 'w-[286px] max-h-[132px]', + sm: 'sm:w-[312px] sm:max-h-[152px]', + md: 'md:w-[384px] md:max-h-[180px]', + lg: 'lg:w-[540px] lg:max-h-[160px]', + xl: 'xl:w-[640px] xl:max-h-[196px]', + '2xl': '2xl:w-[744px] 2xl:max-h-[196px]', +}; + +const textSizeStyles = { + xs: 'text-xs', + sm: 'sm:text-sm', + md: 'md:text-base', + lg: 'lg:text-xl', + xl: 'xl:text-2xl', + '2xl': '2xl:text-2xl', +}; + +function EpigramCard() { + return ( + + + {/* eslint-disable-next-line */} + {/* 줄무늬를 만들려면 비어있는 div가 필요합니다. */} + + + + {/* 테스트 텍스트입니다. */} + 오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다. + + + {/* 테스트 텍스트입니다. */}- 앙드레 말로 - + + + + + + + {/* 테스트 텍스트입니다. */} + #나아가야할때 + + + {/* 테스트 텍스트입니다. */} + #꿈을이루고싶을때 + + + + ); +} + +export default EpigramCard; diff --git a/src/components/Emotion/EmotionCard.tsx b/src/components/Emotion/EmotionCard.tsx new file mode 100644 index 00000000..1896f7cd --- /dev/null +++ b/src/components/Emotion/EmotionCard.tsx @@ -0,0 +1,113 @@ +/* + 1개의 감정 아이콘 카드를 랜더링 합니다. + 아이콘의 타입, 상태, 크기, 클릭 이벤트를 관리합니다. + 아이콘 타입과 상태에 따라 아이콘의 모양과 스타일을 조정합니다. + */ + +import React from 'react'; +import cn from '@/lib/utils'; +import Image from 'next/image'; +import { EmotionIconCardProps } from '@/types/EmotionTypes'; + +// 아이콘 파일 경로 매핑 +const iconPaths = { + Color: { + 감동: '/icon/Color/HeartFaceColorIcon.svg', + 기쁨: '/icon/Color/SmileFaceColorIcon.svg', + 고민: '/icon/Color/ThinkFaceColorIcon.svg', + 슬픔: '/icon/Color/SadFaceColorIcon.svg', + 분노: '/icon/Color/AngryFaceColorIcon.svg', + }, + BW: { + 감동: '/icon/BW/HeartFaceBWIcon.svg', + 기쁨: '/icon/BW/SmileFaceBWIcon.svg', + 고민: '/icon/BW/ThinkFaceBWIcon.svg', + 슬픔: '/icon/BW/SadFaceBWIcon.svg', + 분노: '/icon/BW/AngryFaceBWIcon.svg', + }, +}; + +// EmotionIconCard 컴포넌트 함수 선언 +function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm', onClick }: EmotionIconCardProps) { + // 크기에 따른 클래스 설정 + let sizeClass = ''; + let iconSizeClass = ''; + let textSizeClass = ''; + switch (size) { + case 'lg': + sizeClass = 'w-20 h-28'; + iconSizeClass = 'w-12 h-12'; + textSizeClass = 'text-base leading-relaxed'; + break; + case 'md': + sizeClass = 'w-16 h-24'; + iconSizeClass = 'w-10 h-10'; + textSizeClass = 'text-sm leading-normal'; + break; + case 'sm': + default: + sizeClass = 'w-14 h-21'; + iconSizeClass = 'w-8 h-8'; + textSizeClass = 'text-xs leading-tight'; + break; + } + + // 상태에 따른 아이콘 경로 설정 + const iconPath = state === 'Clicked' || state === 'Default' ? iconPaths.Color[iconType] : iconPaths.BW[iconType]; + + // 상태에 따른 클래스 설정 + let borderClass = ''; + const textColorClass = 'text-neutral-400'; + let backgroundClass = 'bg-slate-400/20'; + let textVisibilityClass = ''; + + if (state === 'Clicked') { + textVisibilityClass = 'hidden'; + backgroundClass = 'bg-transparent'; + + // iconType에 따라 다른 border 색상을 설정 + switch (iconType) { + case '감동': + borderClass = 'border-4 border-illust-yellow'; + break; + case '기쁨': + borderClass = 'border-4 border-illust-green'; + break; + case '고민': + borderClass = 'border-4 border-illust-purple'; + break; + case '슬픔': + borderClass = 'border-4 border-illust-blue'; + break; + case '분노': + borderClass = 'border-4 border-illust-red'; + break; + default: + borderClass = 'border-4 border-sub_blue_1'; + break; + } + } + + return ( + { + if (e.key === 'Enter' || e.key === ' ') { + if (onClick) { + onClick(); + } + } + }} + > + + + + {iconType} + + ); +} + +export default EmotionIconCard; diff --git a/src/components/Emotion/EmotionSelector.tsx b/src/components/Emotion/EmotionSelector.tsx new file mode 100644 index 00000000..5a73639e --- /dev/null +++ b/src/components/Emotion/EmotionSelector.tsx @@ -0,0 +1,66 @@ +/* + 여러 개의 EmotionIconCard를 관리합니다. + 사용자 인터페이스에 필요한 상호 작용 로직을 포함합니다. + */ + +import React, { useState } from 'react'; +import EmotionIconCard from '@/components/Emotion/EmotionCard'; +import useMediaQuery from '@/hooks/useMediaQuery'; +import { EmotionType, EmotionState } from '@/types/EmotionTypes'; + +// EmotionSelector 컴포넌트 함수 선언 +function EmotionSelector() { + const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1024px)'); + const isMobile = useMediaQuery('(max-width: 767px)'); + + // 감정 카드 상태 관리 + const [states, setStates] = useState>({ + 감동: 'Default', + 기쁨: 'Default', + 고민: 'Default', + 슬픔: 'Default', + 분노: 'Default', + }); + + // 감정 카드 클릭 핸들러 + const handleCardClick = (iconType: EmotionType) => { + setStates((prevStates) => { + const newStates = { ...prevStates }; + + if (prevStates[iconType] === 'Clicked') { + // 현재 클릭된 카드가 다시 클릭되면 모두 Default로 설정 + Object.keys(newStates).forEach((key) => { + newStates[key as EmotionType] = 'Default'; + }); + } else { + // 하나의 카드가 클릭되면 그 카드만 Clicked, 나머지는 Unclicked로 설정 + Object.keys(newStates).forEach((key) => { + newStates[key as EmotionType] = key === iconType ? 'Clicked' : 'Unclicked'; + }); + } + + return newStates; + }); + }; + + let containerClass = 'w-[544px] h-[136px] gap-4'; + let cardSize: 'lg' | 'md' | 'sm' = 'lg'; + + if (isTablet) { + containerClass = 'w-[352px] h-[96px] gap-2'; + cardSize = 'md'; + } else if (isMobile) { + containerClass = 'w-[312px] h-[84px] gap-2'; + cardSize = 'sm'; + } + + return ( + + {(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => ( + handleCardClick(iconType)} /> + ))} + + ); +} + +export default EmotionSelector; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 00000000..d342347f --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import Image from 'next/image'; +import { useToast } from '../ui/use-toast'; +import LOGO_ICON from '../../../public/epigram-icon.png'; +import ARROW_LEFT_ICON from '../../../public/icon/arrow-left-icon.svg'; +import PROFILE_ICON from '../../../public/icon/profile-icon.svg'; +import SEARCH_ICON from '../../../public/icon/search-icon.svg'; +import SHARE_ICON from '../../../public/icon/share-icon.svg'; + +// NOTE 네비게이션 바를 나타내는 컴포넌트 입니다. +// NOTE 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다. +// NOTE 사용 예시 +// NOTE +// NOTE {}} />; +// NOTE icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다. +// NOTE isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다. +// NOTE isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다. +// NOTE SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 를 추가해주세요. + +export interface HeaderProps { + icon: 'back' | 'search' | ''; + routerPage: string; + isLogo: boolean; + insteadOfLogo: string; + isProfileIcon: boolean; + isShareIcon: boolean; + isButton: boolean; + textInButton: string; + disabled: boolean; + onClick: (e: React.MouseEvent) => void; +} + +function Header({ isLogo, icon, insteadOfLogo, isButton, isProfileIcon, isShareIcon, textInButton, routerPage, disabled, onClick }: HeaderProps) { + const router = useRouter(); + const { toast } = useToast(); + + // 페이지 이동 함수 + const handleNavigateTo = (path: string) => { + router.push(path); + }; + + // 현재 링크 복사 함수 + const handleCopyToClipboard = async () => { + try { + // 현재 URL 가져오기 + const currentURL = window.location.href; + // 클립보드에 복사하기 + await navigator.clipboard.writeText(currentURL); + toast({ + title: '성공', + description: '링크가 클립보드에 복사되었습니다!', + }); + } catch (err) { + toast({ + title: '실패', + description: '링크 복사에 실패했습니다. 다시 시도해 주세요.', + variant: 'destructive', + }); + } + }; + + return ( + + + + {icon === 'back' && ( + handleNavigateTo(routerPage)} aria-label='뒤로가기 버튼'> + + + )} + {icon === 'search' && ( + handleNavigateTo('/search')} aria-label='검색 버튼'> + + + )} + + + {isLogo ? ( + handleNavigateTo('/')} aria-label='홈으로 이동'> + + Epigram + + ) : ( + {insteadOfLogo} + )} + + + {isProfileIcon && ( + handleNavigateTo('/mypage')} aria-label='프로필 페이지로 이동'> + + + )} + {isShareIcon && ( + + + + )} + {isButton && ( + + {textInButton} + + )} + + + + ); +} + +export default Header; diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 00000000..899c0fce --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,19 @@ +import { useState, useEffect } from 'react'; + +function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + if (media.matches !== matches) { + setMatches(media.matches); + } + const listener = () => setMatches(media.matches); + media.addEventListener('change', listener); + return () => media.removeEventListener('change', listener); + }, [matches, query]); + + return matches; +} + +export default useMediaQuery; diff --git a/src/hooks/useSignInMutation.ts b/src/hooks/useSignInMutation.ts new file mode 100644 index 00000000..eaf9fd76 --- /dev/null +++ b/src/hooks/useSignInMutation.ts @@ -0,0 +1,22 @@ +import postSignin from '@/apis/auth'; +import { toast } from '@/components/ui/use-toast'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; + +const useSigninMutation = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignin, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: () => { + toast({ description: '이메일 혹은 비밀번호를 확인해주세요.', className: 'border-state-error text-state-error font-semibold' }); + }, + }); +}; + +export default useSigninMutation; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 37d2f8d3..107acf01 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import Toaster from '@/components/ui/toaster'; export default function App({ Component, pageProps }: AppProps) { const [queryClient] = React.useState(() => new QueryClient()); @@ -10,6 +11,7 @@ export default function App({ Component, pageProps }: AppProps) { + diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx new file mode 100644 index 00000000..400d0edd --- /dev/null +++ b/src/pages/auth/SignIn.tsx @@ -0,0 +1,100 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; +import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; +import useSigninMutation from '@/hooks/useSignInMutation'; + +export default function SignIn() { + const mutationSignin = useSigninMutation(); + // 폼 정의 + const form = useForm({ + resolver: zodResolver(PostSigninRequest), + mode: 'onBlur', + defaultValues: { + email: '', + password: '', + }, + }); + + // TODO: 나중에 컴포넌트 분리하기 + return ( + + + + + + + + + mutationSignin.mutate(values))} className='flex flex-col items-center lg:gap-6 gap-5 w-full px-6'> + + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> + + + 로그인 + + + + + 회원이 아니신가요? + + + 가입하기 + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6a3a652a..1bbe9ac9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -29,7 +29,6 @@ export default function Home() { Epigram Main - Pretendard Iropke Batang diff --git a/src/schema/auth.ts b/src/schema/auth.ts new file mode 100644 index 00000000..33466608 --- /dev/null +++ b/src/schema/auth.ts @@ -0,0 +1,25 @@ +import * as z from 'zod'; + +export const PostSigninRequest = z.object({ + email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), + password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), +}); + +const User = z.object({ + id: z.number(), + email: z.string().email(), + nickname: z.string(), + teamId: z.string(), + updatedAt: z.coerce.date(), + createdAt: z.coerce.date(), + image: z.string(), +}); + +export const PostSigninResponse = z.object({ + accessToken: z.string(), + refreshToken: z.string(), + user: User, +}); + +export type PostSigninRequestType = z.infer; +export type PostSigninResponseType = z.infer; diff --git a/src/styles/CommentCardStyles.ts b/src/styles/CommentCardStyles.ts new file mode 100644 index 00000000..6d7a6a66 --- /dev/null +++ b/src/styles/CommentCardStyles.ts @@ -0,0 +1,44 @@ +export const sizeStyles = { + sm: 'w-[360px] h-[130px]', + md: 'md:w-[384px] md:h-[162px]', + lg: 'lg:w-[640px] lg:h-[176px]', +}; + +export const textSizeStyles = { + sm: { + name: 'text-xs', + time: 'text-xs', + action: 'text-xs', + content: 'text-sm', + }, + md: { + name: 'md:text-sm', + time: 'md:text-sm', + action: 'md:text-sm', + content: 'md:text-base', + }, + lg: { + name: 'lg:text-base', + time: 'lg:text-base', + action: 'lg:text-base', + content: 'lg:text-xl', + }, +}; + +export const gapStyles = { + sm: 'gap-2', + md: 'md:gap-3', + lg: 'lg:gap-4', +}; + +export const paddingStyles = { + sm: 'py-4 px-6', // 위아래 16px, 양 옆 24px + md: 'md:p-6', // 위아래, 양옆 24px + lg: 'lg:py-[35px] lg:px-6', // 위아래 35px, 양 옆 24px +}; + +export const contentWidthStyles = { + sm: 'w-[248px]', + md: 'md:w-[272px]', + lg: 'lg:w-[528px]', +}; diff --git a/src/types/EmotionTypes.ts b/src/types/EmotionTypes.ts new file mode 100644 index 00000000..e64f3d3f --- /dev/null +++ b/src/types/EmotionTypes.ts @@ -0,0 +1,14 @@ +export type EmotionType = '감동' | '기쁨' | '고민' | '슬픔' | '분노'; +export type EmotionState = 'Default' | 'Unclicked' | 'Clicked'; + +export interface EmotionIconCardProps { + iconType: EmotionType; // 아이콘 종류 + state: EmotionState; // 상태 + size: 'sm' | 'md' | 'lg'; // 크기 + onClick?: () => void; // 클릭 이벤트 핸들러 +} + +export interface InteractiveEmotionIconCardProps extends Omit { + state: EmotionState; + onClick: () => void; +} diff --git a/tailwind.config.js b/tailwind.config.js index 3b6e9ee8..383e106d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,235 +2,7 @@ module.exports = { content: ['./pages/**/*.{js,ts,jsx,tsx,mdx}', './src/**/*.{js,ts,jsx,tsx,mdx}'], theme: { - extend: { - fontFamily: { - pretendard: ['Pretendard'], - iropkeBatang: ['IropkeBatang'], - }, - fontSize: { - // Pretendard settings - 'pretendard-3xl': [ - '32px', - { - lineHeight: '42px', - fontWeight: '700', - }, - ], - 'pretendard-2xl-bold': [ - '24px', - { - lineHeight: '32px', - fontWeight: '700', - }, - ], - 'pretendard-2xl-semibold': [ - '24px', - { - lineHeight: '32px', - fontWeight: '600', - }, - ], - 'pretendard-2xl-medium': [ - '24px', - { - lineHeight: '32px', - fontWeight: '500', - }, - ], - 'pretendard-2xl-regular': [ - '24px', - { - lineHeight: '32px', - fontWeight: '400', - }, - ], - 'pretendard-xl-semibold': [ - '20px', - { - lineHeight: '32px', - fontWeight: '600', - }, - ], - 'pretendard-xl-medium': [ - '20px', - { - lineHeight: '32px', - fontWeight: '500', - }, - ], - 'pretendard-xl-regular': [ - '20px', - { - lineHeight: '32px', - fontWeight: '400', - }, - ], - 'pretendard-lg-semibold': [ - '16px', - { - lineHeight: '26px', - fontWeight: '600', - }, - ], - 'pretendard-lg-medium': [ - '16px', - { - lineHeight: '26px', - fontWeight: '500', - }, - ], - 'pretendard-lg-regular': [ - '16px', - { - lineHeight: '26px', - fontWeight: '400', - }, - ], - 'pretendard-md-bold': [ - '14px', - { - lineHeight: '24px', - fontWeight: '700', - }, - ], - 'pretendard-md-semibold': [ - '14px', - { - lineHeight: '24px', - fontWeight: '600', - }, - ], - 'pretendard-md-medium': [ - '14px', - { - lineHeight: '24px', - fontWeight: '500', - }, - ], - 'pretendard-md-regular': [ - '14px', - { - lineHeight: '24px', - fontWeight: '400', - }, - ], - 'pretendard-sm-medium': [ - '13px', - { - lineHeight: '22px', - fontWeight: '500', - }, - ], - 'pretendard-xs-semibold': [ - '12px', - { - lineHeight: '20px', - fontWeight: '600', - }, - ], - 'pretendard-xs-regular': [ - '12px', - { - lineHeight: '18px', - fontWeight: '400', - }, - ], - - // IropkeBatang settings - 'iropke-4xl': [ - '40px', - { - lineHeight: '52px', - fontWeight: '500', - }, - ], - 'iropke-3xl': [ - '32px', - { - lineHeight: '48px', - fontWeight: '500', - }, - ], - 'iropke-2xl': [ - '24px', - { - lineHeight: '40px', - fontWeight: '500', - }, - ], - 'iropke-xl': [ - '20px', - { - lineHeight: '28px', - fontWeight: '500', - }, - ], - 'iropke-lg': [ - '16px', - { - lineHeight: '26px', - fontWeight: '500', - }, - ], - 'iropke-md': [ - '14px', - { - lineHeight: '24px', - fontWeight: '500', - }, - ], - 'iropke-xs': [ - '12px', - { - lineHeight: '18px', - fontWeight: '500', - }, - ], - }, - colors: { - 'black-100': '#787878', - 'black-200': '#6B6B6B', - 'black-300': '#5E5E5E', - 'black-400': '#525252', - 'black-500': '#454545', - 'black-600': '#373737', - 'black-700': '#2B2B2B', - 'black-800': '#1F1F1F', - 'black-900': '#121212', - 'black-950': '#050505', - 'blue-100': '#FFFFFF', - 'blue-200': '#ECEFF4', - 'blue-300': '#CBD3E1', - 'blue-400': '#ABB8CE', - 'blue-500': '#8B9DBC', - 'blue-600': '#6A82A9', - 'blue-700': '#52698E', - 'blue-800': '#40516E', - 'blue-900': '#2D394E', - 'blue-950': '#1A212D', - 'gray-100': '#DEDEDE', - 'gray-200': '#C4C4C4', - 'gray-300': '#ABABAB', - 'gray-400': '#919191', - 'background-100': '#F5F7FA', - 'state-error': '#FF6577', - 'line-100': '#F2F2F2', - 'line-200': '#CFDBEA', - 'illust-yellow': '#FBC85B', - 'illust-green': '#48BB98', - 'illust-purple': '#8E80E3', - 'illust-blue': '#5195EE', - 'illust-red': '#E46E80', - 'illust-brown': '#9A695E', - 'sub-yellow': '#E8AA26', - 'sub-blue_1': '#3E3E3E', - 'sub-blue_2': '#3E414D', - 'sub-blue_3': '#494D59', - 'sub-gray_1': '#C7D1E0', - 'sub-gray_2': '#E3E9F1', - 'sub-gray_3': '#EFF3F8', - }, - }, + extend: {}, }, plugins: [], };
Pretendard
Iropke Batang