diff --git a/package-lock.json b/package-lock.json index defc30c1..acf07114 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "17-sprint-mission", "version": "0.0.0", "dependencies": { + "classnames": "^2.5.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-helmet": "^6.1.0", "react-router-dom": "^7.6.2", - "styled-components": "^6.1.19", "vite-plugin-svgr": "^4.3.0" }, "devDependencies": { @@ -30,6 +30,7 @@ "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^16.2.0", "prettier": "^3.6.2", + "sass": "^1.90.0", "vite": "^7.0.0" } }, @@ -312,27 +313,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1026,6 +1006,302 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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/@rolldown/pluginutils": { "version": "1.0.0-beta.19", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", @@ -1620,12 +1896,6 @@ "@types/react": "^19.0.0" } }, - "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/@vitejs/plugin-react": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", @@ -1950,6 +2220,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==", + "license": "MIT", + "optional": true, + "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", @@ -2061,15 +2344,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001726", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", @@ -2107,6 +2381,28 @@ "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2198,30 +2494,11 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2345,6 +2622,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3089,6 +3379,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==", + "license": "MIT", + "optional": true, + "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", @@ -3424,6 +3727,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3610,7 +3920,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, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3655,7 +3965,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, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3690,6 +4000,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-number-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", @@ -4082,6 +4402,33 @@ "node": ">= 0.4" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "optional": true, + "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==", + "license": "MIT", + "optional": true, + "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", @@ -4146,6 +4493,13 @@ "tslib": "^2.0.3" } }, + "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/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -4464,12 +4818,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4622,6 +4970,20 @@ "react-dom": ">=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==", + "devOptional": true, + "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", @@ -4790,6 +5152,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "devOptional": true, + "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", @@ -4860,12 +5243,6 @@ "node": ">= 0.4" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5157,68 +5534,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/styled-components": { - "version": "6.1.19", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", - "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", - "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5287,6 +5602,19 @@ "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==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index 791a8eaf..77e84669 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "classnames": "^2.5.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-helmet": "^6.1.0", "react-router-dom": "^7.6.2", - "styled-components": "^6.1.19", "vite-plugin-svgr": "^4.3.0" }, "devDependencies": { @@ -32,6 +32,7 @@ "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^16.2.0", "prettier": "^3.6.2", + "sass": "^1.90.0", "vite": "^7.0.0" } } diff --git a/src/assets/icons/ic_back.svg b/src/assets/icons/ic_back.svg new file mode 100644 index 00000000..9ba5ad94 --- /dev/null +++ b/src/assets/icons/ic_back.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/ic_kebab.svg b/src/assets/icons/ic_kebab.svg new file mode 100644 index 00000000..63a0344c --- /dev/null +++ b/src/assets/icons/ic_kebab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/ic_loading_dots.svg b/src/assets/icons/ic_loading_dots.svg new file mode 100644 index 00000000..6f260eba --- /dev/null +++ b/src/assets/icons/ic_loading_dots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/imgs/Img_inquiry_empty.png b/src/assets/imgs/Img_inquiry_empty.png new file mode 100644 index 00000000..e2d626e8 Binary files /dev/null and b/src/assets/imgs/Img_inquiry_empty.png differ diff --git a/src/components/layout/Footer.jsx b/src/components/layout/Footer.jsx index 00ae7776..168679b9 100644 --- a/src/components/layout/Footer.jsx +++ b/src/components/layout/Footer.jsx @@ -1,26 +1,25 @@ import { Link } from 'react-router-dom'; -import styled from 'styled-components'; import FacebookIcon from '@/assets/icons/ic_facebook.svg'; import InstagramIcon from '@/assets/icons/ic_instagram.svg'; import TwitterIcon from '@/assets/icons/ic_twitter.svg'; import YoutubeIcon from '@/assets/icons/ic_youtube.svg'; -import { device } from '@/styles/media'; +import styles from '@/components/layout/styles/Footer.module.scss'; export default function Footer() { return ( - - - ©codeit - 2024 - +
+
+ ©codeit - 2024 +
Privacy Policy FAQ - - +
+
- - - +
+
+
); } - -const StyledFooter = styled.footer` - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - width: 100%; - background-color: #111827; -`; -const Container = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - width: 100%; - padding: ${({ theme }) => theme.spacing.xl}; - font-size: ${({ theme }) => theme.fontSize.sm}; - font-weight: 400; - @media ${device.TABLET} { - flex-wrap: nowrap; - } - @media ${device.DESKTOP} { - padding: 2rem 6.5rem 6.75rem; - } -`; -const Copyright = styled.span` - color: ${({ theme }) => theme.colors.gray400}; - padding-top: 36px; - order: 3; - width: 100%; - @media ${device.TABLET} { - padding-top: 0; - order: 1; - width: auto; - } -`; -const Info = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - gap: ${({ theme }) => theme.spacing.xl}; - color: ${({ theme }) => theme.colors.gray200}; -`; -const Icons = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - gap: ${({ theme }) => theme.spacing.sm}; - & svg { - width: ${({ theme }) => theme.fontSize.md}; - height: ${({ theme }) => theme.fontSize.md}; - } -`; diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx index 1515eeca..bc9d1397 100644 --- a/src/components/layout/Header.jsx +++ b/src/components/layout/Header.jsx @@ -1,35 +1,31 @@ import { Link, NavLink, useLocation } from 'react-router-dom'; -import styled from 'styled-components'; +import classNames from 'classnames/bind'; import PandaLogo from '@/assets/icons/panda_icon_small.svg'; import defaultProfileImg from '@/assets/imgs/default_profile.png'; -import { device } from '@/styles/media'; -import theme from '@/styles/theme'; - -function getLinkStyle({ isActive }) { - return { - color: isActive ? theme.colors.primary : theme.colors.gray600, - }; -} +import styles from '@/components/layout/styles/Header.module.scss'; export default function Header() { const { pathname } = useLocation(); + const cn = classNames.bind(styles); return ( - - - - - + <header className={styles.container}> + <nav className={styles.navbar}> + <div className={styles.logoWrapper}> + <PandaLogo aria-label='판다마켓 로고' /> + <h1 className={styles.title}> <Link to={'/'} aria-label='홈으로 이동'> 판다마켓 </Link> - - - + + +
  • { + return cn({ activeLink: isActive }); + }} to='/community' - style={getLinkStyle} aria-label='자유게시판으로 이동' > 자유게시판 @@ -38,110 +34,23 @@ export default function Header() {
  • { + className={({ isActive }) => { const isItemsPage = isActive || pathname === '/additem'; - return { - color: isItemsPage - ? theme.colors.primary - : theme.colors.gray600, - }; + return cn({ activeLink: isItemsPage }); }} aria-label='중고마켓 페이지로 이동' > 중고마켓
  • - +
{/* 로그인 */} - +
회색 기본 프로필 이미지 - - - +
+ + ); } -const StyledHeader = styled.header` - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 999; - display: flex; - justify-content: center; - align-items: center; - background-color: #ffffff; - border-bottom: 1px solid #dfdfdf; - padding: ${({ theme }) => theme.spacing.sm} 0; -`; -const NavBar = styled.nav` - display: flex; - justify-content: space-between; - align-items: center; - flex-grow: 1; - margin: 0 ${({ theme }) => theme.spacing.md}; - - @media ${device.TABLET} { - margin: 0 ${({ theme }) => theme.spacing.lg}; - } - @media ${device.DESKTOP} { - margin: 0 200px; - } -`; -const LogoWrapper = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; -`; -const Logo = styled(PandaLogo)` - width: 3.125rem; - height: 3.125rem; - display: none; - @media ${device.DESKTOP} { - display: block; - } -`; -const Title = styled.h1` - font-size: ${({ theme }) => theme.fontSize.lg}; - font-weight: 700; - font-family: ${({ theme }) => theme.fontFamily.logo}; - color: ${({ theme }) => theme.colors.primary}; -`; -const NavList = styled.ul` - flex: 1 1; - display: flex; - align-items: center; - justify-content: flex-start; - gap: ${({ theme }) => theme.spacing.sm}; - font-size: ${({ theme }) => theme.fontSize.sm}; - margin-left: ${({ theme }) => theme.spacing.md}; - color: ${({ theme }) => theme.colors.gray600}; - @media ${device.DESKTOP} { - font-size: ${({ theme }) => theme.fontSize.lg}; - margin-left: ${({ theme }) => theme.spacing['3xl']}; - gap: ${({ theme }) => theme.spacing.xl}; - } -`; -const ProfileImgWrapper = styled.div` - border-radius: ${({ theme }) => theme.borderRadius.circle}; - background-color: ${({ theme }) => theme.colors.gray400}; - width: 40px; - height: 40px; -`; -// const LoginButton = styled.button` -// font-weight: 600; -// font-size: ${({ theme }) => theme.fontSize.md}; -// line-height: 26px; -// text-align: center; -// padding: ${({ theme }) => theme.spacing.sm} ${({ theme }) => theme.spacing.lg}; -// color: ${({ theme }) => theme.colors.gray100}; -// background-color: ${({ theme }) => theme.colors.primary}; -// border-radius: ${({ theme }) => theme.borderRadius.xs}; -// border: none; -// @media ${device.DESKTOP} { -// width: 8rem; -// height: 3rem; -// } -// `; diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx new file mode 100644 index 00000000..3aa2e473 --- /dev/null +++ b/src/components/layout/MainLayout.tsx @@ -0,0 +1,14 @@ +import Header from '@/components/layout/Header'; +import { Outlet } from 'react-router-dom'; +import styles from '@/components/layout/styles/MainLayout.module.scss'; + +export function MainLayout() { + return ( + <> +
+
+ +
+ + ); +} diff --git a/src/components/layout/styles/Footer.module.scss b/src/components/layout/styles/Footer.module.scss new file mode 100644 index 00000000..dbf281d1 --- /dev/null +++ b/src/components/layout/styles/Footer.module.scss @@ -0,0 +1,53 @@ +.footer { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 100%; + background-color: #cddbf8; + .container { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + width: 100%; + padding: var(--spacing-xl); + font-size: var(--font-size-sm); + font-weight: 400; + @include tablet { + flex-wrap: nowrap; + } + @include desktop { + padding: 2rem 6.5rem 6.75rem; + } + + .copyright { + color: var(--color-gray-400); + padding-top: 2rem; + order: 3; + width: 100%; + @include tablet { + padding-top: 0; + order: 1; + width: auto; + } + } + .info { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--spacing-xl); + color: var(--color-gray-200); + } + .icons { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--spacing-sm); + & svg { + width: 1rem; + height: 1rem; + } + } + } +} diff --git a/src/components/layout/styles/Header.module.scss b/src/components/layout/styles/Header.module.scss new file mode 100644 index 00000000..8517242f --- /dev/null +++ b/src/components/layout/styles/Header.module.scss @@ -0,0 +1,89 @@ +.container { + @include flex-center; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 999; + background-color: var(--color-white); + border-bottom: 1px solid #dfdfdf; + padding: var(--spacing-sm) 0; + + .navbar { + display: flex; + justify-content: space-between; + align-items: center; + flex-grow: 1; + margin: 0 var(--spacing-md); + + @include tablet { + margin: 0 var(--spacing-lg); + } + @include desktop { + margin: 0 rem(200px); + } + .logoWrapper { + display: flex; + justify-content: space-between; + align-items: center; + gap: rem(10px); + + svg { + width: 3.125rem; + height: 3.125rem; + display: none; + @include desktop { + display: block; + } + } + .title { + font-size: var(--font-size-lg); + font-weight: 700; + font-family: var(--font-secondary); + color: var(--color-primary); + } + } + .navList { + flex: 1 1; + display: flex; + align-items: center; + justify-content: flex-start; + gap: var(--spacing-md); + font-size: var(--font-size-sm); + margin-left: var(--spacing-md); + color: var(--color-gray-600); + @include desktop { + font-size: var(--font-size-lg); + margin-left: var(--spacing-3xl); + gap: var(--spacing-xl); + } + .activeLink { + color: var(--color-primary); + } + } + .profileImgWrapper { + border-radius: var(--radius-circle); + background-color: var(--color-gray-400); + width: rem(40px); + height: rem(40px); + } + .loginButton { + font-weight: 600; + font-size: var(--font-size-md); + line-height: rem(26px); + text-align: center; + padding: var(--spacing-sm) var(--spacing-lg); + color: var(--color-gray-100); + background-color: var(--color-primary); + border-radius: var(--radius-xs); + border: none; + @include desktop { + width: 8rem; + height: 3rem; + } + } + } +} +.activeLink { + color: var(--color-primary); +} diff --git a/src/components/layout/styles/MainLayout.module.scss b/src/components/layout/styles/MainLayout.module.scss new file mode 100644 index 00000000..2e365e12 --- /dev/null +++ b/src/components/layout/styles/MainLayout.module.scss @@ -0,0 +1,3 @@ +.layoutContainer { + margin-top: var(--spacing-header); +} diff --git a/src/components/ui/Button.jsx b/src/components/ui/Button.jsx index e1fc52b8..9ea1e322 100644 --- a/src/components/ui/Button.jsx +++ b/src/components/ui/Button.jsx @@ -1,50 +1,27 @@ import { Link } from 'react-router-dom'; -import styled, { css } from 'styled-components'; -const commonStyles = css` - height: auto; - padding: ${({ theme }) => theme.spacing.sm} ${({ theme }) => theme.spacing.lg}; - background-color: ${({ theme }) => theme.colors.primary}; - border-radius: ${({ theme }) => theme.borderRadius.xs}; - border: none; - text-align: center; - font-weight: 600; - font-size: ${({ theme }) => theme.fontSize.sm}; - color: ${({ theme }) => theme.colors.gray100}; -`; -const StyledButton = styled.button` - ${commonStyles} - &:disabled { - background-color: ${({ theme }) => theme.colors.gray400}; - } -`; -const StyledDiv = styled.div` - ${commonStyles} -`; -const StyledLink = styled(Link)` - ${commonStyles} -`; +import styles from '@/components/ui/styles/Button.module.scss'; + export default function Button({ - text, onClick = () => {}, as = 'button', link = '', disabled = false, + ariaLabel = '', + children, }) { switch (as) { case 'button': return ( - - {text} - + ); case 'a': return ( - - {text} - + + {children} + ); - case 'div': - return {text}; } } diff --git a/src/components/ui/ItemImg.jsx b/src/components/ui/ItemImg.jsx index 1e492ea4..114410c9 100644 --- a/src/components/ui/ItemImg.jsx +++ b/src/components/ui/ItemImg.jsx @@ -1,27 +1,17 @@ -import styled from 'styled-components'; +import classNames from 'classnames/bind'; import defaultBox from '@/assets/imgs/default_box.png'; +import styles from '@/components/ui/styles/ItemImg.module.scss'; export default function ItemImg({ imgUrl = '', alt = '' }) { + const cn = classNames.bind(styles); const onErrorImg = (e) => { e.target.src = defaultBox; }; if (imgUrl === '') { - return ; + return
; } - return {alt}; + return ( + {alt} + ); } - -const Image = styled.img` - border-radius: ${({ theme }) => theme.borderRadius.md}; - width: 100%; - height: auto; - aspect-ratio: 1; -`; -const ImageSkeleton = styled.div` - border-radius: ${({ theme }) => theme.borderRadius.md}; - width: 100%; - height: auto; - aspect-ratio: 1; - background-color: ${({ theme }) => theme.colors.gray100}; -`; diff --git a/src/components/ui/Loading.jsx b/src/components/ui/Loading.jsx new file mode 100644 index 00000000..31604b8c --- /dev/null +++ b/src/components/ui/Loading.jsx @@ -0,0 +1,10 @@ +import LoadingDots from '@/assets/icons/ic_loading_dots.svg'; +import styles from '@/components/ui/styles/Loading.module.scss'; + +export default function Loading() { + return ( +
+ +
+ ); +} diff --git a/src/components/ui/Tag.jsx b/src/components/ui/Tag.jsx index b952d26f..e7c52800 100644 --- a/src/components/ui/Tag.jsx +++ b/src/components/ui/Tag.jsx @@ -1,36 +1,19 @@ -import styled from 'styled-components'; - import DeleteIcon from '@/assets/icons/ic_X.svg'; +import styles from '@/components/ui/styles/Tag.module.scss'; export default function Tag({ - text = '', canDelete = false, onDeleteClick = () => {}, + children, }) { return ( - - #{text} +
+ #{children} {canDelete && ( - + )} - +
); } - -const Container = styled.div` - display: flex; - justify-content: center; - align-items: center; - gap: 8px; - background-color: ${({ theme }) => theme.colors.gray100}; - padding: ${({ theme }) => `${theme.spacing.xs} ${theme.spacing.md}`}; - font-weight: 300; - border-radius: 26px; -`; -const Button = styled.button` - display: flex; - justify-content: center; - align-items: center; -`; diff --git a/src/components/ui/styles/Button.module.scss b/src/components/ui/styles/Button.module.scss new file mode 100644 index 00000000..d818dd3c --- /dev/null +++ b/src/components/ui/styles/Button.module.scss @@ -0,0 +1,19 @@ +.button { + @include flex-center; + height: auto; + padding: var(--spacing-sm) var(--spacing-lg); + background-color: var(--color-primary); + border-radius: var(--spacing-xs); + border: none; + text-align: center; + font-weight: 600; + font-size: var(--font-size-sm); + color: var(--color-gray-100); + transition: scale 0.2s ease-out; + &:disabled { + background-color: var(--color-gray-400); + } + &:hover { + scale: 1.03; + } +} diff --git a/src/components/ui/styles/ItemImg.module.scss b/src/components/ui/styles/ItemImg.module.scss new file mode 100644 index 00000000..2636ef97 --- /dev/null +++ b/src/components/ui/styles/ItemImg.module.scss @@ -0,0 +1,10 @@ +.image { + width: 100%; + height: auto; + aspect-ratio: 1; + border-radius: var(--radius-md); + &Skeleton { + background-color: var(--color-gray-100); + border-radius: var(--radius-md); + } +} diff --git a/src/components/ui/styles/Loading.module.scss b/src/components/ui/styles/Loading.module.scss new file mode 100644 index 00000000..71d1b6dd --- /dev/null +++ b/src/components/ui/styles/Loading.module.scss @@ -0,0 +1,7 @@ +.wrapper { + position: absolute; + width: 7.5rem; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/src/components/ui/styles/Tag.module.scss b/src/components/ui/styles/Tag.module.scss new file mode 100644 index 00000000..3776e8c6 --- /dev/null +++ b/src/components/ui/styles/Tag.module.scss @@ -0,0 +1,11 @@ +.wrapper { + @include flex-center; + gap: 0.5rem; + background-color: var(--color-gray-100); + padding: var(--spacing-xs) var(--spacing-md); + font-weight: 300; + border-radius: rem(26px); +} +.button { + @include flex-center; +} diff --git a/src/hooks/useAsync.js b/src/hooks/useAsync.js deleted file mode 100644 index ee0bb11e..00000000 --- a/src/hooks/useAsync.js +++ /dev/null @@ -1,22 +0,0 @@ -import { useCallback, useState } from 'react'; - -export default function useAsync(asyncFunction) { - const [pending, setPending] = useState(false); - const [error, setError] = useState(null); - const wrappedFunction = useCallback( - async (...args) => { - setError(null); - setPending(true); - try { - return await asyncFunction(args[0]); - } catch (error) { - setError(error); - return; - } finally { - setPending(false); - } - }, - [asyncFunction] - ); - return [pending, error, wrappedFunction]; -} diff --git a/src/hooks/useDebouncedResizeEffect.js b/src/hooks/useDebouncedResizeEffect.js index 18d6f2a2..3b9a5a61 100644 --- a/src/hooks/useDebouncedResizeEffect.js +++ b/src/hooks/useDebouncedResizeEffect.js @@ -1,6 +1,7 @@ import { useEffect } from 'react'; -const useDebouncedResizeEffect = (callback, delay = 300) => { +const DEFAULT_DELAY_MS = 300; +const useDebouncedResizeEffect = (callback, delay = DEFAULT_DELAY_MS) => { let timer; useEffect(() => { const handleResize = () => { diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js new file mode 100644 index 00000000..ff9d50fa --- /dev/null +++ b/src/hooks/useFetch.js @@ -0,0 +1,51 @@ +import { useCallback, useEffect, useState } from 'react'; + +export default function useFetch({ + asyncFunction, + deps = [], + immediate = false, +}) { + const [state, setState] = useState({ + data: null, + loading: false, + error: null, + }); + const refetch = useCallback(async () => { + setState((prev) => ({ ...prev, error: null, loading: true })); + try { + const response = await asyncFunction(); + setState((prev) => ({ ...prev, loading: false, data: response })); + } catch (error) { + setState({ data: null, loading: false, error }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps]); + + useEffect(() => { + if (immediate) refetch(); + }, [immediate, refetch]); + + return { ...state, refetch }; +} +export function useQuery({ queryFn, deps = [] }) { + const { loading, error, data, refetch } = useFetch({ + asyncFunction: queryFn, + immediate: true, + deps, + }); + return { loading, error, data, refetch }; +} + +export function useMutation({ mutationFn, deps = [] }) { + const { + loading, + error, + data, + refetch: mutate, + } = useFetch({ + asyncFunction: mutationFn, + immediate: false, + deps, + }); + return { loading, error, data, mutate }; +} diff --git a/src/main.jsx b/src/main.jsx index 090ef623..b53c3415 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,37 +1,38 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; -import { ThemeProvider } from 'styled-components'; +import { MainLayout } from '@/components/layout/MainLayout'; import AddItem from '@/pages/AddItem'; import Community from '@/pages/Community'; -import Faq from '@/pages/Faq.jsx'; -import Home from '@/pages/Home.jsx'; +import Faq from '@/pages/Faq/index.js'; +import Home from '@/pages/Home'; import Items from '@/pages/Items'; -import Login from '@/pages/Login.jsx'; -import NotFound from '@/pages/NotFound.jsx'; -import Privacy from '@/pages/Privacy.jsx'; -import Signup from '@/pages/Signup.jsx'; -import { GlobalStyle } from '@/styles/global'; -import theme from '@/styles/theme'; +import Login from '@/pages/Login'; +import NotFound from '@/pages/NotFound'; +import Privacy from '@/pages/Privacy'; +import Product from '@/pages/Product'; +import Signup from '@/pages/Signup'; + +import '@/styles/global.scss'; createRoot(document.getElementById('root')).render( - - - - - } /> + + + }> + } /> } /> + } /> } /> - } /> - } /> } /> } /> } /> } /> - - - + + } /> + } /> + + ); diff --git a/src/pages/AddItem/ImageFileInput.jsx b/src/pages/AddItem/ImageFileInput.jsx deleted file mode 100644 index 76a49727..00000000 --- a/src/pages/AddItem/ImageFileInput.jsx +++ /dev/null @@ -1,127 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import styled from 'styled-components'; - -import PlusIconSvg from '@/assets/icons/ic_plus.svg'; -import ClearIcon from '@/assets/icons/ic_X.svg'; -import ItemImg from '@/components/ui/ItemImg'; -import { device } from '@/styles/media'; - -export default function ImageFileInput({ imgFile, onChange }) { - const inputRef = useRef(null); - const [preview, setPreview] = useState(null); - const [error, setError] = useState(false); - - const handleClick = () => { - if (imgFile) { - setError(true); - return; - } - if (!inputRef.current) return; - inputRef.current.click(); - }; - const handleChange = (e) => { - const file = e.target.files?.[0]; - if (file) { - onChange(e.target.name, file); - } - }; - const handleClearClick = () => { - const inputNode = inputRef.current; - if (!inputNode) return; - inputNode.value = ''; - onChange('imgFile', null); - setError(false); - }; - useEffect(() => { - if (!imgFile) return; - const objectURL = URL.createObjectURL(imgFile); - setPreview(objectURL); - return () => { - URL.revokeObjectURL(objectURL); - setPreview(null); - }; - }, [imgFile, setPreview]); - - return ( - <> - - - - - - - {preview && ( - - - - - - - )} - - {error && ( - *이미지 등록은 최대 1개까지 가능합니다. - )} - - ); -} -const FileInputWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 12px; - background-color: ${({ theme }) => theme.colors.gray100}; - border-radius: ${({ theme }) => theme.borderRadius.md}; - width: 168px; - height: auto; - aspect-ratio: 1; - cursor: pointer; - &:hover { - background-color: ${({ theme }) => theme.colors.gray50}; - } - @media ${device.TABLET} { - width: 100%; - } -`; -const Input = styled.input` - display: none; -`; -const Label = styled.label` - color: ${({ theme }) => theme.colors.gray400}; -`; -const PreviewWrapper = styled.div` - position: relative; - width: 10.5rem; - - @media ${device.TABLET} { - width: 100%; - } -`; -const PlusIcon = styled(PlusIconSvg)` - width: 3rem; - height: 3rem; -`; -const ClearButton = styled.button` - background: none; - position: absolute; - right: 10px; - top: 10px; - cursor: pointer; -`; -const FileSection = styled.div` - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: ${({ theme }) => theme.spacing.sm}; -`; -const ErrorMessage = styled.div` - color: ${({ theme }) => theme.colors.errorRed}; -`; diff --git a/src/pages/AddItem/InputSection.jsx b/src/pages/AddItem/InputSection.jsx deleted file mode 100644 index 2fa6e4aa..00000000 --- a/src/pages/AddItem/InputSection.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import styled from 'styled-components'; - -import ImageFileInput from '@/pages/AddItem/ImageFileInput'; -import TagInput from '@/pages/AddItem/TagInput'; -import getNumberOnly from '@/utils/getNumberOnly'; - -export default function InputSection({ values, setValues }) { - const setValuesWithParemeter = (name, value) => { - setValues((prev) => ({ - ...prev, - [name]: value, - })); - }; - const handleTextChange = (e) => { - let { name, value } = e.target; - setValuesWithParemeter(name, value); - }; - const handlePriceChange = (e) => { - let { name, value } = e.target; - const cleanedValue = Number(getNumberOnly(value)); - setValuesWithParemeter(name, cleanedValue); - }; - const priceValue = - values.price === 0 ? '' : values.price.toLocaleString('ko-KR'); - return ( - <> -
- - -
-
- - -
-
- -