diff --git a/next.config.ts b/next.config.ts index e0bb3e07..ae82c005 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from 'next' const nextConfig: NextConfig = { reactStrictMode: true, + webpack(config, { isServer }) { if (!isServer) { config.resolve.fallback = { fs: false } diff --git a/package-lock.json b/package-lock.json index 784a89f4..25860c78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,17 @@ "name": "sprintnext", "version": "0.1.0", "dependencies": { + "@babel/runtime": "^7.27.1", "@types/styled-components": "^5.1.34", "axios": "^1.9.0", + "clsx": "^2.1.1", "next": "15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.5.0", - "styled-components": "^6.1.17" + "sass": "^1.89.0", + "styled-components": "^6.1.18", + "zustand": "^5.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -35,6 +39,15 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -910,6 +923,315 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1848,7 +2170,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1981,12 +2303,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3146,7 +3492,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3603,6 +3949,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3815,7 +4167,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -3859,7 +4211,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3884,7 +4236,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4278,7 +4630,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4444,6 +4796,13 @@ } } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4721,7 +5080,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -4968,6 +5327,19 @@ "node": ">=18" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5181,6 +5553,26 @@ "dev": true, "license": "MIT" }, + "node_modules/sass": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz", + "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -5790,7 +6182,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -6189,6 +6581,35 @@ "peerDependencies": { "zod": "^3.24.1" } + }, + "node_modules/zustand": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz", + "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 1ea366c9..7a91c4f6 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,17 @@ "lint": "next lint" }, "dependencies": { + "@babel/runtime": "^7.27.1", "@types/styled-components": "^5.1.34", "axios": "^1.9.0", + "clsx": "^2.1.1", "next": "15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.5.0", - "styled-components": "^6.1.17" + "sass": "^1.89.0", + "styled-components": "^6.1.18", + "zustand": "^5.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/assets/image/Logo.png b/public/assets/image/Logo.png deleted file mode 100644 index 19b0c1c0..00000000 Binary files a/public/assets/image/Logo.png and /dev/null differ diff --git a/public/assets/image/ArrowDown.png b/public/assets/image/arrow_down.png similarity index 100% rename from public/assets/image/ArrowDown.png rename to public/assets/image/arrow_down.png diff --git a/public/assets/image/BestBadge.png b/public/assets/image/best_badge.png similarity index 100% rename from public/assets/image/BestBadge.png rename to public/assets/image/best_badge.png diff --git a/public/assets/image/HeartActive.png b/public/assets/image/heart_active.png similarity index 100% rename from public/assets/image/HeartActive.png rename to public/assets/image/heart_active.png diff --git a/public/assets/image/HeartInactive.png b/public/assets/image/heart_inactive.png similarity index 100% rename from public/assets/image/HeartInactive.png rename to public/assets/image/heart_inactive.png diff --git a/public/assets/image/LogoFace.png b/public/assets/image/logo_face.png similarity index 100% rename from public/assets/image/LogoFace.png rename to public/assets/image/logo_face.png diff --git a/public/assets/image/logo_text.png b/public/assets/image/logo_text.png new file mode 100644 index 00000000..ac05f654 Binary files /dev/null and b/public/assets/image/logo_text.png differ diff --git a/public/assets/image/NoImage.png b/public/assets/image/no_image.png similarity index 100% rename from public/assets/image/NoImage.png rename to public/assets/image/no_image.png diff --git a/public/assets/svg/Best_Badge.svg b/public/assets/svg/Best_Badge.svg deleted file mode 100644 index 8470f48f..00000000 --- a/public/assets/svg/Best_Badge.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/public/assets/svg/Delete.svg b/public/assets/svg/Delete.svg deleted file mode 100644 index f6674f7f..00000000 --- a/public/assets/svg/Delete.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/public/assets/svg/Google.svg b/public/assets/svg/Google.svg index 0aa34364..9a013c32 100644 --- a/public/assets/svg/Google.svg +++ b/public/assets/svg/Google.svg @@ -1,10 +1,10 @@ - + - - + + - + diff --git a/public/assets/svg/Kakao.svg b/public/assets/svg/Kakao.svg index a503e487..e779fa4e 100644 --- a/public/assets/svg/Kakao.svg +++ b/public/assets/svg/Kakao.svg @@ -1,11 +1,11 @@ - - + + - + diff --git a/public/assets/svg/Setting.svg b/public/assets/svg/Setting.svg deleted file mode 100644 index 63a0344c..00000000 --- a/public/assets/svg/Setting.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/public/assets/svg/ArrowLeft.svg b/public/assets/svg/arrow_left.svg similarity index 100% rename from public/assets/svg/ArrowLeft.svg rename to public/assets/svg/arrow_left.svg diff --git a/public/assets/svg/ArrowRight.svg b/public/assets/svg/arrow_right.svg similarity index 100% rename from public/assets/svg/ArrowRight.svg rename to public/assets/svg/arrow_right.svg diff --git a/public/assets/svg/BackIcon.svg b/public/assets/svg/back_icon.svg similarity index 100% rename from public/assets/svg/BackIcon.svg rename to public/assets/svg/back_icon.svg diff --git a/public/assets/svg/delete_tag.svg b/public/assets/svg/delete_tag.svg new file mode 100644 index 00000000..1af75bea --- /dev/null +++ b/public/assets/svg/delete_tag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/svg/InquiryEmpty.svg b/public/assets/svg/inquiry_empty.svg similarity index 100% rename from public/assets/svg/InquiryEmpty.svg rename to public/assets/svg/inquiry_empty.svg diff --git a/public/assets/svg/no_comment.svg b/public/assets/svg/no_comment.svg new file mode 100644 index 00000000..d17ed062 --- /dev/null +++ b/public/assets/svg/no_comment.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/svg/Plus.svg b/public/assets/svg/plus_icon.svg similarity index 100% rename from public/assets/svg/Plus.svg rename to public/assets/svg/plus_icon.svg diff --git a/public/assets/svg/ProfileIcon.svg b/public/assets/svg/profile_icon.svg similarity index 100% rename from public/assets/svg/ProfileIcon.svg rename to public/assets/svg/profile_icon.svg diff --git a/public/assets/svg/setting_icon.svg b/public/assets/svg/setting_icon.svg new file mode 100644 index 00000000..dd7ed7f5 --- /dev/null +++ b/public/assets/svg/setting_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/svg/Sort.svg b/public/assets/svg/sort_arrow.svg similarity index 100% rename from public/assets/svg/Sort.svg rename to public/assets/svg/sort_arrow.svg diff --git a/src/app/ClientLayout.tsx b/src/app/ClientLayout.tsx index a504592f..abc71bd9 100644 --- a/src/app/ClientLayout.tsx +++ b/src/app/ClientLayout.tsx @@ -1,8 +1,8 @@ 'use client' import { ThemeProvider } from 'styled-components' -import { theme } from './styles/theme' -import GlobalStyles from './styles/GlobalStyles' +import { theme } from '../styles/theme' +import GlobalStyles from '../styles/GlobalStyles' export default function ClientLayout({ children, diff --git a/src/app/Home/page.style.ts b/src/app/Home/home.module.scss similarity index 54% rename from src/app/Home/page.style.ts rename to src/app/Home/home.module.scss index 04c6fcd8..510a4363 100644 --- a/src/app/Home/page.style.ts +++ b/src/app/Home/home.module.scss @@ -1,91 +1,91 @@ -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +@import '../../styles/variables.scss'; -export const HeaderTop = styled.header` +.header-top { display: flex; justify-content: center; position: sticky; top: 0; background-color: #ffffff; -` + position: sticky; + z-index: 10; +} -export const HeaderNav = styled.nav` +.header-nav { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 0.6rem 0px; margin: 0 13rem; - @media (max-width: 744px) { + @media (max-width: 1023px) { margin: 0 1.5rem; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 0 1rem; } -` +} -export const HeaderLogo = styled.div` +.ghader-logo { width: 100%; height: 100%; display: flex; align-items: center; -` +} -export const ButtonWrapper = styled.div` +.button-wrapper { width: auto; height: auto; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: max-content; } -` +} -export const HeaderLogoFace = styled.img` +.header-logo-face { width: 2.5rem; height: 2.5rem; margin-right: 0.5rem; display: flex; - @media (max-width: 375px) { + @media (max-width: 743px) { width: 0; } -` +} -export const HeaderLogoName = styled.img` +.header-logo-name { width: 6.44rem; height: 2.19rem; display: flex; -` +} -export const HeaderLogin = styled.a` +.header-login { width: 10.16rem; height: 3rem; - background-color: #3692ff; + background-color: $primaryBlue-100; border-radius: 8px; - ${(props) => textStyle(16, 600)(props)} - color: ${theme.colors.SecondaryGray[50]}; + @include apply-font($font-16, 600); + color: $secondaryGray-50; text-decoration: none; display: flex; justify-content: center; align-items: center; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 9.73rem; } - @media (max-width: 375px) { + @media (max-width: 743px) { width: 9.73rem; } -` +} -export const HeaderMain = styled.div` +.header-main { width: 100%; background-color: #cfe5ff; -` +} -export const HeaderMainContainer = styled.div` +.header-main-container { width: max-content; display: flex; padding: 12.5rem 0px 0px 0px; margin: 0 auto; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; height: 771px; flex-direction: column; @@ -93,110 +93,110 @@ export const HeaderMainContainer = styled.div` justify-content: space-between; padding: 0; } - @media (max-width: 375px) { + @media (max-width: 743px) { width: 100%; height: auto; img { width: 100%; } } -` +} -export const HeaderMainTitle = styled.div` +.header-main-title { width: auto; margin: auto 0; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; } - @media (max-width: 375px) { + @media (max-width: 743px) { width: 15rem; margin-bottom: 8.25rem; } -` +} -export const HeaderTitleFont = styled.h1` +.header-title-font { width: 20rem; - ${(props) => textStyle(40, 700)(props)} + @include apply-font($font-40, 700); margin: 0 4rem 2rem 0; - color: ${theme.colors.SecondaryGray[700]}; - @media (max-width: 744px) { + color: $secondaryGray-700; + @media (max-width: 1023px) { margin-top: 84px; width: auto; margin: 84px auto 24px; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin-bottom: 1.125rem; text-align: center; - ${(props) => textStyle(32, 700)(props)} + @include apply-font($font-32, 700); width: 254px; } -` +} -export const HeaderItems = styled.a` +.header-items { width: 7.25rem; - background-color: ${theme.colors.PrimaryBlue[100]}; + background-color: $primaryBlue-100; border-radius: 40px; - ${(props) => textStyle(20, 600)(props)} - color: ${theme.colors.SecondaryGray[50]}; + @include apply-font($font-20, 600); + color: $secondaryGray-50; text-decoration: none; padding: 0.75rem 7.75rem; display: inline-block; - @media (max-width: 375px) { + @media (max-width: 743px) { padding: 0.5rem 4rem; margin-top: 1.13rem; } -` +} /* 메인 */ -export const MainBasic = styled.main` +.main-basic { display: flex; flex-direction: column; align-items: center; - @media (max-width: 744px) { + @media (max-width: 1023px) { margin: 24px; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 52px 16px 2.5rem 15px; width: 344px; } -` +} -export const MainTheme = styled.div` +.main-theme { display: flex; background-color: #fcfcfc; margin: 8.63rem auto; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; flex-direction: column; margin: 0; } -` +} -export const MainPopularSellImage = styled.picture` +.main-popular-sell-image { width: auto; height: 27.75rem; - @media (max-width: 744px) { + @media (max-width: 1023px) { img { width: 100%; height: 32.813rem; } } - @media (max-width: 375px) { + @media (max-width: 743px) { height: 16.187rem; img { width: 100%; height: 100%; } } -` +} -export const MainThemeBasic = styled.div` +.main-theme-basic { width: 18.625rem; display: flex; justify-content: center; @@ -204,89 +204,87 @@ export const MainThemeBasic = styled.div` background-color: #fcfcfc; flex-direction: column; margin: auto 4rem; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; margin: 0; } - @media (max-width: 375px) { + @media (max-width: 743px) { height: 134px; margin: 1.5rem auto 2.69rem 0; } -` +} -export const MainPopularSellFontTop = styled.p` +.main-popular-sell-font-top { margin: 0 0px 0.75rem 0; - ${(props) => textStyle(18, 700)(props)} - color: ${theme.colors.PrimaryBlue[100]}; - @media (max-width: 744px) { + @include apply-font($font-18, 700); + color: $primaryBlue-100; + @media (max-width: 1023px) { margin: 1.5rem auto 1rem; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 0 auto 0.5rem 0rem; - ${(props) => textStyle(16, 700)(props)} + @include apply-font($font-16, 700); } -` +} -export const MainPopularSellFontMiddle = styled.h2` +.main-poular-sell-font-middle { width: 306px; margin: 0 auto 1.5rem 0; - ${(props) => textStyle(40, 700)(props)} - color: ${theme.colors.SecondaryGray[700]}; - @media (max-width: 744px) { - ${(props) => textStyle(32, 700)(props)} - width:100%; - } - @media (max-width: 375px) { - ${(props) => textStyle(24, 700)(props)} + @include apply-font($font-40, 700); + color: $secondaryGray-700; + @media (max-width: 1023px) { + @include apply-font($font-32, 700); + width: 100%; + } + @media (max-width: 743px) { + @include apply-font($font-24, 700); margin-bottom: 1rem; line-height: 2rem; } -` +} -export const MainPopularSellFontBottom = styled.p` - ${(props) => textStyle(24, 500)(props)} - color: ${theme.colors.SecondaryGray[700]}; +.main-popular-sell-font-bottom { + @include apply-font($font-24, 500); + color: $secondaryGray-700; width: 100%; - @media (max-width: 744px) { - ${(props) => textStyle(18, 500)(props)} - width:100%; + @media (max-width: 1023px) { + @include apply-font($font-18, 500); + width: 100%; width: 15.75rem; margin-bottom: 3.25rem; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 0; - ${(props) => textStyle(16, 500)(props)} + @include apply-font($font-16, 500); width: 205px; } -` - -export const MainThemeCenter = styled.div` +} +.main-theme-center { display: flex; background-color: #fcfcfc; margin: 8.63rem auto; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; flex-direction: column-reverse; margin: 0; } - @media (max-width: 375px) { + @media (max-width: 743px) { flex-wrap: wrap-reverse; } -` - -export const MainThemeBasicMiddle = styled(MainThemeBasic)` +} +.main-theme-basic-middle { align-items: flex-end; text-align: right; - @media (max-width: 375px) { + @media (max-width: 743px) { width: 330px; margin-right: 0; } -` +} -export const MainSearchImage = styled.picture` +.main-search-image { width: 579px; height: 444px; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 100%; height: 100%; margin: 0; @@ -295,106 +293,106 @@ export const MainSearchImage = styled.picture` height: 32.5rem; } } - @media (max-width: 375px) { + @media (max-width: 743px) { height: 16.187rem; img { width: 100%; height: 100%; } } -` +} /* footer */ -export const FooterMainContainer = styled.footer` +.footer-main-container { display: flex; flex-direction: column; align-items: center; -` +} -export const FooterEmpty = styled.div` +.footer-empty { width: 100%; height: 138px; background-color: #fcfcfc; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 0; height: 0; } -` +} -export const FooterContainer = styled.div` +.footer-container { display: flex; justify-content: center; align-items: center; width: 100%; background-color: #cfe5ff; - @media (max-width: 744px) { + @media (max-width: 1023px) { } - @media (max-width: 375px) { + @media (max-width: 743px) { height: 33.75rem; } -` -export const FooterBackground = styled.div` +} +.footer-background { display: flex; align-items: center; height: 397px; margin-top: 9rem; width: 69.375rem; - @media (max-width: 744px) { + @media (max-width: 1023px) { flex-direction: column; justify-content: space-between; width: 100%; margin-top: 0; height: 100%; } -` +} -export const FooterFont = styled.h2` - ${(props) => textStyle(40, 700)(props)} - @media (max-width: 744px) { +.footer-font { + @include apply-font($font-40, 700); + @media (max-width: 1023px) { margin: 12.562rem auto 13.562rem; width: 20.438rem; text-align: center; line-height: 56px; } - @media (max-width: 375px) { - ${(props) => textStyle(32, 700)(props)} + @media (max-width: 743px) { + @include apply-font($font-32, 700); margin: 121px auto 131px; width: 254px; height: 90px; text-align: center; } -` +} -export const FooterImage = styled.picture` +.footer-image { width: 46.63rem; height: 24.81rem; - @media (max-width: 375px) { + @media (max-width: 743px) { width: 100%; img { width: 100%; } } -` +} -export const FooterNav = styled.div` +.footer-nav { width: 100%; - background-color: ${theme.colors.SecondaryGray[900]}; + background-color: $secondaryGray-900; display: flex; justify-content: center; -` +} -export const FooterNavMain = styled.div` +.footern-nav-main { height: 1.25rem; width: 70rem; display: flex; justify-content: space-between; align-items: center; margin: 2rem 0 6.75rem 0; - @media (max-width: 744px) { + @media (max-width: 1023px) { width: 33.5rem; margin: 2rem 8rem 6.75rem 8rem; } - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 2rem; display: grid; grid-template-areas: @@ -403,14 +401,14 @@ export const FooterNavMain = styled.div` width: 100%; height: 98px; } -` +} -export const Codeit = styled.p` - color: ${theme.colors.SecondaryGray[400]}; - @media (max-width: 744px) { - ${(props) => textStyle(16, 400)(props)} +.codeit { + color: $secondaryGray-400; + @media (max-width: 1023px) { + @include apply-font($font-16, 400); } - @media (max-width: 375px) { + @media (max-width: 743px) { grid-area: ct; font-weight: 400; font-size: 16px; @@ -420,44 +418,44 @@ export const Codeit = styled.p` line-height: 18.4px; text-align: center; } -` +} -export const PrivacyFaq = styled.div` +.privacy-faq { margin-right: 13px; - @media (max-width: 375px) { + @media (max-width: 743px) { grid-area: hd; align-items: center; justify-items: center; margin: 0; width: 12.32rem; } -` +} -export const Social = styled.div` +.social { width: 7.25rem; height: 20px; display: flex; justify-content: space-between; - @image { + image { width: 18px; height: 18px; } -` +} -export const Privacy = styled.div` +.privacy { margin: auto 2rem auto 0; - color: ${theme.colors.SecondaryGray[200]}; + color: $secondaryGray-200; text-decoration: none; - @media (max-width: 375px) { + @media (max-width: 743px) { margin: 0 30px 0 0; } -` +} -export const Faq = styled.div` - color: ${theme.colors.SecondaryGray[200]}; +.faq { + color: $secondaryGray-200; text-decoration: none; margin: auto 0; - @media (max-width: 375px) { + @media (max-width: 743px) { margin-right: 2rem; } -` +} diff --git a/src/app/Home/page.tsx b/src/app/Home/page.tsx index 8a37f74c..3a3ad21a 100644 --- a/src/app/Home/page.tsx +++ b/src/app/Home/page.tsx @@ -2,8 +2,8 @@ import React from 'react' import Link from 'next/link' -import * as S from './page.style' -import Button from '../common/Button' +import styles from './home.module.scss' +import Button from '../../components/common/Button' import Logo from '../../../public/assets/image/Logo.png' import LogoFace from '../../../public/assets/image/LogoFace.png' @@ -21,124 +21,130 @@ import Image from 'next/image' function Home() { return ( <> - - - +
+
판다마켓 로고 사진 판다마켓 로고 사진 - - +
+
- - - - - - - +
+ + +
+
+
+

일상의 모든 물건을 거래해 보세요 - - +

+
- - +
+
판다마켓 백그라운드사진 - - - - - +
+
+
+
+
판다마켓 인기 상품 사진 - - +
+
- Hot item +
+ Hot item +
- +
인기 상품을 확인해 보세요 - +
- +
가장 HOT한 중고거래 물품을 판다 마켓에서 확인해 보세요 - +
- - - - +
+
+
+
- Search +
Search
- +
구매를 원하는 상품을 검색하세요 - +
- +
구매하고 싶은 물품은 검색해서 쉽게 찾아보세요 - +
- - +
+
판다마켓 상품 검색 사진 - - - - +
+
+
+
판다마켓 인기 상품 등록 - - +
+
- Register +
+ Register +
- +
판매를 원하는 상품을 등록하세요 - +
- +
어떤 물건이든 판매하고 싶은  상품을 쉽게 등록하세요 - +
- - - - - - - +
+
+
+
+
+
+
- 믿을 수 있는 판다마켓 중고 거래 +
+ 믿을 수 있는 판다마켓 중고 거래 +
- +
판다마켓 백그라운드사진 - - - - - - ©codeit - 2024 - +
+
+
+
+
+
©codeit - 2024
+
- Privacy Policy +
Privacy Policy
- FAQ +
FAQ
- - +
+
페이스북 로고 사진 @@ -151,10 +157,10 @@ function Home() { 인스타그램 로고 사진 - - - - +
+
+
+
) } diff --git a/src/app/Items/BestItems.module.scss b/src/app/Items/BestItems.module.scss new file mode 100644 index 00000000..1502726f --- /dev/null +++ b/src/app/Items/BestItems.module.scss @@ -0,0 +1,85 @@ +@import '../../styles//variables.scss'; + +.bone { + height: 42.6rem; + width: auto; + margin-bottom: 4rem; + @media (max-width: 1023px) { + height: 48.2rem; + } + @media (max-width: 743px) { + margin-bottom: 2.4rem; + } +} +.title { + @include apply-font($font-20, 700); + color: $secondaryGray-900; + margin-bottom: 1.6rem; +} + +.best-item { + display: flex; + height: 37.8rem; + flex-direction: column; + cursor: pointer; + justify-content: space-between; + img { + width: 28.2rem; + height: 28.2rem; + border-radius: 1rem; + } + @media (max-width: 1023px) { + height: 43.4rem; + img { + width: 34.3rem; + height: 34.3rem; + } + } +} +.best-item-image { + width: 17.625rem; + height: 17.625rem; + border-radius: 1rem; + @media (max-width: 1023px) { + height: 21.437rem; + width: 100%; + } +} +.best-items-display { + display: flex; + align-items: center; + justify-content: space-between; + @media (max-width: 1023px) { + gap: 10px; + } +} +.product-description { + width: 100%; + height: 8rem; + margin-top: 1.6rem; + display: flex; + flex-direction: column; + justify-content: space-between; + @media (max-width: 1023px) { + margin-top: 0.625rem; + } +} +.product-name { + @include apply-font($font-14, 500); + color: $secondaryGray-800; +} +.product-price { + @include apply-font($font-16, 700); + color: $secondaryGray-800; +} +.product-favorite-count { + @include apply-font($font-12, 500); + color: $secondaryGray-600; + display: flex; + align-items: center; + gap: 0.4rem; + img { + width: 1.6rem; + height: 1.6rem; + } +} diff --git a/src/app/Items/BestItems.tsx b/src/app/Items/BestItems.tsx index d01f1f0c..d9235641 100644 --- a/src/app/Items/BestItems.tsx +++ b/src/app/Items/BestItems.tsx @@ -1,15 +1,13 @@ 'use client' import React, { useEffect, useState } from 'react' import Link from 'next/link' +import Image from 'next/image' -import { GetProductIdTypes } from '../types/product' +import { GetProductIdTypes } from '../../types/product' -import HeartInactive from '../../../public/assets/image/HeartInactive.png' +import HeartInactive from '../../../public/assets/image/heart_inactive.png' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import styled from 'styled-components' -import Image from 'next/image' +import styles from './BestItems.module.scss' interface BestItemsProps { products: GetProductIdTypes[] @@ -36,14 +34,16 @@ const BestItems = ({ products }: BestItemsProps) => { }, []) return ( - - 베스트 상품 +
+ {/* 점 표기법이 아니고 [] 사용한 이유는 클래스명에 - 하이픈이 있을 수도 있고 동적일 때도 []를 쓰지만 여기서는 하이픈 이유 때문에 []를 씀*/} +
베스트 상품
- +
{list.slice(0, itemsDisplay).map((product) => ( - - + 0 ? product.images[0] @@ -51,106 +51,22 @@ const BestItems = ({ products }: BestItemsProps) => { } alt={product.name} /> - - {product.name} - +
+
{product.name}
+
{product.price.toLocaleString('ko-KR')}원 - - +
+
HeartInactive {product.favoriteCount} - - - +
+
+
))} -
- +
+
) } export default BestItems - -const Bone = styled.div` - height: 42.6rem; - width: auto; - margin-bottom: 4rem; - @media (max-width: 1023px) { - height: 48.2rem; - } - @media (max-width: 743px) { - margin-bottom: 2.4rem; - } -` -const Title = styled.div` - ${(props) => textStyle(20, 700)(props)} - color: ${theme.colors.SecondaryGray[900]}; - margin-bottom: 1.6rem; -` - -const BestItem = styled.div` - display: flex; - height: 37.8rem; - flex-direction: column; - cursor: pointer; - justify-content: space-between; - img { - width: 28.2rem; - height: 28.2rem; - border-radius: 1rem; - } - @media (max-width: 1023px) { - height: 43.4rem; - img { - width: 34.3rem; - height: 34.3rem; - } - } -` -const BestItemImage = styled.img` - width: 17.625rem; - height: 17.625rem; - border-radius: 1rem; - @media (max-width: 1023px) { - height: 21.437rem; - width: 100%; - } -` -const BestItemsDisplay = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - @media (max-width: 1023px) { - gap: 10px; - } -` -const ProductDescription = styled.div` - width: 100%; - height: 8rem; - margin-top: 1.6rem; - display: flex; - flex-direction: column; - justify-content: space-between; - @media (max-width: 1023px) { - margin-top: 0.625rem; - } -` -const ProductName = styled.div` - ${(props) => textStyle(14, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ProductPrice = styled.div` - ${(props) => textStyle(16, 700)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ProductFavoriteCount = styled.div` - ${(props) => textStyle(12, 500)(props)} - color: ${theme.colors.SecondaryGray[600]}; - display: flex; - align-items: center; - gap: 0.4rem; - img { - width: 1.6rem; - height: 1.6rem; - } -` diff --git a/src/app/Items/RecentItems.module.scss b/src/app/Items/RecentItems.module.scss new file mode 100644 index 00000000..b20d5639 --- /dev/null +++ b/src/app/Items/RecentItems.module.scss @@ -0,0 +1,73 @@ +@import '../../styles//variables.scss'; + +.recent-item { + height: 67.4rem; + @media (max-width: 743px) { + height: fit-content; + } +} +.recent-item-key { + display: flex; + height: 31.7rem; + width: 22.1rem; + justify-content: center; + flex-direction: column; + img { + width: 22.1rem; + height: 22.1rem; + border-radius: 1rem; + } + @media (max-width: 743px) { + height: 26.4rem; + width: 16.8rem; + img { + width: 16.8rem; + height: 16.8rem; + border-radius: 1rem; + } + } +} +.recent-item-image { + width: inherit; + height: 100%; + border-radius: 1rem; +} +.recent-items-display { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; + flex-wrap: wrap; + align-content: flex-start; + row-gap: 4rem; + @media (max-width: 743px) { + row-gap: 2rem; + } +} +.product-description { + width: 100%; + height: 8rem; + margin-top: 1.6rem; + display: flex; + flex-direction: column; + justify-content: space-between; +} +.product-name { + @include apply-font($font-14, 500); + color: $secondaryGray-800; +} +.product-price { + @include apply-font($font-16, 700); + color: $secondaryGray-800; +} +.product-favorite-count { + @include apply-font($font-12, 500); + color: $secondaryGray-600; + display: flex; + align-items: center; + gap: 0.4rem; + img { + width: 1.6rem; + height: 1.6rem; + } +} diff --git a/src/app/Items/RecentItems.tsx b/src/app/Items/RecentItems.tsx index 616ae9a7..a7e5bcd0 100644 --- a/src/app/Items/RecentItems.tsx +++ b/src/app/Items/RecentItems.tsx @@ -4,14 +4,12 @@ import React, { useEffect, useState } from 'react' import Link from 'next/link' import Image from 'next/image' -import { GetProductIdTypes } from '../types/product' +import { GetProductIdTypes } from '../../types/product' -import HeartInactive from '../../../public/assets/image/HeartInactive.png' -import NoImage from '../../../public/assets/image/NoImage.png' +import HeartInactive from '../../../public/assets/image/heart_inactive.png' +import NoImage from '../../../public/assets/image/no_image.png' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +import styles from './RecentItems.module.scss' const RecentItems = ({ products }: { products: GetProductIdTypes[] }) => { const [itemsDisplay, setItemsDisplay] = useState(1) @@ -32,113 +30,36 @@ const RecentItems = ({ products }: { products: GetProductIdTypes[] }) => { return () => window.removeEventListener('resize', handleReasize) }, []) return ( - <> - - - {products.slice(0, itemsDisplay).map((product) => ( - - - 0 - ? product.images[0] - : NoImage.src - } - alt={product.name} - /> - - {product.name} - - {product.price.toLocaleString('ko-KR')}원 - - - HeartInactive - {product.favoriteCount} - - - - - ))} - - - +
+
+ {products.slice(0, itemsDisplay).map((product) => ( + +
+ 0 + ? product.images[0] + : NoImage.src + } + alt={product.name} + /> +
+
{product.name}
+
+ {product.price.toLocaleString('ko-KR')}원 +
+
+ HeartInactive + {product.favoriteCount} +
+
+
+ + ))} +
+
) } export default RecentItems - -const RecentItem = styled.div` - height: 67.4rem; - @media (max-width: 743px) { - height: fit-content; - } -` -const RecentItemKey = styled.div` - display: flex; - height: 31.7rem; - width: 22.1rem; - justify-content: center; - flex-direction: column; - img { - width: 22.1rem; - height: 22.1rem; - border-radius: 1rem; - } - @media (max-width: 743px) { - height: 26.4rem; - width: 16.8rem; - img { - width: 16.8rem; - height: 16.8rem; - border-radius: 1rem; - } - } -` -const RecentItemImage = styled.img` - width: inherit; - height: 100%; - border-radius: 1rem; -` -const RecentItemsDisplay = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - flex-direction: row; - flex-wrap: wrap; - align-content: flex-start; - row-gap: 4rem; - @media (max-width: 743px) { - row-gap: 2rem; - } -` -const ProductDescription = styled.div` - width: 100%; - height: 8rem; - margin-top: 1.6rem; - display: flex; - flex-direction: column; - justify-content: space-between; -` -const ProductName = styled.div` - ${(props) => textStyle(14, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ProductPrice = styled.div` - ${(props) => textStyle(16, 700)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ProductFavoriteCount = styled.div` - ${(props) => textStyle(12, 500)(props)} - color: ${theme.colors.SecondaryGray[600]}; - display: flex; - align-items: center; - gap: 0.4rem; - img { - width: 1.6rem; - height: 1.6rem; - } -` diff --git a/src/app/Items/[productId]/ItemsDetailDescription.module.scss b/src/app/Items/[productId]/ItemsDetailDescription.module.scss new file mode 100644 index 00000000..c149cb41 --- /dev/null +++ b/src/app/Items/[productId]/ItemsDetailDescription.module.scss @@ -0,0 +1,144 @@ +@import '../../../styles/variables'; +.bone { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 4rem; + border-bottom: 1px solid $secondaryGray-200; + @media (max-width: 1023px) { + align-items: flex-start; + } + @media (max-width: 743px) { + flex-direction: column; + padding-bottom: 2.4rem; + } +} +.product-image { + width: 48.6rem; + height: 48.6rem; + border-radius: 1rem; + @media (max-width: 1023px) { + width: 34rem; + height: 34rem; + } + @media (max-width: 743px) { + margin-bottom: 1rem; + } +} +.title-description { + width: 69rem; + height: 49.6rem; + position: relative; + @media (max-width: 1023px) { + width: 34rem; + height: 48.4rem; + } + @media (max-width: 743px) { + height: 49.2rem; + } +} +.product-title-wrapper { + height: 9.8rem; + display: flex; + justify-content: space-evenly; + flex-direction: column; + border-bottom: 1px solid $secondaryGray-200; + @media (max-width: 743px) { + height: 6.6rem; + justify-content: flex-start; + } +} +.product-title { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +} +.product-name { + @include apply-font($font-24, 600); + color: $secondaryGray-800; + @media (max-width: 1023px) { + @include apply-font($font-20, 600); + } + @media (max-width: 743px) { + @include apply-font($font-16, 600); + } +} +.product-price { + @include apply-font($font-40, 600); + color: $secondaryGray-800; + @media (max-width: 1023px) { + @include apply-font($font-32, 600); + } + @media (max-width: 743px) { + @include apply-font($font-24, 600); + } +} +.product-description-wrapper { + margin: 2.4rem 0; + height: auto; + @media (max-width: 1023px) { + margin: 1rem 0 2.3rem; + } +} +.product-description-text { + @include apply-font($font-16, 600); + color: $secondaryGray-600; + + margin-bottom: 1rem; + @media (max-width: 1023px) { + @include apply-font($font-14, 600); + } +} +.product-description { + @include apply-font($font-16, 400); + color: $secondaryGray-600; +} +.product-tag-wrapper { + @include apply-font($font-16, 400); + color: $secondaryGray-600; + @media (max-width: 1023px) { + @include apply-font($font-14, 600); + } + @media (max-width: 743px) { + margin-bottom: 2.5rem; + } +} +.product-tag { + display: flex; + gap: 0.8rem; + flex-direction: row; + flex-wrap: wrap; +} +.product-footer { + width: 100%; + position: absolute; + bottom: 1px; + display: flex; + align-items: center; + justify-content: space-between; +} +.product-footer-left { + align-items: center; + justify-content: center; + display: flex; + gap: 1.6rem; +} +.product-footer-right { + display: flex; + gap: 0.6rem; + align-items: center; + padding: 0.4rem 1.2rem; + border: 1px solid $secondaryGray-200; + border-radius: 3.5rem; +} +.owner-nickname { + @include apply-font($font-14, 500); + color: $secondaryGray-600; +} +.updatedat { + @include apply-font($font-14, 400); + color: $secondaryGray-400; +} diff --git a/src/app/Items/[productId]/ItemsDetailDescription.tsx b/src/app/Items/[productId]/ItemsDetailDescription.tsx index 493220f4..ca35d8fc 100644 --- a/src/app/Items/[productId]/ItemsDetailDescription.tsx +++ b/src/app/Items/[productId]/ItemsDetailDescription.tsx @@ -2,19 +2,17 @@ import React from 'react' import { useParams } from 'next/navigation' +import Image from 'next/image' -import { useGetProductId } from '../../hooks/useProductService' -import Tag from '../../common/Tag' -import { formatDate } from '../../utils/datetime' +import { useGetProductId } from '../../../hooks/useProductService' +import Tag from '../../../components/common/Tag' +import { formatDate } from '../../../utils/datetime' -import Setting from '../../../../public/assets/svg/Setting.svg' -import ProfileIcon from '../../../../public/assets/svg/ProfileIcon.svg' -import HeartInactive from '../../../../public/assets/image/HeartInactive.png' +import Setting from '../../../../public/assets/svg/setting_icon.svg' +import ProfileIcon from '../../../../public/assets/svg/profile_icon.svg' +import HeartInactive from '../../../../public/assets/image/heart_inactive.png' -import styled from 'styled-components' -import { theme } from '../../styles/theme' -import { textStyle } from '../../styles/textStyle' -import Image from 'next/image' +import styles from './ItemsDetailDescription.module.scss' const ItemsDetailDescription = () => { const params = useParams() @@ -27,188 +25,56 @@ const ItemsDetailDescription = () => { return
상품 정보를 불러오는 중입니다...
} return ( - - - - - - {product.name} +
+ {product.name} +
+
+
+
{product.name}
상품설정버튼 - - {product.price.toLocaleString()}원 - - - 상품 소개 - {product.description} - - - 상품 태그 - +
+
+ {product.price.toLocaleString()}원 +
+
+
+
상품 소개
+
+ {product.description} +
+
+
+
상품 태그
+
{product.tags?.map((tag, index) => ( ))} - - - - +
+
+
+
프로필아이콘
- {product.ownerNickname} - {formatDate(product.createdAt)} +
+ {product.ownerNickname} +
+
+ {formatDate(product.createdAt)} +
- - +
+
하트활성화/비활성화
{product.favoriteCount}
- - - - +
+
+
+
) } export default ItemsDetailDescription - -const Bone = styled.div` - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: space-between; - padding-bottom: 4rem; - border-bottom: 1px solid ${theme.colors.SecondaryGray[200]}; - @media (max-width: 1023px) { - align-items: flex-start; - } - @media (max-width: 743px) { - flex-direction: column; - padding-bottom: 2.4rem; - } -` -const ProductImage = styled.img` - width: 48.6rem; - height: 48.6rem; - border-radius: 1rem; - @media (max-width: 1023px) { - width: 34rem; - height: 34rem; - } - @media (max-width: 743px) { - margin-bottom: 1rem; - } -` -const TitleDescription = styled.div` - width: 69rem; - height: 49.6rem; - position: relative; - @media (max-width: 1023px) { - width: 34rem; - height: 48.4rem; - } - @media (max-width: 743px) { - height: 49.2rem; - } -` -const ProductTitleWrapper = styled.div` - height: 9.8rem; - display: flex; - justify-content: space-evenly; - flex-direction: column; - border-bottom: 1px solid ${theme.colors.SecondaryGray[200]}; - @media (max-width: 743px) { - height: 6.6rem; - justify-content: flex-start; - } -` -const ProductTitle = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; -` -const ProductName = styled.div` - ${(props) => textStyle(24, 600)(props)} - color: ${theme.colors.SecondaryGray[800]}; - @media (max-width: 1023px) { - ${(props) => textStyle(20, 600)(props)} - } - @media (max-width: 743px) { - ${(props) => textStyle(16, 600)(props)} - } -` -const ProductPrice = styled.div` - ${(props) => textStyle(40, 600)(props)} - color: ${theme.colors.SecondaryGray[800]}; - @media (max-width: 1023px) { - ${(props) => textStyle(32, 600)(props)} - } - @media (max-width: 743px) { - ${(props) => textStyle(24, 600)(props)} - } -` -const ProductDescriptionWrapper = styled.div` - margin: 2.4rem 0; - height: auto; - @media (max-width: 1023px) { - margin: 1rem 0 2.3rem; - } -` -const ProductDescriptionText = styled.div` - ${(props) => textStyle(16, 600)(props)} - color: ${theme.colors.SecondaryGray[600]}; - - margin-bottom: 1rem; - @media (max-width: 1023px) { - ${(props) => textStyle(14, 600)(props)} - } -` -const ProductDescription = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[600]}; -` -const ProductTagWrapper = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[600]}; - @media (max-width: 1023px) { - ${(props) => textStyle(14, 600)(props)} - } - @media (max-width: 743px) { - margin-bottom: 2.5rem; - } -` -const ProductTag = styled.div` - display: flex; - gap: 0.8rem; - flex-direction: row; - flex-wrap: wrap; -` -const ProductFooter = styled.div` - width: 100%; - position: absolute; - bottom: 1px; - display: flex; - align-items: center; - justify-content: space-between; -` -const ProductFooterLeft = styled.div` - align-items: center; - justify-content: center; - display: flex; - gap: 1.6rem; -` -const ProductFooterRight = styled.div` - display: flex; - gap: 0.6rem; - align-items: center; - padding: 0.4rem 1.2rem; - border: 1px solid ${theme.colors.SecondaryGray[200]}; - border-radius: 3.5rem; -` -const OwnerNickname = styled.div` - ${(props) => textStyle(14, 500)(props)} - color: ${theme.colors.SecondaryGray[600]}; -` -const UpdatedAt = styled.div` - ${(props) => textStyle(14, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; -` diff --git a/src/app/Items/[productId]/ItemsDetailQuestionArrary.module.scss b/src/app/Items/[productId]/ItemsDetailQuestionArrary.module.scss new file mode 100644 index 00000000..f12fe0ff --- /dev/null +++ b/src/app/Items/[productId]/ItemsDetailQuestionArrary.module.scss @@ -0,0 +1,126 @@ +@import '../../../styles/variables'; + +.edit-bone { + position: relative; + margin: 1rem 0 7rem; + z-index: 1; +} +.editing-wrapper { + position: absolute; + width: 100%; + bottom: -212px; + @media (max-width: 1023px) { + bottom: -190px; + } +} +.button-wrapper { + width: max-content; +} +.edit-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.button-edit-wrapper { + display: flex; + align-items: center; + justify-content: end; + margin: 1.6rem 0 2.4rem; + gap: 0.4rem; +} +.edit-cancel-button { + @include apply-font($font-16, 600); + color: $secondaryGray-500; + width: 68px; + height: 47px; + display: flex; + align-items: center; + justify-content: center; +} + +.bone { + border-bottom: 1px solid $secondaryGray-200; + display: flex; + align-items: end; + justify-content: space-between; + height: 10rem; + position: relative; + padding-bottom: 1.2rem; + + margin-top: 2.4rem; + @media (max-width: 1023px) { + margin-top: 0; + } +} + +.question-content { + @include apply-font($font-14, 400); + color: $secondaryGray-800; + margin-bottom: 2.4rem; +} +.user-profile-image-wrapper { + display: flex; + gap: 0.5rem; + img { + border-radius: 50%; + } +} +.user-profile-image-right { + display: flex; + flex-direction: column; + justify-content: space-between; +} +.user-profile-name { + @include apply-font($font-12, 400); + color: $secondaryGray-600; +} +.diff-date { + @include apply-font($font-12, 400); + color: $secondaryGray-400; +} +.setting-button-wrapper { + position: relative; + top: -42px; + cursor: pointer; +} + +.select-option { + position: absolute; + top: 1.8rem; + left: -124px; + border: 1px solid #cccccc; + border-radius: 12px; + background-color: #ffffff; + color: #181818; + font-weight: 400; + font-size: 16px; + line-height: 26px; + max-height: 300px; + overflow-y: auto; + z-index: 10; + list-style: none; + padding: 0; + display: flex; + align-items: center; + flex-direction: column; + justify-content: space-around; + @media (max-width: 743px) { + position: absolute; + top: 30px; + left: -110px; + z-index: 1; + width: 130px; + height: 84px; + } +} +.option { + @include apply-font($font-16, 400); + color: $secondaryGray-500; + padding: 12px 40px; + cursor: pointer; + &:hover { + background-color: #f6f6f6; + } + @media (max-width: 743px) { + padding: 7px 35px; + } +} diff --git a/src/app/Items/[productId]/ItemsDetailQuestionArrary.tsx b/src/app/Items/[productId]/ItemsDetailQuestionArrary.tsx index 3b3002f9..3f31d252 100644 --- a/src/app/Items/[productId]/ItemsDetailQuestionArrary.tsx +++ b/src/app/Items/[productId]/ItemsDetailQuestionArrary.tsx @@ -3,23 +3,21 @@ import React, { useState, useEffect, useRef } from 'react' import Image from 'next/image' -import { PostCommentType } from '../../types/comment' -import Button from '../../common/Button' -import TextInputPlaceholder from '../../common/TextInputPlaceholder' -import commentService from '../../../../src/app/api/services/commentService' -import { diffDate } from '../../utils/datetime' -import { formatDate } from '../../utils/datetime' +import { PostCommentType } from '../../../types/comment' +import Button from '../../../components/common/Button' +import TextInputPlaceholder from '../../../components/common/TextInputPlaceholder' +import commentService from '../../../lib/api/service/commentService' +import { diffDate } from '../../../utils/datetime' +import { formatDate } from '../../../utils/datetime' -import Setting from '../../../../public/assets/svg/Setting.svg' +import Setting from '../../../../public/assets/svg/setting_icon.svg' -import styled from 'styled-components' -import { theme } from '../../styles/theme' -import { textStyle } from '../../styles/textStyle' +import styles from './ItemsDetailQuestionArrary.module.scss' interface ItemsDetailQuestionArraryProps { productQuestion: PostCommentType - setIsEditing: React.Dispatch> - isEditing: boolean + setIsEditing?: React.Dispatch> + isEditing?: boolean } const ItemsDetailQuestionArrary = ({ @@ -68,8 +66,8 @@ const ItemsDetailQuestionArrary = ({ return ( <> {isEditingState ? ( - - +
+
{/*로그인을 하지 않아 토큰?전달이 되지 않은 상태*/} setUserComment(e.target.value)} /> - - +
+ +
+ +
+
+
+
) : ( <> )} - +
- {productQuestion.content} - +
+ {productQuestion.content} +
+
{/*이미지가 없을 경우 기본 이미지 적용*/} 유저프로필사진 - - +
+
{productQuestion.writer.nickname} - +
{/*날짜 차이가 31일을 넘길 경우 createAt을 출력*/} - +
{diffDate(productQuestion.createdAt) > 31 ? ( <> {formatDate(productQuestion.createdAt)} @@ -122,151 +129,28 @@ const ItemsDetailQuestionArrary = ({ 일 전 )} - - - +
+
+
- +
수정, 삭제 선택 버튼 {isDropDownOpen && ( - - - - +
    +
  • + 수정하기 +
  • +
  • 삭제하기
  • +
)} - - +
+
) } export default ItemsDetailQuestionArrary - -const EditBone = styled.div` - position: relative; - margin: 1rem 0 7rem; - z-index: 1; -` -const EditingWrapper = styled.div` - position: absolute; - width: 100%; - bottom: -212px; - @media (max-width: 1023px) { - bottom: -190px; - } -` -const ButtonWrapper = styled.div` - width: max-content; -` -const EditButton = styled(Button)` - padding: 0.8rem 2.3rem; - width: max-content; -` -const ButtonEditWrapper = styled.div` - display: flex; - align-items: center; - justify-content: end; - margin: 1.6rem 0 2.4rem; - gap: 0.4rem; -` -const EditCalcelButton = styled.button` - ${(props) => textStyle(16, 600)(props)} - color: ${theme.colors.SecondaryGray[500]}; - width: 68px; - height: 47px; - display: flex; - align-items: center; - justify-content: center; -` - -const Bone = styled.div` - border-bottom: 1px solid ${theme.colors.SecondaryGray[200]}; - display: flex; - align-items: end; - justify-content: space-between; - height: 10rem; - position: relative; - padding-bottom: 1.2rem; - - margin-top: 2.4rem; - @media (max-width: 1023px) { - margin-top: 0; - } -` - -const QuestionContent = styled.div` - ${(props) => textStyle(14, 400)(props)} - color: ${theme.colors.SecondaryGray[800]}; - margin-bottom: 2.4rem; -` -const UserProfileImageWrapper = styled.div` - display: flex; - gap: 0.5rem; - img { - border-radius: 50%; - } -` -const UserProfileImageRight = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; -` -const UserProfileName = styled.div` - ${(props) => textStyle(12, 400)(props)} - color: ${theme.colors.SecondaryGray[600]}; -` -const DiffDate = styled.div` - ${(props) => textStyle(12, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; -` -const SettingButtonWrapper = styled.div` - position: relative; - top: -42px; - cursor: pointer; -` - -const SelectOption = styled.ul` - position: absolute; - top: 1.8rem; - left: -124px; - border: 1px solid #cccccc; - border-radius: 12px; - background-color: #ffffff; - color: #181818; - font-weight: 400; - font-size: 16px; - line-height: 26px; - max-height: 300px; - overflow-y: auto; - z-index: 10; - list-style: none; - padding: 0; - display: flex; - align-items: center; - flex-direction: column; - justify-content: space-around; - @media (max-width: 743px) { - position: absolute; - top: 30px; - left: -110px; - z-index: 1; - width: 130px; - height: 84px; - } -` -const Option = styled.li` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[500]}; - padding: 12px 40px; - cursor: pointer; - &:hover { - background-color: #f6f6f6; - } - @media (max-width: 743px) { - padding: 7px 35px; - } -` diff --git a/src/app/Items/[productId]/ItemsDetailQuestionTextarea.module.scss b/src/app/Items/[productId]/ItemsDetailQuestionTextarea.module.scss new file mode 100644 index 00000000..5de50885 --- /dev/null +++ b/src/app/Items/[productId]/ItemsDetailQuestionTextarea.module.scss @@ -0,0 +1,42 @@ +@import '../../../styles/variables'; + +.product-question-wrapper { + margin: 4rem auto 2.4rem; + @media (max-width: 1023px) { + margin: 4rem auto; + } + @media (max-width: 743px) { + margin: 2.5rem auto; + } +} +.product-question-text { + @include apply-font($font-16, 600); + color: $secondaryGray-900; + margin-bottom: 0.9rem; +} + +.button-wrapper { + width: fit-content; + margin: 1.6rem 0 0 auto; +} +.register-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.items-question-wrapper { + position: relative; + display: flex; + flex-direction: column; +} + +.inquiry-empty-wrapper { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.inquiry-empty-text { + @include apply-font($font-16, 400); + color: $secondaryGray-400; + margin-top: 0.5rem; +} diff --git a/src/app/Items/[productId]/ItemsDetailQuestionTextarea.tsx b/src/app/Items/[productId]/ItemsDetailQuestionTextarea.tsx index d01656b1..549ba2e0 100644 --- a/src/app/Items/[productId]/ItemsDetailQuestionTextarea.tsx +++ b/src/app/Items/[productId]/ItemsDetailQuestionTextarea.tsx @@ -4,16 +4,14 @@ import React, { useEffect, useState } from 'react' import { useParams } from 'next/navigation' import Image from 'next/image' -import { useGetCommentService } from '../../hooks/useCommentService' +import { useGetCommentService } from '../../../hooks/useCommentService' import ItemsDetailQuestionArrary from './ItemsDetailQuestionArrary' -import TextInputPlaceholder from '../../common/TextInputPlaceholder' -import Button from '../../common/Button' +import TextInputPlaceholder from '../../../components/common/TextInputPlaceholder' +import Button from '../../../components/common/Button' -import InquiryEmpty from '../../../../public/assets/svg/InquiryEmpty.svg' +import InquiryEmpty from '../../../../public/assets/svg/inquiry_empty.svg' -import styled from 'styled-components' -import { theme } from '../../styles/theme' -import { textStyle } from '../../styles/textStyle' +import styles from './ItemsDetailQuestionTextarea.module.scss' const ItemsDetailQuestionTextarea = () => { const params = useParams() // product 데어터 받기 @@ -44,9 +42,9 @@ const ItemsDetailQuestionTextarea = () => { }, []) return ( - - - 문의하기 + <> +
+
문의하기
{ value={questionText} onChange={(e) => setQuestionText(e.target.value)} /> - - + +
+ {productQuestion.list.length === 0 ? ( - +
문의가 없습니다 - 문의가 없습니다 - +
문의가 없습니다
+
) : ( <> - +
{productQuestion.list.map((question, index) => ( { isEditing={isEditing} /> ))} - +
)} -
+ ) } export default ItemsDetailQuestionTextarea - -const Bone = styled.div`` -const ProductQuestionWrapper = styled.div` - margin: 4rem auto 2.4rem; - @media (max-width: 1023px) { - margin: 4rem auto; - } - @media (max-width: 743px) { - margin: 2.5rem auto; - } -` -const ProductQuestionText = styled.div` - ${(props) => textStyle(16, 600)(props)} - color: ${theme.colors.SecondaryGray[900]}; - margin-bottom: 0.9rem; -` - -const ButtonWrapper = styled.div` - width: fit-content; - margin: 1.6rem 0 0 auto; -` -const RegisterButton = styled(Button)` - padding: 0.8rem 2.3rem; - width: max-content; -` -const ItemsQuestionWrapper = styled.div` - position: relative; - display: flex; - flex-direction: column; -` - -const InquiryEmptyWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -` -const InquiryEmptyText = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; - margin-top: 0.5rem; -` diff --git a/src/app/Items/[productId]/page.tsx b/src/app/Items/[productId]/page.tsx index cf04b23e..ba93e368 100644 --- a/src/app/Items/[productId]/page.tsx +++ b/src/app/Items/[productId]/page.tsx @@ -1,72 +1,38 @@ 'use client' import React from 'react' -import ItemsNavVar from '@/app/common/ItemsNavVar' +import Image from 'next/image' +import ItemsNavVar from '../../../components/domain/Nav/ItemsNavVar' import ItemsDetailDescription from './ItemsDetailDescription' import ItemsDetailQuestionTextarea from './ItemsDetailQuestionTextarea' -import Button from '../../common/Button' +import Button from '../../../components/common/Button' -import BackIcon from '../../../../public/assets/svg/BackIcon.svg' +import BackIcon from '../../../../public/assets/svg/back_icon.svg' -import styled from 'styled-components' -import { theme } from '../../styles/theme' -import { textStyle } from '../../styles/textStyle' -import Image from 'next/image' +import styles from './productId.module.scss' const ItemsDetail = () => { return ( <> - +
- - + +
+ ) } export default ItemsDetail - -const Bone = styled.div` - width: 120rem; - margin: 1.5rem auto 22.2rem; - - @media (max-width: 1023px) { - width: 69.6rem; - margin: 1.5rem auto 24.3rem auto; - } - @media (max-width: 743px) { - width: 34.4rem; - margin: 1rem auto 6.5rem auto; - } -` - -const ButtonWrapper = styled.div` - display: flex; - justify-content: center; - margin: 4.6875rem auto; - width: max-content; - ${(props) => textStyle(18, 600)(props)} - color: ${theme.colors.SecondaryGray[100]}; - @media (max-width: 1023px) { - margin: 3.5rem auto 10.4375rem auto; - } - @media (max-width: 743px) { - margin: 4rem auto 2.5rem auto; - } -` -const ListButton = styled(Button)` - padding: 1.1rem 3.9rem; - width: max-content; -` diff --git a/src/app/Items/[productId]/productId.module.scss b/src/app/Items/[productId]/productId.module.scss new file mode 100644 index 00000000..a6f49f63 --- /dev/null +++ b/src/app/Items/[productId]/productId.module.scss @@ -0,0 +1,34 @@ +@import '../../../styles/variables.scss'; + +.bone { + width: 120rem; + margin: 1.5rem auto 22.2rem; + + @media (max-width: 1023px) { + width: 69.6rem; + margin: 1.5rem auto 24.3rem auto; + } + @media (max-width: 743px) { + width: 34.4rem; + margin: 1rem auto 6.5rem auto; + } +} + +.button-wrapper { + display: flex; + justify-content: center; + margin: 4.6875rem auto; + width: max-content; + @include apply-font($font-18, 600); + color: $secondaryGray-100; + @media (max-width: 1023px) { + margin: 3.5rem auto 10.4375rem auto; + } + @media (max-width: 743px) { + margin: 4rem auto 2.5rem auto; + } +} +.list-button { + padding: 1.1rem 3.9rem; + width: max-content; +} diff --git a/src/app/Items/items.module.scss b/src/app/Items/items.module.scss new file mode 100644 index 00000000..471f8ce0 --- /dev/null +++ b/src/app/Items/items.module.scss @@ -0,0 +1,167 @@ +@import '../../styles/variables.scss'; + +.bone { + width: 120rem; + margin: 2.4rem auto; + @media (max-width: 1023px) { + width: 69.6rem; + } + @media (max-width: 743px) { + width: 34.4rem; + margin: 1rem auto; + } +} +.nav-var { + height: 4.2rem; + width: 100%; + margin: 0 auto 2.4rem; + display: flex; + align-items: center; + justify-content: space-between; + @media (max-width: 743px) { + flex-wrap: wrap; + margin: 0 auto 6.6rem; + } +} +.nav-title { + @include apply-font($font-20, 700); + color: $secondaryGray-900; + @media (max-width: 743px) { + height: 2.625rem; + display: flex; + align-items: center; + justify-content: center; + } +} +.nav-right-wrapper { + height: 100%; + display: flex; + align-items: center; + position: relative; + @media (max-width: 743px) { + flex-wrap: wrap; + } +} +.search-icon { + position: absolute; + right: 585px; + z-index: 10; + display: flex; + @media (max-width: 1023px) { + right: 494px; + } + @media (max-width: 743px) { + right: 308px; + top: 28px; + } +} +.register-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.nav-search { + width: 32.5rem; + height: 100%; + padding: 0.9rem 10.7rem 0.9rem 4.4rem; + border-radius: 1.2rem; + @include apply-font($font-16, 400); + color: $secondaryGray-400; + background-color: $secondaryGray-100; + border: none; + margin-right: 1.2rem; + @media (max-width: 1023px) { + width: 24.2rem; + padding: 9px 24px 9px 44px; + } + @media (max-width: 743px) { + position: relative; + top: 19px; + padding: 9px 40px 9px 44px; + width: max-content; + margin: 0; + } +} +.search-input { + border: none; + background-color: $secondaryGray-100; +} + +.button-wrapper { + margin-right: 1.2rem; + @media (max-width: 743px) { + position: relative; + width: max-content; + top: -74px; + left: 206px; + } + button { + transition: all 0.3s ease-in-out; + + &:hover { + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + } + } +} + +.pagination { + width: 30.4rem; + height: 4rem; + display: flex; + align-items: center; + justify-content: space-between; + margin: 4.3rem auto 0 auto; + @media (max-width: 1023px) { + margin: 2.5rem auto 0 auto; + } +} +.arrow-button { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: 1px solid $secondaryGray-200; + background: #ffffff; + color: $secondaryGray-600; + border-radius: 40px; + height: 40px; + width: 40px; + padding: 12px; + + &:hover { + background: $primaryBlue-200; + } + + &:disabled { + background: $secondaryGray-400; + cursor: not-allowed; + } +} + +interface PageButtonProps { + $isActive: boolean; +} +.page-button { + width: 40px; + height: 40px; + border-radius: 50%; + border: 1px solid $secondaryGray-100; + background: #fff; + color: $secondaryGray-900; + cursor: pointer; + transition: background 0.2s, color 0.2s; + display: flex; + align-items: center; + justify-content: center; + &:hover { + background: $primaryBlue-200; + } + + &.active { + background-color: $primaryBlue-100; + color: $secondaryGray-50; + } +} diff --git a/src/app/Items/page.tsx b/src/app/Items/page.tsx index 8919356e..ea2d758f 100644 --- a/src/app/Items/page.tsx +++ b/src/app/Items/page.tsx @@ -4,22 +4,20 @@ import React, { useEffect, useState } from 'react' import Image from 'next/image' import { useRouter } from 'next/navigation' -import { GetProductIdTypes } from '../types/product' +import { GetProductIdTypes } from '../../types/product' -import ItemsNavVar from '../common/ItemsNavVar' +import ItemsNavVar from '../../components/domain/Nav/ItemsNavVar' import BestItems from './BestItems' import RecentItems from './RecentItems' -import DropDown from '../common/DropDown' -import productService from '../api/services/productService' -import Button from '../common/Button' +import DropDown from '../../components/common/DropDown' +import productService from '../../lib/api/service/productService' +import Button from '../../components/common/Button' -import Search from '../../../public/assets/svg/Search.svg' -import ArrowLeft from '../../../public/assets/svg/ArrowLeft.svg' -import ArrowRight from '../../../public/assets/svg/ArrowRight.svg' +import Search from '../../../public/assets/svg/search.svg' +import ArrowLeft from '../../../public/assets/svg/arrow_left.svg' +import ArrowRight from '../../../public/assets/svg/arrow_right.svg' -import styled, { css } from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +import styles from './items.module.scss' type SelectOption = { value: string @@ -38,6 +36,7 @@ const Items = () => { const [selectedOption, setSelectedOption] = useState(selectList[0].value) const itemsPerPage = 10 // 페이지 네이션 const [currentPage, setCurrentPage] = useState(1) + const [isMobile, setIsMobile] = useState(false) useEffect(() => { const queryParams = new URLSearchParams() @@ -124,217 +123,82 @@ const Items = () => { return prevPage }) } + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth <= 743) + } + checkIsMobile() + window.addEventListener('resize', checkIsMobile) + + return () => window.removeEventListener('resize', checkIsMobile) + }, []) return ( <> - +
- - 전체상품 - - +
+
전체상품
+
+
검색 아이콘 - - - - +
+
+ +
+
+ +
{ setSelectedOption(value) }} /> - - +
+
- - +
+
왼쪽 페이지 화살표 - +
{pages.map((page) => ( - { setCurrentPage(page) }} > {page} - +
))} - +
오른쪽 페이지 화살표 - - - +
+
+ ) } export default Items - -const Bone = styled.div` - width: 120rem; - margin: 2.4rem auto; - @media (max-width: 1023px) { - width: 69.6rem; - } - @media (max-width: 743px) { - width: 34.4rem; - margin: 1rem auto; - } -` -const NavVAr = styled.div` - height: 4.2rem; - width: 100%; - margin: 0 auto 2.4rem; - display: flex; - align-items: center; - justify-content: space-between; - @media (max-width: 743px) { - flex-wrap: wrap; - margin: 0 auto 6.6rem; - } -` -const NavTitle = styled.div` - ${(props) => textStyle(20, 700)(props)} - color: ${theme.colors.SecondaryGray[900]}; - @media (max-width: 743px) { - height: 2.625rem; - display: flex; - align-items: center; - justify-content: center; - } -` -const NavRightWrapper = styled.div` - height: 100%; - display: flex; - align-items: center; - position: relative; - @media (max-width: 743px) { - flex-wrap: wrap; - } -` -const SearchIcon = styled.div` - position: absolute; - right: 585px; - z-index: 10; - display: flex; - @media (max-width: 1023px) { - right: 412px; - } - @media (max-width: 743px) { - right: 308px; - top: 28px; - } -` -const RegisterButton = styled(Button)` - padding: 0.8rem 2.3rem; - width: max-content; -` -const NavSearch = styled.input` - width: 32.5rem; - height: 100%; - padding: 0.9rem 10.7rem 0.9rem 4.4rem; - border-radius: 1.2rem; - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; - background-color: ${theme.colors.SecondaryGray[100]}; - border: none; - margin-right: 1.2rem; - @media (max-width: 1023px) { - width: 15.125rem; - padding: 9px 24px 9px 44px; - } - @media (max-width: 743px) { - position: relative; - top: 19px; - padding: 9px 40px 9px 44px; - width: max-content; - margin: 0; - } -` -const ButtonWrapper = styled.div` - margin-right: 1.2rem; - @media (max-width: 743px) { - position: relative; - width: max-content; - top: -74px; - left: 206px; - } - button { - transition: all 0.3s ease-in-out; - - &:hover { - transform: scale(1.05); - } - - &:active { - transform: scale(0.95); - } - } -` - -const Pagenation = styled.div` - width: 30.4rem; - height: 4rem; - display: flex; - align-items: center; - justify-content: space-between; - margin: 4.3rem auto 0 auto; - @media (max-width: 1023px) { - margin: 2.5rem auto 0 auto; - } -` -const LeftButton = styled.button` - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - border: 1px solid ${theme.colors.SecondaryGray[200]}; - background: #ffffff; - color: ${({ theme }) => theme.colors.SecondaryGray[600]}; - border-radius: 40px; - height: 40px; - width: 40px; - padding: 12px; - - &:hover { - background: ${({ theme }) => theme.colors.PrimaryBlue[200]}; - } - - &:disabled { - background: ${({ theme }) => theme.colors.SecondaryGray[400]}; - cursor: not-allowed; - } -` - -interface PageButtonProps { - $isActive: boolean -} -const PageButton = styled.button` - width: 40px; - height: 40px; - border-radius: 50%; - border: 1px solid ${theme.colors.SecondaryGray[100]}; - &:hover { - background: ${({ theme }) => theme.colors.PrimaryBlue[200]}; - } - - ${({ $isActive, theme }) => - $isActive && - css` - background-color: ${theme.colors.PrimaryBlue[100]}; - color: ${theme.colors.SecondaryGray[50]}; - `} -` diff --git a/src/app/LoginAndSignup/LoginField.tsx b/src/app/LoginAndSignup/LoginField.tsx deleted file mode 100644 index 911ace5c..00000000 --- a/src/app/LoginAndSignup/LoginField.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useState } from 'react' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import styled from 'styled-components' -import Image from 'next/image' - -const Field = styled.span` - display: inline-block; - margin-bottom: 1.6rem; - @media (max-width: 743px) { - margin-bottom: 0.8rem; - } -` -interface InputProps { - $isError?: boolean -} -const Input = styled.input` - width: 100%; - padding: 1.5rem 2.4rem; - - border-radius: 1.2rem; - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[800]}; - background-color: ${theme.colors.SecondaryGray[100]}; - - border: 1px solid - ${({ $isError }) => - $isError ? theme.colors.error : theme.colors.PrimaryBlue[100]}; - &:hover { - border: 1px solid - ${({ $isError }) => - $isError ? theme.colors.error : theme.colors.PrimaryBlue[100]}; - } -` -const InputWrapper = styled.label` - width: 100%; - height: 9.8rem; - margin-bottom: 2.4rem; - ${(props) => textStyle(18, 700)(props)} - color: ${theme.colors.SecondaryGray[800]}; - @media (max-width: 743px) { - ${(props) => textStyle(14, 700)(props)} - height: 8.8rem; - } -` - -const IconWrapper = styled.div` - width: 24px; - height: 24px; - position: relative; - left: 594px; - top: -41px; - cursor: pointer; - @media (max-width: 743px) { - left: 302px; - } -` -const ErrorPosition = styled.div` - position: relative; -` -const ErrorMessage = styled.div` - color: ${theme.colors.error}; - ${(props) => textStyle(14, 600)(props)} - margin: 0.5rem; - top: -28px; - position: absolute; -` -interface LoginFieldtProps { - label: string - type: string - id: string - placeholder: string - icon?: string | null - onIconClick?: () => void - validate?: (value: string) => string - value: string - onChange?: (e: React.ChangeEvent) => void - error?: string -} -const LoginField = ({ - // 부모에게 받음 - label, - type = 'text', - id, - placeholder, - icon = null, - onIconClick, - validate, - value, - onChange, -}: LoginFieldtProps) => { - const [error, setError] = useState('') - - const handleBlur = () => { - // 포커스를 잃었을 때 사용 - if (validate) { - setError(validate(value)) - } - } - - return ( - <> - - {label} - <> - - - {icon ? ( - {`${label} - ) : ( -
- )} - - - - {error && {error}} - - - - ) -} - -export default LoginField diff --git a/src/app/addboard/addboard.module.scss b/src/app/addboard/addboard.module.scss new file mode 100644 index 00000000..e309416b --- /dev/null +++ b/src/app/addboard/addboard.module.scss @@ -0,0 +1,56 @@ +@import '../../styles/variables.scss'; + +.bone { + width: 120rem; + display: flex; + align-items: center; + margin: 1.5rem auto 6.9rem auto; + flex-direction: column; + @media (max-width: 1023px) { + width: 69.6rem; + margin-bottom: 7.8rem; + } + @media (max-width: 743px) { + width: 34.6rem; + margin-bottom: 7rem; + } +} +.header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-bottom: 3.2rem; +} +.product-register { + @include apply-font($font-20, 700); + color: $secondaryGray-800; +} +.button-wrapper { + width: max-content; +} +.register-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.main { + width: 100%; +} +.display-wrapper { + width: 100%; + margin-bottom: 2.4rem; + @media (max-width: 743px) { + margin-bottom: 1.6rem; + } +} +.product-text { + @include apply-font($font-18, 700); + color: $secondaryGray-800; + margin-bottom: 1.6rem; +} +.tag-display { + display: flex; + gap: 0.75rem; + width: 100%; + flex-wrap: wrap; +} diff --git a/src/app/addboard/page.tsx b/src/app/addboard/page.tsx new file mode 100644 index 00000000..595f7bde --- /dev/null +++ b/src/app/addboard/page.tsx @@ -0,0 +1,65 @@ +'use client' +import React, { useState } from 'react' + +import ItemsNavVar from '../../components/domain/Nav/ItemsNavVar' +import Button from '../../components/common/Button' +import ButtonImage from '../../components/common/ButtonImageInput' +import TextInputPlaceholder from '../../components/common/TextInputPlaceholder' + +import styles from './addboard.module.scss' + +const AddBoard = () => { + const [productTitle, setProductTitle] = useState('') + const [productDescription, setProductDescription] = useState('') + + const isState = productTitle.length >= 1 && productDescription.length >= 1 + + return ( + <> + +
+
+
게시글 쓰기
+
+ +
+
+
+
+
제목
+ setProductTitle(e.target.value)} + /> +
+
+
내용
+ setProductDescription(e.target.value)} + /> +
+ +
+
상품 이미지
+ +
+
+
+ + ) +} + +export default AddBoard diff --git a/src/app/additem/additem.module.scss b/src/app/additem/additem.module.scss new file mode 100644 index 00000000..66e9c3c5 --- /dev/null +++ b/src/app/additem/additem.module.scss @@ -0,0 +1,56 @@ +@import '../../styles/variables.scss'; + +.bone { + width: 120rem; + display: flex; + align-items: center; + margin: 1.5rem auto 6.9rem auto; + flex-direction: column; + @media (max-width: 1023px) { + width: 69.6rem; + margin-bottom: 7.8rem; + } + @media (max-width: 743px) { + width: 34.6rem; + margin-bottom: 7rem; + } +} +.header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-bottom: 2.4rem; +} +.product-register { + @include apply-font($font-20, 700); + color: $secondaryGray-800; +} +.button-wrapper { + width: max-content; +} +.register-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.main { + width: 100%; +} +.display-wrapper { + width: 100%; + margin-bottom: 3.2rem; + @media (max-width: 743px) { + margin-bottom: 2.4rem; + } +} +.product-text { + @include apply-font($font-18, 700); + color: $secondaryGray-800; + margin-bottom: 1.6rem; +} +.tag-display { + display: flex; + gap: 0.75rem; + width: 100%; + flex-wrap: wrap; +} diff --git a/src/app/additem/page.tsx b/src/app/additem/page.tsx index 71412973..f8a2235c 100644 --- a/src/app/additem/page.tsx +++ b/src/app/additem/page.tsx @@ -1,15 +1,13 @@ 'use client' import React, { useState } from 'react' -import ItemsNavVar from '../common/ItemsNavVar' -import Button from '../common/Button' -import ButtonImage from '../common/ButtonImage' -import TextInputPlaceholder from '../common/TextInputPlaceholder' -import Tag from '../common/Tag' +import ItemsNavVar from '../../components/domain/Nav/ItemsNavVar' +import Button from '../../components/common/Button' +import ButtonImage from '../../components/common/ButtonImageInput' +import TextInputPlaceholder from '../../components/common/TextInputPlaceholder' +import Tag from '../../components/common/Tag' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +import styles from './additem.module.scss' const AddItem = () => { const [productName, setProductName] = useState('') @@ -46,30 +44,36 @@ const AddItem = () => { return ( <> - -
- 싱품 등록하기 - - +
+
+
상품 등록하기
+
+
-
- - 상품 이미지 + +
+ +
+
+
상품 이미지
- - - 상품명 +
+
+
상품명
setProductName(e.target.value)} /> - - - 상품 소개 +
+
+
상품 소개
{ value={productDescription} onChange={(e) => setProductDescription(e.target.value)} /> - - - 판매 가격 +
+
+
판매 가격
setProductPrice(e.target.value)} /> - - - 태그 +
+
+
태그
setTagInput(e.target.value)} onKeyDown={handleEnterDown} /> - - +
+
{productTags.map((tag, index) => ( { onClick={() => handleDeleteTag(tag)} /> ))} - - - +
+
+ ) } export default AddItem - -const Bone = styled.div` - width: 120rem; - display: flex; - align-items: center; - margin: 1.5rem auto auto auto; - flex-direction: column; - @media (max-width: 1023px) { - width: 69.6rem; - margin-bottom: 7.8rem; - } - @media (max-width: 743px) { - width: 34.6rem; - margin-bottom: 7rem; - } -` -const Header = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - margin-bottom: 2.4rem; -` -const ProductRegister = styled.div` - ${(props) => textStyle(20, 700)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ButtonWrapper = styled.div` - width: max-content; -` -const RegisterButton = styled(Button)` - padding: 0.8rem 2.3rem; - width: max-content; -` -const Main = styled.div` - width: 100%; -` -const DisplayWrapper = styled.div` - width: 100%; - margin-bottom: 3.2rem; - @media (max-width: 743px) { - margin-bottom: 2.4rem; - } -` -const ProductText = styled.div` - ${(props) => textStyle(18, 700)(props)} - color: ${theme.colors.SecondaryGray[800]}; - margin-bottom: 1.6rem; -` -const TagDisplay = styled.div` - display: flex; - gap: 0.75rem; - width: 100%; - flex-wrap: wrap; -` diff --git a/src/app/boards/BestBoards.module.scss b/src/app/boards/BestBoards.module.scss new file mode 100644 index 00000000..eb321eed --- /dev/null +++ b/src/app/boards/BestBoards.module.scss @@ -0,0 +1,79 @@ +@import '../../styles/variables'; + +.bone-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4rem; + @media (max-width: 1023px) { + margin-bottom: 2.4rem; + } +} +.best-boards-title { + @include apply-font($font-20, 800); + margin-bottom: 2.4rem; +} +.main-wrapper { + width: 38.4rem; + height: 16.9rem; + padding: 0 2.4rem 1.6rem 2.4rem; + border-radius: 8px; + background-color: $secondaryGray-50; + display: flex; + justify-content: space-between; + flex-direction: column; + @media (max-width: 1023px) { + width: 34rem; + height: 19.8rem; + } +} + +.description { + display: flex; + gap: 0.8rem; +} +.main-footer { + display: flex; + align-items: center; + justify-content: space-between; +} +.text-description { + @include apply-font($font-20, 600); + @media (max-width: 1023px) { + @include apply-font($font-18, 600); + } +} +.description-image { + img { + width: 7.2rem; + height: 7.2rem; + } +} +.name-heart { + display: flex; + align-items: center; + justify-content: center; + gap: 0.8rem; + height: 2.4rem; +} +.create-date { + @include apply-font($font-14, 400); + color: $secondaryGray-500; +} +.user-name { + @include apply-font($font-14, 400); + color: $secondaryGray-500; +} +.user-heart { + display: flex; + align-items: center; + img { + width: 1.6rem; + height: 1.6rem; + } +} + +.heart-count { + @include apply-font($font-14, 400); + color: $secondaryGray-500; +} diff --git a/src/app/boards/BestBoards.tsx b/src/app/boards/BestBoards.tsx index 9c978c4b..36d81040 100644 --- a/src/app/boards/BestBoards.tsx +++ b/src/app/boards/BestBoards.tsx @@ -1,130 +1,76 @@ -import React from 'react' +'use client' +import React, { useState, useEffect } from 'react' import Image from 'next/image' -import { GetArticleType } from '../types/article' +import { useGetBestArticles } from '../../hooks/useGetBeatArticle' -import HeartInactive from '../../../public/assets/image/HeartInactive.png' -import BestBadge from '../../../public/assets/image/BestBadge.png' -import { formatDate } from '../utils/datetime' +import HeartInactive from '../../../public/assets/image/heart_inactive.png' +import BestBadge from '../../../public/assets/image/best_badge.png' +import { formatDate } from '../../utils/datetime' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +import styles from './BestBoards.module.scss' +const BestBoards = () => { + const [bestPageSize, setBestPageSize] = useState(3) + useEffect(() => { + const handleResize = () => { + if (window.innerWidth <= 743) { + setBestPageSize(1) + } else if (window.innerWidth <= 1023) { + setBestPageSize(2) + } else { + setBestPageSize(3) + } + } -type BestBoardsProps = { - articleList?: GetArticleType -} + handleResize() // 처음 렌더링 시에도 계산 + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, []) + + // useGetBestArticles 훅을 사용하여 베스트 게시글 목록을 가져옵니다. + const bestArticleList = useGetBestArticles(bestPageSize) -const BestBoards = ({ articleList }: BestBoardsProps) => { - if (!articleList) return null + if (!bestArticleList) { + return
게시글을 불러오는 중입니다...
+ } return ( <> - 베스트 게시글 - - {articleList.list.map((article) => ( - - +
베스트 게시글
+
+ {bestArticleList.list.map((article) => ( +
+ <> 베스트 게시글 오피셜 아이콘 - - - {article.content} - + +
+
+ {article.content} +
+
게시글 이미지 - - - - - {article.writer.nickname} - +
+
+
+
+
+ {article.writer.nickname} +
+
하트 비활성화/활성화 - {article.likeCount} - - - {formatDate(article.createdAt)} - - +
+ {article.likeCount} +
+
+
+
+ {formatDate(article.createdAt)} +
+
+
))} - +
) } export default BestBoards - -const BoneWrapper = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 4rem; - @media (max-width: 1023px) { - margin-bottom: 2.4rem; - } -` -const BestBoardsTitle = styled.div` - ${(props) => textStyle(20, 800)(props)} - margin-bottom:2.4rem; -` -const MainWrapper = styled.div` - width: 38.4rem; - height: 16.9rem; - padding: 0 2.4rem 1.6rem 2.4rem; - border-radius: 8px; - background-color: ${theme.colors.SecondaryGray[50]}; - display: flex; - justify-content: space-between; - flex-direction: column; - @media (max-width: 1023px) { - width: 34rem; - height: 19.8rem; - } -` -const BestIcon = styled.div`` -const Description = styled.div` - display: flex; - gap: 0.8rem; -` -const MainFooter = styled.div` - display: flex; - align-items: center; - justify-content: space-between; -` -const TextDescription = styled.div` - ${(props) => textStyle(20, 600)(props)} - @media (max-width: 1023px) { - ${(props) => textStyle(18, 600)(props)} - } -` -const DescriptionImage = styled.div` - img { - width: 7.2rem; - height: 7.2rem; - } -` -const NameHeart = styled.div` - display: flex; - align-items: center; - justify-content: center; - gap: 0.8rem; - height: 2.4rem; -` -const CreateDate = styled.div` - ${(props) => textStyle(14, 400)(props)} - color: ${theme.colors.SecondaryGray[500]}; -` -const UserName = styled.div` - ${(props) => textStyle(14, 400)(props)} - color: ${theme.colors.SecondaryGray[500]}; -` -const UserHeart = styled.div` - display: flex; - align-items: center; - img { - width: 1.6rem; - height: 1.6rem; - } -` - -const HeartCount = styled.div` - ${(props) => textStyle(14, 400)(props)} - color: ${theme.colors.SecondaryGray[500]}; -` diff --git a/src/app/boards/BoardDetailArrary.module.scss b/src/app/boards/BoardDetailArrary.module.scss new file mode 100644 index 00000000..9535f784 --- /dev/null +++ b/src/app/boards/BoardDetailArrary.module.scss @@ -0,0 +1,66 @@ +@import '../../styles/variables.scss'; + +.bone { + display: flex; + flex-direction: column; + justify-content: space-between; + height: max-content; + border-bottom: 1px solid $secondaryGray-200; + position: relative; + padding-bottom: 2.4rem; + padding-right: 2rem; + margin-top: 2.4rem; + cursor: pointer; + @media (max-width: 743px) { + margin-top: 0; + margin-bottom: 2.4rem; + } +} +.content-wrapper { + display: flex; + align-items: flex-start; + justify-content: space-between; + img { + width: 7.2rem; + height: 7.2rem; + } +} + +.question-content { + @include apply-font($font-20, 600); + color: $secondaryGray-800; + margin-bottom: 2.4rem; + @media (max-width: 743px) { + @include apply-font($font-18, 600); + } +} +.user-profile-image-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + img { + width: 2.6rem; + height: 2.6rem; + border-radius: 50%; + } +} +.name-profile { + display: flex; + align-items: center; + justify-content: center; + gap: 0.8rem; +} +.user-profile-name { + @include apply-font($font-12, 400); + color: $secondaryGray-600; +} +.diff-date { + @include apply-font($font-12, 400); + color: $secondaryGray-400; +} +.heart-count { + @include apply-font($font-16, 400); + color: $secondaryGray-500; + display: flex; + gap: 0.8rem; +} diff --git a/src/app/boards/BoardDetailArrary.tsx b/src/app/boards/BoardDetailArrary.tsx index 41041500..be0162ed 100644 --- a/src/app/boards/BoardDetailArrary.tsx +++ b/src/app/boards/BoardDetailArrary.tsx @@ -1,43 +1,48 @@ -'use client' - import React from 'react' +import Image from 'next/image' +import { useRouter } from 'next/navigation' +import { GetArticleIdType } from '../../types/article' +import { diffDate } from '../../utils/datetime' +import { formatDate } from '../../utils/datetime' -import { GetArticleIdType } from '../types/article' -import { diffDate } from '../utils/datetime' -import { formatDate } from '../utils/datetime' - -import HeartInactive from '../../../public/assets/image/HeartInactive.png' +import HeartInactive from '../../../public/assets/image/heart_inactive.png' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import Image from 'next/image' +import styles from './BoardDetailArrary.module.scss' type BestBoardsProps = { article: GetArticleIdType } const BoardDetailArrary = ({ article }: BestBoardsProps) => { + console.log('BoardDetailArrary', article) + const router = useRouter() + const handleClickBoardID = () => { + router.push(`/boards/${article.id}`) + } return ( <> - - - {article.content} - 게시글 상세 이미지 - - - +
+
+
{article.content}
+ {article.image && ( + 게시글 상세 이미지 + )} +
+
+
{/*이미지가 없을 경우 기본 이미지 적용*/} - 유저프로필사진 - {article.writer.nickname} +
+ {article.writer.nickname} +
{/*날짜 차이가 31일을 넘길 경우 createAt을 출력*/} - +
{diffDate(article.createdAt) > 31 ? ( <> {formatDate(article.createdAt)} @@ -48,9 +53,9 @@ const BoardDetailArrary = ({ article }: BestBoardsProps) => { 일 전 )} - - - +
+
+
HeartInactive { height={24} /> <>{article.likeCount} - - - +
+
+
) } export default BoardDetailArrary - -const Bone = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - height: max-content; - border-bottom: 1px solid ${theme.colors.SecondaryGray[200]}; - position: relative; - padding-bottom: 2.4rem; - padding-right: 2rem; - margin-top: 2.4rem; - @media (max-width: 743px) { - margin-top: 0; - margin-bottom: 2.4rem; - } -` -const ContentWrappeer = styled.div` - display: flex; - align-items: flex-start; - justify-content: space-between; - img { - width: 7.2rem; - height: 7.2rem; - } -` - -const QuestionContent = styled.div` - ${(props) => textStyle(20, 600)(props)} - color: ${theme.colors.SecondaryGray[800]}; - margin-bottom: 2.4rem; - @media (max-width: 743px) { - ${(props) => textStyle(18, 600)(props)} - } -` -const UserProfileImageWrapper = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - img { - width: 2.6rem; - height: 2.6rem; - border-radius: 50%; - } -` -const NameProfile = styled.div` - display: flex; - align-items: center; - justify-content: center; - gap: 0.8rem; -` -const UserProfileName = styled.div` - ${(props) => textStyle(12, 400)(props)} - color: ${theme.colors.SecondaryGray[600]}; -` -const DiffDate = styled.div` - ${(props) => textStyle(12, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; -` -const HeartCount = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[500]}; - display: flex; - gap: 0.8rem; -` diff --git a/src/app/boards/BoardList.module.scss b/src/app/boards/BoardList.module.scss new file mode 100644 index 00000000..6c5919cd --- /dev/null +++ b/src/app/boards/BoardList.module.scss @@ -0,0 +1,87 @@ +@import '../../styles/variables.scss'; + +.board-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2.4rem; + @media (max-width: 1023px) { + margin-bottom: 4.8rem; + } + @media (max-width: 743px) { + margin-bottom: 1.6rem; + } +} +.best-boards-title { + @include apply-font($font-20, 800); +} +.writer-button { + padding: 0.8rem 2.3rem; + width: max-content; +} + +.search-list { + display: flex; + align-items: center; + justify-content: space-between; + @media (max-width: 1023px) { + margin-bottom: 4rem; + } + @media (max-width: 743px) { + margin-bottom: 1.6rem; + } +} +.text-input-icon { + position: relative; +} +.nav-search { + width: 105.4rem; + height: 4.2rem; + padding: 0.9rem 8.7rem 0.9rem 4.4rem; + border-radius: 1.2rem; + @include apply-font($font-16, 400); + color: $secondaryGray-400; + background-color: $secondaryGray-100; + border: none; + margin-right: 1.2rem; + @media (max-width: 1023px) { + width: 55rem; + padding: 9px 24px 9px 44px; + } + @media (max-width: 743px) { + position: relative; + + padding: 9px 40px 9px 44px; + width: max-content; + margin: 0; + } +} +.placeholder-icon { + width: max-content; + position: absolute; + top: 10px; + left: 20px; +} + +.boards-list { + overflow-y: auto; + height: 60vh; + position: relative; +} +.items-question-wrapper { + position: relative; + display: flex; + flex-direction: column; +} + +.inquiry-empty-wrapper { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.inquiry-empty-text { + @include apply-font($font-16, 400); + color: $secondaryGray-400; + margin-top: 0.5rem; +} diff --git a/src/app/boards/BoardList.tsx b/src/app/boards/BoardList.tsx index 7e7225a3..a773e4b1 100644 --- a/src/app/boards/BoardList.tsx +++ b/src/app/boards/BoardList.tsx @@ -1,203 +1,122 @@ 'use client' -import React, { useRef, useEffect } from 'react' +import React, { useRef, useEffect, useState } from 'react' import Image from 'next/image' -import { GetArticleType } from '../types/article' +import { useInfiniteScroll } from '../../hooks/useInfiniteScroll' +import { useDebounce } from '../../hooks/useDebounce' +import { useGetArticles } from '../../hooks/useGetArticle' import BoardDetailArrary from './BoardDetailArrary' -import DropDown from '../common/DropDown' -import Button from '../common/Button' +import DropDown from '../../components/common/DropDown' +import Button from '../../components/common/Button' -import InquiryEmpty from '../../../public/assets/svg/InquiryEmpty.svg' -import Search from '../../../public/assets/svg/Search.svg' +import InquiryEmpty from '../../../public/assets/svg/inquiry_empty.svg' +import Search from '../../../public/assets/svg/search.svg' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' +import styles from './BoardList.module.scss' type SelectOption = { value: string name: string } -type BestBoardsProps = { - articleList: GetArticleType - selectedOption: string - setSelectedOption: (value: string) => void - loadMore: () => void - hasMore: boolean - searchTerm: string - setSearchTerm: (value: string) => void -} -const BoardList = ({ - articleList, - selectedOption, - setSelectedOption, - loadMore, - hasMore, - searchTerm, - setSearchTerm, -}: BestBoardsProps) => { - console.log(articleList) + +const BoardList = () => { const selectList: SelectOption[] = [ { value: 'recent', name: '최신순' }, { value: 'like', name: '좋아요순' }, ] const observerRef = useRef(null) const scrollContainerRef = useRef(null) - const loadingRef = useRef(false) - + const [page, setPage] = useState(1) + const [selectedOption, setSelectedOption] = useState('recent') + const [searchTerm, setSearchTerm] = useState('') + const debouncedSearchTerm = useDebounce(searchTerm, 500) + const [isMobile, setIsMobile] = useState(false) + const orderBy = selectedOption === 'recent' ? 'recent' : 'like' + // 페이지네이션을 위한 상태 useEffect(() => { - if (!hasMore || !observerRef.current || !scrollContainerRef.current) return - - let observer: IntersectionObserver | null = null - - const callback = async (entries: IntersectionObserverEntry[]) => { - if (entries[0].isIntersecting && !loadingRef.current) { - loadingRef.current = true - await loadMore() - setTimeout(() => { - loadingRef.current = false - }, 500) // 시간을 넣어 무한 스크롤이 한 번에 여러번 호출되지 않게 - } + if (page !== 1) { + setPage(1) + setHasMore(true) } + }, [selectedOption, debouncedSearchTerm]) - observer = new IntersectionObserver(callback, { - root: scrollContainerRef.current, - threshold: 0.9, - }) + // 게시글 목록을 가져오는 커스텀 훅 + const { articleList, hasMore, setHasMore } = useGetArticles( + page, + orderBy, + debouncedSearchTerm + ) + // 페이지네이션을 위한 상태 + const loadMore = async () => { + if (hasMore) setPage((prev) => prev + 1) + } + //무한스크롤 + useInfiniteScroll({ + hasMore, + loadMore, + observerRef, + scrollContainerRef, + }) + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth <= 743) + } - observer.observe(observerRef.current) + checkIsMobile() + window.addEventListener('resize', checkIsMobile) - return () => { - if (observer) observer.disconnect() - } - }, [hasMore, loadMore]) + return () => window.removeEventListener('resize', checkIsMobile) + }, []) + if (!articleList) { + // 데이터가 없을 때 로딩 상태를 보여줄 수 있고, 데이터가 도착하면 다시 렌더링되어 실제 게시글 목록이 보임임 + return null + } return ( <> - - 게시글 - 글쓰기 - - - - +
게시글
+ + +
+
+ setSearchTerm(e.target.value)} /> - +
검색아이콘 - - +
+
{ - setSelectedOption(value) - }} + onChange={(value) => setSelectedOption(value)} /> - - +
+
{articleList.list?.length === 0 ? ( - +
관련된 게시글이 없습니다. - 문의가 없습니다 - +
문의가 없습니다
+
) : ( - +
{articleList.list.map((article) => ( ))} {hasMore &&
} - +
)} - +
) } export default BoardList -const BoardHeader = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 2.4rem; - @media (max-width: 1023px) { - margin-bottom: 4.8rem; - } - @media (max-width: 743px) { - margin-bottom: 1.6rem; - } -` -const BestBoardsTitle = styled.div` - ${(props) => textStyle(20, 800)(props)} -` -const WriterButton = styled(Button)` - padding: 0.8rem 2.3rem; - width: max-content; -` - -const SearchList = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - @media (max-width: 1023px) { - margin-bottom: 4rem; - } - @media (max-width: 743px) { - margin-bottom: 1.6rem; - } -` -const TextInputIcon = styled.div` - position: relative; -` -const NavSearch = styled.input` - width: 105.4rem; - height: 4.2rem; - padding: 0.9rem 8.7rem 0.9rem 4.4rem; - border-radius: 1.2rem; - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; - background-color: ${theme.colors.SecondaryGray[100]}; - border: none; - margin-right: 1.2rem; - @media (max-width: 1023px) { - width: 55rem; - padding: 9px 24px 9px 44px; - } - @media (max-width: 743px) { - position: relative; - - padding: 9px 40px 9px 44px; - width: max-content; - margin: 0; - } -` -const PlaceholderIcon = styled.div` - width: max-content; - position: absolute; - top: 10px; - left: 20px; -` - -const BoardsList = styled.div` - overflow-y: auto; - height: 60vh; -` -const ItemsQuestionWrapper = styled.div` - position: relative; - display: flex; - flex-direction: column; -` - -const InquiryEmptyWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -` -const InquiryEmptyText = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; - margin-top: 0.5rem; -` diff --git a/src/app/boards/[id]/BoardCommentList.module.scss b/src/app/boards/[id]/BoardCommentList.module.scss new file mode 100644 index 00000000..07b21ee2 --- /dev/null +++ b/src/app/boards/[id]/BoardCommentList.module.scss @@ -0,0 +1,55 @@ +@import '../../../styles/variables.scss'; + +.title-comment-font { + @include apply-font($font-16, 600); + padding-bottom: 0.9rem; +} + +.register-button { + padding: 0.8rem 2.3rem; + width: max-content; +} +.register-button-container { + display: flex; + justify-content: end; + padding-top: 1.6rem; +} + +.inquiry-empty-wrapper { + display: flex; + align-items: center; + justify-content: center; + padding-top: 4rem; + @media (max-width: 1023px) { + padding-top: 3.2rem; + } +} +.items-question-wrapper { + position: relative; + display: flex; + flex-direction: column; + @media (max-width: 1023px) { + padding-top: 3.2rem; + } + @media (max-width: 743px) { + padding-top: 2.4rem; + } +} +.button-wrapper { + display: flex; + justify-content: center; + margin: 4.6875rem auto; + width: max-content; + @include apply-font($font-18, 600); + color: $secondaryGray-100; + @media (max-width: 1023px) { + margin: 3.5rem auto 10.4375rem auto; + } + @media (max-width: 743px) { + margin: 4rem auto 2.5rem auto; + } +} +.list-button { + padding: 1.1rem 3.9rem; + width: max-content; +} diff --git a/src/app/boards/[id]/BoardCommentList.tsx b/src/app/boards/[id]/BoardCommentList.tsx new file mode 100644 index 00000000..a8b09e1b --- /dev/null +++ b/src/app/boards/[id]/BoardCommentList.tsx @@ -0,0 +1,88 @@ +'use client' +import React, { useEffect, useState } from 'react' +import Image from 'next/image' +import { useParams } from 'next/navigation' + +import ItemsDetailQuestionArrary from '@/app/items/[productId]/ItemsDetailQuestionArrary' +import commentService from '../../../lib/api/service/commentService' +import TextInputPlaceholder from '@/components/common/TextInputPlaceholder' +import Button from '../../../components/common/Button' + +import BackIcon from '../../../../public/assets/svg/back_icon.svg' +import NoComment from '../../../../public/assets/svg/no_comment.svg' + +import styles from './BoardCommentList.module.scss' + +const BoardCommentList = () => { + const [articleComment, setArticleComment] = useState('') + const [productQuestion, setProductQuestion] = useState([]) + const { id } = useParams() + useEffect(() => { + if (!id) return + const fetchData = async () => { + try { + const response = await commentService.getArticleComment(id, 5) + setProductQuestion(response.data?.list ?? []) + console.log('BoardDescription', response.data) + } catch (error) { + console.error('Error fetching article:', error) + } + } + fetchData() + }, [id]) + + const isState = articleComment.length >= 1 + return ( +
+
+
댓글달기
+ setArticleComment(e.target.value)} + /> +
+ +
+
+
+ {!productQuestion || productQuestion.length === 0 ? ( +
+ 댓글이 없습니다 +
+ ) : ( + <> +
+ {productQuestion.map((question, index) => ( + + ))} +
+ + )} +
+ +
+
+
+ ) +} + +export default BoardCommentList diff --git a/src/app/boards/[id]/BoardDescription.module.scss b/src/app/boards/[id]/BoardDescription.module.scss new file mode 100644 index 00000000..03f4503a --- /dev/null +++ b/src/app/boards/[id]/BoardDescription.module.scss @@ -0,0 +1,69 @@ +@import '../../../styles/variables.scss'; + +.article-wrapper { + display: flex; + flex-direction: column; + gap: 1.6rem; + padding-bottom: 1.6rem; + border-bottom: 1px solid $secondaryGray-200; +} +.article-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + @include apply-font($font-20, 700); +} +.article-content { + display: flex; + align-items: center; +} +.article-profile-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.6rem; + padding-right: 3.2rem; + border-right: 1px solid $secondaryGray-200; +} +.article-profile-image { + display: flex; +} +.article-profile-info { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.8rem; +} + +.article-profile-nickname { + @include apply-font($font-14, 500); +} + +.article-profile-date { + @include apply-font($font-14, 400); + color: $secondaryGray-400; +} +.article-heart-main-wrapper { + padding-left: 3.2rem; +} +.article-heart-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.4rem; + padding: 0.4rem 1.2rem; + border: 1px solid $secondaryGray-200; + border-radius: 3.5rem; +} + +.article-heart-image { + display: flex; +} + +.article-content-bottom { + @include apply-font($font-18, 400); + padding-top: 2.4rem; + @media (max-width: 1023px) { + padding-top: 1.6rem; + } +} diff --git a/src/app/boards/[id]/BoardDescription.tsx b/src/app/boards/[id]/BoardDescription.tsx new file mode 100644 index 00000000..9c1fdf82 --- /dev/null +++ b/src/app/boards/[id]/BoardDescription.tsx @@ -0,0 +1,84 @@ +'use client' + +import { useParams } from 'next/navigation' +import { useEffect, useState } from 'react' + +import ArticleService from '../../../lib/api/service/articleService' +import { GetArticleIdType } from '../../../types/article' +import { diffDate } from '../../../utils/datetime' +import { formatDate } from '../../../utils/datetime' + +import Setting from '../../../../public/assets/svg/setting_icon.svg' +import HeartInactive from '../../../../public/assets/image/heart_inactive.png' +import ProfileIcon from '../../../../public/assets/svg/profile_icon.svg' + +import styles from './BoardDescription.module.scss' +import Image from 'next/image' +const BoardDescription = () => { + const [articleId, setArticleId] = useState() + const { id } = useParams() + + useEffect(() => { + if (!id) return + const fetchData = async () => { + try { + const response = await ArticleService.getArticleId(id) + setArticleId(response.data) + console.log('BoardDescription', response.data) + } catch (error) { + console.error('Error fetching article:', error) + } + } + fetchData() + }, [id]) + if (!articleId) return null + return ( +
+
+
+ {articleId?.title} + 설정 아이콘 +
+
+
+
+ 프로필 아이콘 +
+
+
+ {articleId?.writer.nickname} +
+
+ {diffDate(articleId.createdAt) > 31 ? ( + <> + {formatDate(articleId.createdAt)} + + ) : ( + <> + {diffDate(articleId.createdAt)} + 일 전 + + )} +
+
+
+
+
+
+ 하트 비활성화/활성화 +
+
+ {articleId?.likeCount} +
+
+
+
+
+
+ {articleId?.content} +
+
+ ) +} + +export default BoardDescription diff --git a/src/app/boards/[id]/BoardId.module.scss b/src/app/boards/[id]/BoardId.module.scss new file mode 100644 index 00000000..a94b2055 --- /dev/null +++ b/src/app/boards/[id]/BoardId.module.scss @@ -0,0 +1,16 @@ +.bone { + width: 120rem; + height: 88.3rem; + margin: 3.2rem auto 9.5rem auto; + display: flex; + flex-direction: column; + gap: 3.2rem; + @media (max-width: 1023px) { + width: 69.6rem; + gap: 4rem; + } + @media (max-width: 743px) { + width: 34.3rem; + gap: 3.2rem; + } +} diff --git a/src/app/boards/[id]/page.tsx b/src/app/boards/[id]/page.tsx new file mode 100644 index 00000000..4c48bab5 --- /dev/null +++ b/src/app/boards/[id]/page.tsx @@ -0,0 +1,21 @@ +'use Client' + +import BoardDescription from './BoardDescription' +import BoardCommentList from './BoardCommentList' +import ItemsNavVar from '../../../components/domain/Nav/ItemsNavVar' +import styles from './BoardId.module.scss' + +const BoardId = () => { + return ( +
+ + +
+ + +
+
+ ) +} + +export default BoardId diff --git a/src/app/boards/boards.module.scss b/src/app/boards/boards.module.scss new file mode 100644 index 00000000..29524c84 --- /dev/null +++ b/src/app/boards/boards.module.scss @@ -0,0 +1,12 @@ +.bone { + width: 120rem; + margin: 2.4rem auto auto auto; + @media (max-width: 1023px) { + width: 69.6rem; + margin: 2.4rem auto auto auto; + } + @media (max-width: 743px) { + width: 34.4rem; + margin: 1rem auto 6.5rem auto; + } +} diff --git a/src/app/boards/page.tsx b/src/app/boards/page.tsx index 78b23672..a4140904 100644 --- a/src/app/boards/page.tsx +++ b/src/app/boards/page.tsx @@ -1,142 +1,22 @@ 'use client' -import React, { useEffect, useState } from 'react' +import React from 'react' -import ItemsNavVar from '../common/ItemsNavVar' +import ItemsNavVar from '../../components/domain/Nav/ItemsNavVar' import BestBoards from './BestBoards' import BoardList from './BoardList' -import articleService from '../api/services/articleService' -import { GetArticleType } from '../types/article' -import styled from 'styled-components' +import styles from './boards.module.scss' const Boards = () => { - const [articleList, setArticleList] = useState() - const [bestArticleList, setBestArticleList] = useState() - const [page, setPage] = useState(1) - const [hasMore, setHasMore] = useState(true) - const [selectedOption, setSelectedOption] = useState('recent') - const [searchTerm, setSearchTerm] = useState('') - const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') - const [bestPageSize, setBestPageSize] = useState(3) // 심화 페이지 사이즈 조절 - - const orderBy = selectedOption === 'recent' ? 'recent' : 'like' - // BestBoards에 관한 useEffect - useEffect(() => { - const timer = setTimeout(() => { - setDebouncedSearchTerm(searchTerm) - }, 500) // <- 디바운스 지연 시간 - - return () => clearTimeout(timer) - }, [searchTerm]) - - //BestBoards - useEffect(() => { - articleService - .getArticle(1, bestPageSize, 'like', '') - .then((response) => { - const sorted = [...(response.data.list || [])].sort( - (a, b) => b.likeCount - a.likeCount - ) - setBestArticleList({ - totalCount: response.data.totalCount, - list: sorted, - }) - }) - .catch((error) => { - console.error('베스트 게시글 불러오기 실패:', error) - }) - }, [bestPageSize]) - - // BoardList - useEffect(() => { - const fetchArticles = async () => { - try { - const response = await articleService.getArticle( - page, - 5, - orderBy, - debouncedSearchTerm // ← 디바운스된 검색어 사용 - ) - const newList = response.data.list || [] - - setArticleList((prev) => { - const combinedList = - page === 1 ? newList : [...(prev?.list || []), ...newList] - return { - totalCount: response.data.totalCount, - list: combinedList, - } - }) - - if (newList.length < 5) { - setHasMore(false) - } - } catch (error) { - console.error('게시글 불러오기 실패:', error) - } - } - - fetchArticles() - }, [page, selectedOption, debouncedSearchTerm]) - - // 선택/검색이 바뀌면 초기화 - useEffect(() => { - setPage(1) - setHasMore(true) - }, [selectedOption]) - - useEffect(() => { - setPage(1) - setHasMore(true) - }, [debouncedSearchTerm]) - - useEffect(() => { - const handleResize = () => { - if (window.innerWidth <= 743) { - setBestPageSize(1) - } else if (window.innerWidth <= 1023) { - setBestPageSize(2) - } else { - setBestPageSize(3) - } - } - - handleResize() // 처음 렌더링 시에도 계산 - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, []) return ( <> - - {articleList && } - {articleList && ( - setPage((prev) => prev + 1)} - hasMore={hasMore} - searchTerm={searchTerm} - setSearchTerm={setSearchTerm} - /> - )} - +
+ + +
) } export default Boards - -const Bone = styled.div` - width: 120rem; - margin: 2.4rem auto auto auto; - @media (max-width: 1023px) { - width: 69.6rem; - margin: 2.4rem auto auto auto; - } - @media (max-width: 743px) { - width: 34.4rem; - margin: 1rem auto 6.5rem auto; - } -` diff --git a/src/app/common/Button.tsx b/src/app/common/Button.tsx deleted file mode 100644 index 8439db93..00000000 --- a/src/app/common/Button.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import styled from 'styled-components' -import Link from 'next/link' -import { ReactNode, CSSProperties } from 'react' - -interface ButtonProps { - size: number - onClick?: () => void - disabled?: boolean - children?: ReactNode - style?: CSSProperties - prefix?: ReactNode - suffix?: ReactNode - as?: 'button' | typeof Link - to?: string - className?: string -} - -const Button = ({ - size = 48.5, - onClick, - disabled, - children, - style, - prefix, - suffix, - to, - className, -}: ButtonProps) => { - const isLink = !!to - - if (isLink && !to) { - console.error('Error: "to" prop is required when "as" is Link.') - return null - } - if (isLink) { - return ( - - - - {prefix && {prefix}} - {children} - {suffix && {suffix}} - - - - ) - } - return ( - - - {prefix && {prefix}} - {children} - {suffix && {suffix}} - - - ) -} - -export default Button - -interface StyledButtonWrapper { - size?: number -} - -const ButtonWrapper = styled.button` - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - border: none; - background: ${({ theme }) => theme.colors.PrimaryBlue[100]}; - color: ${({ theme }) => theme.colors.SecondaryGray[50]}; - font-weight: ${({ size }) => ButtonSize[size!]?.fontWeight || 600}; - border-radius: ${({ size }) => ButtonSize[size!]?.borderRadius || '40px'}; - - font-size: ${({ size }) => ButtonSize[size!]?.fontSize || '16px'}; - - transition: all 0.3s ease-in-out; - &:hover { - background: ${({ theme }) => theme.colors.PrimaryBlue[200]}; - transform: scale(1.01); - } - &:active { - transform: scale(0.95); - } - &:disabled { - background: ${({ theme }) => theme.colors.SecondaryGray[400]}; - cursor: not-allowed; - } -` -const ButtonInner = styled.span` - display: flex; - align-items: center; - gap: 0.3rem; -` -const ButtonInnerText = styled.span` - display: flex; - align-items: center; - justify-content: center; -` - -//Record는 TypeScript의 내장 제네릭 유틸리티 타입: 객체의 키와 값의 타입을 명확하게 정의 -// theme.d.ts와 다른 이유는 DefaultTheme 타입을 확장했고 여기는 안 함 -type ButtonSizeType = Record< - number, - { - fontSize: string - fontWeight: number - - borderRadius: string - } -> -const ButtonSize: ButtonSizeType = { - 56: { - fontSize: '20px', - fontWeight: 600, - - borderRadius: '40px', - }, - 48: { - fontSize: '18px', - fontWeight: 600, - - borderRadius: '40px', - }, - - 42.5: { - fontSize: '16px', - fontWeight: 600, - - borderRadius: '8px', - }, - 48.5: { - fontSize: '16px', - fontWeight: 600, - - borderRadius: '8px', - }, - 0: { - fontSize: '16px', - fontWeight: 600, - borderRadius: '40px', - }, -} diff --git a/src/app/common/ButtonImage.tsx b/src/app/common/ButtonImage.tsx deleted file mode 100644 index 8aa315dd..00000000 --- a/src/app/common/ButtonImage.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React, { useRef, useState } from 'react' - -import Plus from '../../../public/assets/svg/Plus.svg' -import Delete from '../../../public/assets/svg/Delete.svg' - -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import Image from 'next/image' - -const ButtonImage = () => { - const fileInputRef = useRef(null) - const [imagePreview, setImagePreview] = useState('') - const [errorMeassage, setErrorMeassage] = useState('') - - const handleButton = () => { - if (fileInputRef.current) { - // if문을 쓰는 이유는 useRef 값이 null일 수도 있기 때문문 - fileInputRef.current.click() - } - } - - const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (file) { - if (imagePreview) { - setErrorMeassage('*이미지 등록은 최대 1개까지 가능합니다.') - return - } - const imageUrl = URL.createObjectURL(file) - setImagePreview(imageUrl) - setErrorMeassage('') - } - } - - const handleDeleteClick = () => { - setImagePreview('') - } - console.log(imagePreview) - return ( - <> - - - - 이미지등록아이콘 - 이미지 등록 - - - {imagePreview && ( -
- - 미리보기 이미지 - - - 삭제버튼 - -
- )} -
- {errorMeassage && {errorMeassage}} - - - ) -} - -export default ButtonImage - -const ImageWrapper = styled.div` - width: 100%; - height: auto; - display: flex; - gap: 24px; - @media (max-width: 1023px) { - gap: 10px; - } - @media (max-width: 743px) { - width: 34.6rem; - height: 16.8rem; - position: relative; - } -` -const Bone = styled.div` - width: 28.2rem; - height: 28.2rem; - background-color: #e5e7eb; - border-radius: 12px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - position: relative; - @media (max-width: 1023px) { - width: 16.8rem; - height: 16.8rem; - } -` -const Container = styled.div` - width: fit-content; - height: 8.6rem; - display: flex; - align-items: center; - justify-content: space-between; - flex-direction: column; -` -const ImageInput = styled.input` - display: none; -` -const Text = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[400]}; -` -const PreviewImageWrapper = styled.div` - width: fit-content; - height: fit-content; - border-radius: 12px; - - img { - width: 28.2rem; - height: 28.2rem; - border-radius: 12px; - } - @media (max-width: 1023px) { - img { - width: 16.8rem; - height: 16.8rem; - } - } -` - -const DeleteIcon = styled.div` - img { - width: 2rem; - height: 2rem; - position: relative; - right: -25.25rem; - top: -27.25rem; - cursor: pointer; - } - @media (max-width: 1023px) { - img { - right: -14.25rem; - top: -16.25rem; - } - } -` -const ErrorMessage = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.error}; - margin-top: 1rem; -` diff --git a/src/app/common/DropDown.tsx b/src/app/common/DropDown.tsx deleted file mode 100644 index ea10e2d0..00000000 --- a/src/app/common/DropDown.tsx +++ /dev/null @@ -1,160 +0,0 @@ -'use client' -import React, { useRef, useState, useEffect } from 'react' -import styled from 'styled-components' -import Image from 'next/image' - -import ArrowDown from '../../../public/assets/image/ArrowDown.png' -import Sort from '../../../public/assets/svg/Sort.svg' - -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' - -type OptionType = { - name: string - value: string -} - -type DropDownProps = { - selectList: OptionType[] - selected: string - onChange: (value: string) => void - left?: string - top?: string -} - -const DropDown = ({ - selectList, - selected, - onChange, - left = '0', - top = '0', -}: DropDownProps) => { - const [isOpen, setIsOpen] = useState(false) - const selectRef = useRef(null) - - const toggleDropdown = () => { - setIsOpen(!isOpen) - } - - const handleSelect = (value: string) => { - onChange(value) - setIsOpen(false) - } - - useEffect(() => { - const handleResize = () => {} - - window.addEventListener('resize', handleResize) // resize: 창 크기가 변경될 때 발생하는 이벤트 / handleResize: 이벤트가 발생할 때 실행할 함수 - return () => window.removeEventListener('resize', handleResize) - }, []) - - return ( -
- - {window.innerWidth <= 743 ? ( - Sort - ) : ( - selectList.find((item) => item.value === selected)?.name - )} - - - {isOpen && ( - - {selectList.map((item) => ( - - ))} - - )} -
- ) -} - -export default DropDown - -const SelectBox = styled.div<{ $left?: string; $top?: string }>` - width: 13rem; - height: 100%; - cursor: pointer; - padding: 0.8rem 2rem; - border: 1px solid ${theme.colors.SecondaryGray[200]}; - border-radius: 1.2rem; - background-color: white; - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - @media (max-width: 743px) { - width: max-content; - height: max-content; - justify-content: center; - - left: ${({ $left }) => $left || '0'}; - top: ${({ $top }) => $top || '0'}; - padding: 0.9rem; - } -` -const ArrowDownImage = styled.img.withConfig({ - shouldForwardProp: (prop) => prop !== 'isOpen', -})<{ isOpen: boolean }>` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - transition: transform 0.3s ease; - transform: ${({ isOpen }) => (isOpen ? 'rotate(180deg)' : 'rotate(0deg)')}; - - @media (max-width: 743px) { - width: 0; - height: 0; - } -` -const SelectOption = styled.ul` - position: absolute; - top: 5.8rem; - - width: 100%; - border: 1px solid #cccccc; - border-radius: 12px; - background-color: #ffffff; - color: #181818; - ${(props) => textStyle(16, 400)(props)} - - z-index: 10; - list-style: none; - - display: flex; - align-items: center; - flex-direction: column; - - justify-content: space-around; - @media (max-width: 743px) { - position: absolute; - top: 62px; - left: -72px; - z-index: 1; - width: 130px; - height: 84px; - } -` -const Option = styled.li` - ${(props) => textStyle(16, 400)(props)} - padding: 9px 28px; - cursor: pointer; - width: inherit; - display: flex; - align-items: center; - justify-content: center; - border-radius: 12px; - &:hover { - background-color: #f6f6f6; - } - @media (max-width: 743px) { - padding: 7px 35px; - } -` diff --git a/src/app/common/Tag.tsx b/src/app/common/Tag.tsx deleted file mode 100644 index d39a4b04..00000000 --- a/src/app/common/Tag.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' - -import Delete from '../../../public/assets/svg/Delete.svg' - -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import Image from 'next/image' - -interface TagProps { - tag: string - onClick?: (tag: string) => void - showDelete?: boolean -} - -const Tag = ({ tag, onClick, showDelete = false }: TagProps) => { - console.log('Tag 컴포넌트에 전달된 productTags:', tag) - return ( -
- - #{tag} - {showDelete && ( - 삭제 onClick?.(tag)} /> - )} - -
- ) -} - -export default Tag - -const Bone = styled.div` - width: fit-content; - height: fit-content; - display: flex; - align-items: center; - justify-content: center; - padding: 5px 12px 5px 16px; - background-color: ${theme.colors.SecondaryGray[100]}; - border-radius: 26px; -` -const Text = styled.div` - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[800]}; - margin-right: 0.5rem; -` diff --git a/src/app/common/TextInputPlaceholder.tsx b/src/app/common/TextInputPlaceholder.tsx deleted file mode 100644 index b1c3ffdf..00000000 --- a/src/app/common/TextInputPlaceholder.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' - -interface TextInputPlaceholderProps { - placeholder?: string - height?: string - padding?: string - value: string - onChange: (e: React.ChangeEvent) => void - onKeyDown?: React.KeyboardEventHandler -} - -const TextInputPlaceholder = ({ - placeholder, - height, - padding, - value, - onChange, - onKeyDown, -}: TextInputPlaceholderProps) => { - return ( - - ) -} - -export default TextInputPlaceholder - -const Bone = styled.textarea<{ $height?: string; $padding?: string }>` - width: 100%; - height: ${(props) => props.$height ?? '56px'}; - padding: ${(props) => props.$padding ?? '15px 24px'}; - background-color: ${theme.colors.SecondaryGray[100]}; - ${(props) => textStyle(16, 400)(props)} - color: ${theme.colors.SecondaryGray[800]}; - border-radius: 12px; - border: none; - resize: none; - box-sizing: border-box; - display: block; - cursor: text; - word-break: break-word; - overflow-wrap: break-word; - white-space: pre-wrap; - - ::placeholder { - color: ${theme.colors.SecondaryGray[400]}; - } - - @media (max-width: 743px) { - ${(props) => textStyle(14, 400)(props)} - } -` diff --git a/src/app/globals.css b/src/app/globals.css index ebf1acbc..2b12cd88 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -16,7 +16,6 @@ html { body { font-size: 1.6rem !important; max-width: 100vw; - overflow-x: hidden; } body { diff --git a/src/app/login/login.module.scss b/src/app/login/login.module.scss new file mode 100644 index 00000000..c9b99113 --- /dev/null +++ b/src/app/login/login.module.scss @@ -0,0 +1,109 @@ +@import '../../styles/variables.scss'; + +.bone { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + width: 64rem; + margin: 23.1rem auto 23rem auto; + @media (max-width: 1023px) { + margin: 19rem auto 32rem auto; + } + @media (max-width: 743px) { + margin: 8rem auto 23rem auto; + width: 34.3rem; + } +} +.logo-container { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4rem; + @media (max-width: 743px) { + width: 19.8rem; + height: 6.6rem; + margin-bottom: 2.4rem; + } +} +.logo-panda { + img { + width: 10.3rem; + height: 10.3rem; + margin-right: 2.2rem; + display: flex; + } + @media (max-width: 743px) { + img { + width: 5.1rem; + height: 5.1rem; + margin-right: 1.1rem; + display: flex; + } + } +} +.logo-panda-text { + img { + width: 26.6rem; + height: 9rem; + display: flex; + align-items: center; + } + @media (max-width: 743px) { + img { + width: 13.3rem; + height: 4.5rem; + } + } +} + +.button-wrapper { + margin-bottom: 2.4rem; + width: 100%; +} +.login-button { + padding: 1.2rem 29.4rem; + width: max-content; + @media (max-width: 743px) { + padding: 1.2rem 14.5rem; + } +} +.simple-login-wrapper { + height: fit-content; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #e6f2ff; + padding: 1.6rem 2.3rem; + margin-bottom: 2.4rem; +} +.simple-login { + @include apply-font($font-16, 500); + color: $secondaryGray-800; +} +.image-wrapper { + height: 4.2rem; + width: 10rem; + display: flex; + gap: 1.6rem; + flex-direction: row; + align-items: center; + img { + width: 4.2rem; + height: 4.2rem; + display: flex; + } +} +.footer-container { + display: flex; + justify-content: space-between; +} +.first { + @include apply-font($font-14, 500); + color: $secondaryGray-800; +} +.register { + text-decoration: underline; + color: $primaryBlue-100; +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 074190e9..531f752b 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -3,129 +3,19 @@ import React, { useState, useEffect } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' +import Image from 'next/image' -import LoginField from '../LoginAndSignup/LoginField' -import Button from '../common/Button' +import LoginField from '../../components/domain/LoginAndSignup/LoginField' +import Button from '../../components/common/Button' -import Logo from '../../../public/assets/image/Logo.png' -import LogoFace from '../../../public/assets/image/LogoFace.png' -import Google from '../../../public/assets/svg/Google.svg' -import Kakao from '../../../public/assets/svg/Kakao.svg' +import Logo from '../../../public/assets/image/logo_text.png' +import LogoFace from '../../../public/assets/image/logo_face.png' +import Google from '../../../public/assets/svg/google.svg' +import Kakao from '../../../public/assets/svg/kakao.svg' import VisibillityOff from '../../../public/assets/svg/btn_visibillity_off.svg' import Visibillity from '../../../public/assets/svg/btn_visibillity.svg' -import styled from 'styled-components' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import Image from 'next/image' - -const Bone = styled.div` - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - width: 64rem; - margin: 23.1rem auto auto auto; - @media (max-width: 1023px) { - margin: 19rem auto auto auto; - } - @media (max-width: 743px) { - margin: 8rem auto auto auto; - width: 34.3rem; - } -` -const LogoContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 4rem; - @media (max-width: 743px) { - width: 19.8rem; - height: 6.6rem; - margin-bottom: 2.4rem; - } -` -const LogoPanda = styled.div` - img { - width: 10.3rem; - height: 10.3rem; - margin-right: 2.2rem; - display: flex; - } - @media (max-width: 743px) { - img { - width: 5.1rem; - height: 5.1rem; - margin-right: 1.1rem; - display: flex; - } - } -` -const LogoPandaText = styled.div` - img { - width: 26.6rem; - height: 9rem; - display: flex; - align-items: center; - } - @media (max-width: 743px) { - img { - width: 13.3rem; - height: 4.5rem; - } - } -` - -const ButtonWrapper = styled.div` - margin-bottom: 2.4rem; - width: 100%; -` -const LoginButton = styled(Button)` - padding: 1.2rem 29.4rem; - width: max-content; - @media (max-width: 743px) { - padding: 1.2rem 14.5rem; - } -` -const SimpleLoginWrapper = styled.div` - height: fit-content; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - background-color: #e6f2ff; - padding: 1.6rem 2.3rem; - margin-bottom: 2.4rem; -` -const SimpleLogin = styled.div` - ${(props) => textStyle(16, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ImageWrapper = styled.div` - height: 4.2rem; - width: 10rem; - display: flex; - gap: 1.6rem; - flex-direction: row; - align-items: center; - img { - width: 4.2rem; - height: 4.2rem; - display: flex; - } -` -const FooterContainer = styled.div` - display: flex; - justify-content: space-between; -` -const First = styled.div` - ${(props) => textStyle(14, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const Register = styled.div` - text-decoration: underline; - color: ${theme.colors.PrimaryBlue[100]}; -` +import styles from './login.module.scss' const Login = () => { const [email, setEmail] = useState('') @@ -135,6 +25,7 @@ const Login = () => { const [showPassword, setShowPassword] = useState(false) const [isState, setIsState] = useState(false) const router = useRouter() + const togglePasswordVisibility = () => { setShowPassword((prev) => !prev) } @@ -172,19 +63,19 @@ const Login = () => { setIsState(valid) }, [email, password]) return ( - - - +
+
+
판다마켓 로고 사진 - - +
+
판다마켓 로고 사진 - - +
+
{ placeholder="비밀번호를 입력해주세요" id="password" icon={showPassword ? Visibillity : VisibillityOff} - onIconClick={togglePasswordVisibility} + onIconClick={(e) => { + e.stopPropagation() + togglePasswordVisibility() + }} validate={validatePassword} value={password} onChange={(e) => { @@ -211,31 +105,36 @@ const Login = () => { }} error={passwordError} /> - - +
+ +
+
+
간편 로그인하기
+
구글 로고 사진 카카오 로고 사진 - - - - 판다마켓이 처음이신가요?   - +
+
+
+
판다마켓이 처음이신가요?  
+
회원가입 - - - +
+
+
) } diff --git a/src/app/page.style.ts b/src/app/page.style.ts index 1f2f390f..17719a23 100644 --- a/src/app/page.style.ts +++ b/src/app/page.style.ts @@ -1,12 +1,12 @@ import styled from 'styled-components' -import { theme } from './styles/theme' -import { textStyle } from './styles/textStyle' -import Button from './common/Button' +import { theme } from '../styles/theme' +import { textStyle } from '../styles/textStyle' +import Button from '../components/common/Button' export const HeaderTop = styled.header` display: flex; justify-content: center; - position: sticky; + top: 0; background-color: #ffffff; ` @@ -90,6 +90,7 @@ export const HeaderLogin = styled.a` export const HeaderMain = styled.div` width: 100%; background-color: #cfe5ff; + position: relative; ` export const HeaderMainContainer = styled.div` diff --git a/src/app/page.tsx b/src/app/page.tsx index 31a0c09b..cd10a712 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,10 +4,10 @@ import Image from 'next/image' import Link from 'next/link' import * as S from './page.style' -import Button from './common/Button' +import Button from '../components/common/Button' -import Logo from '../../public/assets/image/Logo.png' -import LogoFace from '../../public/assets/image/LogoFace.png' +import Logo from '../../public/assets/image/logo_text.png' +import LogoFace from '../../public/assets/image/logo_face.png' import HomeTop from '../../public/assets/image/home_top.png' import HomeBottom from '../../public/assets/image/home_bottom.png' import HomeHotItems from '../../public/assets/image/home_hot_items.png' diff --git "a/src/app/Privacy/Privacy\342\200\231.jsx" b/src/app/privacy/page.tsx similarity index 100% rename from "src/app/Privacy/Privacy\342\200\231.jsx" rename to src/app/privacy/page.tsx diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 6beddfb3..e3d62d2d 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -3,128 +3,19 @@ import React, { useState, useEffect } from 'react' import Image from 'next/image' import { useRouter } from 'next/navigation' +import Link from 'next/link' -import LoginField from '../LoginAndSignup/LoginField' -import Button from '../common/Button' +import LoginField from '../../components/domain/LoginAndSignup/LoginField' +import Button from '../../components/common/Button' -import Logo from '../../../public/assets/image/Logo.png' -import LogoFace from '../../../public/assets/image/LogoFace.png' -import Google from '../../../public/assets/svg/Google.svg' -import Kakao from '../../../public/assets/svg/Kakao.svg' +import Logo from '../../../public/assets/image/logo_text.png' +import LogoFace from '../../../public/assets/image/logo_face.png' +import Google from '../../../public/assets/svg/google.svg' +import Kakao from '../../../public/assets/svg/kakao.svg' import VisibillityOff from '../../../public/assets/svg/btn_visibillity_off.svg' import Visibillity from '../../../public/assets/svg/btn_visibillity.svg' -import { theme } from '../styles/theme' -import { textStyle } from '../styles/textStyle' -import styled from 'styled-components' -import Link from 'next/link' - -const Bone = styled.div` - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - width: 64rem; - margin: 23.1rem auto auto auto; - @media (max-width: 1023px) { - margin: 19rem auto auto auto; - } - @media (max-width: 743px) { - margin: 8rem auto auto auto; - width: 34.3rem; - } -` -const LogoContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 4rem; - @media (max-width: 743px) { - width: 19.8rem; - height: 6.6rem; - margin-bottom: 2.4rem; - } -` -const LogoPanda = styled.div` - img { - width: 10.3rem; - height: 10.3rem; - margin-right: 2.2rem; - display: flex; - } - @media (max-width: 743px) { - img { - width: 5.1rem; - height: 5.1rem; - margin-right: 1.1rem; - display: flex; - } - } -` -const LogoPandaText = styled.div` - img { - width: 26.6rem; - height: 9rem; - display: flex; - align-items: center; - } - @media (max-width: 743px) { - img { - width: 13.3rem; - height: 4.5rem; - } - } -` -const ButtonWrapper = styled.div` - margin-bottom: 2.4rem; - width: 100%; -` -const SignupButton = styled(Button)` - padding: 1.2rem 28.5rem; - width: max-content; - @media (max-width: 743px) { - padding: 1.2rem 13.6rem; - } -` -const SimpleLoginWrapper = styled.div` - height: fit-content; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - background-color: #e6f2ff; - padding: 1.6rem 2.3rem; - margin-bottom: 2.4rem; -` -const SimpleLogin = styled.div` - ${(props) => textStyle(16, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const ImageWrapper = styled.div` - height: 4.2rem; - width: 10rem; - display: flex; - gap: 1.6rem; - flex-direction: row; - align-items: center; - img { - width: 4.2rem; - height: 4.2rem; - display: flex; - } -` -const FooterContainer = styled.div` - display: flex; - justify-content: space-between; -` -const First = styled.div` - ${(props) => textStyle(14, 500)(props)} - color: ${theme.colors.SecondaryGray[800]}; -` -const Register = styled.div` - text-decoration: underline; - color: ${theme.colors.PrimaryBlue[100]}; -` +import styles from './signup.module.scss' const Signup = () => { const [email, setEmail] = useState('') @@ -187,19 +78,19 @@ const Signup = () => { setIsState(valid) }, [email, password, passwordConfirm]) return ( - - - +
+
+
판다마켓 로고 사진 - - +
+
판다마켓 로고 사진 - - +
+
{ }} error={passwordConfirmError} /> - - +
+ +
+
+
간편 로그인하기
+
구글 로고 사진 카카오 로고 사진 - - - - 이미 회원이신가요?   - +
+
+
+
이미 회원이신가요?  
+
로그인 - - - +
+
+
) } diff --git a/src/app/signup/signup.module.scss b/src/app/signup/signup.module.scss new file mode 100644 index 00000000..8ad3a43b --- /dev/null +++ b/src/app/signup/signup.module.scss @@ -0,0 +1,108 @@ +@import '../../styles/variables.scss'; + +.bone { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + width: 64rem; + margin: 23.1rem auto 17rem auto; + @media (max-width: 1023px) { + margin: 19rem auto 23rem auto; + } + @media (max-width: 743px) { + margin: 8rem auto 17rem auto; + width: 34.3rem; + } +} +.logo-container { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4rem; + @media (max-width: 743px) { + width: 19.8rem; + height: 6.6rem; + margin-bottom: 2.4rem; + } +} +.logo-panda { + img { + width: 10.3rem; + height: 10.3rem; + margin-right: 2.2rem; + display: flex; + } + @media (max-width: 743px) { + img { + width: 5.1rem; + height: 5.1rem; + margin-right: 1.1rem; + display: flex; + } + } +} +.logo-panda-text { + img { + width: 26.6rem; + height: 9rem; + display: flex; + align-items: center; + } + @media (max-width: 743px) { + img { + width: 13.3rem; + height: 4.5rem; + } + } +} +.button-wrapper { + margin-bottom: 2.4rem; + width: 100%; +} +.signup-button { + padding: 1.2rem 28.5rem; + width: max-content; + @media (max-width: 743px) { + padding: 1.2rem 13.6rem; + } +} +.simple-login-wrapper { + height: fit-content; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #e6f2ff; + padding: 1.6rem 2.3rem; + margin-bottom: 2.4rem; +} +.simple-login { + @include apply-font($font-16, 500); + color: $secondaryGray-800; +} +.image-wrapper { + height: 4.2rem; + width: 10rem; + display: flex; + gap: 1.6rem; + flex-direction: row; + align-items: center; + img { + width: 4.2rem; + height: 4.2rem; + display: flex; + } +} +.footer-container { + display: flex; + justify-content: space-between; +} +.first { + @include apply-font($font-14, 500); + color: $secondaryGray-800; +} +.register { + text-decoration: underline; + color: $primaryBlue-100; +} diff --git a/src/app/styles/Home.module.css b/src/app/styles/Home.module.css deleted file mode 100644 index 6676d2c6..00000000 --- a/src/app/styles/Home.module.css +++ /dev/null @@ -1,229 +0,0 @@ -.main { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - padding: 6rem; - min-height: 100vh; -} - -.description { - display: inherit; - justify-content: inherit; - align-items: inherit; - font-size: 0.85rem; - max-width: var(--max-width); - width: 100%; - z-index: 2; - font-family: var(--font-mono); -} - -.description a { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; -} - -.description p { - position: relative; - margin: 0; - padding: 1rem; - background-color: rgba(var(--callout-rgb), 0.5); - border: 1px solid rgba(var(--callout-border-rgb), 0.3); - border-radius: var(--border-radius); -} - -.code { - font-weight: 700; - font-family: var(--font-mono); -} - -.grid { - display: grid; - grid-template-columns: repeat(4, minmax(25%, auto)); - max-width: 100%; - width: var(--max-width); -} - -.card { - padding: 1rem 1.2rem; - border-radius: var(--border-radius); - background: rgba(var(--card-rgb), 0); - border: 1px solid rgba(var(--card-border-rgb), 0); - transition: background 200ms, border 200ms; -} - -.card span { - display: inline-block; - transition: transform 200ms; -} - -.card h2 { - font-weight: 600; - margin-bottom: 0.7rem; -} - -.card p { - margin: 0; - opacity: 0.6; - font-size: 0.9rem; - line-height: 1.5; - max-width: 30ch; -} - -.center { - display: flex; - justify-content: center; - align-items: center; - position: relative; - padding: 4rem 0; -} - -.center::before { - background: var(--secondary-glow); - border-radius: 50%; - width: 480px; - height: 360px; - margin-left: -400px; -} - -.center::after { - background: var(--primary-glow); - width: 240px; - height: 180px; - z-index: -1; -} - -.center::before, -.center::after { - content: ''; - left: 50%; - position: absolute; - filter: blur(45px); - transform: translateZ(0); -} - -.logo { - position: relative; -} -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - .card:hover { - background: rgba(var(--card-rgb), 0.1); - border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - - .card:hover span { - transform: translateX(4px); - } -} - -@media (prefers-reduced-motion) { - .card:hover span { - transform: none; - } -} - -/* Mobile */ -@media (max-width: 700px) { - .content { - padding: 4rem; - } - - .grid { - grid-template-columns: 1fr; - margin-bottom: 120px; - max-width: 320px; - text-align: center; - } - - .card { - padding: 1rem 2.5rem; - } - - .card h2 { - margin-bottom: 0.5rem; - } - - .center { - padding: 8rem 0 6rem; - } - - .center::before { - transform: none; - height: 300px; - } - - .description { - font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; - } -} - -/* Tablet and Smaller Desktop */ -@media (min-width: 701px) and (max-width: 1120px) { - .grid { - grid-template-columns: repeat(2, 50%); - } -} - -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} - -@keyframes rotate { - from { - transform: rotate(360deg); - } - to { - transform: rotate(0deg); - } -} diff --git a/src/components/common/Button.module.scss b/src/components/common/Button.module.scss new file mode 100644 index 00000000..8bef815f --- /dev/null +++ b/src/components/common/Button.module.scss @@ -0,0 +1,65 @@ +@import '../../styles/variables.scss'; + +.button-wrapper { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: none; + background: #007aff; + color: $white; + transition: all 0.3s ease-in-out; + + &:hover { + background: #005fcc; // PrimaryBlue[200] + transform: scale(1.01); + } + + &:active { + transform: scale(0.95); + } + + &:disabled { + background: #a0a0a0; // SecondaryGray[400] + cursor: not-allowed; + } +} + +.button-inner { + display: flex; + align-items: center; + gap: 0.3rem; +} + +.button-inner-text { + display: flex; + align-items: center; + justify-content: center; +} + +/* 사이즈별 클래스 */ +.size56 { + font-size: 20px; + font-weight: 600; + border-radius: 40px; +} +.size48 { + font-size: 18px; + font-weight: 600; + border-radius: 40px; +} +.size48_5 { + font-size: 16px; + font-weight: 600; + border-radius: 8px; +} +.size42_5 { + font-size: 16px; + font-weight: 600; + border-radius: 8px; +} +.size0 { + font-size: 16px; + font-weight: 600; + border-radius: 40px; +} diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx new file mode 100644 index 00000000..099bd1aa --- /dev/null +++ b/src/components/common/Button.tsx @@ -0,0 +1,73 @@ +import Link from 'next/link' +import { ReactNode, CSSProperties } from 'react' +import styles from './Button.module.scss' + +interface ButtonProps { + size: number + onClick?: () => void + disabled?: boolean + children?: ReactNode + style?: CSSProperties + prefix?: ReactNode + suffix?: ReactNode + to?: string + className?: string +} + +const Button = ({ + size = 48.5, + onClick, + disabled, + children, + style, + prefix, + suffix, + to, + className, +}: ButtonProps) => { + const isLink = !!to + + const sizeClassMap: Record = { + 56: styles.size56, + 48: styles.size48, + 48.5: styles.size48_5, + 42.5: styles.size42_5, + } + + const sizeClass = sizeClassMap[size] || styles.size48_5 + + const buttonClass = `${styles['button-wrapper']} ${sizeClass} ${ + className ?? '' + }` + + const content = ( +
+ {prefix && {prefix}} + {children} + {suffix && {suffix}} +
+ ) + + if (isLink) { + return ( + +
+ {content} +
+ + ) + } + + return ( + + ) +} + +export default Button diff --git a/src/components/common/ButtonImageInput.module.scss b/src/components/common/ButtonImageInput.module.scss new file mode 100644 index 00000000..e6b2bd9f --- /dev/null +++ b/src/components/common/ButtonImageInput.module.scss @@ -0,0 +1,85 @@ +@import '../../styles/variables.scss'; + +.image-wrapper { + width: 100%; + height: auto; + display: flex; + gap: 24px; + @media (max-width: 1023px) { + gap: 10px; + } + @media (max-width: 743px) { + width: 34.6rem; + height: 16.8rem; + position: relative; + } +} +.bone { + width: 28.2rem; + height: 28.2rem; + background-color: #e5e7eb; + border-radius: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + position: relative; + @media (max-width: 1023px) { + width: 16.8rem; + height: 16.8rem; + } +} +.container { + width: fit-content; + height: 8.6rem; + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: column; +} +.image-input { + display: none; +} +.text { + @include apply-font($font-16, 400); + color: $secondaryGray-400; +} +.preview-image-wrapper { + width: fit-content; + height: fit-content; + border-radius: 12px; + + img { + width: 28.2rem; + height: 28.2rem; + border-radius: 12px; + } + @media (max-width: 1023px) { + img { + width: 16.8rem; + height: 16.8rem; + } + } +} + +.delete-icon { + img { + width: 2rem; + height: 2rem; + position: relative; + right: -25.25rem; + top: -27.25rem; + cursor: pointer; + } + @media (max-width: 1023px) { + img { + right: -14.25rem; + top: -16.25rem; + } + } +} +.error-message { + @include apply-font($font-16, 400); + color: $error; + margin-top: 1rem; +} diff --git a/src/components/common/ButtonImageInput.tsx b/src/components/common/ButtonImageInput.tsx new file mode 100644 index 00000000..1b03f034 --- /dev/null +++ b/src/components/common/ButtonImageInput.tsx @@ -0,0 +1,72 @@ +import React, { useRef, useState } from 'react' +import Image from 'next/image' + +import Plus from '../../../public/assets/svg/plus_icon.svg' +import Delete from '../../../public/assets/svg/delete_tag.svg' + +import styles from './ButtonImageInput.module.scss' + +const ButtonImageInput = () => { + const fileInputRef = useRef(null) + const [imagePreview, setImagePreview] = useState('') + const [errorMeassage, setErrorMeassage] = useState('') + + const handleButton = () => { + if (fileInputRef.current) { + // if문을 쓰는 이유는 useRef 값이 null일 수도 있기 때문문 + fileInputRef.current?.click() + } + } + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) { + if (imagePreview) { + setErrorMeassage('*이미지 등록은 최대 1개까지 가능합니다.') + return + } + const imageUrl = URL.createObjectURL(file) + setImagePreview(imageUrl) + setErrorMeassage('') + } + } + + const handleDeleteClick = () => { + setImagePreview('') + } + console.log(imagePreview) + return ( + <> +
+
+
+ 이미지등록아이콘 +
이미지 등록
+
+
+ {imagePreview && ( +
+
+ 미리보기 이미지 +
+
+ 삭제버튼 +
+
+ )} +
+ {errorMeassage && ( +
{errorMeassage}
+ )} + + + ) +} + +export default ButtonImageInput diff --git a/src/components/common/DropDown.module.scss b/src/components/common/DropDown.module.scss new file mode 100644 index 00000000..2038136a --- /dev/null +++ b/src/components/common/DropDown.module.scss @@ -0,0 +1,89 @@ +@import '../../styles/variables.scss'; + +.dropdown-container { + position: relative; +} + +.select-box { + width: 13rem; + height: 100%; + cursor: pointer; + padding: 0.8rem 2rem; + border: 1px solid #ccc; + border-radius: 1.2rem; + background-color: white; + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + + @media (max-width: 743px) { + width: max-content; + height: max-content; + justify-content: center; + padding: 0.9rem; + position: absolute; + } +} + +.arrow-icon { + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + transition: transform 0.3s ease; + + &.open { + transform: rotate(180deg); + } + + @media (max-width: 743px) { + width: 0; + height: 0; + } +} + +.select-option { + position: absolute; + top: 5.8rem; + width: 100%; + border: 1px solid #cccccc; + border-radius: 12px; + background-color: #ffffff; + color: #181818; + z-index: 10; + list-style: none; + display: flex; + align-items: center; + flex-direction: column; + justify-content: space-around; + + @include apply-font($font-16, 400); + + @media (max-width: 743px) { + top: 62px; + left: -72px; + z-index: 1; + width: 130px; + height: 84px; + } +} + +.option { + padding: 9px 28px; + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 12px; + + @include apply-font($font-16, 400); + + &:hover { + background-color: #f6f6f6; + } + + @media (max-width: 743px) { + padding: 7px 35px; + } +} diff --git a/src/components/common/DropDown.tsx b/src/components/common/DropDown.tsx new file mode 100644 index 00000000..0c6a4eb3 --- /dev/null +++ b/src/components/common/DropDown.tsx @@ -0,0 +1,90 @@ +'use client' +import React, { useRef, useState, useEffect } from 'react' +import Image from 'next/image' + +import ArrowDown from '../../../public/assets/image/arrow_down.png' +import Sort from '../../../public/assets/svg/sort_arrow.svg' + +import styles from './DropDown.module.scss' + +type OptionType = { + name: string + value: string +} + +type DropDownProps = { + selectList: OptionType[] + selected: string + onChange: (value: string) => void + left?: string + top?: string +} + +const DropDown = ({ + selectList, + selected, + onChange, + left = '0', + top = '0', +}: DropDownProps) => { + const [isOpen, setIsOpen] = useState(false) + const [isMobile, setIsMobile] = useState(false) + const selectRef = useRef(null) + + const toggleDropdown = () => { + setIsOpen(!isOpen) + } + + const handleSelect = (value: string) => { + onChange(value) + setIsOpen(false) + } + + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth <= 743) + } + + checkIsMobile() // 초기 실행 + window.addEventListener('resize', checkIsMobile) + + return () => window.removeEventListener('resize', checkIsMobile) + }, []) + return ( +
+
+ {isMobile ? ( + Sort + ) : ( + selectList.find((item) => item.value === selected)?.name + )} + Arrow +
+ + {isOpen && ( +
    + {selectList.map((item) => ( +
  • handleSelect(item.value)} + > + {item.name} +
  • + ))} +
+ )} +
+ ) +} + +export default DropDown diff --git a/src/components/common/Tag.module.scss b/src/components/common/Tag.module.scss new file mode 100644 index 00000000..fd2e0366 --- /dev/null +++ b/src/components/common/Tag.module.scss @@ -0,0 +1,17 @@ +@import '../../styles/variables.scss'; + +.bone { + width: fit-content; + height: fit-content; + display: flex; + align-items: center; + justify-content: center; + padding: 5px 12px 5px 16px; + background-color: $secondaryGray-100; + border-radius: 26px; +} +.text { + @include apply-font($font-16, 400); + color: $secondaryGray-800; + margin-right: 0.5rem; +} diff --git a/src/components/common/Tag.tsx b/src/components/common/Tag.tsx new file mode 100644 index 00000000..88d19e0c --- /dev/null +++ b/src/components/common/Tag.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import Image from 'next/image' + +import Delete from '../../../public/assets/svg/delete_tag.svg' + +import styles from './Tag.module.scss' + +interface TagProps { + tag: string + onClick?: (tag: string) => void + showDelete?: boolean +} + +const Tag = ({ tag, onClick, showDelete = false }: TagProps) => { + console.log('Tag 컴포넌트에 전달된 productTags:', tag) + return ( +
+
+
#{tag}
+ {showDelete && ( + 삭제 onClick?.(tag)} /> + )} +
+
+ ) +} + +export default Tag diff --git a/src/components/common/TextInputPlaceholder.module.scss b/src/components/common/TextInputPlaceholder.module.scss new file mode 100644 index 00000000..b7e0fe21 --- /dev/null +++ b/src/components/common/TextInputPlaceholder.module.scss @@ -0,0 +1,26 @@ +@import '../../styles/variables.scss'; + +.bone { + width: 100%; + + background-color: $secondaryGray-100; + @include apply-font($font-16, 400); + color: $secondaryGray-800; + border-radius: 12px; + border: none; + resize: none; + box-sizing: border-box; + display: block; + cursor: text; + word-break: break-word; + overflow-wrap: break-word; + white-space: pre-wrap; + + &::placeholder { + color: $secondaryGray-400; + } + + @media (max-width: 743px) { + @include apply-font($font-14, 400); + } +} diff --git a/src/components/common/TextInputPlaceholder.tsx b/src/components/common/TextInputPlaceholder.tsx new file mode 100644 index 00000000..2a6579c9 --- /dev/null +++ b/src/components/common/TextInputPlaceholder.tsx @@ -0,0 +1,63 @@ +import React from 'react' + +import styles from './TextInputPlaceholder.module.scss' + +interface TextInputPlaceholderProps { + placeholder?: string + height?: string + padding?: string + value: string + onChange: (e: React.ChangeEvent) => void + onKeyDown?: React.KeyboardEventHandler +} + +const TextInputPlaceholder = ({ + placeholder, + height, + padding, + value, + onChange, + onKeyDown, +}: TextInputPlaceholderProps) => { + return ( +