diff --git a/vite-project/package-lock.json b/vite-project/package-lock.json index 2d849630..265724f9 100644 --- a/vite-project/package-lock.json +++ b/vite-project/package-lock.json @@ -15,13 +15,17 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^24.3.0", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", + "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^4.4.1", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "typescript": "^5.9.2", + "typescript-eslint": "^8.42.0", "vite": "^6.3.5" } }, @@ -1043,6 +1047,44 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1382,6 +1424,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1389,6 +1444,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@types/react": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", @@ -1409,12 +1474,295 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", + "integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/type-utils": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz", + "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz", + "integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.42.0", + "@typescript-eslint/types": "^8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", + "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz", + "integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz", + "integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz", + "integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz", + "integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.42.0", + "@typescript-eslint/tsconfig-utils": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz", + "integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz", + "integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1517,6 +1865,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.25.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", @@ -1973,6 +2334,36 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1987,6 +2378,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", @@ -2015,6 +2416,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2104,6 +2518,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2114,6 +2535,16 @@ "node": ">=8" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2174,6 +2605,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2305,6 +2746,43 @@ "yallist": "^3.0.2" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2514,6 +2992,27 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -2535,6 +3034,13 @@ "react": "^19.1.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -2593,6 +3099,17 @@ "node": ">=4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.45.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", @@ -2633,6 +3150,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -2798,6 +3339,32 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-regex-range": { + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -2817,6 +3384,51 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz", + "integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", diff --git a/vite-project/package.json b/vite-project/package.json index 85b90b29..a69dc0c3 100644 --- a/vite-project/package.json +++ b/vite-project/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, @@ -17,13 +17,17 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^24.3.0", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", + "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^4.4.1", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "typescript": "^5.9.2", + "typescript-eslint": "^8.42.0", "vite": "^6.3.5" } } diff --git a/vite-project/src/app.jsx b/vite-project/src/app.jsx index 39842696..1950f9f1 100644 --- a/vite-project/src/app.jsx +++ b/vite-project/src/app.jsx @@ -1,22 +1,56 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; -import HomeLayout from "./layouts/home-layout/home-layout"; -import AddItemPage from "./pages/add-item/add-item-page"; -import ItemDetailPage from "./pages/items/item-detail-page"; -import ItemsPage from "./pages/items/items-page"; +import HomeLayout from "./layouts/home-layout"; +import OnboardingLayout from "./layouts/onboarding-layout"; +import AddItemPage from "./pages/add-item-page"; +import ItemDetailPage from "./pages/item-detail-page"; +import ItemsPage from "./pages/items-page"; +import LoginPage from "./pages/login-page"; +import NotFoundPage from "./pages/not-found-page"; +import OnboardingPage from "./pages/onboarding-page"; +import SignUpPage from "./pages/signup-page"; import "./styles/global.css"; import "./styles/palette.css"; function App() { return ( - - - } /> - } /> - } /> - NOT IMPLEMENTED} /> - - + + + + + } + /> + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + ); } diff --git a/vite-project/src/assets/ic-facebook.svg b/vite-project/src/assets/ic-facebook.svg new file mode 100644 index 00000000..b9c9d493 --- /dev/null +++ b/vite-project/src/assets/ic-facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/vite-project/src/assets/ic-google.svg b/vite-project/src/assets/ic-google.svg new file mode 100644 index 00000000..df44db81 --- /dev/null +++ b/vite-project/src/assets/ic-google.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/vite-project/src/assets/ic-instagram.svg b/vite-project/src/assets/ic-instagram.svg new file mode 100644 index 00000000..0b9337b0 --- /dev/null +++ b/vite-project/src/assets/ic-instagram.svg @@ -0,0 +1,3 @@ + + + diff --git a/vite-project/src/assets/ic-kakaotalk.svg b/vite-project/src/assets/ic-kakaotalk.svg new file mode 100644 index 00000000..a7990c3e --- /dev/null +++ b/vite-project/src/assets/ic-kakaotalk.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/vite-project/src/assets/ic-twitter.svg b/vite-project/src/assets/ic-twitter.svg new file mode 100644 index 00000000..14a6069a --- /dev/null +++ b/vite-project/src/assets/ic-twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/vite-project/src/assets/ic-youtube.svg b/vite-project/src/assets/ic-youtube.svg new file mode 100644 index 00000000..ad8b97ba --- /dev/null +++ b/vite-project/src/assets/ic-youtube.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/vite-project/src/assets/landing-image-bottom.png b/vite-project/src/assets/landing-image-bottom.png new file mode 100644 index 00000000..1258dabb Binary files /dev/null and b/vite-project/src/assets/landing-image-bottom.png differ diff --git a/vite-project/src/assets/landing-image_top.png b/vite-project/src/assets/landing-image_top.png new file mode 100644 index 00000000..7ccc211f Binary files /dev/null and b/vite-project/src/assets/landing-image_top.png differ diff --git a/vite-project/src/assets/logo-large.svg b/vite-project/src/assets/logo-large.svg index ceaa7bf0..4373386b 100644 --- a/vite-project/src/assets/logo-large.svg +++ b/vite-project/src/assets/logo-large.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/vite-project/src/assets/logo-medium.svg b/vite-project/src/assets/logo-medium.svg new file mode 100644 index 00000000..47b920af --- /dev/null +++ b/vite-project/src/assets/logo-medium.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/vite-project/src/assets/logo-small.svg b/vite-project/src/assets/logo-small.svg index dfab2145..ceaa7bf0 100644 --- a/vite-project/src/assets/logo-small.svg +++ b/vite-project/src/assets/logo-small.svg @@ -1,3 +1,15 @@ - - + + + + + + + + + + + + + + diff --git a/vite-project/src/assets/logo-text-xsmall.svg b/vite-project/src/assets/logo-text-xsmall.svg new file mode 100644 index 00000000..dfab2145 --- /dev/null +++ b/vite-project/src/assets/logo-text-xsmall.svg @@ -0,0 +1,3 @@ + + + diff --git a/vite-project/src/assets/onboarding-hot-item.png b/vite-project/src/assets/onboarding-hot-item.png new file mode 100644 index 00000000..01e055e0 Binary files /dev/null and b/vite-project/src/assets/onboarding-hot-item.png differ diff --git a/vite-project/src/assets/onboarding-register.png b/vite-project/src/assets/onboarding-register.png new file mode 100644 index 00000000..42ca8636 Binary files /dev/null and b/vite-project/src/assets/onboarding-register.png differ diff --git a/vite-project/src/assets/onboarding-search.png b/vite-project/src/assets/onboarding-search.png new file mode 100644 index 00000000..ad93c905 Binary files /dev/null and b/vite-project/src/assets/onboarding-search.png differ diff --git a/vite-project/src/components/avatar/avatar.jsx b/vite-project/src/components/avatar/avatar.tsx similarity index 67% rename from vite-project/src/components/avatar/avatar.jsx rename to vite-project/src/components/avatar/avatar.tsx index 4751b651..92f47395 100644 --- a/vite-project/src/components/avatar/avatar.jsx +++ b/vite-project/src/components/avatar/avatar.tsx @@ -1,7 +1,8 @@ +import type { JSX } from "react"; import styled from "styled-components"; import defaultImg from "../../assets/profile-default.svg"; -const StyledAvatar = styled.div` +const StyledAvatar = styled.div<{ $size: number }>` width: ${({ $size }) => $size}px; height: ${({ $size }) => $size}px; @@ -15,7 +16,12 @@ const StyledAvatar = styled.div` const DEFAULT_SIZE = 40; -function Avatar({ imageUrl, size = DEFAULT_SIZE }) { +interface Props { + imageUrl?: string; + size?: number; +} + +function Avatar({ imageUrl, size = DEFAULT_SIZE }: Props): JSX.Element { return ( diff --git a/vite-project/src/components/button/button-styles.js b/vite-project/src/components/button/button-styles.js deleted file mode 100644 index b97948b3..00000000 --- a/vite-project/src/components/button/button-styles.js +++ /dev/null @@ -1,11 +0,0 @@ -const BUTTON_SIZE = Object.freeze({ - medium: "medium", - small: "small", -}); - -const BUTTON_TYPE = Object.freeze({ - round: "round", - pill: "pill", -}); - -export { BUTTON_SIZE, BUTTON_TYPE }; diff --git a/vite-project/src/components/button/button-styles.ts b/vite-project/src/components/button/button-styles.ts new file mode 100644 index 00000000..db267f90 --- /dev/null +++ b/vite-project/src/components/button/button-styles.ts @@ -0,0 +1,15 @@ +const ButtonSize = { + medium: "medium", + small: "small", +} as const; + +type ButtonSize = (typeof ButtonSize)[keyof typeof ButtonSize]; + +const ButtonShape = { + round: "round", + pill: "pill", +} as const; + +type ButtonShape = (typeof ButtonShape)[keyof typeof ButtonShape]; + +export { ButtonShape, ButtonSize }; diff --git a/vite-project/src/components/button/button.jsx b/vite-project/src/components/button/button.tsx similarity index 54% rename from vite-project/src/components/button/button.jsx rename to vite-project/src/components/button/button.tsx index 979be3c0..7f52009b 100644 --- a/vite-project/src/components/button/button.jsx +++ b/vite-project/src/components/button/button.tsx @@ -1,25 +1,27 @@ +import type { ButtonHTMLAttributes } from "react"; import styled from "styled-components"; -import { BUTTON_SIZE, BUTTON_TYPE } from "./button-styles"; +import { ButtonShape, ButtonSize } from "./button-styles"; const BUTTON_STYLE = { - [BUTTON_SIZE.medium]: { + [ButtonSize.medium]: { padding: "11px 40px", fontSize: 18, }, - [BUTTON_SIZE.small]: { + [ButtonSize.small]: { padding: "8px 23px", fontSize: 16, }, - [BUTTON_TYPE.round]: { + [ButtonShape.round]: { borderRadius: 8, }, - [BUTTON_TYPE.pill]: { + [ButtonShape.pill]: { borderRadius: 24, }, }; -const StyledButton = styled.button` +const StyledButton = styled.button<{ $size: ButtonSize; $shape: ButtonShape }>` display: flex; + justify-content: center; gap: 8px; background-color: var(--color-primary-100); padding: ${({ $size }) => BUTTON_STYLE[$size].padding}; @@ -27,7 +29,7 @@ const StyledButton = styled.button` font-size: ${({ $size }) => BUTTON_STYLE[$size].fontSize}px; font-weight: 600; line-height: 26px; - border-radius: ${({ $type }) => BUTTON_STYLE[$type].borderRadius}px; + border-radius: ${({ $shape }) => BUTTON_STYLE[$shape].borderRadius}px; border: none; cursor: pointer; @@ -41,14 +43,20 @@ const StyledButton = styled.button` } `; +interface Props extends ButtonHTMLAttributes { + size?: ButtonSize; + shape?: ButtonShape; + children: React.ReactNode; +} + function Button({ children, - size = BUTTON_SIZE.small, - type = BUTTON_TYPE.round, + size = ButtonSize.small, + shape = ButtonShape.round, ...props -}) { +}: Props) { return ( - + {children} ); diff --git a/vite-project/src/components/button/social-login-button.tsx b/vite-project/src/components/button/social-login-button.tsx new file mode 100644 index 00000000..eb0c2ee2 --- /dev/null +++ b/vite-project/src/components/button/social-login-button.tsx @@ -0,0 +1,24 @@ +import type { JSX } from "react/jsx-dev-runtime"; +import { styled } from "styled-components"; +import type { SocialLoginType } from "../social/social-login"; + +const StyledSocialLoginButton = styled.button` + background: none; + border: none; + cursor: pointer; + padding: 0; +`; + +interface Props { + socialLogin: SocialLoginType; +} + +function SocialLoginButton({ socialLogin }: Props): JSX.Element { + return ( + + + + ); +} + +export default SocialLoginButton; diff --git a/vite-project/src/components/input/form-input.tsx b/vite-project/src/components/input/form-input.tsx new file mode 100644 index 00000000..9e1182c1 --- /dev/null +++ b/vite-project/src/components/input/form-input.tsx @@ -0,0 +1,31 @@ +import type { InputHTMLAttributes, JSX } from "react"; +import styled from "styled-components"; +import TextInput from "./text-input"; + +const StyledFormInput = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; + + label { + font-size: 18px; + font-weight: 700; + line-height: 26px; + } +`; + +interface Props extends InputHTMLAttributes { + labelText: string; +} + +function FormInput({ type, labelText, id, ...props }: Props): JSX.Element { + return ( + + {labelText} + + + ); +} + +export default FormInput; diff --git a/vite-project/src/components/input/input-styles.ts b/vite-project/src/components/input/input-styles.ts new file mode 100644 index 00000000..4b94a7f1 --- /dev/null +++ b/vite-project/src/components/input/input-styles.ts @@ -0,0 +1,29 @@ +import { css } from "styled-components"; + +const fontStyle = css` + font-size: 16px; + font-weight: 400; + line-height: 26px; +`; + +const inputStyle = css` + background-color: var(--color-secondary-100); + padding: 16px 24px; + border-radius: 12px; +`; + +const inputTextStyle = css` + ${fontStyle} + width: 100%; + border: none; + background: none; + padding: 0; + color: var(--color-secondary-800); +`; + +const inputPlaceholderStyle = css` + color: var(--color-secondary-400); + ${fontStyle} +`; + +export { inputPlaceholderStyle, inputStyle, inputTextStyle }; diff --git a/vite-project/src/components/input/text-area-input.tsx b/vite-project/src/components/input/text-area-input.tsx new file mode 100644 index 00000000..7d3fcd90 --- /dev/null +++ b/vite-project/src/components/input/text-area-input.tsx @@ -0,0 +1,31 @@ +import type { JSX, TextareaHTMLAttributes } from "react"; +import styled from "styled-components"; +import { inputPlaceholderStyle, inputStyle } from "./input-styles"; + +const StyledTextAreaInput = styled.div` + ${inputStyle} + + textarea { + ${inputStyle} + height: 100%; + resize: none; + } + textarea:focus { + outline: none; + } + textarea::placeholder { + ${inputPlaceholderStyle} + } +`; + +interface Props extends TextareaHTMLAttributes {} + +function TextAreaInput({ ...props }: Props): JSX.Element { + return ( + + + + ); +} + +export default TextAreaInput; diff --git a/vite-project/src/components/input/text-input.jsx b/vite-project/src/components/input/text-input.jsx deleted file mode 100644 index 8a3c79c2..00000000 --- a/vite-project/src/components/input/text-input.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import styled, { css } from "styled-components"; - -const textStyle = css` - font-size: 16px; - font-weight: 400; - line-height: 26px; -`; - -const inputStyle = css` - ${textStyle} - width: 100%; - border: none; - background: none; - padding: 0; - color: var(--color-secondary-800); -`; - -const inputPlaceholderStyle = css` - color: var(--color-secondary-400); - ${textStyle} -`; - -const StyledTextInput = styled.div` - background-color: var(--color-secondary-100); - padding: 16px 24px; - border-radius: 12px; - - input { - ${inputStyle} - } - input:focus { - outline: none; - } - input::placeholder { - ${inputPlaceholderStyle} - } - - textarea { - ${inputStyle} - height: 100%; - resize: none; - } - textarea:focus { - outline: none; - } - textarea::placeholder { - ${inputPlaceholderStyle} - } -`; - -function TextInput({ multiline = false, ...props }) { - return ( - - {multiline ? : } - - ); -} - -export default TextInput; diff --git a/vite-project/src/components/input/text-input.tsx b/vite-project/src/components/input/text-input.tsx new file mode 100644 index 00000000..2ae896d1 --- /dev/null +++ b/vite-project/src/components/input/text-input.tsx @@ -0,0 +1,35 @@ +import type { InputHTMLAttributes, JSX } from "react"; +import styled from "styled-components"; +import { + inputPlaceholderStyle, + inputStyle, + inputTextStyle, +} from "./input-styles"; + +const StyledTextInput = styled.div` + ${inputStyle} + + input { + ${inputTextStyle} + } + input:focus { + outline: none; + } + input::placeholder { + ${inputPlaceholderStyle} + } +`; + +interface Props extends InputHTMLAttributes { + trailingIcon?: string; +} + +function TextInput({ type = "text", ...props }: Props): JSX.Element { + return ( + + + + ); +} + +export default TextInput; diff --git a/vite-project/src/components/link/link-list.tsx b/vite-project/src/components/link/link-list.tsx new file mode 100644 index 00000000..ac54400b --- /dev/null +++ b/vite-project/src/components/link/link-list.tsx @@ -0,0 +1,36 @@ +import type { JSX } from "react"; +import styled from "styled-components"; + +const LinkListItem = styled.li` + padding: 21px 15px; + font-size: 18px; + font-weight: 700; + line-height: 26px; + + @media (max-width: 767px) { + padding: 21px 0; + font-size: 16px; + } +`; + +const StyledLinkList = styled.ul` + display: flex; + align-items: center; + margin: 0; + padding: 0; + list-style: none; /* Tablet */ + + @media (max-width: 767px) { + gap: 8px; + } +`; + +interface Props { + children: React.ReactNode; +} + +function LinkList({ children }: Props): JSX.Element { + return {children}; +} + +export { LinkList, LinkListItem }; diff --git a/vite-project/src/components/link/social-link.tsx b/vite-project/src/components/link/social-link.tsx new file mode 100644 index 00000000..9458de92 --- /dev/null +++ b/vite-project/src/components/link/social-link.tsx @@ -0,0 +1,12 @@ +import type { JSX } from "react"; +import { type SocialType } from "../social/social"; + +function SocialLink({ social }: { social: SocialType }): JSX.Element { + return ( + + + + ); +} + +export default SocialLink; diff --git a/vite-project/src/components/link/styled-nav-link.tsx b/vite-project/src/components/link/styled-nav-link.tsx new file mode 100644 index 00000000..501fbf27 --- /dev/null +++ b/vite-project/src/components/link/styled-nav-link.tsx @@ -0,0 +1,29 @@ +import type { JSX } from "react"; +import { NavLink, useLocation } from "react-router-dom"; + +interface Props { + to: string; + activePaths?: string[]; + children: React.ReactNode; +} + +type NavLinkRenderCallback = (isActive: boolean) => React.CSSProperties; + +function StyledNavLink({ to, activePaths = [], children }: Props): JSX.Element { + const location = useLocation(); + + const linkStyle: NavLinkRenderCallback = (isActive) => ({ + color: `var(--color-${isActive ? "primary-100" : "secondary-600"})`, + textDecoration: "none", + }); + + const active = activePaths.includes(location.pathname); + + return ( + linkStyle(isActive || active)}> + {children} + + ); +} + +export default StyledNavLink; diff --git a/vite-project/src/components/logo/form-logo.tsx b/vite-project/src/components/logo/form-logo.tsx new file mode 100644 index 00000000..569c0920 --- /dev/null +++ b/vite-project/src/components/logo/form-logo.tsx @@ -0,0 +1,14 @@ +import largeLogo from "../../assets/logo-large.svg"; +import mediumLogo from "../../assets/logo-medium.svg"; +import { MediaQueryBreakpoint } from "../../utils/breakpoint"; + +function FormLogo() { + return ( + + + + + ); +} + +export default FormLogo; diff --git a/vite-project/src/components/logo/nav-logo.tsx b/vite-project/src/components/logo/nav-logo.tsx new file mode 100644 index 00000000..c845e65f --- /dev/null +++ b/vite-project/src/components/logo/nav-logo.tsx @@ -0,0 +1,28 @@ +import type { JSX } from "react"; +import styled from "styled-components"; +import smallLogo from "../../assets/logo-small.svg"; +import textLogo from "../../assets/logo-text-xsmall.svg"; +import { MediaQueryBreakpoint } from "../../utils/breakpoint"; + +const StyledNavLogo = styled.picture` + margin-right: 32px; + + @media (max-width: 1199px) { + margin-right: 20px; + } + + @media (max-width: 767px) { + margin-right: 0px; + } +`; + +function NavLogo(): JSX.Element { + return ( + + + + + ); +} + +export default NavLogo; diff --git a/vite-project/src/components/nav/home-nav-bar.tsx b/vite-project/src/components/nav/home-nav-bar.tsx new file mode 100644 index 00000000..ee464c43 --- /dev/null +++ b/vite-project/src/components/nav/home-nav-bar.tsx @@ -0,0 +1,32 @@ +import type { JSX } from "react"; +import { Link } from "react-router-dom"; +import Avatar from "../avatar/avatar"; +import { LinkList, LinkListItem } from "../link/link-list"; +import StyledNavLink from "../link/styled-nav-link"; +import NavLogo from "../logo/nav-logo"; +import NavBar from "./nav-bar"; + +function HomeNavBar(): JSX.Element { + return ( + + + + + + + + + 자유게시판 + + + + 중고마켓 + + + + + + ); +} + +export default HomeNavBar; diff --git a/vite-project/src/components/nav/nav-bar.tsx b/vite-project/src/components/nav/nav-bar.tsx new file mode 100644 index 00000000..43150666 --- /dev/null +++ b/vite-project/src/components/nav/nav-bar.tsx @@ -0,0 +1,39 @@ +import type { JSX } from "react"; +import styled from "styled-components"; + +const Content = styled.div` + max-width: 1200px; + margin: 0 auto; + width: 100%; + height: 70px; + display: flex; + justify-content: space-between; + align-items: center; +`; + +const StyledNavBar = styled.nav` + border-bottom: 1px solid #dfdfdf; + background-color: white; + + @media (max-width: 1199px) { + padding: 0 24px; + } + + @media (max-width: 767px) { + padding: 0 16px; + } +`; + +interface Props { + children: React.ReactNode; +} + +function NavBar({ children }: Props): JSX.Element { + return ( + + {children} + + ); +} + +export default NavBar; diff --git a/vite-project/src/components/nav/onboarding-nav-bar.tsx b/vite-project/src/components/nav/onboarding-nav-bar.tsx new file mode 100644 index 00000000..ef212fa9 --- /dev/null +++ b/vite-project/src/components/nav/onboarding-nav-bar.tsx @@ -0,0 +1,24 @@ +import { useNavigate } from "react-router-dom"; +import Button from "../button/button"; +import { ButtonSize } from "../button/button-styles"; +import NavLogo from "../logo/nav-logo"; +import NavBar from "./nav-bar"; + +function OnboardingNavBar() { + const navigate = useNavigate(); + + const handleLoginClick = () => { + navigate("/login"); + }; + + return ( + + + + 로그인 + + + ); +} + +export default OnboardingNavBar; diff --git a/vite-project/src/components/social/form-social-login.tsx b/vite-project/src/components/social/form-social-login.tsx new file mode 100644 index 00000000..79b59e5f --- /dev/null +++ b/vite-project/src/components/social/form-social-login.tsx @@ -0,0 +1,45 @@ +import type { JSX } from "react"; +import { styled } from "styled-components"; +import SocialLoginButton from "../button/social-login-button"; +import { SocialLogin } from "./social-login"; + +const StyledFormSocialLogin = styled.div` + background-color: #e6f2ff; + border-radius: 8px; + padding: 16px 24px; + display: flex; + justify-content: space-between; + align-items: center; + + span { + font-weight: 500; + line-height: 26px; + color: var(--color-secondary-800); + } + + ul { + margin: 0; + padding: 0; + list-style: none; + display: flex; + gap: 16px; + } +`; + +function FormSocialLogin(): JSX.Element { + return ( + + 간편 로그인하기 + + + + + + + + + + ); +} + +export default FormSocialLogin; diff --git a/vite-project/src/components/social/social-login.ts b/vite-project/src/components/social/social-login.ts new file mode 100644 index 00000000..067a2617 --- /dev/null +++ b/vite-project/src/components/social/social-login.ts @@ -0,0 +1,17 @@ +import googleImage from "../../assets/ic-google.svg"; +import kakaoTalkImage from "../../assets/ic-kakaotalk.svg"; + +const SocialLogin = { + kakaotalk: { + name: "kakaotalk", + image: kakaoTalkImage, + }, + google: { + name: "google", + image: googleImage, + }, +} as const; + +export type SocialLoginType = (typeof SocialLogin)[keyof typeof SocialLogin]; + +export { SocialLogin }; diff --git a/vite-project/src/components/social/social.ts b/vite-project/src/components/social/social.ts new file mode 100644 index 00000000..68901733 --- /dev/null +++ b/vite-project/src/components/social/social.ts @@ -0,0 +1,27 @@ +import facebookIcon from "../../assets/ic-facebook.svg"; +import instagramIcon from "../../assets/ic-instagram.svg"; +import twitterIcon from "../../assets/ic-twitter.svg"; +import youtubeIcon from "../../assets/ic-youtube.svg"; + +const Social = { + facebook: { + icon: facebookIcon, + link: "https://www.facebook.com", + }, + twitter: { + icon: twitterIcon, + link: "https://www.twitter.com/", + }, + youtube: { + icon: youtubeIcon, + link: "https://www.youtube.com/", + }, + instagram: { + icon: instagramIcon, + link: "https://www.instagram.com/", + }, +} as const; + +export type SocialType = (typeof Social)[keyof typeof Social]; + +export { Social }; diff --git a/vite-project/src/features/comment/components/comment-form.jsx b/vite-project/src/features/comment/components/comment-form.jsx index f6b1a11d..116db37e 100644 --- a/vite-project/src/features/comment/components/comment-form.jsx +++ b/vite-project/src/features/comment/components/comment-form.jsx @@ -1,7 +1,7 @@ import { useState } from "react"; import styled from "styled-components"; import Button from "../../../components/button/button"; -import TextInput from "../../../components/input/text-input"; +import TextAreaInput from "../../../components/input/text-area-input"; const StyledCommentForm = styled.form` display: flex; @@ -44,12 +44,11 @@ function CommentForm() { return ( 문의하기 - 등록 diff --git a/vite-project/src/features/onboarding/components/content-section.tsx b/vite-project/src/features/onboarding/components/content-section.tsx new file mode 100644 index 00000000..b8403ff8 --- /dev/null +++ b/vite-project/src/features/onboarding/components/content-section.tsx @@ -0,0 +1,147 @@ +import type { JSX } from "react"; +import { styled } from "styled-components"; +import { useDevice } from "../../../hooks/useDevice"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; +import { type OnboardingContentType } from "../models/onboarding-content"; +import OnboardingTitle from "./onboarding-title"; + +interface Props { + content: OnboardingContentType; + reverse?: boolean; +} + +function breakText(text: string): JSX.Element { + const elements = text.split("\n").map((value) => ( + + {value} + + )); + return <>{elements}>; +} + +function Title({ children }: { children: string }): JSX.Element { + const { isDesktop } = useDevice(); + const title = isDesktop ? breakText(children) : children; + return {title}; +} + +const TextContainer = styled.div<{ $reverse: boolean }>` + display: flex; + flex-direction: column; + text-align: ${({ $reverse }) => ($reverse ? "right" : "left")}; + + & > span { + font-size: 18px; + font-weight: 800; + line-height: 26px; + color: var(--color-primary-100); + } + + h2 { + margin-top: 12px; + margin-bottom: 24px; + } + + p { + margin: 0; + font-size: 24px; + font-weight: 500; + line-height: 32px; + } + + @media ${MediaQueryBreakpoint.tablet} { + span { + font-weight: 700; + } + + h2 { + margin-top: 16px; + } + + p { + font-size: 18px; + line-height: 26px; + } + } + + @media ${MediaQueryBreakpoint.mobile} { + span { + font-size: 16px; + font-weight: 700; + } + + h2 { + margin-top: 8px; + margin-bottom: 16px; + } + + p { + font-size: 16px; + } + } +`; + +const Content = styled.div<{ $reverse: boolean }>` + width: 988px; + height: 444px; + margin: 0 auto; + background-color: #f9f9f9; + display: flex; + gap: 64px; + flex-direction: ${({ $reverse }) => ($reverse ? "row-reverse" : "row")}; + justify-content: center; + align-items: center; + border-radius: 12px; + overflow: hidden; + + @media ${MediaQueryBreakpoint.tablet} { + width: auto; + height: auto; + gap: 24px; + flex-direction: column; + align-items: ${({ $reverse }) => ($reverse ? "flex-end" : "flex-start")}; + background-color: transparent; + + img { + width: 100%; + border-radius: 12px; + } + } +`; + +const Container = styled.div` + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 138px 0; + + @media ${MediaQueryBreakpoint.tablet} { + margin: 0; + padding: 24px; + } + + @media ${MediaQueryBreakpoint.mobile} { + padding: 16px; + } +`; + +const StyledContentSection = styled.section``; + +function ContentSection({ content, reverse = false }: Props): JSX.Element { + return ( + + + + + + {content.label} + {content.title} + {breakText(content.description)} + + + + + ); +} + +export default ContentSection; diff --git a/vite-project/src/features/onboarding/components/footer-section.tsx b/vite-project/src/features/onboarding/components/footer-section.tsx new file mode 100644 index 00000000..7bd0d41b --- /dev/null +++ b/vite-project/src/features/onboarding/components/footer-section.tsx @@ -0,0 +1,53 @@ +import type { JSX } from "react"; +import styled from "styled-components"; +import landingImageBottom from "../../../assets/landing-image-bottom.png"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; +import HeaderFooterSection from "./header-footer-section"; +import OnboardingTitle from "./onboarding-title"; + +const TitleContainer = styled.div` + padding-bottom: 30px; + flex-grow: 1; +`; + +const StyledFooterSection = styled.div` + display: flex; + align-items: center; + gap: 8px; + + img { + height: 397px; + } + + @media ${MediaQueryBreakpoint.tablet} { + height: 100%; + flex-direction: column; + justify-content: space-between; + gap: 0px; + } + + @media ${MediaQueryBreakpoint.mobile} { + img { + height: 198px; + } + } +`; + +function FooterSection(): JSX.Element { + return ( + + + + + 믿을 수 있는 + + 판다마켓 중고 거래 + + + + + + ); +} + +export default FooterSection; diff --git a/vite-project/src/features/onboarding/components/header-footer-section.tsx b/vite-project/src/features/onboarding/components/header-footer-section.tsx new file mode 100644 index 00000000..003a59cf --- /dev/null +++ b/vite-project/src/features/onboarding/components/header-footer-section.tsx @@ -0,0 +1,39 @@ +import type { JSX } from "react"; +import styled from "styled-components"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; + +const Content = styled.div` + width: 100%; + max-width: 1200px; + margin: 0px auto; +`; + +const StyledHeaderFooterSection = styled.section` + background-color: #cfe5ff; + height: 540px; + display: flex; + align-items: flex-end; + + @media ${MediaQueryBreakpoint.tablet} { + height: 771px; + align-items: stretch; + } + + @media ${MediaQueryBreakpoint.mobile} { + height: 540px; + } +`; + +interface Props { + children: React.ReactNode; +} + +function HeaderFooterSection({ children }: Props): JSX.Element { + return ( + + {children} + + ); +} + +export default HeaderFooterSection; diff --git a/vite-project/src/features/onboarding/components/header-section.tsx b/vite-project/src/features/onboarding/components/header-section.tsx new file mode 100644 index 00000000..70f39813 --- /dev/null +++ b/vite-project/src/features/onboarding/components/header-section.tsx @@ -0,0 +1,75 @@ +import type { JSX } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import landingImageTop from "../../../assets/landing-image_top.png"; +import Button from "../../../components/button/button"; +import { + ButtonShape, + ButtonSize, +} from "../../../components/button/button-styles"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; +import HeaderFooterSection from "./header-footer-section"; +import OnboardingTitle from "./onboarding-title"; + +const TitleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 32px; + flex-grow: 1; + padding-bottom: 60px; +`; + +const StyledHeaderSection = styled.div` + display: flex; + align-items: center; + gap: 8px; + + img { + height: 340px; + } + + @media ${MediaQueryBreakpoint.tablet} { + height: 100%; + flex-direction: column; + justify-content: space-between; + gap: 0px; + } + + @media ${MediaQueryBreakpoint.mobile} { + img { + height: 204px; + } + } +`; + +function HeaderSection(): JSX.Element { + const navigate = useNavigate(); + + const handleLookClick = () => { + navigate("/items"); + }; + + return ( + + + + + 일상의 모든 물건을 + + 거래해 보세요 + + + 구경하러 가기 + + + + + + ); +} + +export default HeaderSection; diff --git a/vite-project/src/features/onboarding/components/onboarding-page-footer.tsx b/vite-project/src/features/onboarding/components/onboarding-page-footer.tsx new file mode 100644 index 00000000..bf0eb84c --- /dev/null +++ b/vite-project/src/features/onboarding/components/onboarding-page-footer.tsx @@ -0,0 +1,86 @@ +import type { JSX } from "react"; +import { styled } from "styled-components"; +import SocialLink from "../../../components/link/social-link"; +import { Social } from "../../../components/social/social"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; + +const Copyright = styled.div` + color: var(--color-secondary-400); + + @media ${MediaQueryBreakpoint.mobile} { + position: absolute; + left: 0; + bottom: 0; + } +`; + +const CustomerService = styled.div` + color: var(--color-secondary-200); + display: flex; + gap: 30px; +`; + +const SocialList = styled.ul` + display: flex; + gap: 12px; + margin: 0; + padding: 0; + list-style: none; +`; + +const SocialListItem = styled.li``; + +const Content = styled.div` + max-width: 1200px; + margin: 0 auto; + width: 100%; + display: flex; + justify-content: space-between; + position: relative; + + @media ${MediaQueryBreakpoint.mobile} { + height: 63px; + } +`; + +const StyledOnboardingPageFooter = styled.footer` + background-color: var(--color-secondary-900); + height: 160px; + padding-top: 32px; + + @media ${MediaQueryBreakpoint.tablet} { + padding: 32px 24px 0; + } +`; + +function OnboardingPageFooter(): JSX.Element { + return ( + + + + ©codeit - 2024 + + + Privacy Policy + FAQ + + + + + + + + + + + + + + + + + + ); +} + +export default OnboardingPageFooter; diff --git a/vite-project/src/features/onboarding/components/onboarding-title.tsx b/vite-project/src/features/onboarding/components/onboarding-title.tsx new file mode 100644 index 00000000..64ceb725 --- /dev/null +++ b/vite-project/src/features/onboarding/components/onboarding-title.tsx @@ -0,0 +1,21 @@ +import styled from "styled-components"; +import { MediaQueryBreakpoint } from "../../../utils/breakpoint"; + +const OnboardingTitle = styled.h2` + margin: 0; + font-size: 40px; + font-weight: 700; + line-height: 1.4; + + @media ${MediaQueryBreakpoint.tablet} { + text-align: center; + margin-top: 84px; + } + + @media ${MediaQueryBreakpoint.mobile} { + font-size: 32px; + margin-top: 60px; + } +`; + +export default OnboardingTitle; diff --git a/vite-project/src/features/onboarding/models/onboarding-content.ts b/vite-project/src/features/onboarding/models/onboarding-content.ts new file mode 100644 index 00000000..b70e60bc --- /dev/null +++ b/vite-project/src/features/onboarding/models/onboarding-content.ts @@ -0,0 +1,29 @@ +import hotItemImage from "../../../assets/onboarding-hot-item.png"; +import registerImage from "../../../assets/onboarding-register.png"; +import searchImage from "../../../assets/onboarding-search.png"; + +const OnboardingContent = { + hotItem: { + image: hotItemImage, + label: "Hot Item", + title: "인기 상품을\n확인해 보세요", + description: "가장 HOT한 중고거래 물품을\n판다 마켓에서 확인해 보세요", + }, + search: { + image: searchImage, + label: "Search", + title: "구매를 원하는\n상품을 검색하세요", + description: "구매하고 싶은 물품은 검색해서\n쉽게 찾아보세요", + }, + register: { + image: registerImage, + label: "Register", + title: "판매를 원하는\n상품을 등록하세요", + description: "어떤 물건이든 판매하고 싶은 상품을\n쉽게 등록하세요", + }, +}; + +export type OnboardingContentType = + (typeof OnboardingContent)[keyof typeof OnboardingContent]; + +export { OnboardingContent }; diff --git a/vite-project/src/features/product/apis/products.js b/vite-project/src/features/product/apis/products.js index 1d4c132e..7c4ce38b 100644 --- a/vite-project/src/features/product/apis/products.js +++ b/vite-project/src/features/product/apis/products.js @@ -1,10 +1,11 @@ import { client } from "../../../api/HttpClient"; +import { ORDER_BY_DEFAULT } from "../utils/order-by-values"; async function fetchProducts({ keyword = "", page = 1, pageSize = 10, - orderBy = "recent", + orderBy = ORDER_BY_DEFAULT, } = {}) { const params = { page, diff --git a/vite-project/src/components/select/order-by-select.jsx b/vite-project/src/features/product/components/product-sort-select.jsx similarity index 79% rename from vite-project/src/components/select/order-by-select.jsx rename to vite-project/src/features/product/components/product-sort-select.jsx index dff465fa..9868ce00 100644 --- a/vite-project/src/components/select/order-by-select.jsx +++ b/vite-project/src/features/product/components/product-sort-select.jsx @@ -1,13 +1,12 @@ import styled, { css } from "styled-components"; -import selectDownImgMobile from "../../assets/ic-arrow-down-list.svg"; -import selectDownImg from "../../assets/ic-triangle-down.svg"; +import selectDownImgMobile from "../../../assets/ic-arrow-down-list.svg"; +import selectDownImg from "../../../assets/ic-triangle-down.svg"; +import { ORDER_BY_DEFAULT, ORDER_BY_VALUES } from "../utils/order-by-values"; const ORDER_BY_TITLE = { recent: "최신순", favorite: "좋아요순", }; -const ORDER_BY_VALUES = Array.from(Object.keys(ORDER_BY_TITLE)); -export const ORDER_BY_DEFAULT = "recent"; const baseStyle = css` /* typography */ @@ -21,7 +20,7 @@ const baseStyle = css` border-radius: 12px; `; -const StyledOrderBySelect = styled.button` +const StyledProductSortSelect = styled.button` ${baseStyle} background-color: white; padding: 8px 20px; @@ -61,7 +60,7 @@ const StyledSelectDropdown = styled.div` function SelectDropdown({ options, onOptionClick }) { return ( - {options.map((option, index) => { + {options.map((option) => { return ( onOptionClick(option)}> {ORDER_BY_TITLE[option]} @@ -72,7 +71,7 @@ function SelectDropdown({ options, onOptionClick }) { ); } -function OrderBySelect({ +function ProductSortSelect({ value, isOpen = false, isMobile, @@ -83,7 +82,7 @@ function OrderBySelect({ const selectImage = isMobile ? selectDownImgMobile : selectDownImg; return ( - + {isMobile || {ORDER_BY_TITLE[currentOption]}} {isOpen && ( @@ -92,8 +91,8 @@ function OrderBySelect({ onOptionClick={onOptionClick} /> )} - + ); } -export default OrderBySelect; +export default ProductSortSelect; diff --git a/vite-project/src/features/product/utils/order-by-values.js b/vite-project/src/features/product/utils/order-by-values.js new file mode 100644 index 00000000..8170b359 --- /dev/null +++ b/vite-project/src/features/product/utils/order-by-values.js @@ -0,0 +1,8 @@ +const ORDER_BY_DEFAULT = "recent"; + +const ORDER_BY_VALUES = { + recent: "recent", + favorite: "favorite", +}; + +export { ORDER_BY_DEFAULT, ORDER_BY_VALUES }; diff --git a/vite-project/src/hooks/useDevice.jsx b/vite-project/src/hooks/useDevice.tsx similarity index 83% rename from vite-project/src/hooks/useDevice.jsx rename to vite-project/src/hooks/useDevice.tsx index 05156a0e..46138e41 100644 --- a/vite-project/src/hooks/useDevice.jsx +++ b/vite-project/src/hooks/useDevice.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -let _matchesDesktop; -let _matchesTablet; -let _matchesMobile; +let _matchesDesktop: MediaQueryList | null; +let _matchesTablet: MediaQueryList | null; +let _matchesMobile: MediaQueryList | null; function useDevice() { const matchesDesktop = _matchesDesktop ?? matchMedia("(min-width: 1200px)"); @@ -17,7 +17,7 @@ function useDevice() { }); useEffect(() => { - const handleDesktopMatchesChange = (e) => { + const handleDesktopMatchesChange = (e: MediaQueryListEvent) => { if (!e.matches) return; setDeviceInfo({ isDesktop: true, @@ -35,7 +35,7 @@ function useDevice() { }, [matchesDesktop]); useEffect(() => { - const handleTabletMatchesChange = (e) => { + const handleTabletMatchesChange = (e: MediaQueryListEvent) => { if (!e.matches) return; setDeviceInfo({ isDesktop: false, @@ -51,7 +51,7 @@ function useDevice() { }, [matchesTablet]); useEffect(() => { - const handleMobileMatchesChange = (e) => { + const handleMobileMatchesChange = (e: MediaQueryListEvent) => { if (!e.matches) return; setDeviceInfo({ isDesktop: false, diff --git a/vite-project/src/layouts/home-layout.tsx b/vite-project/src/layouts/home-layout.tsx new file mode 100644 index 00000000..77cc005c --- /dev/null +++ b/vite-project/src/layouts/home-layout.tsx @@ -0,0 +1,34 @@ +import type { JSX } from "react"; +import styled from "styled-components"; +import HomeNavBar from "../components/nav/home-nav-bar"; +import { MediaQueryBreakpoint } from "../utils/breakpoint"; + +const Content = styled.div` + max-width: 1200px; + margin: 24px auto 0px; + + @media ${MediaQueryBreakpoint.tablet} { + max-width: none; + margin: 24px 24px 0px; + } + + @media ${MediaQueryBreakpoint.mobile} { + max-width: none; + margin: 16px 16px 0px; + } +`; + +interface Props { + children: React.ReactNode; +} + +function HomeLayout({ children }: Props): JSX.Element { + return ( + <> + + {children} + > + ); +} + +export default HomeLayout; diff --git a/vite-project/src/layouts/home-layout/home-layout.jsx b/vite-project/src/layouts/home-layout/home-layout.jsx deleted file mode 100644 index a4d43689..00000000 --- a/vite-project/src/layouts/home-layout/home-layout.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import styled from "styled-components"; -import HomeNav from "./home-nav"; - -const Content = styled.div` - max-width: 1200px; - margin: 24px auto 0px; - - @media (max-width: 1199px) { - max-width: none; - margin: 24px 24px 0px; - } - - @media (max-width: 767px) { - max-width: none; - margin: 16px 16px 0px; - } -`; - -function HomeLayout({ children }) { - return ( - <> - - {children} - > - ); -} - -export default HomeLayout; diff --git a/vite-project/src/layouts/home-layout/home-nav.jsx b/vite-project/src/layouts/home-layout/home-nav.jsx deleted file mode 100644 index 6af155aa..00000000 --- a/vite-project/src/layouts/home-layout/home-nav.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import { NavLink, useLocation } from "react-router-dom"; -import styled from "styled-components"; -import largeLogo from "../../assets/logo-large.svg"; -import smallLogo from "../../assets/logo-small.svg"; -import Avatar from "../../components/avatar/avatar"; - -const StyledHomeNav = styled.nav` - padding: 0 200px; - border-bottom: 1px solid #dfdfdf; - background-color: white; - - @media (max-width: 1199px) { - padding: 0 24px; - } - - @media (max-width: 767px) { - padding: 0 16px; - } -`; - -const Content = styled.div` - width: 100%; - height: 70px; - display: flex; - justify-content: space-between; - align-items: center; -`; - -const LinkList = styled.ul` - display: flex; - align-items: center; - margin: 0; - padding: 0; - list-style: none; /* Tablet */ - - @media (max-width: 767px) { - gap: 8px; - } -`; - -const LinkListItem = styled.li` - padding: 21px 15px; - font-size: 18px; - font-weight: 700; - line-height: 26px; - - @media (max-width: 767px) { - padding: 21px 0; - font-size: 16px; - } -`; - -const Logo = styled.picture` - margin-right: 32px; - - @media (max-width: 1199px) { - margin-right: 20px; - } - - @media (max-width: 767px) { - margin-right: 0px; - } -`; - -function NavigationLink({ to, activePaths = [], children }) { - const location = useLocation(); - - const linkStyle = (isActive) => ({ - color: `var(--color-${isActive ? "primary-100" : "secondary-600"})`, - textDecoration: "none", - }); - - const active = activePaths.includes(location.pathname); - - return ( - linkStyle(isActive || active)}> - {children} - - ); -} - -function HomeNav() { - return ( - - - - - - - - - - - 자유게시판 - - - - 중고마켓 - - - - - - - ); -} - -export default HomeNav; diff --git a/vite-project/src/layouts/onboarding-layout.tsx b/vite-project/src/layouts/onboarding-layout.tsx new file mode 100644 index 00000000..46a42c37 --- /dev/null +++ b/vite-project/src/layouts/onboarding-layout.tsx @@ -0,0 +1,17 @@ +import type { JSX } from "react"; +import OnboardingNavBar from "../components/nav/onboarding-nav-bar"; + +interface Props { + children: React.ReactNode; +} + +function OnboardingLayout({ children }: Props): JSX.Element { + return ( + <> + + {children} + > + ); +} + +export default OnboardingLayout; diff --git a/vite-project/src/pages/add-item/add-item-page.jsx b/vite-project/src/pages/add-item-page.jsx similarity index 85% rename from vite-project/src/pages/add-item/add-item-page.jsx rename to vite-project/src/pages/add-item-page.jsx index db93836e..14651743 100644 --- a/vite-project/src/pages/add-item/add-item-page.jsx +++ b/vite-project/src/pages/add-item-page.jsx @@ -1,13 +1,14 @@ import { useState } from "react"; import styled from "styled-components"; -import TextInput from "../../components/input/text-input"; -import Section from "../../components/section/section"; -import SectionHeader from "../../components/section/section-header"; -import SectionHeaderAction from "../../components/section/section-header-action"; -import SECTION_HEADER_SIZE from "../../components/section/section-header-size"; -import TagList from "../../components/tag/tag-list"; -import AddingProductImageContainer from "../../features/product/components/adding-product-image-container"; -import { formatPrice } from "../../utils/formatter"; +import TextAreaInput from "../components/input/text-area-input"; +import TextInput from "../components/input/text-input"; +import Section from "../components/section/section"; +import SectionHeader from "../components/section/section-header"; +import SectionHeaderAction from "../components/section/section-header-action"; +import SECTION_HEADER_SIZE from "../components/section/section-header-size"; +import TagList from "../components/tag/tag-list"; +import AddingProductImageContainer from "../features/product/components/adding-product-image-container"; +import { formatPrice } from "../utils/formatter"; const INITIAL_INPUT_VALUES = { title: "", @@ -104,12 +105,11 @@ function AddItemPage() { title={"상품 소개"} size={SECTION_HEADER_SIZE.small} /> - diff --git a/vite-project/src/pages/items/item-detail-page.jsx b/vite-project/src/pages/item-detail-page.jsx similarity index 73% rename from vite-project/src/pages/items/item-detail-page.jsx rename to vite-project/src/pages/item-detail-page.jsx index 51595e90..8ce29f8f 100644 --- a/vite-project/src/pages/items/item-detail-page.jsx +++ b/vite-project/src/pages/item-detail-page.jsx @@ -1,13 +1,10 @@ import { Link } from "react-router-dom"; import styled from "styled-components"; -import backImg from "../../assets/ic-arrow-back.svg"; -import Button from "../../components/button/button"; -import { - BUTTON_SIZE, - BUTTON_TYPE, -} from "../../components/button/button-styles"; -import ProductDetailComment from "../../features/product/components/product-detail-comment"; -import ProductDetailInfo from "../../features/product/components/product-detail-info"; +import backImg from "../assets/ic-arrow-back.svg"; +import Button from "../components/button/button"; +import { ButtonShape, ButtonSize } from "../components/button/button-styles"; +import ProductDetailComment from "../features/product/components/product-detail-comment"; +import ProductDetailInfo from "../features/product/components/product-detail-info"; const StyledItemDetailPage = styled.div` display: flex; @@ -51,7 +48,7 @@ const StyledBackButton = styled(Link)` function BackButton({ children }) { return ( - + {children} diff --git a/vite-project/src/pages/items/items-page.jsx b/vite-project/src/pages/items-page.jsx similarity index 80% rename from vite-project/src/pages/items/items-page.jsx rename to vite-project/src/pages/items-page.jsx index 0170d330..26433dfe 100644 --- a/vite-project/src/pages/items/items-page.jsx +++ b/vite-project/src/pages/items-page.jsx @@ -1,17 +1,16 @@ import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import SearchInput from "../../components/input/search-input"; -import PageControl from "../../components/page-control/page-control"; -import Section from "../../components/section/section"; -import SectionHeader from "../../components/section/section-header"; -import SectionHeaderAction from "../../components/section/section-header-action"; -import OrderBySelect, { - ORDER_BY_DEFAULT, -} from "../../components/select/order-by-select"; -import { fetchProducts } from "../../features/product/apis/products"; -import ProductsGrid from "../../features/product/components/products-grid"; -import { useDevice } from "../../hooks/useDevice"; +import SearchInput from "../components/input/search-input"; +import PageControl from "../components/page-control/page-control"; +import Section from "../components/section/section"; +import SectionHeader from "../components/section/section-header"; +import SectionHeaderAction from "../components/section/section-header-action"; +import { fetchProducts } from "../features/product/apis/products"; +import ProductSortSelect from "../features/product/components/product-sort-select"; +import ProductsGrid from "../features/product/components/products-grid"; +import { ORDER_BY_DEFAULT } from "../features/product/utils/order-by-values"; +import { useDevice } from "../hooks/useDevice"; function getNumberOfColumns(deviceInfo) { let bestProductsColumns = 4; @@ -98,7 +97,7 @@ function ItemsPage() { 상품 등록하기 - ({ email: "", password: "" }); + + const canLogin = useMemo(() => { + return inputValue.email.trim() !== "" && inputValue.password.trim() !== ""; + }, [inputValue]); + + const handleInputChange: ChangeEventHandler = (event) => { + const { name, value } = event.target; + setInputValue((prev) => ({ ...prev, [name]: value })); + }; + + return ( + + + + + + + + + + + 로그인 + + + + + 판다마켓이 처음이신가요? 회원가입 + + + + + ); +} + +export default LoginPage; diff --git a/vite-project/src/pages/not-found-page.tsx b/vite-project/src/pages/not-found-page.tsx new file mode 100644 index 00000000..cb6949dc --- /dev/null +++ b/vite-project/src/pages/not-found-page.tsx @@ -0,0 +1,19 @@ +import type { JSX } from "react"; +import styled from "styled-components"; + +const StyledNotFoundPage = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +`; + +function NotFoundPage(): JSX.Element { + return ( + + 404 Not Found + + ); +} + +export default NotFoundPage; diff --git a/vite-project/src/pages/onboarding-page.tsx b/vite-project/src/pages/onboarding-page.tsx new file mode 100644 index 00000000..41b00ee1 --- /dev/null +++ b/vite-project/src/pages/onboarding-page.tsx @@ -0,0 +1,39 @@ +import type { JSX } from "react"; +import styled from "styled-components"; +import ContentSection from "../features/onboarding/components/content-section"; +import FooterSection from "../features/onboarding/components/footer-section"; +import HeaderSection from "../features/onboarding/components/header-section"; +import OnboardingPageFooter from "../features/onboarding/components/onboarding-page-footer"; +import { OnboardingContent } from "../features/onboarding/models/onboarding-content"; +import { MediaQueryBreakpoint } from "../utils/breakpoint"; + +const StyledOnboardingPage = styled.div` + display: flex; + flex-direction: column; + gap: 138px; + + @media ${MediaQueryBreakpoint.tablet} { + gap: 56px; + } + + @media (${MediaQueryBreakpoint.mobile}) { + gap: 83px; + } +`; + +function OnboardingPage(): JSX.Element { + return ( + + + + + + + + + + + ); +} + +export default OnboardingPage; diff --git a/vite-project/src/pages/signup-page.tsx b/vite-project/src/pages/signup-page.tsx new file mode 100644 index 00000000..183e2955 --- /dev/null +++ b/vite-project/src/pages/signup-page.tsx @@ -0,0 +1,149 @@ +import { useMemo, useState, type ChangeEventHandler } from "react"; +import { Link, NavLink } from "react-router-dom"; +import styled from "styled-components"; +import Button from "../components/button/button"; +import { ButtonShape, ButtonSize } from "../components/button/button-styles"; +import FormInput from "../components/input/form-input"; +import FormLogo from "../components/logo/form-logo"; +import FormSocialLogin from "../components/social/form-social-login"; +import { MediaQueryBreakpoint } from "../utils/breakpoint"; + +const SignUpContainer = styled.p` + text-align: center; + font-size: 14px; + font-weight: 500; + line-height: 24px; + color: var(--color-secondary-800); + + a { + color: var(--color-primary-100); + } +`; + +const LoginForm = styled.form` + display: flex; + flex-direction: column; + gap: 24px; +`; + +const Content = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 24px; +`; + +const Container = styled.div` + max-width: 640px; + margin: 60px auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 40px; + + @media ${MediaQueryBreakpoint.tablet} { + margin: 48px auto; + } + + @media ${MediaQueryBreakpoint.mobile} { + margin: 24px auto; + gap: 24px; + } +`; + +const StyledSignUpPage = styled.main` + @media ${MediaQueryBreakpoint.tablet} { + padding: 0 56px; + } + + @media ${MediaQueryBreakpoint.mobile} { + padding: 0 24px; + gap: 16px; + } +`; + +function SignUpPage() { + const [inputValue, setInputValue] = useState<{ + email: string; + username: string; + password: string; + confirmPassword: string; + }>({ email: "", username: "", password: "", confirmPassword: "" }); + + const canSignUp = useMemo(() => { + return ( + inputValue.email.trim() !== "" && + inputValue.username.trim() !== "" && + inputValue.password.trim() !== "" && + inputValue.confirmPassword.trim() !== "" && + inputValue.password === inputValue.confirmPassword + ); + }, [inputValue]); + + const handleInputChange: ChangeEventHandler = (event) => { + const { name, value } = event.target; + setInputValue((prev) => ({ ...prev, [name]: value })); + }; + + return ( + + + + + + + + + + + + + 회원가입 + + + + + 이미 회원이신가요? 로그인 + + + + + ); +} + +export default SignUpPage; diff --git a/vite-project/src/utils/breakpoint.ts b/vite-project/src/utils/breakpoint.ts new file mode 100644 index 00000000..f237c1be --- /dev/null +++ b/vite-project/src/utils/breakpoint.ts @@ -0,0 +1,14 @@ +const Breakpoint = { + tablet: 1199, + mobile: 767, +} as const; + +const MediaQueryBreakpoint = { + tablet: `(max-width: ${Breakpoint.tablet}px)`, + mobile: `(max-width: ${Breakpoint.mobile}px)`, +} as const; + +type MediaQueryBreakpoint = + (typeof MediaQueryBreakpoint)[keyof typeof MediaQueryBreakpoint]; + +export { MediaQueryBreakpoint }; diff --git a/vite-project/src/vite-env.d.ts b/vite-project/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/vite-project/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vite-project/tsconfig.app.json b/vite-project/tsconfig.app.json new file mode 100644 index 00000000..6b0b9ee8 --- /dev/null +++ b/vite-project/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es6", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "esnext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["./src"] +} diff --git a/vite-project/tsconfig.json b/vite-project/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/vite-project/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/vite-project/tsconfig.node.json b/vite-project/tsconfig.node.json new file mode 100644 index 00000000..f85a3990 --- /dev/null +++ b/vite-project/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite-project/vite.config.js b/vite-project/vite.config.js deleted file mode 100644 index 8b0f57b9..00000000 --- a/vite-project/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], -}) diff --git a/vite-project/vite.config.ts b/vite-project/vite.config.ts new file mode 100644 index 00000000..f16e2be3 --- /dev/null +++ b/vite-project/vite.config.ts @@ -0,0 +1,7 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +});
{breakText(content.description)}