diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a0dd7d2..0d57e10 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,8 @@ ### PR 타입(하나 이상의 PR 타입을 선택해주세요) --[] 기능 추가 --[] 기능 삭제 --[] 버그 수정 --[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 +- [ ] 기능 추가 +- [ ] 기능 삭제 +- [ ] 버그 수정 +- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 ### 반영 브랜치 ex) feat/login -> dev diff --git a/.gitignore b/.gitignore index 24cdedf..8692cf6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local @@ -20,4 +21,4 @@ npm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9c243a0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3d82215..71e58b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,13 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-router-dom": "^6.26.2", "react-scripts": "5.0.1", + "styled-components": "^6.1.13", "web-vitals": "^2.1.4" } }, @@ -2281,6 +2284,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "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==", + "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==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4474,6 +4495,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "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==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5389,6 +5415,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -5940,6 +5989,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6367,6 +6424,14 @@ "postcss": "^8.4" } }, + "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==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6508,6 +6573,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "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==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -14617,6 +14692,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14958,6 +15038,26 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -15073,6 +15173,14 @@ } } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -15873,6 +15981,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "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==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16432,6 +16545,65 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "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.38", + "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.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16447,6 +16619,11 @@ "postcss": "^8.2.15" } }, + "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==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -19731,6 +19908,24 @@ "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "requires": {} }, + "@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==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -21362,6 +21557,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -22012,6 +22212,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==" }, + "axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -22422,6 +22644,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -22737,6 +22964,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "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==" + }, "css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -22809,6 +23041,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "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==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -28437,6 +28679,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -28678,6 +28925,22 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -28760,6 +29023,12 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "requires": {} + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -29335,6 +29604,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -29755,6 +30029,39 @@ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "requires": {} }, + "styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "requires": { + "@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.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "dependencies": { + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -29764,6 +30071,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/package.json b/package.json index bad47f9..8b982c4 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,13 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-router-dom": "^6.26.2", "react-scripts": "5.0.1", + "styled-components": "^6.1.13", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..f824337 --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/public/index.html b/public/index.html index aa069f2..5875483 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + @@ -14,7 +14,7 @@ manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> - + React App + + + - +
- diff --git a/public/netlify.toml b/public/netlify.toml new file mode 100644 index 0000000..ff1c050 --- /dev/null +++ b/public/netlify.toml @@ -0,0 +1,4 @@ +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 \ No newline at end of file diff --git a/src/App.js b/src/App.js index d1bdbfd..b3b04e2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,12 +1,42 @@ -import './App.css'; +import React, { useEffect } from "react"; +import "./styles/global.css"; import Router from "./Router"; function App() { + useEffect(() => { + if (window.Kakao) { + if (!window.Kakao.isInitialized()) { + window.Kakao.init(process.env.REACT_APP_KAKKAO); + console.log("카카오 SDK 초기화 완료"); + } else { + console.log("Kakao SDK는 이미 초기화되었습니다."); + } + } else { + console.error("Kakao SDK가 로드되지 않았습니다."); + } + }, []); + + useEffect(() => { + window.fbAsyncInit = function () { + window.FB.init({ + appId: process.env.REACT_APP_FACEBOOK, + }); + }; + + (function(d, s, id) { + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "https://connect.facebook.net/en_US/sdk.js"; + fjs.parentNode.insertBefore(js, fjs); + }(document, 'script', 'facebook-jssdk')); + }, []); + return ( - <> +
- +
); } -export default App; \ No newline at end of file +export default App; diff --git a/src/IsThereAPI.js b/src/IsThereAPI.js new file mode 100644 index 0000000..fcedba6 --- /dev/null +++ b/src/IsThereAPI.js @@ -0,0 +1,12 @@ +import axios from "axios"; + +export async function getItems(limit, page) { // 인덱스 0부터시작하게된다, api에서는 0페이지부터 세고있다, 개발자가 페이지를 셀때는 1페이지부터 센다. + try { + const offset = (page-1)*limit + const response = await axios.get(`https://openmind-api.vercel.app/10-1/subjects/?limit=${limit}&offset=${offset}`); + return response.data; // 이미 JSON 형태로 변환되어 있음 + } catch (error) { + console.error("Error fetching items:", error); + throw error; // 오류를 다시 던져서 호출자에게 알려줄 수 있음 + } +} \ No newline at end of file diff --git a/src/Router.js b/src/Router.js index b50a3c8..6b0f929 100644 --- a/src/Router.js +++ b/src/Router.js @@ -1,12 +1,30 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; +import QuestionListPage from "./pages/QuestionListPage/QuestionListPage"; +import ArrayCard from "./pages/QuestionListPage/components/user_card/ArrayCard"; +import FeedCard from "./components/FeedCard"; +import PostQuestionPage from "./pages/PostQuestionPage/PostQuestionPage"; +import PostAnswerPage from "./pages/PostAnswerPage/WriteAnswer"; +import QuestionModal from "./components/QuestionModal"; +import Main from "./pages/MainPage/Main"; + + + function Router() { - return ( - - - - - - ) + return ( + + + } /> + } /> + } /> + {/* 메인페이지에서 라우팅 */} + } /> + {/* 리스트페이지에서 라우팅 */} + } /> + } /> + + + ); + } -export default Router; \ No newline at end of file +export default Router; diff --git a/src/api/storage/LocalStore.js b/src/api/storage/LocalStore.js new file mode 100644 index 0000000..f051d03 --- /dev/null +++ b/src/api/storage/LocalStore.js @@ -0,0 +1,52 @@ +const LocalStore = { + setItem: (key, value) => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.setItem(key, JSON.stringify(value)); + } + }, + getItem: (key) => { + if (typeof window !== 'undefined' && window.localStorage) { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : null; + } + return null; + }, + removeItem: (key) => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.removeItem(key); + } + }, + clear: () => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.clear(); + } + }, + setArray: (key, array) => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.setItem(key, JSON.stringify(array)); + } + }, + getArray: (key) => { + if (typeof window !== 'undefined' && window.localStorage) { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : []; + } + return []; + }, + pushToArray: (key, value) => { + if (typeof window !== 'undefined' && window.localStorage) { + const array = localStorage.getArray(key); + array.push(value); + localStorage.setArray(key, array); + } + }, + removeFromArray: (key, index) => { + if (typeof window !== 'undefined' && window.localStorage) { + const array = localStorage.getArray(key); + array.splice(index, 1); + localStorage.setArray(key, array); + } + }, +}; + +export default LocalStore; \ No newline at end of file diff --git a/src/api/storage/SessionStore.js b/src/api/storage/SessionStore.js new file mode 100644 index 0000000..5c48321 --- /dev/null +++ b/src/api/storage/SessionStore.js @@ -0,0 +1,52 @@ +const SessionStore = { + setItem: (key, value) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + window.sessionStorage.setItem(key, JSON.stringify(value)); + } + }, + getItem: (key) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + const item = window.sessionStorage.getItem(key); + return item ? JSON.parse(item) : null; + } + return null; + }, + removeItem: (key) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + window.sessionStorage.removeItem(key); + } + }, + clear: () => { + if (typeof window !== 'undefined' && window.sessionStorage) { + window.sessionStorage.clear(); + } + }, + setArray: (key, array) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + window.sessionStorage.setItem(key, JSON.stringify(array)); + } + }, + getArray: (key) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + const item = window.sessionStorage.getItem(key); + return item ? JSON.parse(item) : []; + } + return []; + }, + pushToArray: (key, value) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + const array = sessionStorage.getArray(key); + array.push(value); + sessionStorage.setArray(key, array); + } + }, + removeFromArray: (key, index) => { + if (typeof window !== 'undefined' && window.sessionStorage) { + const array = sessionStorage.getArray(key); + array.splice(index, 1); + sessionStorage.setArray(key, array); + } + }, +}; + +export default SessionStore; \ No newline at end of file diff --git a/src/api/swagger/Answers.js b/src/api/swagger/Answers.js index 4e99b05..d56dfc6 100644 --- a/src/api/swagger/Answers.js +++ b/src/api/swagger/Answers.js @@ -2,18 +2,6 @@ import axios from 'axios'; const team = '10-1'; -/** - * Represents the properties of a new answer to be created. - * const _AnswerCreate = { - * id, //integer, readOnly: true - * questionId, //*integer - * content, //*string, minLength: 1 - * isRejected, //*boolean - * createdAt, //string($date-time), readOnly: true - * team, //*string, minLength: 1 - * } - */ - /** * Fetches an answer from the API by its ID. * GET /{team}/answers/{id}/ @@ -22,7 +10,7 @@ const team = '10-1'; * @returns {Promise} - The fetched answer object. * @throws {Error} - If there is an error fetching the answer. */ -const getAnswer = async (id) => { +const answers_read = async (id) => { try { const response = await axios.get(`https://openmind-api.vercel.app/${team}/answers/${id}/`, { headers: { @@ -57,7 +45,7 @@ const getAnswer = async (id) => { * @returns {Promise} - The updated answer object. * @throws {Error} - If there is an error updating the answer. */ -const updateAnswer = async (id, content, isRejected) => { +const answers_update = async (id, content, isRejected) => { try { const response = await axios.put(`https://openmind-api.vercel.app/${team}/answers/${id}/`, { content, @@ -96,7 +84,7 @@ const updateAnswer = async (id, content, isRejected) => { * @returns {Promise} - The updated answer object. * @throws {Error} - If there is an error updating the answer. */ -const patchAnswer = async (id, content, isRejected) => { +const answers_partial_update = async (id, content, isRejected) => { try { const response = await axios.patch(`https://openmind-api.vercel.app/${team}/answers/${id}/`, { content, @@ -129,7 +117,7 @@ const patchAnswer = async (id, content, isRejected) => { * @returns {Promise} - True if the answer was successfully deleted, false otherwise. * @throws {Error} - If there is an error deleting the answer. */ -const deleteAnswer = async (id) => { +const answers_delete = async (id) => { try { const response = await axios.delete(`https://openmind-api.vercel.app/${team}/answers/${id}/`, { headers: { @@ -149,4 +137,9 @@ const deleteAnswer = async (id) => { } }; -export { getAnswer, updateAnswer, patchAnswer, deleteAnswer }; +export { + answers_read, + answers_update, + answers_partial_update, + answers_delete +}; \ No newline at end of file diff --git a/src/api/swagger/Question.js b/src/api/swagger/Question.js new file mode 100644 index 0000000..1fb21cc --- /dev/null +++ b/src/api/swagger/Question.js @@ -0,0 +1,19 @@ +import axios from 'axios'; +const BASE_URL = 'https://openmind-api.vercel.app/10-1/subjects'; + +export async function getQuestions(subjectId, limit, offset) { + try { + const response = await axios.get(`${BASE_URL}/${subjectId}/questions/?limit=${limit}&offset=${offset}`) + if(response.status === 200){ + return response.data; + } else { + throw new Error(`HTTP error: ${response.status}`); + } + } catch (error) { + console.log("Failed to get questions: " , error) + throw error; + } +} + +export default getQuestions; + diff --git a/src/api/swagger/Questions.js b/src/api/swagger/Questions.js index 8a06730..021a202 100644 --- a/src/api/swagger/Questions.js +++ b/src/api/swagger/Questions.js @@ -2,38 +2,6 @@ import axios from 'axios'; const team = '10-1'; -/** _AnswerRetrieveUpdate - * Represents the structure of an answer that can be retrieved and updated. - * const _AnswerRetrieveUpdate = { - * id, //integer, readOnly: true - * questionId, //integer. readOnly: true - * content, //*string, minLength: 1 - * isRejected, //*boolean - * createdAt, //string($date-time), readOnly: true - * } - */ - -/** _Question - * Represents the structure of a question that can be retrieved and updated. - * const _Question = [{ - * id, //integer, readOnly: true - * subjectId, //*integer - * content, //*string, minLength: 1 - * like, //integer, maximum: 2147483647, minimum: -2147483648 - * dislike, //integer, maximum: 2147483647, minimum: -2147483648 - * createdAt, //string($date-time), readOnly: true - * team, //*string, minLength: 1 - * answer, //_AnswerRetrieveUpdate - * }] - */ - -/** _Reaction - * Represents the structure of a reaction that can be associated with a question. - * const _Reaction = { - * type, //*string, Enum: Array [ like, dislike ] - * } - */ - /** * Retrieves a question from the API based on the provided ID. * GET /{team}/questions/{id}/ @@ -41,7 +9,7 @@ const team = '10-1'; * @param {number} id - The unique identifier of the question to retrieve. * @returns {Promise} - The question data, or throws an error if the request fails. */ -const getQuestions = async (id) => { +const questions_read = async (id) => { try { const response = await axios.get(`https://openmind-api.vercel.app/${team}/questions/${id}/`, { headers: { @@ -54,7 +22,7 @@ const getQuestions = async (id) => { localStorage.setItem(`question_${id}`, JSON.stringify(questionData)); return questionData; } else { - throw new Error('Failed to fetch question'); + throw new Error(`Failed to getQuestions(${id}) and return ststus is ${response.status}`); } } catch (error) { console.error('Error fetching question:', error); @@ -69,7 +37,7 @@ const getQuestions = async (id) => { * @param {number} id - The unique identifier of the question to delete. * @returns {Promise} - True if the question was successfully deleted, or throws an error if the request fails. */ -const deleteQuestion = async (id) => { +const questions_delete = async (id) => { try { const response = await axios.delete(`https://openmind-api.vercel.app/${team}/questions/${id}/`, { headers: { @@ -97,11 +65,11 @@ const deleteQuestion = async (id) => { * }' * * @param {number} id - The unique identifier of the question to react to. - * @param {string} reactionType - The type of reaction to add (e.g. "like"). + * @param {string} reactionType - The type of reaction to add (e.g. "like" or "dislike"). * @returns {Promise} - The updated question object after the reaction is added. * @throws {Error} - If the request to add the reaction fails. */ -const reactToQuestion = async (id, reactionType) => { +const questions_reaction_create = async (id, reactionType) => { try { const response = await axios.post(`https://openmind-api.vercel.app/${team}/questions/${id}/reaction/`, { type: reactionType @@ -112,7 +80,7 @@ const reactToQuestion = async (id, reactionType) => { } }); - if (response.status === 200) { + if (response.status === 201) { const updatedQuestion = response.data; localStorage.setItem(`question_${id}`, JSON.stringify(updatedQuestion)); return updatedQuestion; @@ -126,14 +94,7 @@ const reactToQuestion = async (id, reactionType) => { }; /** - * Sends a POST request to the API to add an answer to the specified question. - * POST /{team}/questions/{question_id}/answers/ - * -d '{ - * "questionId": 0, - * "content": "string", - * "isRejected": true, - * "team": "string" - * }' + * * @param {number} questionId - The unique identifier of the question to answer. * @param {string} content - The content of the answer. @@ -142,7 +103,7 @@ const reactToQuestion = async (id, reactionType) => { * @returns {Promise} - The updated question object after the answer is added. * @throws {Error} - If the request to add the answer fails. */ -const answerQuestion = async (questionId, content, isRejected, team) => { +const questions_answers_create = async (questionId, content, isRejected, team) => { try { const response = await axios.post(`https://openmind-api.vercel.app/${team}/questions/${questionId}/answers/`, { questionId, @@ -156,7 +117,7 @@ const answerQuestion = async (questionId, content, isRejected, team) => { } }); - if (response.status === 200) { + if (response.status === 201) { const updatedQuestion = response.data; localStorage.setItem(`question_${questionId}`, JSON.stringify(updatedQuestion)); return updatedQuestion; @@ -169,5 +130,9 @@ const answerQuestion = async (questionId, content, isRejected, team) => { } }; -export { getQuestions, deleteQuestion, reactToQuestion, answerQuestion }; - +export { + questions_read, + questions_delete, + questions_reaction_create, + questions_answers_create +}; \ No newline at end of file diff --git a/src/api/swagger/Subject.js b/src/api/swagger/Subject.js new file mode 100644 index 0000000..76a72b4 --- /dev/null +++ b/src/api/swagger/Subject.js @@ -0,0 +1,18 @@ +import axios from 'axios'; +const BASE_URL = 'https://openmind-api.vercel.app/10-1/subjects'; + +export async function getSubject(subjectId) { + try { + const response = await axios.get(`${BASE_URL}/${subjectId}/`) + if(response.status === 200){ + return response.data; + } else { + throw new Error(`HTTP error: ${response.status}`); + } + } catch (error) { + console.log("Failed to get questions: " , error) + throw error; + } +} + +export default getSubject; \ No newline at end of file diff --git a/src/api/swagger/Subjects.js b/src/api/swagger/Subjects.js index 74bec5e..b1c89b0 100644 --- a/src/api/swagger/Subjects.js +++ b/src/api/swagger/Subjects.js @@ -24,7 +24,7 @@ import LocalStorage from '../storage/LocalStorage'; * } * @throws {Error} - If there is an error fetching the subjects. */ -const getSubjects = async (team, offset = 0, limit = 10) => { +const subjects_list = async (team, offset = 0, limit = 10) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/?limit=${limit}&offset=${offset}`; try { @@ -74,7 +74,7 @@ const getSubjects = async (team, offset = 0, limit = 10) => { * @throws {Error} - If there is an error creating the subject. */ -const createSubject = async (team, name) => { +const subjects_create = async (team, name) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/` try { @@ -123,7 +123,7 @@ const createSubject = async (team, name) => { * } * @throws {Error} - If there is an error querying the subject. */ -const getSubjectById = async (team, id) => { +const subjects_read = async (team, id) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/${id}/` try { @@ -160,7 +160,7 @@ const getSubjectById = async (team, id) => { * @returns {Promise} - The res data from the delete request. * @throws {Error} - If there is an error deleting the subject. */ -const deleteSubject = async (team, id) => { +const subjects_delete = async (team, id) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/${id}/` try { @@ -211,7 +211,7 @@ const deleteSubject = async (team, id) => { * } * @throws {Error} - If there is an error querying the subject questions. */ -const getSubjectQuestions = async (team, id, limit = 10, offset = 0) => { +const subjects_questions_list = async (team, id, limit = 10, offset = 0) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/${id}/questions/?limit=${limit}&offset=${offset}` const storageKey = `subject_${id}_questions` @@ -279,7 +279,7 @@ const getSubjectQuestions = async (team, id, limit = 10, offset = 0) => { * } * @throws {Error} - If there is an error creating the subject question. */ -const createSubjectQuestion = async (team, id, questionData) => { +const subjects_questions_create = async (team, id, questionData) => { const URL = `https://openmind-api.vercel.app/${team}/subjects/${id}/questions/` const storageKey = `subject_${id}_questions` @@ -311,5 +311,11 @@ const createSubjectQuestion = async (team, id, questionData) => { } } - -export { getSubjects, createSubject, getSubjectById, deleteSubject, getSubjectQuestions, createSubjectQuestion }; +export { + subjects_list, + subjects_create, + subjects_read, + subjects_delete, + subjects_questions_list, + subjects_questions_create, +} \ No newline at end of file diff --git a/src/assets/buttonshare/Type=Facebook.svg b/src/assets/buttonshare/Type=Facebook.svg new file mode 100644 index 0000000..40e0c7f --- /dev/null +++ b/src/assets/buttonshare/Type=Facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/buttonshare/Type=Kakao.svg b/src/assets/buttonshare/Type=Kakao.svg new file mode 100644 index 0000000..6683cd9 --- /dev/null +++ b/src/assets/buttonshare/Type=Kakao.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/buttonshare/Type=Link.svg b/src/assets/buttonshare/Type=Link.svg new file mode 100644 index 0000000..971597e --- /dev/null +++ b/src/assets/buttonshare/Type=Link.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/default/background.svg b/src/assets/default/background.svg new file mode 100644 index 0000000..bf7fff3 --- /dev/null +++ b/src/assets/default/background.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/default/defaultLogo.svg b/src/assets/default/defaultLogo.svg new file mode 100644 index 0000000..414e657 --- /dev/null +++ b/src/assets/default/defaultLogo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/default/defaultProfile.svg b/src/assets/default/defaultProfile.svg new file mode 100644 index 0000000..6d0a420 --- /dev/null +++ b/src/assets/default/defaultProfile.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/default/default_profile.png b/src/assets/default/default_profile.png deleted file mode 100644 index bd5042e..0000000 Binary files a/src/assets/default/default_profile.png and /dev/null differ diff --git a/src/assets/default/deleteIcon.svg b/src/assets/default/deleteIcon.svg new file mode 100644 index 0000000..df8f1fc --- /dev/null +++ b/src/assets/default/deleteIcon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/default/editLogo.svg b/src/assets/default/editLogo.svg new file mode 100644 index 0000000..0854473 --- /dev/null +++ b/src/assets/default/editLogo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/default/logo.svg b/src/assets/default/logo.svg new file mode 100644 index 0000000..4a60cd1 --- /dev/null +++ b/src/assets/default/logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/frame-70.svg b/src/assets/figma.named.svg/frame-70.svg new file mode 100644 index 0000000..23cf698 --- /dev/null +++ b/src/assets/figma.named.svg/frame-70.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/figma.named.svg/frame-79.svg b/src/assets/figma.named.svg/frame-79.svg new file mode 100644 index 0000000..4cf9858 --- /dev/null +++ b/src/assets/figma.named.svg/frame-79.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/group-34.svg b/src/assets/figma.named.svg/group-34.svg new file mode 100644 index 0000000..e605e25 --- /dev/null +++ b/src/assets/figma.named.svg/group-34.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/ic-x.svg b/src/assets/figma.named.svg/ic-x.svg new file mode 100644 index 0000000..5e72e05 --- /dev/null +++ b/src/assets/figma.named.svg/ic-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-arrow-down.svg b/src/assets/figma.named.svg/icon-arrow-down.svg new file mode 100644 index 0000000..d5208b3 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-10.svg b/src/assets/figma.named.svg/icon-arrow-right-10.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-10.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-11.svg b/src/assets/figma.named.svg/icon-arrow-right-11.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-11.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-12.svg b/src/assets/figma.named.svg/icon-arrow-right-12.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-12.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-13.svg b/src/assets/figma.named.svg/icon-arrow-right-13.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-13.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-14.svg b/src/assets/figma.named.svg/icon-arrow-right-14.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-14.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-15.svg b/src/assets/figma.named.svg/icon-arrow-right-15.svg new file mode 100644 index 0000000..0bb7762 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-15.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-16.svg b/src/assets/figma.named.svg/icon-arrow-right-16.svg new file mode 100644 index 0000000..0bb7762 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-16.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-17.svg b/src/assets/figma.named.svg/icon-arrow-right-17.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-17.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-18.svg b/src/assets/figma.named.svg/icon-arrow-right-18.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-18.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-19.svg b/src/assets/figma.named.svg/icon-arrow-right-19.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-19.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-2.svg b/src/assets/figma.named.svg/icon-arrow-right-2.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-20.svg b/src/assets/figma.named.svg/icon-arrow-right-20.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-21.svg b/src/assets/figma.named.svg/icon-arrow-right-21.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-21.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-22.svg b/src/assets/figma.named.svg/icon-arrow-right-22.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-22.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-23.svg b/src/assets/figma.named.svg/icon-arrow-right-23.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-23.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-24.svg b/src/assets/figma.named.svg/icon-arrow-right-24.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-24.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-25.svg b/src/assets/figma.named.svg/icon-arrow-right-25.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-25.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-26.svg b/src/assets/figma.named.svg/icon-arrow-right-26.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-26.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-27.svg b/src/assets/figma.named.svg/icon-arrow-right-27.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-27.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-28.svg b/src/assets/figma.named.svg/icon-arrow-right-28.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-28.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-29.svg b/src/assets/figma.named.svg/icon-arrow-right-29.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-29.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-3.svg b/src/assets/figma.named.svg/icon-arrow-right-3.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-30.svg b/src/assets/figma.named.svg/icon-arrow-right-30.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-30.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-31.svg b/src/assets/figma.named.svg/icon-arrow-right-31.svg new file mode 100644 index 0000000..0bb7762 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-31.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-32.svg b/src/assets/figma.named.svg/icon-arrow-right-32.svg new file mode 100644 index 0000000..0bb7762 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-32.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-4.svg b/src/assets/figma.named.svg/icon-arrow-right-4.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-4.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-5.svg b/src/assets/figma.named.svg/icon-arrow-right-5.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-5.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-6.svg b/src/assets/figma.named.svg/icon-arrow-right-6.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-6.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-7.svg b/src/assets/figma.named.svg/icon-arrow-right-7.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-7.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-8.svg b/src/assets/figma.named.svg/icon-arrow-right-8.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-8.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right-9.svg b/src/assets/figma.named.svg/icon-arrow-right-9.svg new file mode 100644 index 0000000..c52f143 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right-9.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-right.svg b/src/assets/figma.named.svg/icon-arrow-right.svg new file mode 100644 index 0000000..12d7c9f --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-arrow-up.svg b/src/assets/figma.named.svg/icon-arrow-up.svg new file mode 100644 index 0000000..c2f3710 --- /dev/null +++ b/src/assets/figma.named.svg/icon-arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-close.svg b/src/assets/figma.named.svg/icon-close.svg new file mode 100644 index 0000000..7b56d32 --- /dev/null +++ b/src/assets/figma.named.svg/icon-close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-edit-2.svg b/src/assets/figma.named.svg/icon-edit-2.svg new file mode 100644 index 0000000..6ae8b8a --- /dev/null +++ b/src/assets/figma.named.svg/icon-edit-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/icon-edit-3.svg b/src/assets/figma.named.svg/icon-edit-3.svg new file mode 100644 index 0000000..a13345d --- /dev/null +++ b/src/assets/figma.named.svg/icon-edit-3.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/icon-edit.svg b/src/assets/figma.named.svg/icon-edit.svg new file mode 100644 index 0000000..f02cd92 --- /dev/null +++ b/src/assets/figma.named.svg/icon-edit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/icon-facebook-2.svg b/src/assets/figma.named.svg/icon-facebook-2.svg new file mode 100644 index 0000000..86add37 --- /dev/null +++ b/src/assets/figma.named.svg/icon-facebook-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-facebook.svg b/src/assets/figma.named.svg/icon-facebook.svg new file mode 100644 index 0000000..86add37 --- /dev/null +++ b/src/assets/figma.named.svg/icon-facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-kakaotalk-2.svg b/src/assets/figma.named.svg/icon-kakaotalk-2.svg new file mode 100644 index 0000000..3e11db4 --- /dev/null +++ b/src/assets/figma.named.svg/icon-kakaotalk-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-kakaotalk.svg b/src/assets/figma.named.svg/icon-kakaotalk.svg new file mode 100644 index 0000000..3e11db4 --- /dev/null +++ b/src/assets/figma.named.svg/icon-kakaotalk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-link-2.svg b/src/assets/figma.named.svg/icon-link-2.svg new file mode 100644 index 0000000..a466e92 --- /dev/null +++ b/src/assets/figma.named.svg/icon-link-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/icon-link.svg b/src/assets/figma.named.svg/icon-link.svg new file mode 100644 index 0000000..bd9d3fd --- /dev/null +++ b/src/assets/figma.named.svg/icon-link.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/icon-messages-2.svg b/src/assets/figma.named.svg/icon-messages-2.svg new file mode 100644 index 0000000..30b2dca --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-3.svg b/src/assets/figma.named.svg/icon-messages-3.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-4.svg b/src/assets/figma.named.svg/icon-messages-4.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-4.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-5.svg b/src/assets/figma.named.svg/icon-messages-5.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-5.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-6.svg b/src/assets/figma.named.svg/icon-messages-6.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-6.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-7.svg b/src/assets/figma.named.svg/icon-messages-7.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-7.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages-8.svg b/src/assets/figma.named.svg/icon-messages-8.svg new file mode 100644 index 0000000..908ec93 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages-8.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-messages.svg b/src/assets/figma.named.svg/icon-messages.svg new file mode 100644 index 0000000..81bc115 --- /dev/null +++ b/src/assets/figma.named.svg/icon-messages.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-more-2.svg b/src/assets/figma.named.svg/icon-more-2.svg new file mode 100644 index 0000000..05f2eb0 --- /dev/null +++ b/src/assets/figma.named.svg/icon-more-2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/figma.named.svg/icon-more-3.svg b/src/assets/figma.named.svg/icon-more-3.svg new file mode 100644 index 0000000..05f2eb0 --- /dev/null +++ b/src/assets/figma.named.svg/icon-more-3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/figma.named.svg/icon-more.svg b/src/assets/figma.named.svg/icon-more.svg new file mode 100644 index 0000000..e46a76e --- /dev/null +++ b/src/assets/figma.named.svg/icon-more.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/figma.named.svg/icon-person-2.svg b/src/assets/figma.named.svg/icon-person-2.svg new file mode 100644 index 0000000..f70275c --- /dev/null +++ b/src/assets/figma.named.svg/icon-person-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-person-3.svg b/src/assets/figma.named.svg/icon-person-3.svg new file mode 100644 index 0000000..f70275c --- /dev/null +++ b/src/assets/figma.named.svg/icon-person-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-person-4.svg b/src/assets/figma.named.svg/icon-person-4.svg new file mode 100644 index 0000000..f70275c --- /dev/null +++ b/src/assets/figma.named.svg/icon-person-4.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-person.svg b/src/assets/figma.named.svg/icon-person.svg new file mode 100644 index 0000000..f70275c --- /dev/null +++ b/src/assets/figma.named.svg/icon-person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-down-2.svg b/src/assets/figma.named.svg/icon-thumbs-down-2.svg new file mode 100644 index 0000000..b32d6d2 --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-down-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-down-3.svg b/src/assets/figma.named.svg/icon-thumbs-down-3.svg new file mode 100644 index 0000000..3e8a6ed --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-down-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-down.svg b/src/assets/figma.named.svg/icon-thumbs-down.svg new file mode 100644 index 0000000..40d4352 --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-up-2.svg b/src/assets/figma.named.svg/icon-thumbs-up-2.svg new file mode 100644 index 0000000..5560e77 --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-up-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-up-3.svg b/src/assets/figma.named.svg/icon-thumbs-up-3.svg new file mode 100644 index 0000000..b6386cb --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-up-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/icon-thumbs-up.svg b/src/assets/figma.named.svg/icon-thumbs-up.svg new file mode 100644 index 0000000..7d03b31 --- /dev/null +++ b/src/assets/figma.named.svg/icon-thumbs-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/logo.svg b/src/assets/figma.named.svg/logo.svg new file mode 100644 index 0000000..bf8438e --- /dev/null +++ b/src/assets/figma.named.svg/logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-2.svg b/src/assets/figma.named.svg/profile-photo-2.svg new file mode 100644 index 0000000..5170665 --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-2.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-3.svg b/src/assets/figma.named.svg/profile-photo-3.svg new file mode 100644 index 0000000..7356e1b --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-4.svg b/src/assets/figma.named.svg/profile-photo-4.svg new file mode 100644 index 0000000..4187992 --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-4.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-5.svg b/src/assets/figma.named.svg/profile-photo-5.svg new file mode 100644 index 0000000..a5e979e --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-5.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-6.svg b/src/assets/figma.named.svg/profile-photo-6.svg new file mode 100644 index 0000000..3b9eb31 --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-6.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-7.svg b/src/assets/figma.named.svg/profile-photo-7.svg new file mode 100644 index 0000000..334509d --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-7.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo-8.svg b/src/assets/figma.named.svg/profile-photo-8.svg new file mode 100644 index 0000000..334509d --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo-8.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/profile-photo.svg b/src/assets/figma.named.svg/profile-photo.svg new file mode 100644 index 0000000..41bf0c6 --- /dev/null +++ b/src/assets/figma.named.svg/profile-photo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/figma.named.svg/rectangle-26.svg b/src/assets/figma.named.svg/rectangle-26.svg new file mode 100644 index 0000000..cb6856e --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-26.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/rectangle-28-2.svg b/src/assets/figma.named.svg/rectangle-28-2.svg new file mode 100644 index 0000000..ff4e081 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-28-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-28-3.svg b/src/assets/figma.named.svg/rectangle-28-3.svg new file mode 100644 index 0000000..ff4e081 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-28-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-28-4.svg b/src/assets/figma.named.svg/rectangle-28-4.svg new file mode 100644 index 0000000..ff4e081 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-28-4.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-28.svg b/src/assets/figma.named.svg/rectangle-28.svg new file mode 100644 index 0000000..abaef2d --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-28.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-29.svg b/src/assets/figma.named.svg/rectangle-29.svg new file mode 100644 index 0000000..f9dc254 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-29.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-10.svg b/src/assets/figma.named.svg/rectangle-30-10.svg new file mode 100644 index 0000000..d70c4d8 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-10.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-11.svg b/src/assets/figma.named.svg/rectangle-30-11.svg new file mode 100644 index 0000000..cddc3f4 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-11.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-12.svg b/src/assets/figma.named.svg/rectangle-30-12.svg new file mode 100644 index 0000000..01b1c7e --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-12.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-13.svg b/src/assets/figma.named.svg/rectangle-30-13.svg new file mode 100644 index 0000000..844d46e --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-13.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-14.svg b/src/assets/figma.named.svg/rectangle-30-14.svg new file mode 100644 index 0000000..3dc4450 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-14.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-15.svg b/src/assets/figma.named.svg/rectangle-30-15.svg new file mode 100644 index 0000000..27844f0 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-15.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/rectangle-30-2.svg b/src/assets/figma.named.svg/rectangle-30-2.svg new file mode 100644 index 0000000..f5c8933 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-3.svg b/src/assets/figma.named.svg/rectangle-30-3.svg new file mode 100644 index 0000000..9e46855 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-4.svg b/src/assets/figma.named.svg/rectangle-30-4.svg new file mode 100644 index 0000000..b9fc107 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-4.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-5.svg b/src/assets/figma.named.svg/rectangle-30-5.svg new file mode 100644 index 0000000..03c92d5 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-5.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-6.svg b/src/assets/figma.named.svg/rectangle-30-6.svg new file mode 100644 index 0000000..4b0e1df --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-6.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-7.svg b/src/assets/figma.named.svg/rectangle-30-7.svg new file mode 100644 index 0000000..cc5a78b --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-7.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-8.svg b/src/assets/figma.named.svg/rectangle-30-8.svg new file mode 100644 index 0000000..1efa77d --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-8.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30-9.svg b/src/assets/figma.named.svg/rectangle-30-9.svg new file mode 100644 index 0000000..4c61db8 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30-9.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-30.svg b/src/assets/figma.named.svg/rectangle-30.svg new file mode 100644 index 0000000..f48f86a --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-30.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/rectangle-31.svg b/src/assets/figma.named.svg/rectangle-31.svg new file mode 100644 index 0000000..c28549a --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-31.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/rectangle-32.svg b/src/assets/figma.named.svg/rectangle-32.svg new file mode 100644 index 0000000..828b243 --- /dev/null +++ b/src/assets/figma.named.svg/rectangle-32.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/figma.named.svg/v-872-batch-5-nunny-04-1.svg b/src/assets/figma.named.svg/v-872-batch-5-nunny-04-1.svg new file mode 100644 index 0000000..43e3c25 --- /dev/null +++ b/src/assets/figma.named.svg/v-872-batch-5-nunny-04-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/figma.named.svg/vector-2.svg b/src/assets/figma.named.svg/vector-2.svg new file mode 100644 index 0000000..86d2e59 --- /dev/null +++ b/src/assets/figma.named.svg/vector-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/vector-3.svg b/src/assets/figma.named.svg/vector-3.svg new file mode 100644 index 0000000..86d2e59 --- /dev/null +++ b/src/assets/figma.named.svg/vector-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/figma.named.svg/vector.svg b/src/assets/figma.named.svg/vector.svg new file mode 100644 index 0000000..86d2e59 --- /dev/null +++ b/src/assets/figma.named.svg/vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Arrow-down.svg b/src/assets/icon/Arrow-down.svg new file mode 100644 index 0000000..c30473b --- /dev/null +++ b/src/assets/icon/Arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Arrow-left.svg b/src/assets/icon/Arrow-left.svg new file mode 100644 index 0000000..289a8cb --- /dev/null +++ b/src/assets/icon/Arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Arrow-up.svg b/src/assets/icon/Arrow-up.svg new file mode 100644 index 0000000..fa451eb --- /dev/null +++ b/src/assets/icon/Arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Close.svg b/src/assets/icon/Close.svg new file mode 100644 index 0000000..0e83e57 --- /dev/null +++ b/src/assets/icon/Close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/Edit.svg b/src/assets/icon/Edit.svg new file mode 100644 index 0000000..5f59a14 --- /dev/null +++ b/src/assets/icon/Edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/Facebook.svg b/src/assets/icon/Facebook.svg new file mode 100644 index 0000000..f6fe2d6 --- /dev/null +++ b/src/assets/icon/Facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Kakaotalk.svg b/src/assets/icon/Kakaotalk.svg new file mode 100644 index 0000000..cb56d72 --- /dev/null +++ b/src/assets/icon/Kakaotalk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/Link.svg b/src/assets/icon/Link.svg new file mode 100644 index 0000000..ed5939c --- /dev/null +++ b/src/assets/icon/Link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/Messages.svg b/src/assets/icon/Messages.svg new file mode 100644 index 0000000..a4e87f5 --- /dev/null +++ b/src/assets/icon/Messages.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/More.svg b/src/assets/icon/More.svg new file mode 100644 index 0000000..32a0f0b --- /dev/null +++ b/src/assets/icon/More.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icon/Person.svg b/src/assets/icon/Person.svg new file mode 100644 index 0000000..f2022b5 --- /dev/null +++ b/src/assets/icon/Person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/arrow-right.svg b/src/assets/icon/arrow-right.svg new file mode 100644 index 0000000..e352a87 --- /dev/null +++ b/src/assets/icon/arrow-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/thumbs-down.svg b/src/assets/icon/thumbs-down.svg new file mode 100644 index 0000000..f4c1d15 --- /dev/null +++ b/src/assets/icon/thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/thumbs-up.svg b/src/assets/icon/thumbs-up.svg new file mode 100644 index 0000000..2116b1d --- /dev/null +++ b/src/assets/icon/thumbs-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/kakao_sns_logo.png b/src/assets/kakao_sns_logo.png deleted file mode 100644 index 062bd9d..0000000 Binary files a/src/assets/kakao_sns_logo.png and /dev/null differ diff --git a/src/assets/reaction/thumbs-down.svg b/src/assets/reaction/thumbs-down.svg new file mode 100644 index 0000000..100284d --- /dev/null +++ b/src/assets/reaction/thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/reaction/thumbs-up.svg b/src/assets/reaction/thumbs-up.svg new file mode 100644 index 0000000..ea12770 --- /dev/null +++ b/src/assets/reaction/thumbs-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Badge/Badge.jsx b/src/components/Badge/Badge.jsx new file mode 100644 index 0000000..acb866d --- /dev/null +++ b/src/components/Badge/Badge.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledBadge = styled.span` + display: inline-block; + border-radius: 8px; + padding: 4px 12px; + font-size: 14px; + border: 2px solid; + color: ${(props) => + props.status === "answered" ? "var(--brown-40)" : "var(--gray-30)"}; + border-color: ${(props) => + props.status === "answered" ? "var(--brown-40)" : "var(--gray-30)"}; +`; + +const Badge = ({ status = "unanswered" }) => { + return ( + + {status === "answered" ? "답변 완료" : "미답변"} + + ); +}; + +export default Badge; \ No newline at end of file diff --git a/src/components/Badge/Badge.styles.js b/src/components/Badge/Badge.styles.js new file mode 100644 index 0000000..0fd8cb4 --- /dev/null +++ b/src/components/Badge/Badge.styles.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +// Styled Components +const BadgeContainer = styled.div` + display: flex; + align-items: center; + padding: 4px 12px; + background: #ffffff; + border: 1px solid #542f1a; + border-radius: 8px; + width: fit-content; +`; + +const BadgeText = styled.span` + font-family: 'Pretendard', sans-serif; + font-weight: 500; + font-size: 14px; + line-height: 18px; + color: #542f1a; +`; + +export { BadgeContainer, BadgeText }; diff --git a/src/components/Buttonfloating/Buttonfloating.jsx b/src/components/Buttonfloating/Buttonfloating.jsx new file mode 100644 index 0000000..bb60f1f --- /dev/null +++ b/src/components/Buttonfloating/Buttonfloating.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledFloatingButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + width: 208px; + height: 54px; + padding: 12px 24px; + gap: 8px; + font-size: 20px; + font-weight: 400; + line-height: 25px; + border-radius: 200px; + color: var(--gray-10); + background-color: var(--brown-40); + cursor: pointer; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); +`; + +const FloatingButton = ({ onClick }) => { + const handleButtonClick = () => { + onClick(true); + }; + + return ( + + 질문 작성하기 + + ); +}; + +export default FloatingButton; diff --git a/src/components/Buttonshare/Button.css b/src/components/Buttonshare/Button.css new file mode 100644 index 0000000..44e19af --- /dev/null +++ b/src/components/Buttonshare/Button.css @@ -0,0 +1,5 @@ +button { + border: none; + background: transparent; + cursor: pointer; +} diff --git a/src/components/Buttonshare/Buttonshare.jsx b/src/components/Buttonshare/Buttonshare.jsx new file mode 100644 index 0000000..4266463 --- /dev/null +++ b/src/components/Buttonshare/Buttonshare.jsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { ReactComponent as KakaoSVG } from "../../assets/buttonshare/Type=Kakao.svg"; +import { ReactComponent as FacebookSVG } from "../../assets/buttonshare/Type=Facebook.svg"; +import { ReactComponent as LinkSVG } from "../../assets/buttonshare/Type=Link.svg"; +import Thumbnail from '../../assets/default/drawing.png'; +import { Helmet } from 'react-helmet'; +import "./Button.css"; +import "./Toast.css"; + +export const KakaoShareButton = ({ subject }) => { + const location = useLocation(); + const baseUrl = "https://openmind-10-1.netlify.app" + useEffect(() => { + if (!window.Kakao.isInitialized()) { + window.Kakao.init("4efde28e8b63fd9c48dfdf697ecbf6d6"); + } + }, []); + + const handleKakaoShare = () => { + console.log(subject); + window.Kakao.Link.sendDefault({ + objectType: "feed", + content: { + title: '오픈마인드', + description: `${subject.name}님의 오픈마인드입니다.`, + imageUrl:subject.imageSource, + link: { + mobileWebUrl: `${baseUrl}${location.pathname}`, + webUrl: `${baseUrl}${location.pathname}`, + }, + }, + }); + + }; + + return ( + + ); +}; + +export const FacebookShareButton = ({ subject }) => { + const location = useLocation(); + const baseUrl = "https://openmind-10-1.netlify.app" + const handleFacebookShare = () => { + window.FB.ui({ + method: 'share', + href: `${baseUrl}${location.pathname}`, + }); + }; + + return ( + <> + + + + + + + + {`오픈마인드: ${subject.name}`} + + + + ); +}; + +export const LinkShareButton = () => { + const [copied, setCopied] = useState(false); + const location = useLocation(); + const baseUrl = "https://openmind-10-1.netlify.app" + const handleLinkCopy = () => { + + console.log(location); + const url = `${baseUrl}${location.pathname}`; + navigator.clipboard + .writeText(url) + .then(() => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 5000); + }) + .catch((err) => { + console.error("복사 실패:", err); + }); + }; + + return ( +
+ + {copied &&
URL이 복사되었습니다.
} +
+ ); +}; diff --git a/src/components/Buttonshare/Toast.css b/src/components/Buttonshare/Toast.css new file mode 100644 index 0000000..ffb0039 --- /dev/null +++ b/src/components/Buttonshare/Toast.css @@ -0,0 +1,12 @@ +.toast-message { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background-color: var(--gray-60); + color: var(--gray-10); + padding: 12px 20px; + border-radius: 8px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + z-index: 1000; +} diff --git a/src/components/Component.jsx b/src/components/Component.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/CustomButton.jsx b/src/components/CustomButton.jsx new file mode 100644 index 0000000..c61cc6a --- /dev/null +++ b/src/components/CustomButton.jsx @@ -0,0 +1,242 @@ +import React from "react"; +import styled from "styled-components"; +import "../styles/global.css"; +import { ReactComponent as ArrowRight } from "../assets/icon/arrow-right.svg"; + +const StyledButton = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + padding: 12px 24px; + border-radius: 8px; + font-size: 16px; + font-weight: 400; + line-height: 22px; + border: none; + gap: 8px; + background-color: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "var(--brown-10)"; + case "getQuestion": + case "SendQuestion": + default: + return "var(--brown-40)"; + } + }}; + + color: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "var(--brown-40)"; + case "getQuestion": + case "SendQuestion": + default: + return "var(--gray-10)"; + } + }}; + + outline: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "1px solid var(--brown-40);"; + case "getQuestion": + case "SendQuestion": + default: + return "none"; + } + }}; + + &:hover { + outline: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "2px solid var(--brown-40)"; + case "getQuestion": + case "SendQuestion": + default: + return "2px solid var(--brown-50)"; + } + }}; + } + + &:active { + outline: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "2px solid var(--brown-40)"; + case "getQuestion": + case "SendQuestion": + default: + return "none"; + } + }}; + + background-color: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "var(--brown-20)"; + case "getQuestion": + case "SendQuestion": + default: + return "var(--brown-50)"; + } + }}; + } + + &:disabled { + background-color: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "var(--brown-10)"; + case "getQuestion": + case "SendQuestion": + default: + return "var(--brown-30)"; + } + }}; + } + + @media (min-width: 375px) and (max-width: 766px) { + ${({ mode }) => + (mode === "goToReply" || mode === "askQuestion") && + ` + padding: 8px 12px; + font-size: 14px; + line-height: 12px; + gap: 4px; + `} + + width: ${({ mode }) => { + switch (mode) { + case "SendQuestion": + return "279px"; + case "goToReply": + case "askQuestion": + return "130px"; + case "getQuestion": + default: + return "257px"; + } + }}; + + height: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + return "34px"; + case "getQuestion": + case "SendQuestion": + default: + return "46px"; + } + }}; + } + + @media (min-width: 767px) and (max-width: 1024px) { + width: ${({ mode }) => { + switch (mode) { + case "SendQuestion": + return "532px"; + case "goToReply": + case "askQuestion": + return "168px"; + case "getQuestion": + default: + return "336px"; + } + }}; + + height: ${({ mode }) => { + switch (mode) { + case "goToReply": + case "askQuestion": + case "getQuestion": + case "SendQuestion": + default: + return "46px"; + } + }}; + } + + @media (min-width: 1025px) { + width: ${({ mode }) => { + switch (mode) { + case "SendQuestion": + return "532px"; + case "goToReply": + case "askQuestion": + return "168px"; + case "getQuestion": + default: + return "336px"; + } + }}; + + height: ${({ mode }) => { + switch (mode) { + case "getQuestion": + case "goToReply": + case "askQuestion": + case "SendQuestion": + default: + return "46px"; + } + }}; + } +`; + +const ArrowRightIcon = () => { + return ( + + + + ); +}; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: center; + + .arrow_right_icon { + width: 18px; + height: 18px; + path { + fill: var(--brown-40); + } + } +`; + +const CustomButton = ({ + mode = "getQuestion", + onClick, + disabled = false, + chindren, +}) => { + const buttonText = + { + getQuestion: "질문 받기", + goToReply: "답변하러 가기", + askQuestion: "질문하러 가기", + SendQuestion: "질문 보내기", + }[mode] || "버튼"; + + return ( + + {buttonText} + {(mode === "goToReply" || mode === "askQuestion") && ( + + )} + + ); +}; + +export default CustomButton; diff --git a/src/components/FeedCard.jsx b/src/components/FeedCard.jsx new file mode 100644 index 0000000..d767353 --- /dev/null +++ b/src/components/FeedCard.jsx @@ -0,0 +1,166 @@ +import React from "react"; +import styled from "styled-components"; +import FeedCardQuestion from "./FeedCardQuestion"; +import DefaultImg from "../assets/default/defaultProfile.svg"; +import { ThumbsDown, ThumbsUp } from "./Reaction/Reaction"; +import Badge from "./Badge/Badge"; + +const FeedCardContainer = styled.div` + width: 620px; + padding: 32px; + border-radius: 16px; + background: var(--Grayscale-10, #FFF); + box-shadow: var(--box-shadow-1px); + @media (max-width:375px){ + width: 247px; + } +`; + +const FeedCardBox = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 32px; + @media (max-width:375px){ + gap:24px; + } +`; + +const FeedCardButtonWrapper = styled.div` + width: 100%; + display: flex; + justify-content: space-between; +`; + +const FeedCardAnswerContainer = styled.div` + width: 100%; + display: flex; + gap: 12px; +`; + +const FeedCardUserProfile = styled.img` + width: 48px; + height: 48px; + flex-shrink: 0; + border-radius: 48px; + @media (max-width:375px){ + width: 32px; + height: 32px; + } +`; + +const FeedCardAnswerBox = styled.div` + width: 100%; + display: flex; + gap: 4px; + flex-direction: column; +`; +const FeedCardAnswerHead = styled.div` + width: 100%; + display: flex; + gap: 8px; + display: flex; +`; +const FeedCardAnswerWriter = styled.div` + color: var(--Grayscale-60, #000); + font-size: 18px; + font-weight: 400; + line-height: 24px; + + @media (max-width: 767px) { + font-size: 16px; + } +`; + +const FeedCardAnswerTime = styled.div` + color: var(--Grayscale-40, #818181); + font-size: 14px; + font-weight: 500; + line-height: 18px; + display: flex; + align-items: flex-end; +`; +const FeedCardAnswerText = styled.div` + width: 100%; + color: var(--Grayscale-60, #000); + font-size: 16px; + font-weight: 400; + line-height: 22px; +`; +const FeedCardLine = styled.hr` + height: 1px; + align-self: stretch; + background: var(--Grayscale-30, #cfcfcf); +`; + +const FeedCardReactionContainer = styled.div` + width:100%; + display: flex; + align-items: flex-start; + gap: 32px; +`; + +const RejectAnswer = styled.div` + color: var(--Red-50, #b93333); + font-size: 16px; + font-weight: 400; + line-height: 22px; +`; + +const elapsedTime = (date) => { + const start = new Date(date); + const end = new Date(); + + const seconds = Math.floor((end.getTime() - start.getTime()) / 1000); + if (seconds < 60) return "방금 전"; + + const minutes = seconds / 60; + if (minutes < 60) return `${Math.floor(minutes)}분 전`; + + const hours = minutes / 60; + if (hours < 24) return `${Math.floor(hours)}시간 전`; + + const days = hours / 24; + return `${Math.floor(days)}일 전`; +}; + +const FeedCard = ({ question, subjectId }) => { + const answer = question.answer; + return ( + + + + + + + {answer ? ( + + + + + {subjectId} + + {elapsedTime(answer.createdAt)} + + + {answer.isRejected ? ( + 답변 거절 + ) : ( + {answer.content} + )} + + + ) : ( + <> + )} + + + + + + + + ); +}; + +export default FeedCard; diff --git a/src/components/FeedCardQuestion.jsx b/src/components/FeedCardQuestion.jsx new file mode 100644 index 0000000..6e3846a --- /dev/null +++ b/src/components/FeedCardQuestion.jsx @@ -0,0 +1,64 @@ +import React from 'react' +import styled from 'styled-components'; + +const FeedCardQuestionWrapper = styled.div` + width:100%; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; +`; + +const FeedCardQuestionBox = styled.div` + width:100%; + display:flex; + gap:2px; + color: var(--Grayscale-40, #818181); + font-size: 14px; + font-weight: 500; + line-height: 18px; +`; + +const FeedCardQuestionText = styled.div` + color: var(--Grayscale-60, #000); + font-size: 18px; + font-weight: 400; + line-height: 24px; + + @media (max-width: 767px) { + font-size: 16px; + } +`; + +const elapsedTime = (date) => { + const start = new Date(date); + const end = new Date(); + + const seconds = Math.floor((end.getTime() - start.getTime()) / 1000); + if (seconds < 60) return '방금 전'; + + const minutes = seconds / 60; + if (minutes < 60) return `${Math.floor(minutes)}분 전`; + + const hours = minutes / 60; + if (hours < 24) return `${Math.floor(hours)}시간 전`; + + const days = hours / 24; + return `${Math.floor(days)}일 전`; +}; + +function FeedCardQuestion({ question }) { + return ( + + +
질문 ·
+
{elapsedTime(question.createdAt)}
+
+ + {question.content} + +
+ ) +} + +export default FeedCardQuestion \ No newline at end of file diff --git a/src/components/Icon.jsx b/src/components/Icon.jsx new file mode 100644 index 0000000..2c45667 --- /dev/null +++ b/src/components/Icon.jsx @@ -0,0 +1,89 @@ +import React from "react"; +import { ReactComponent as ArrowdownSVG } from "../assets/icon/Arrow-down.svg"; +import { ReactComponent as ArrowleftSVG } from "../assets/icon/Arrow-left.svg"; +import { ReactComponent as ArrowrightSVG } from "../assets/icon/arrow-right.svg"; +import { ReactComponent as ArrowupSVG } from "../assets/icon/Arrow-up.svg"; +import { ReactComponent as CloseSVG } from "../assets/icon/Close.svg"; +import { ReactComponent as EditSVG } from "../assets/icon/Edit.svg"; +import { ReactComponent as FacebookSVG } from "../assets/icon/Facebook.svg"; +import { ReactComponent as KakaotalkSVG } from "../assets/icon/Kakaotalk.svg"; +import { ReactComponent as LinkSVG } from "../assets/icon/Link.svg"; +import { ReactComponent as MessagesSVG } from "../assets/icon/Messages.svg"; +import { ReactComponent as MoreSVG } from "../assets/icon/More.svg"; +import { ReactComponent as PersonSVG } from "../assets/icon/Person.svg"; +import { ReactComponent as ThumbsdownSVG } from "../assets/icon/thumbs-down.svg"; +import { ReactComponent as ThumbsupSVG } from "../assets/icon/thumbs-up.svg"; + +const Arrowdown = (props) => { + return ; +}; + +const Arrowleft = (props) => { + return ; +}; + +const Arrowright = (props) => { + return ; +}; + +const Arrowup = (props) => { + return ; +}; + +const Close = (props) => { + return ; +}; + +const Edit = (props) => { + return ; +}; + +const Facebook = (props) => { + return ; +}; + +const Kakaotalk = (props) => { + return ; +}; + +const Link = (props) => { + return ; +}; + +const Messages = (props) => { + return ; +}; + +const More = (props) => { + return ; +}; + +const Person = (props) => { + return ; +}; + +const Thumbsdown = (props) => { + return ; +}; + +const Thumbsup = (props) => { + return ; +}; +const Icon = { + Arrowdown, + Arrowleft, + Arrowright, + Arrowup, + Close, + Edit, + Facebook, + Kakaotalk, + Link, + Messages, + More, + Person, + Thumbsdown, + Thumbsup, +}; + +export default Icon; diff --git a/src/components/InputTextArea.jsx b/src/components/InputTextArea.jsx new file mode 100644 index 0000000..4154a6e --- /dev/null +++ b/src/components/InputTextArea.jsx @@ -0,0 +1,41 @@ +import React from "react"; +import styled from "styled-components"; +import "../styles/global.css"; + +const StyledTextArea = styled.textarea` + box-sizing: border-box; + overflow: auto; + font-size: 16px; + font-weight: 400; + line-height: 22px; + width: 100%; + height: 100%; + background-color: var(--gray-20); + border-radius: 8px; + padding: 16px; + border: none; + gap: 10px; + color: var(--gray-60); + resize: none; + &:focus { + outline: 1px solid var(--brown-40); + } +`; + +const InputTextArea = ({ mode = "enterTheQuestion", value, onChange }) => { + const placeholderText = + { + enterTheQuestion: "질문을 입력해주세요", + enterTheReply: "답변을 입력해주세요", + }[mode] || "텍스트 입력"; + + return ( + + ); +}; + +export default InputTextArea; diff --git a/src/components/KebabButton/KebabButton.jsx b/src/components/KebabButton/KebabButton.jsx new file mode 100644 index 0000000..0f48ce2 --- /dev/null +++ b/src/components/KebabButton/KebabButton.jsx @@ -0,0 +1,49 @@ +// KebabButton.jsx +import { useState, useRef, useEffect } from 'react'; +import { StyledMoreIcon, KebabButtonWrapper, MenuModal } from './KebabButton.styles'; + +const KebabButton = ({ menuItems, question }) => { + const [isOpen, setIsOpen] = useState(false); + const menuRef = useRef(null); + + const onMenuItemClick = (onClick) => { + onClick(); + setIsOpen(false); + }; + + useEffect(() => { + const onOutsideClose = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + document.addEventListener('click', onOutsideClose); + return () => document.removeEventListener('click', onOutsideClose); + }, [isOpen]); + + return ( + + { + setIsOpen(!isOpen); + }} + /> + {isOpen && ( + + {menuItems.map((item, index) => ( +
  • onMenuItemClick(() => item.onClick(question))} + > + {item.label} + {item.label} +
  • + ))} +
    + )} +
    + ); +}; + +export default KebabButton; \ No newline at end of file diff --git a/src/components/KebabButton/KebabButton.styles.js b/src/components/KebabButton/KebabButton.styles.js new file mode 100644 index 0000000..cb9781e --- /dev/null +++ b/src/components/KebabButton/KebabButton.styles.js @@ -0,0 +1,41 @@ +import styled from 'styled-components'; + +import { ReactComponent as MoreIcon } from '../../assets/figma.named.svg/icon-more.svg'; + +const StyledMoreIcon = styled(MoreIcon)` + width: 26px; + height: 26px; + &:hover { + cursor: pointer; + } +`; + +const KebabButtonWrapper = styled.div` + position: relative; + width: fit-content; +`; + +const MenuModal = styled.ul` + position: absolute; + top: 8px; + right: 1; + list-style-type: none; + width: 128px; + background-color: #ffffff; + border-radius: 4px; + box-shadow: + 0px 3px 14px 2px rgba(0, 0, 0, 0.12), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 5px 5px -3px rgba(0, 0, 0, 0.2); + + li { + display: block; + text-decoration: none; + font-size: 13px; + padding: 8px 8px; + &:hover { + background-color: #e1e2e3}; + } +`; + +export { StyledMoreIcon, KebabButtonWrapper, MenuModal }; diff --git a/src/components/Modal.jsx b/src/components/Modal.jsx new file mode 100644 index 0000000..3cc2ba3 --- /dev/null +++ b/src/components/Modal.jsx @@ -0,0 +1,84 @@ +import React, { useEffect } from "react"; +import ReactDOM from "react-dom"; +import styled from "styled-components"; +import { ReactComponent as CloseIcon } from "../assets/icon/Close.svg"; + +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +`; + +const ModalContent = styled.div` + color: var(--gray-60); + background: var(--gray-10); + border-radius: 24px; + padding: 40px; + width: 612px; + height: 454px; + max-width: 90%; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + position: relative; + + @media (min-width: 375px) and (max-width: 766px) { + width: 327px; + height: 568px; + } +`; + +const CloseButton = styled.button` + position: absolute; + top: 40px; + right: 40px; + background: transparent; + border: none; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + svg { + width: 100%; + height: 100%; + fill: var(--gray-60); + } +`; + +const Modal = ({ isOpen, onClose, children }) => { + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ReactDOM.createPortal( + + e.stopPropagation()}> + + + + {children} + + , + document.getElementById("modal-root") + ); +}; + +export default Modal; diff --git a/src/components/QuestionModal.jsx b/src/components/QuestionModal.jsx new file mode 100644 index 0000000..80037dd --- /dev/null +++ b/src/components/QuestionModal.jsx @@ -0,0 +1,180 @@ +import React, { useState } from "react"; +import { useParams } from "react-router-dom"; +import { ReactComponent as IconMessage } from "../assets/icon/Messages.svg"; +import FloatingButton from "./Buttonfloating/Buttonfloating"; +import "../styles/global.css"; +import Modal from "./Modal"; +import InputTextArea from "./InputTextArea"; +import CustomButton from "./CustomButton"; +import styled from "styled-components"; + +const ModalBody = styled.div` + display: flex; + flex-direction: column; + height: 100%; +`; + +const ContentWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; +`; + +const ModalFooter = styled.div` + display: flex; + justify-content: flex-end; + margin-top: 8px; +`; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: left; + font-family: Actor, sans-serif; + color: var(--gray-60); + + h2 { + font-size: 24px; + font-weight: 400; + line-height: 30px; + margin: 0; + display: flex; + align-items: center; + margin-bottom: 20px; + } + + .message_icon { + margin-right: 8px; + width: 22.75px; + height: 22.75px; + } +`; + +const UserInfo = styled.div` + display: flex; + align-items: center; + margin-top: 10px; + font-size: 16px; + margin-bottom: 12px; + + .user_icon { + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 8px; + } + span { + display: flex; + align-items: center; + } +`; + +const MessageIcon = () => { + return ( + +

    + + 질문을 작성하세요 +

    +
    + ); +}; + +const UserInformation = ({ name, imageSource }) => { + return ( + + + To. + {`${name}의 + {name} + + + ); +}; + +const QuestionModal = ({ name, imageSource, onQuestionAdded }) => { + const { subjectId: subjectid } = useParams(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [questionText, setQuestionText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const openModal = () => setIsModalOpen(true); + const closeModal = () => { + setIsModalOpen(false); + setQuestionText(""); + }; + + const handleSendQuestion = async () => { + console.log("subjectid:", subjectid); + if (!questionText.trim()) { + alert("질문을 입력해주세요."); + return; + } + + setIsLoading(true); + + try { + const response = await fetch( + `https://openmind-api.vercel.app/10-1/subjects/${subjectid}/questions/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + + body: JSON.stringify({ + subjectId: subjectid, + content: questionText, + team: "10-1", + }), + } + ); + + if (!response.ok) { + throw new Error("Failed to send question."); + } + + const result = await response.json(); + console.log("질문이 전송되었습니다.", result); + + closeModal(); + onQuestionAdded(); + } catch (error) { + console.error("질문 전송에 실패하였습니다:", error); + alert(`질문 전송에 실패하였습니다: ${error.message}`); + } finally { + setIsLoading(false); + } + }; + + return ( +
    + + + + + + + + setQuestionText(e.target.value)} + /> + + + + {isLoading ? "전송 중..." : "질문 보내기"} + + + + +
    + ); +}; + +export default QuestionModal; diff --git a/src/components/Reaction/Reaction.css b/src/components/Reaction/Reaction.css new file mode 100644 index 0000000..c85100f --- /dev/null +++ b/src/components/Reaction/Reaction.css @@ -0,0 +1,21 @@ +.reaction-icon { + fill: var(--gray-40); + color: var(--gray-40); + margin-right: 8px; +} + +.reaction-icon.liked { + fill: var(--blue-50); + color: var(--blue-50); +} + +.reaction-icon.disliked { + fill: var(--gray-60); + color: var(--gray-60); +} + +.reaction-button { + cursor: pointer; + display: flex; + align-items: center; +} diff --git a/src/components/Reaction/Reaction.jsx b/src/components/Reaction/Reaction.jsx new file mode 100644 index 0000000..f322ed6 --- /dev/null +++ b/src/components/Reaction/Reaction.jsx @@ -0,0 +1,104 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import "./Reaction.css"; +import { ReactComponent as ThumbsupSVG } from "../../assets/reaction/thumbs-up.svg"; +import { ReactComponent as ThumbsdownSVG } from "../../assets/reaction/thumbs-down.svg"; + +export const ThumbsUp = ({ question }) => { + const [likeCount, setLikeCount] = useState(0); + const [like, setLike] = useState(false); + const [dislike, setDislike] = useState(false); + + useEffect(() => { + const fetchLikes = async () => { + try { + const response = await axios.get( + `https://openmind-api.vercel.app/10-1/questions/${question.id}/` + ); + setLikeCount(response.data.like); + } catch (error) { + console.error("좋아요 수를 가져오는데 실패햇습니다.", error); + } + }; + + fetchLikes(); + }, []); + + const handleLike = async () => { + if (!like) { + try { + const response = await axios.post( + `https://openmind-api.vercel.app/10-1/questions/${question.id}/reaction/`, + { type: "like" }, + { headers: { "Content-Type": "application/json" } } + ); + setLikeCount(response.data.like); + setLike(true); + + if (dislike) { + setDislike(false); + } + } catch (error) { + console.error("좋아요 추가에 실패했습니다.", error); + } + } + }; + + return ( +
    + + + 좋아요 {likeCount} + +
    + ); +}; + +export const ThumbsDown = ({ question }) => { + const [dislikeCount, setDisLikeCount] = useState(0); + const [dislike, setDislike] = useState(false); + const [like, setLike] = useState(false); + + useEffect(() => { + const fetchDisLikes = async () => { + try { + const response = await axios.get( + `https://openmind-api.vercel.app/10-1/questions/${question.id}/` + ); + setDisLikeCount(response.data.dislike); + } catch (error) { + console.error("싫어요 수를 가져오는데 실패햇습니다.", error); + } + }; + fetchDisLikes(); + }, []); + + const handleDislike = async () => { + try { + if (!dislike) { + await axios.post( + `https://openmind-api.vercel.app/10-1/questions/${question.id}/reaction/`, + { type: "dislike" } + ); + setDisLikeCount((prev) => prev + 1); + setDislike(true); + + if (like) { + setLike(false); + } + } + } catch (error) { + console.error("싫어요 변경에 실패했습니다.", error); + } + }; + return ( +
    + + + 싫어요 {dislikeCount} + +
    + ); +}; diff --git a/src/components/edit_delete/EditDelete.jsx b/src/components/edit_delete/EditDelete.jsx new file mode 100644 index 0000000..9383a86 --- /dev/null +++ b/src/components/edit_delete/EditDelete.jsx @@ -0,0 +1,100 @@ +// import styled from "styled-components"; +// import { ReactComponent as editLogo } from "../../../../assets/default/editLogo.svg"; +// import { ReactComponent as deleteLogo } from "../../../../assets/default/deleteIcon.svg"; +// //카멜 버튼을 누른 상태 +// //수정하기 등에 관한 부모 컴포넌트 +// //스타일 적용을 위한 스타일 컴포넌트 + +// const Wraper = styled.div` +// display: flex; +// width: 103px; +// padding: 4px 0px; +// flex-direction: column; +// justify-content: center; +// align-items: center; +// border-radius: 8px; +// border: 1px solid var(--gray-20); +// background: var(--Grayscale-10, #fff); +// /* 1pt */ +// box-shadow: 0px 4px 4px 0px rgba(140, 140, 140, 0.25); +// `; + +// // const PublicBox = styled.div``; + +// const Edit = styled.div` +// display: flex; +// padding: 6px 16px; +// justify-content: center; +// align-items: center; +// gap: 8px; +// align-self: stretch; +// background: var(--Grayscale-10, #fff); +// color: var(--Grayscale-50, #515151); +// font-feature-settings: "liga" off, "clig" off; +// font-family: Pretendard; +// font-size: 14px; +// font-style: normal; +// font-weight: 500; +// line-height: 18px; /* 128.571% */ +// &:hover { +// color: var(--blue-50); // 텍스트 색상 변경 +// svg { +// fill: inherit; // 아이콘 색상 변경 +// } +// } +// `; + +// const Delete = styled.div` +// display: flex; +// padding: 6px 16px; +// justify-content: center; +// align-items: center; +// gap: 8px; +// align-self: stretch; +// color: var(--Grayscale-50, #515151); +// font-feature-settings: "liga" off, "clig" off; +// font-family: Pretendard; +// font-size: 14px; +// font-style: normal; +// font-weight: 500; +// line-height: 18px; /* 128.571% */ +// flex: 1 0 0; +// &:hover { +// color: var(--blue-50); // 텍스트 색상 변경 +// svg { +// fill: inherit; // 아이콘 색상 변경 +// } +// } +// `; + +// const EditLogo = styled(editLogo)` +// width: 14px; +// height: 14px; +// `; + +// const DeleteLogo = styled(deleteLogo)` +// width: 14px; +// height: 14px; +// `; + +// const CombinedKebobIcon = ({ handleEdit, handleDelete }) => { +// // 프롭스가 들어가야함 함수들 받아줘야함 : key +// return ( +// <> +// +// +// +// +// 수정하기 +// +// +// +// 삭제하기 +// +// +// +// +// ); +// }; + +// export default CombinedKebobIcon; diff --git a/src/index.css b/src/index.css index ec2585e..8ac7e18 100644 --- a/src/index.css +++ b/src/index.css @@ -1,6 +1,8 @@ +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css"); + body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + font-family:'Pretendard', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; diff --git a/src/index.js b/src/index.js index 24c23ee..6e1008a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,12 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import "./styles/global.css"; +import App from "./App.js"; - -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - - - -); \ No newline at end of file + + + +); diff --git a/src/pages/Main.jsx b/src/pages/Main.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/MainPage/Main.jsx b/src/pages/MainPage/Main.jsx new file mode 100644 index 0000000..b902624 --- /dev/null +++ b/src/pages/MainPage/Main.jsx @@ -0,0 +1,99 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { subjects_create } from "../../api/swagger/Subjects"; +import { ReactComponent as DefaultLogo } from "../../assets/default/defaultLogo.svg"; +import { ReactComponent as PersonIconSVG } from "../../assets/icon/Person.svg"; +import { ReactComponent as ArrowRightIconSVG } from "../../assets/icon/arrow-right.svg"; +import { ReactComponent as BackgroundSVG } from "../../assets/figma.named.svg/v-872-batch-5-nunny-04-1.svg"; + +import { + Container, + FormContainer, + InputContainer, + Input, + GoToQuestionsButton, + GoToAnswerButton, +} from "./MainStyle"; + +import LocalStore from "../../api/storage/LocalStore"; + +const Main = () => { + const navigate = useNavigate(); + + const [subject, setSubject] = useState( + /* LocalStore.getItem함수에서 JSON.parse를 하고 있음 + JSON.parse(LocalStore.getItem("subject")) + */ + LocalStore.getItem("subject") + ); + const [name, setName] = useState(""); + const [team] = useState("10-1"); + + const handleNameChange = (event) => { + setName(event.target.value); + }; + + const createSubject = async () => { + try { + const res = await subjects_create(team, name); + if (res !== null && res !== undefined) { + /* 윗줄 subjects_create함수에서 JSON.stringify하고 있음 + LocalStore.setItem("subject", JSON.stringify(res)); + */ + LocalStore.setItem("subject", res); + setSubject(res); + console.log(res); + return res; + } + } catch (error) { + console.error("Error creating subject:", error); + } + return null; + }; + + const onClickAnswer = async () => { + if (name.trim() === "" && subject?.name === undefined) { + alert("Enter name and Click the button."); + return; + } else if (name.trim().length > 0) { + const res = await createSubject(); + if (res) { + setSubject(res); + navigate("/post/answer/WriteAnswer"); + } + } else if (subject?.name !== undefined) { + navigate("/post/answer/WriteAnswer"); + } + }; + const onClickListSubjects = () => { + navigate("/list"); + }; + + return ( + + + + + + + + + 질문 받기 + + + + 질문하러 가기 + + + + + ); +}; + +export default Main; diff --git a/src/pages/MainPage/MainStyle.jsx b/src/pages/MainPage/MainStyle.jsx new file mode 100644 index 0000000..aa394f1 --- /dev/null +++ b/src/pages/MainPage/MainStyle.jsx @@ -0,0 +1,137 @@ +import styled from "styled-components"; + +// Styled Components +const Container = styled.div` + background: #f9f9f9; + display: flex; + flex-direction: column; + align-items: center; + padding: 0px; + padding-top: 240px; + margin: 0 auto; + min-height: 100vh; + min-width: 100vw; + overflow: hidden; + height: 832px; + position: relative; +`; + +const Logo = styled.img` + width: 456px; + height: 186px; + margin-top: 20px; +`; + +const BackgroundImage = styled.img` + width: 100%; + height: auto; + position: absolute; + left: 0; + bottom: 0; + z-index: -1; +`; + +const FormContainer = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + padding: 32px; + border-radius: 16px; + align-items: center; + margin-top: 20px; + margin-bottom: 20px; + width: 400px; + height: auto; +`; + +const InputContainer = styled.div` + display: flex; + align-items: center; + padding: 12px 16px; + background: #ffffff; + border: 1px solid #818181; + border-radius: 8px; + gap: 12px; +`; + +const InputIcon = styled.img` + width: 20px; + height: 20px; +`; + +const Input = styled.input` + border: none; + outline: none; + font-family: "Pretendard", sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 22px; + color: #818181; + width: 100%; + ::placeholder { + color: #818181; + } +`; + +const GoToAnswerButton = styled.button` + padding: 16px 110px; + background: #542f1a; + border-radius: 8px; + border: none; + color: #ffffff; + cursor: pointer; + font-family: "Pretendard", sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 22px; + margin-top: 20px; + &:hover { + background: #341d0e; + } + + &:disabled { + background-color: #d3d3d3; + color: #a9a9a9; + cursor: not-allowed; + } +`; + +const GoToQuestionsButton = styled.button` + display: flex; + align-items: center; + gap: 8px; + padding: 12px 24px; + background: #f5f1ee; + border-radius: 8px; + border: 1px solid #542f1a; + color: #542f1a; + cursor: pointer; + font-family: "Pretendard", sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 22px; + position: absolute; + top: 45px; + right: 20px; + &:hover { + background: #e5e1dd; + } +`; + +const ArrowIcon = styled.img` + width: 18px; + height: 18px; +`; + +export { + Container, + Logo, + BackgroundImage, + FormContainer, + InputContainer, + InputIcon, + Input, + GoToAnswerButton, + GoToQuestionsButton, + ArrowIcon, +}; \ No newline at end of file diff --git a/src/pages/PostAnswerPage/WriteAnswer.jsx b/src/pages/PostAnswerPage/WriteAnswer.jsx new file mode 100644 index 0000000..501ef1a --- /dev/null +++ b/src/pages/PostAnswerPage/WriteAnswer.jsx @@ -0,0 +1,282 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Container, + Header, + ProfilePhoto, + ProfilePhotoLarge, + Username, + UsernameLarge, + ShareButton, + ShareIcon, + FeedContainer, + FeedHeader, + FeedIcon, + FeedTitle, + FeedCardHeader, + FeedCard, + QuestionHeader, + AnswerInputContainer, + InputText, + Button, + ButtonSecondary, + ReactionSection, + ReactionItem, + ReactionText, + StyledReactionIcon, + ActionButton, + CommentContent, + CommentUsername, + CommentText, + TimeAgo, + RejectButton, + RejectionItem, +} from './WriteAnswer.styles'; + +import { Badge } from './components/Badge/Badge'; +import KebabButton from '../../components/KebabButton/KebabButton'; +import iconEdit from '../../assets/figma.named.svg/icon-edit.svg'; +import iconDelete from '../../assets/figma.named.svg/icon-close.svg'; + +import { ReactComponent as HeaderIllustration } from '../../assets/figma.named.svg/group-34.svg'; +import { ReactComponent as IconMessages } from '../../assets/figma.named.svg/icon-messages.svg'; +import { ReactComponent as ThumbsUpIcon } from '../../assets/figma.named.svg/icon-thumbs-up.svg'; +import { ReactComponent as ThumbsDownIcon } from '../../assets/figma.named.svg/icon-thumbs-down.svg'; +import LinkAndShareButton from './components/Button/ShareButton'; +import LocalStorage from '../../api/storage/LocalStorage'; +import * as API_Subjects from '../../api/swagger/Subjects'; +import * as API_Questions from '../../api/swagger/Questions'; +import * as API_Answers from '../../api/swagger/Answers'; + +const getElapsedTimeString = (date) => { + const now = new Date(); + const diff = now - date; + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const weeks = Math.floor(days / 7); + const months = Math.floor(days / 30); + const years = Math.floor(days / 365); + if (years > 0) { + return `${years}년전`; + } else if (months > 0) { + return `${months}달전`; + } else if (weeks > 0) { + return `${weeks}주전`; + } else if (days > 0) { + return `${days}일전`; + } else if (hours > 0) { + return `${hours}시간전`; + } else if (minutes > 0) { + return `${minutes}분전`; + } else { + return `${seconds}초전`; + } +}; + + +// Main Component +const WriteAnswer = () => { + const team = '10-1' + const offset = 0; + const limit = 10; + + const navigate = useNavigate(); + const [subject] = useState(LocalStorage.getItem('subject')); + const [questions, setQuestions] = useState({}); + const [answers, setAnswers] = useState({}) + const [inputTextValue, setInputTextValue] = useState('') + const [question, setQuestion] = useState(null); + + //TODO: 케밥메뉴 버튼 클릭 시 현재 quistion ID를 얻어야 한다. + const handleKebabEditMenuClick = (question) => { + console.log('수정하기 클릭', question); + }; + + const handleKebabDeleteMenuClick = (question) => { + console.log('삭제하기 클릭', question); + }; + + const kebabMenuItems = [ + { + label: '수정하기', + icon: iconEdit, + onClick: handleKebabEditMenuClick, + }, + { + label: '삭제하기', + icon: iconDelete, + onClick: handleKebabDeleteMenuClick, + }, + ]; + + + + const getQuestionsBySubjectId = async ({ team, subjectId, offset, limit }) => { + try { + const questions = await API_Subjects.subjects_questions_list(team, subjectId, limit, offset); + setQuestions(questions); + } catch (error) { + console.error('getQuestionsBySubjectId():', error); + } + }; + + const getFetchQuestions = async () => { + try { + const subjectId = subject?.id; + getQuestionsBySubjectId({ team, subjectId, offset, limit }); + } catch (error) { + console.error('getFetchQuestions():', error); + } + } + + const getAnswersByQuestionId = async ({ questionId }) => { + try { + const res = await API_Answers.answers_read(questionId); + setAnswers(res); + } catch (error) { + console.error('getAnswersByQuestionId():', error); + } + }; + + const getFetchAnswers = async (questionId) => { + try { + getAnswersByQuestionId({ questionId }); + } catch (error) { + console.error('getFetchAnswers():', error); + } + } + + useEffect(() => { + if (!subject) { + console.log('localstorage에 선택된 subject가 존재하지 않습니다.'); + navigate('/'); + } + + getFetchQuestions(); + questions?.results?.forEach((question) => { + getFetchAnswers(question?.id); + }); + }, [subject, question]); + + const handleAnswerWriteButton = (questionId, isRejected, e) => { + //TODO: '답변 거절' 버튼 클릭 시 isRejected 값이 true이면 axios리턴 400 에러 발생 + if (isRejected) { + console.log('답변 거절 처리 완료') + API_Questions.questions_answers_create(questionId, '답변 거절', isRejected, team) + } else { + console.log('답변 완료 처리 완료') + API_Questions.questions_answers_create(questionId, inputTextValue, isRejected, team) + } + } + + return ( + +
    + + + {subject?.name + '(' + subject?.id + ')' || '아초는고양이'} + + {/* 공유버튼으로 바꿔야함 */} + +
    + + + + {subject?.questionCount}개의 질문이 있습니다 + + {questions?.results && questions?.results.map((question) => ( + !question.answer ? ( //답변이 없는 경우 + + + 미답변 + + + + + 질문 · {getElapsedTimeString(new Date(question?.createdAt))} + + +

    + {question?.content} +

    + + + + + {subject?.name + '(' + subject?.id + ')' || '아초는고양이'} + setInputTextValue(e.target.value)} placeholder='답변을 입력해주세요'> + + + + + + + + 좋아요 + + + + 싫어요 + + + { + handleAnswerWriteButton(question.id, true, e) + }} + >답변 거절 + + +
    + ) : ( //답변이 있는 경우 + + + 답변 완료 + + + + + 질문 · {getElapsedTimeString(new Date(question?.createdAt))} + + +

    + {question?.isRejected ? '답변 거절' : question?.content} +

    + + + + + {/*TODO: 답변 작성시간 문자 크기 조정이 필요. ??? */} + {subject?.name + '(' + subject?.id + ') ' + getElapsedTimeString(new Date(question.answer?.createdAt)) || '아초는고양이'} +

    + {question.answer?.content} +

    +
    +
    +
    + {/*TODO: 좋아요, 싫어요 버튼 클릭 이벤트를 추가해야 함 */} + + + + 좋아요 + + + + 싫어요 + + +
    + ) + ))} +
    + + 삭제하기 +
    + ); +}; +export default WriteAnswer; \ No newline at end of file diff --git a/src/pages/PostAnswerPage/WriteAnswer.styles.js b/src/pages/PostAnswerPage/WriteAnswer.styles.js new file mode 100644 index 0000000..aef6035 --- /dev/null +++ b/src/pages/PostAnswerPage/WriteAnswer.styles.js @@ -0,0 +1,294 @@ +import styled from 'styled-components'; + +// Styled Components +const Container = styled.div` + background: #f9f9f9; + padding: 20px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const Header = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 30px; +`; + +const ProfilePhoto = styled.img` + width: 48px; + height: 48px; + border-radius: 50%; + margin-bottom: 10px; +`; + +const ProfilePhotoLarge = styled(ProfilePhoto)` + width: 136px; + height: 136px; +`; + +const Username = styled.h4` + font-family: 'Actor', sans-serif; + font-weight: 400; + font-size: 16px; + color: #000; +`; + +const UsernameLarge = styled.h1` + font-family: 'Actor', sans-serif; + font-weight: 400; + font-size: 32px; + color: #000; +`; + +const ShareButtons = styled.div` + display: flex; + gap: 12px; + margin-top: 20px; +`; + +const ShareButton = styled.div` + display: flex; + align-items: center; + padding: 12px 16px; + border-radius: 200px; + background: ${(props) => props.bgColor}; + cursor: pointer; +`; + +const ShareIcon = styled.img` + width: 18px; + height: 18px; +`; + +const FeedContainer = styled.div` + background: #f5f1ee; + padding: 16px; + border-radius: 16px; + border: 1px solid #c7bbb5; + box-sizing: border-box; + margin-bottom: 30px; + width: 100%; + max-width: 620px; +`; + +const FeedHeader = styled.div` + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; +`; + +const FeedIcon = styled.img` + width: 24px; + height: 24px; +`; + +const FeedTitle = styled.div` + font-family: 'Actor', sans-serif; + font-size: 20px; + color: #542f1a; +`; + +const FeedCardHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const FeedCard = styled.div` + width: 520px; + background: #ffffff; + padding: 32px; + border-radius: 16px; + margin-bottom: 20px; + display: flex; + flex-direction: column; + gap: 32px; +`; + +const QuestionHeader = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const AnswerInputContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 20px; +`; + +const InputText = styled.textarea` + width: 420px; + max-height: 154px; + padding: 16px; + background: #f9f9f9; + border-radius: 8px; + font-family: 'Pretendard', sans-serif; + font-size: 16px; + color: #818181; + display: flex; + align-items: center; + height: 154px; + line-height: 22px; +`; + +const Button = styled.button` + width: 100%; + padding: 12px 24px; + background: ${props => props.background || '#542f1a'}; + border: none; + border-radius: 8px; + color: #fff; + cursor: pointer; + font-family: 'Pretendard', sans-serif; + font-size: 16px; + line-height: 22px; + &:disabled { + background:#c7bbb5; + color:#D9D9D9 + } +`; + +const ButtonSecondary = styled.button` + width: 100%; + padding: 12px 24px; + background: #542f1a; + border: none; + border-radius: 8px; + color: #ffffff; + cursor: pointer; + font-family: 'Pretendard', sans-serif; + font-size: 16px; + line-height: 22px; +`; + +const ReactionSection = styled.div` + display: flex; + gap: 32px; + margin-top: 24px; +`; + +const ReactionItem = styled.div` + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; +`; + +const RejectionItem = styled.div` + display: flex; + justify-content: end; + align-items: end; + gap: 6px; + width: 320px; + cursor: pointer; +`; + +const ReactionText = styled.span` + font-family: 'Pretendard', sans-serif; + font-weight: 500; + font-size: 14px; + color: #818181; +`; + +const StyledReactionIcon = styled.svg` + width: 16px; + height: 16px; +`; + +const ActionButton = styled.button` + padding: 12px 24px; + border-radius: 200px; + background: #542f1a; + color: #fff; + border: none; + cursor: pointer; + font-family: 'Pretendard', sans-serif; + font-size: 20px; + position: fixed; + right: 20px; + bottom: 20px; +`; + +const CommentContent = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; +`; + +const CommentUsername = styled.div` + display: flex; + flex-direction: column; + justify-content: left; + font-family: 'Actor', sans-serif; + font-size: 18px; + color: #000; +`; + + +const CommentText = styled.div` + display: flex; + justify-content: flex-start; + gap: 16px; + padding: 1px; + background: ${props => props.background || '#f9f9f9'}; + border-radius: 8px; + font-family: 'Pretendard', sans-serif; + font-size: 16px; + color: ${props => props.color || '#818181'}; + line-height: 22px; +`; + +const TimeAgo = styled.div` + font-family: 'Pretendard', sans-serif; + font-weight: 500; + font-size: 14px; + color: #818181; + margin-left: 108px; +`; + +const RejectButton = styled.span` + font-family: Pretendard, sans-serif; + font-size: 16px; + color: ${props => (props.rejection ? '#b93333' : '#000000')}; + display: ${props => (props.rejection ? 'inline' : 'none')}; +`; + +export { + Container, + Header, + ProfilePhoto, + ProfilePhotoLarge, + Username, + UsernameLarge, + ShareButtons, + ShareButton, + ShareIcon, + FeedContainer, + FeedHeader, + FeedIcon, + FeedTitle, + FeedCardHeader, + FeedCard, + QuestionHeader, + AnswerInputContainer, + InputText, + Button, + ButtonSecondary, + ReactionSection, + ReactionItem, + ReactionText, + StyledReactionIcon, + ActionButton, + CommentContent, + CommentUsername, + CommentText, + TimeAgo, + RejectButton, + RejectionItem, +}; \ No newline at end of file diff --git a/src/pages/PostAnswerPage/components/Badge/Badge.jsx b/src/pages/PostAnswerPage/components/Badge/Badge.jsx new file mode 100644 index 0000000..86db32a --- /dev/null +++ b/src/pages/PostAnswerPage/components/Badge/Badge.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { BadgeContainer, BadgeText } from './Badge.styles'; + +const Badge = ({ children }) => { + return ( + + {children} + + ); +}; + +const SampleBadge = () => { + return ( + 답변 완료 + ); +}; + +export { Badge, SampleBadge } + diff --git a/src/pages/PostAnswerPage/components/Badge/Badge.styles.js b/src/pages/PostAnswerPage/components/Badge/Badge.styles.js new file mode 100644 index 0000000..0fd8cb4 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Badge/Badge.styles.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +// Styled Components +const BadgeContainer = styled.div` + display: flex; + align-items: center; + padding: 4px 12px; + background: #ffffff; + border: 1px solid #542f1a; + border-radius: 8px; + width: fit-content; +`; + +const BadgeText = styled.span` + font-family: 'Pretendard', sans-serif; + font-weight: 500; + font-size: 14px; + line-height: 18px; + color: #542f1a; +`; + +export { BadgeContainer, BadgeText }; diff --git a/src/pages/PostAnswerPage/components/Button/BoxButton.jsx b/src/pages/PostAnswerPage/components/Button/BoxButton.jsx new file mode 100644 index 0000000..75a68d2 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/BoxButton.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { ReactComponent as LeftArrow } from '../../assets/figma.named.svg/icon-arrow-right.svg'; +import { ReactComponent as RightArrow1 } from '../../assets/figma.named.svg/icon-arrow-right-2.svg'; +import { ReactComponent as RightArrow2 } from '../../assets/figma.named.svg/icon-arrow-right-3.svg'; +import { Icon, ButtonWrapper, ButtonText } from './BoxButton.styles'; + +const Button = ({ + variant, + state, + size, + text, + textColor, + leftIcon, + rightIcon, + activeState, + onClick, +}) => { + const colors = { + border: state === 'inactivate' ? '#c7bbb5ff' : (variant === 'outline' ? '#542f1aff' : (state === 'pressed' ? '#e4d5c9ff' : '#542f1aff')), + background: variant === 'fill' ? (state === 'hover' ? '#542f1aff' : (state === 'pressed' ? '#341909ff' : (state === 'inactivate' ? '#c7bbb5ff' : '#f5f1eeff'))) : 'transparent' + }; + + const writing = { + color: (variant === 'fill' && state !== 'inactivate') ? '#ffffff' : '#542f1aff' + } + + const fontRef = size === 'medium' ? { family: 'Pretendard', weight: 400, size: '16px', lineHeight: '22px' } : { family: 'Actor', weight: 400, size: '14px', lineHeight: '18px' }; + + return ( + + + {text} + + + ); +}; + +Button.propTypes = { + variant: PropTypes.oneOf(['fill', 'outline']).isRequired, + state: PropTypes.oneOf(['default', 'hover', 'pressed', 'inactivate']).isRequired, + size: PropTypes.oneOf(['small', 'medium']).isRequired, + text: PropTypes.string.isRequired, + textColor: PropTypes.string, + leftIcon: PropTypes.elementType.isRequired, + rightIcon: PropTypes.elementType.isRequired, + activeState: PropTypes.oneOf(['default', 'inactivate']).isRequired, + onClick: PropTypes.func, +}; + +const BoxButton = () => { + return ( +
    +
    + ); +}; + +export default BoxButton; diff --git a/src/pages/PostAnswerPage/components/Button/BoxButton.styles.js b/src/pages/PostAnswerPage/components/Button/BoxButton.styles.js new file mode 100644 index 0000000..d79de16 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/BoxButton.styles.js @@ -0,0 +1,37 @@ +import styled from 'styled-components'; + +const Icon = styled.svg` + width: 18px; + height: 18px; +`; + +const ButtonWrapper = styled.button` + display: flex; + align-items: center; + gap: ${props => props.size === 'medium' ? '8px' : '4px'}; + padding: ${props => props.size === 'medium' ? '12px 24px' : '8px 12px'}; + border-radius: 8px; + border: ${props => props.variant === 'outline' + ? (props.isActive ? '2px' : '1px') + : 'none'} solid ${props => props.colors.border}; + background-color: ${props => props.variant === 'fill' + ? props.colors.background + : 'transparent'}; + color: ${props => props.writing.color}; + + &:hover { + cursor: pointer; + border-width: ${props => props.variant === 'outline' ? '2px' : 'none'}; + } + opacity: ${props => props.isActive ? 1 : 0.5}; +`; + +const ButtonText = styled.span` + font-family: Pretendard, sans-serif; + font-weight: 400; + font-size: 20px; + line-height: 25px; + color: ${props => props.textColor}; +`; + +export { Icon, ButtonWrapper, ButtonText }; \ No newline at end of file diff --git a/src/pages/PostAnswerPage/components/Button/FloatingButton.jsx b/src/pages/PostAnswerPage/components/Button/FloatingButton.jsx new file mode 100644 index 0000000..c623be0 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/FloatingButton.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import { ButtonWrapper, ButtonText } from './FloatingButton.styles'; + +const FloatingButton = () => { + return ( + + 질문 작성하기 + + ); +} + +export default FloatingButton; diff --git a/src/pages/PostAnswerPage/components/Button/FloatingButton.styles.js b/src/pages/PostAnswerPage/components/Button/FloatingButton.styles.js new file mode 100644 index 0000000..8314d69 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/FloatingButton.styles.js @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +const ButtonWrapper = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + width: auto; + height: 30px; + background-color: #542f1a; + border: none; + border-radius: 100px; + padding: 12px 24px; + cursor: pointer; + + &:hover { + opacity: 0.9; + } + + &:active { + opacity: 0.8; + } +`; + +const ButtonText = styled.span` + font-family: Pretendard, sans-serif; + font-weight: 400; + font-size: 20px; + line-height: 25px; + color: #ffffff; +`; + +export { ButtonWrapper, ButtonText }; \ No newline at end of file diff --git a/src/pages/PostAnswerPage/components/Button/ShareButton.jsx b/src/pages/PostAnswerPage/components/Button/ShareButton.jsx new file mode 100644 index 0000000..d14504f --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/ShareButton.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Container, IconButton, StyledIcon } from './ShareButton.styles'; + +import { ReactComponent as LinkIcon } from '../../../../assets/figma.named.svg/icon-link-2.svg'; +import { ReactComponent as KakaoIcon } from '../../../../assets/figma.named.svg/icon-kakaotalk-2.svg'; +import { ReactComponent as FacebookIcon } from '../../../../assets/figma.named.svg/icon-facebook-2.svg'; + +const LinkAndShareButton = () => { + return ( + + + + + + + + + + + + ); +}; + +export default LinkAndShareButton; diff --git a/src/pages/PostAnswerPage/components/Button/ShareButton.styles.js b/src/pages/PostAnswerPage/components/Button/ShareButton.styles.js new file mode 100644 index 0000000..205ee16 --- /dev/null +++ b/src/pages/PostAnswerPage/components/Button/ShareButton.styles.js @@ -0,0 +1,35 @@ +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 184px; + height: 80px; + border-radius: 5px; + border: 0px dashed #9747ff; + background-color: ${props => props.backgroundcolor ? props.backgroundcolor : 'white'}; + box-sizing: border-box; +`; + +const IconButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + width: 42px; + height: 42px; + border-radius: 100px; + border: none; + background-color: ${props => props.backgroundcolor ? props.backgroundcolor : 'white'}; + margin: 0 8px; + &:hover { + cursor: pointer; + } +`; + +const StyledIcon = styled.svg` + width: 18px; + height: 18px; +`; + +export { Container, IconButton, StyledIcon }; \ No newline at end of file diff --git a/src/pages/PostQuestionPage/PostQuestionPage.jsx b/src/pages/PostQuestionPage/PostQuestionPage.jsx new file mode 100644 index 0000000..639c924 --- /dev/null +++ b/src/pages/PostQuestionPage/PostQuestionPage.jsx @@ -0,0 +1,162 @@ +import React, { useCallback, useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import LogoImg from "../../assets/default/logo.svg"; +import BackgroundImg from "../../assets/default/background.svg"; +import { + FacebookShareButton, + KakaoShareButton, + LinkShareButton, +} from "../../components/Buttonshare/Buttonshare"; +import QuestionList from "./components/QuestionList"; +import { getSubject } from "../../api/swagger/Subject"; + +import QuestionModal from "../../components/QuestionModal"; + +const PostContainer = styled.div` + width: 100%; +`; + +const PostWrapper = styled.div` + max-width: 1200px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @media (max-width: 768px) { + max-width: 768px; + } + @media (max-width: 375px) { + max-width: 375px; + } +`; + +const Background = styled.img` + width: 1200px; + height: auto; + object-fit: cover; +`; + +const Logo = styled.img` + display: flex; + width: 170px; + height: 67px; + justify-content: center; + align-items: center; + flex-shrink: 0; + position: absolute; + top: 50px; +`; + +const Profile = styled.img` + display: flex; + width: 136px; + height: 136px; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 136px; +`; +const UserName = styled.div` + color: var(--Grayscale-60, #000); + font-size: 32px; + font-weight: 400; + line-height: 40px; +`; +const ShareWrapper = styled.div` + display: inline-flex; + align-items: flex-start; + gap: 12px; +`; + +const FloatingButtonWrapper = styled.div` + width: 100%; + margin-top: 58px; + display: flex; + justify-content: flex-end; +`; + +const ProfileContainer = styled.div` + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; + justify-content: center; + align-items: center; + position: relative; + bottom: 80px; +`; +const BackgroundWrapper = styled.div` + width: 100%; + height: 234px; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + + @media (max-width: 768px) { + height: 234px; + } +`; + +const PostQuestionPage = () => { + const { subjectId } = useParams(); + const [subject, setSubject] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [refreshTrigger, setRefreshTrigger] = useState(0); + const fetchSortedData = useCallback(async () => { + console.log(subjectId); + try { + setLoading(true); + const response = await getSubject(subjectId); + setSubject(response); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }, [subjectId]); + + useEffect(() => { + fetchSortedData(); + }, [fetchSortedData, refreshTrigger]); + + const handleQuestionAdded = () => { + setRefreshTrigger((prev) => prev + 1); + }; + + return ( + + + + + + + + + + {subject.name} + + + + + + + + + + + + + + ); +}; + +export default PostQuestionPage; diff --git a/src/pages/PostQuestionPage/components/QuestionList.jsx b/src/pages/PostQuestionPage/components/QuestionList.jsx new file mode 100644 index 0000000..88fcb12 --- /dev/null +++ b/src/pages/PostQuestionPage/components/QuestionList.jsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import Icon from "../../../components/Icon"; +import FeedCard from "../../../components/FeedCard"; +import { getQuestions } from "../../../api/swagger/Question"; +import NoQuestionImg from "../../../assets/default/question_empty_logo.png"; + +const QuestionListContainer = styled.div` + width: 684px; + height: 100%; + display: inline-flex; + padding: 16px; + flex-direction: column; + align-items: center; + gap: 16px; + border-radius: 16px; + border: 1px solid var(--Brown-30, #c7bbb5); + background: var(--Brown-10, #f5f1ee); + @media (max-width: 375px) { + width: 295px; + } +`; + +const QuestionCountContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 8px; +`; + +const StyledMessageIcon = styled(Icon.Messages)` + color: var(--Brown-40, #542f1a); +`; + +const QuestionCountText = styled.div` + color: var(--Brown-40, #542f1a); + font-size: 20px; + font-weight: 400; + line-height: 25px; +`; + +const NoQuestion = styled.img` + width: 150px; + height: 154px; + flex-shrink: 0; +`; + +function QuestionList({ subjectId, refreshTrigger }) { + const [questionList, setQuestionList] = useState([]); + const [response, setResponse] = useState({ count: 0, results: [] }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchAllQuestions = async () => { + setIsLoading(true); + try { + const questions = await getQuestions(subjectId); + setQuestionList(questions.results); + setResponse(questions); + } catch (err) { + setError(err); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchAllQuestions(); + }, [subjectId, refreshTrigger]); + + return ( + + {isLoading ? ( +
    로딩 중...
    + ) : error ? ( +
    에러 발생: {error.message}
    + ) : response.count === 0 ? ( + <> + + + 아직 질문이 없습니다 + + + + ) : ( + <> + + + {`${response.count}개의 질문이 있습니다`} + + {questionList.map((question) => ( + + ))} + + )} +
    + ); +} + +export default QuestionList; diff --git a/src/pages/QuestionListPage/QuestionListPage.jsx b/src/pages/QuestionListPage/QuestionListPage.jsx new file mode 100644 index 0000000..061aff6 --- /dev/null +++ b/src/pages/QuestionListPage/QuestionListPage.jsx @@ -0,0 +1,137 @@ +import DropDown from "./components/drop_down/DropDown"; +import PaginationBar from "./components/pagination/PaginationBar"; +import ArrayCard from "./components/user_card/ArrayCard"; +import { useState, useEffect } from "react"; +import { getItems } from "../../IsThereAPI"; +import Title from "./components/title/Title"; +import style from "styled-components" +import ContainHeader from "./components/header/Headers"; + + + +const Wrapper = style.div` + display: flex; + flex-direction: column; /* 세로 방향으로 정렬 */ + align-items: center; /* 수평 가운데 정렬 */ + justify-content: center; /* 수직 가운데 정렬 (높이가 필요한 경우) */ + width: 100%; /* 전체 너비 사용 */ + margin-top: 20px; /* 여백 추가 (필요에 따라 조정) */ + + @media (max-width: 390px) { + margin-right: 24px; + margin-left: 24px; + width: auto; + } +`; + + +const TitleWrapper = style.div` + display: flex; + flex-direction: column; /* 세로 방향으로 정렬 */ + align-items: center; /* 수평 가운데 정렬 */ + justify-content: center; /* 수직 가운데 정렬 (높이가 필요한 경우) */ + width: 100%; /* 전체 너비 사용 */ + padding: 20px; /* 여백 추가 (필요에 따라 조정) */ + + @media (max-width: 390px) { + display: inline-flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap:42px; + margin-bottom: 16px; + padding: 0; + }; +`; + +const DropDownWrapper = style.div` + margin-bottom: 20px; + margin-top: 12px; + @media(max-width: 390px){ + margin-bottom: 0; + margin-top: 0; + } +`; + + +const CardPGWrapper = style.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 40px; + @media (max-width: 950px) { + gap: 46px; + }; + @media (max-width: 768px) { + gap: 61px; + }; + @media (max-width: 390px) { + gap: 31px; + }; +`; + + +//부모 컴포넌트 +function QuestionListPage() { + const [limit, setLimit] = useState(8); // 페이지당 보여줄 아이템 개수 + const [cardList, setCardList] = useState([]); + const [page, setPage] = useState(1); // 현재 활성화된 페이지 + const [totalPageNum, setTotalPageNum] = useState(); // 전체 아이템수 / 페이지당 보여줄수 + + const fetchApi = async () => { + // 파라미터로 현재 원하느 페이지 정보와, 페이지당 개수 (limit) 전달해서 api호출시 query로 반영 해줘야하는 코드가 필요함 + console.log("limit,page", limit, page); + const data = await getItems(limit, page); // 8,8 limit, offset + setCardList(data.results); + console.log("data.count", data.count); + setTotalPageNum(Math.ceil(data.count / limit)); + return data.results; + }; + + useEffect(() => { + fetchApi(); // 처음 api보낼때 1페이지기준으로 8개 받아오도록 되어있음. 2페이지 기준으로 줘 + }, [page, limit]); + + const onPageChange = (pageNumber) => { + setPage(pageNumber); + }; + + const handleResize = () => { + if (window.innerWidth < 769) { + setLimit(6); // 화면 크기가 768px 이하일 때 limit을 6으로 설정 + } else { + setLimit(8); // 그렇지 않으면 기본값인 8로 설정 + } + }; + + useEffect(() => { + handleResize(); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return ( + + + + + <DropDownWrapper> + <DropDown /> + </DropDownWrapper> + </TitleWrapper> + <CardPGWrapper> + <ArrayCard cardList={cardList} /> + <PaginationBar + totalPageNum={totalPageNum} + activePageNum={page} + onPageChange={onPageChange} + /> + </CardPGWrapper> + </Wrapper> + ); +} + +export default QuestionListPage; diff --git a/src/pages/QuestionListPage/components/drop_down/DropDown.jsx b/src/pages/QuestionListPage/components/drop_down/DropDown.jsx new file mode 100644 index 0000000..e80256b --- /dev/null +++ b/src/pages/QuestionListPage/components/drop_down/DropDown.jsx @@ -0,0 +1,94 @@ +import { useState } from "react"; +import "../../../../styles/global.css"; +import { OptionBox, OptionText, Option } from "./Options"; +import styled from "styled-components"; +import { ReactComponent as upArrow } from "../../../../assets/icon/Arrow-up.svg"; +import { ReactComponent as downArrow } from "../../../../assets/icon/Arrow-down.svg"; + +const DropDownText = styled.div` + flex: 1 0 0; + color: var(--Grayscale-60, #000); + font-feature-settings: "liga" off, "clig" off;fi + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 128.571% */ +`; + +const DownArrowIcon = styled(downArrow)` + width: 14px; + height: 14px; +`; + +const UpArrowIcon = styled(upArrow)` + width: 14px; + height: 14px; +`; + +const DropDown = styled.div` + display: flex; + padding: 8px 12px; + justify-content: center; + align-items: center; + gap: 4px; + align-self: stretch; + border-radius: 8px; + border: 1px solid var(--Grayscale-60, #000); + background: var(--Grayscale-10, #fff); +`; + +const DropDownWrapper = styled.div` + display: flex; + width: 79px; + flex-direction: column; + align-items: flex-start; + gap: 4px; + position: relative; +`; + +const CombinedDropDown = () => { + const [isOpen, setIsOpen] = useState(false); + const [choice, setChoice] = useState("이름순"); + const handleToggle = () => { + setIsOpen(!isOpen); + }; + const handleChangeChoice = (e) => { + console.log(e.target.textContent); + setChoice(e.target.textContent); + setIsOpen(false); + }; + return ( + <> + <DropDownWrapper> + <DropDown onClick={handleToggle}> + <DropDownText>{choice}</DropDownText> + {isOpen ? <UpArrowIcon /> : <DownArrowIcon />} + </DropDown> + <OptionBox $isOpen={isOpen}> + <Option> + <OptionText + value={"이름순"} + $choice={choice} + onClick={handleChangeChoice} + > + 이름순 + </OptionText> + </Option> + <Option> + <OptionText + value={"최신순"} + $choice={choice} + onClick={handleChangeChoice} + > + 최신순 + </OptionText> + </Option> + </OptionBox> + </DropDownWrapper> + </> + ); +}; + +export default CombinedDropDown; +//드롭박스 누르면 옵션 보여주는 컴포넌트 diff --git a/src/pages/QuestionListPage/components/drop_down/Options.jsx b/src/pages/QuestionListPage/components/drop_down/Options.jsx new file mode 100644 index 0000000..d36ece7 --- /dev/null +++ b/src/pages/QuestionListPage/components/drop_down/Options.jsx @@ -0,0 +1,46 @@ +import styled from "styled-components"; + +export const OptionText = styled.div` + flex: 1 0 0; + color: ${({$choice,value})=>{ + return ($choice === value ? ( + "var(--Blue-50, #1877F2)" + ) : ( + "var(--Grayscale-50, #515151)" + )) + }}; + font-feature-settings: "liga" off, "clig" off; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 128.571% */ +`; + +export const Option = styled.div` + display: flex; + padding: 6px 16px; + justify-content: center; + align-items: center; + gap: 8px; + align-self: stretch; + background: var(--Grayscale-10, #fff); +`; + +export const OptionBox = styled.div` + display: ${({ $isOpen }) => {return ($isOpen ? "flex" : "none")}}; + width: 79px; + padding: 4px 0px; + flex-direction: column; + justify-content: center; + align-items: center; + position: absolute; + top: 42px; + border-radius: 8px; + border: 1px solid var(--Grayscale-30, #cfcfcf); + background: var(--Grayscale-10, #fff); + + /* 1pt */ + box-shadow: 0px 4px 4px 0px rgba(140, 140, 140, 0.25); +`; +//드롭박스 옵션 스타일 컴포넌트 \ No newline at end of file diff --git a/src/pages/QuestionListPage/components/header/Headers.jsx b/src/pages/QuestionListPage/components/header/Headers.jsx new file mode 100644 index 0000000..a01474d --- /dev/null +++ b/src/pages/QuestionListPage/components/header/Headers.jsx @@ -0,0 +1,57 @@ +import { ReactComponent as DefaultLogo } from "../../../../assets/default/defaultLogo.svg"; +import style from "styled-components"; +import CustomButton from "../../../../components/CustomButton"; +import LocalStore from "../../../../api/storage/LocalStore"; +import { useNavigate } from "react-router-dom"; + +const StyleBox = style.div` + display: flex; + justify-content: space-between; + gap: 633px; + margin-bottom: 40px; + @media (max-width: 950px) { + gap: 543px; + }; + @media (max-width: 768px) { + gap: 361px; + }; + @media (max-width: 390px) { + display: flex; + flex-wrap: wrap; + flex-direction: column; + gap: 20px; + justify-content: center; + align-items: center; + }; + `; + +// 로컬 스토리지에서 가져온다 값을 +// id부분은 `${id}`이런식으로 리터럴 문법을 쓴다. +// /post/{id}/answer +const ContainHeader = () => { + const navigate = useNavigate(); + const handleTo = () => { + const subject = LocalStore.getItem("subject"); + const subjectId = subject.id; + const idGet = LocalStore.getItem(`subject_${subjectId}_questions`); + + if (!idGet) { + handleToHome(); + return; + } + navigate(`/post/${subjectId}/answer`); + }; + const handleToHome = () => { + navigate("/"); + }; + return ( + <> + <StyleBox> + <DefaultLogo onClick={handleToHome} /> + <CustomButton mode="goToReply" onClick={handleTo} /> + </StyleBox> + </> + ); +}; + +export default ContainHeader; diff --git a/src/pages/QuestionListPage/components/pagination/PaginationBar.jsx b/src/pages/QuestionListPage/components/pagination/PaginationBar.jsx new file mode 100644 index 0000000..b39ef6f --- /dev/null +++ b/src/pages/QuestionListPage/components/pagination/PaginationBar.jsx @@ -0,0 +1,87 @@ +import styled from "styled-components"; + + + +const PaginationButton = styled.button` + color: ${({ isActive })=> (isActive ? 'var(--Brown-40, #542F1A)' : 'var(--Grayscale-40, #818181)')}; + text-align: center; + font-feature-settings: 'liga' off, 'clig' off; + font-family: Actor; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 25px; /* 125% */; + background: none; /* 기본 배경 제거 */ + border: none; /* 기본 테두리 제거 */ + padding: 5px 10px; /* 패딩 추가 */ + margin: 0 5px; /* 버튼 간격 추가 */ + &.active { + /* 활성화된 버튼에 대한 추가 스타일 */ + font-weight: bold; /* 예: 글씨를 볼드체로 */ + &:disabled { + cursor: not-allowed; + } +`; + +const PaginationBar = ({ totalPageNum, activePageNum, onPageChange }) => { + const maxVisiblePages = 5; + let startPage; + console.log("totalPageNum", totalPageNum); + //총 페이지 갯수가 화면상 페이지 갯수보다 작을 때 ex)4 <= 5 + if (totalPageNum <= maxVisiblePages) { + startPage = 1; + } else { + // 6~10이 들어왔을때, 2라는 값이 startPage에 저장되도록 하는 로직을 짜야함 , 11~20은 3 이런식으로 + startPage = 5 + maxVisiblePages; + startPage = + (Math.ceil(activePageNum / maxVisiblePages) - 1) * maxVisiblePages + 1; + console.log("startPage", startPage); + } + + // useState 써서 페이지네이션에서 사용할 상태들 모두 관리 + // useEffect 내부에서 쓸 handler 함수 구현 + // useEffect에서 handler함수 호출 + // state에 따른 map을 돌려서 ArrayCard부분에 데이터 전달 + + // [1,2,3,4,5] , [6,7,8,9,10] + const pages = Array.from( + { length: Math.min(maxVisiblePages, totalPageNum - startPage + 1) }, + (_, i) => startPage + i + ); + console.log("pages", pages); + + return ( + <div> + <PaginationButton + className="paginationButton" + isActive={activePageNum > 1} + disabled={activePageNum === 1} + onClick={() => onPageChange(activePageNum - 1)} + > + < + </PaginationButton> + {pages.map((page) => ( + <PaginationButton + key={page} + className={`paginationButton ${ + activePageNum === page ? "active" : "" + }`} + isActive={activePageNum === page} + onClick={() => onPageChange(page)} + > + {page} + </PaginationButton> + ))} + <PaginationButton + className="paginationButton" + isActive={activePageNum < totalPageNum} + disabled={activePageNum === totalPageNum} + onClick={() => onPageChange(activePageNum + 1)} + > + > + </PaginationButton> + </div> + ); +}; + +export default PaginationBar; diff --git a/src/pages/QuestionListPage/components/title/Title.jsx b/src/pages/QuestionListPage/components/title/Title.jsx new file mode 100644 index 0000000..0edd897 --- /dev/null +++ b/src/pages/QuestionListPage/components/title/Title.jsx @@ -0,0 +1,37 @@ +import style from "styled-components"; + + +const TitleText = style.div` + color: var(--Grayscale-60, #000); + text-align: center; + font-feature-settings: 'liga' off, 'clig' off; + font-family: Pretendard; + font-size: 40px; + font-style: normal; + font-weight: 400; + line-height: normal; + + @media (max-width: 390px) { + color: var(--Grayscale-60, #000); + text-align: center; + font-feature-settings: 'liga' off, 'clig' off; + font-family: Actor; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: 34px; /* 125% */ + white-space: nowrap; + width: 214px; + } +`; + + +const Title = ()=>{ + return( + <TitleText> + 누구에게 질문할까요? + </TitleText> + ) +}; + +export default Title; \ No newline at end of file diff --git a/src/pages/QuestionListPage/components/user_card/ArrayCard.jsx b/src/pages/QuestionListPage/components/user_card/ArrayCard.jsx new file mode 100644 index 0000000..f173fc2 --- /dev/null +++ b/src/pages/QuestionListPage/components/user_card/ArrayCard.jsx @@ -0,0 +1,64 @@ +import styled from "styled-components"; +import UserCard from "./UserCard"; +import { Link } from "react-router-dom"; + +const ArrayCardLine = styled.div` + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 20px; + @media (max-width:768px) { + width: 700px; + display: flex; + align-items: flex-start; + gap: 20px; + } + @media (max-width:390px) { + @media (max-width:375px) { + width: 327px; + display: inline-flex; + align-items: flex-start; + gap: 16px; + } +`; + +const ArrayContainer = styled.div` + display: flex; + width: 940px; + height: auto; + @media (max-width:768px) { + width: auto; + } + +`; + + + +// 유즈 스테이스 쓰고, mockArray 초기값주고, 유즈이펙트+api 부분은 나중에 구현하고 +// 유즈이펙트 쓰고 해서 api 결과 받으면 8개 들어있는 배열 받을것임 +// useState에 업데이트해서 하위 컴포넌트들 리렌더링 시키면서 카드 보여줄것 +//useEffect로 api의 값을 useState의 세터로 가져온다. +const ArrayCard = ({ cardList }) => { + // const { id } = useParams(); + // console.log("URL 파라미터 id:", id); + return ( + <> + <ArrayContainer> + <ArrayCardLine> + {cardList.map((el) => ( + <Link key={el.id} to={`/post/${el.id}`}> + <UserCard + id={el.id} + name={el.name} + imageSource={el.imageSource} + questionCount={el.questionCount} + /> + </Link> + ))} + </ArrayCardLine> + </ArrayContainer> + </> + ); +}; + +export default ArrayCard; diff --git a/src/pages/QuestionListPage/components/user_card/UserCard.jsx b/src/pages/QuestionListPage/components/user_card/UserCard.jsx new file mode 100644 index 0000000..9881cb0 --- /dev/null +++ b/src/pages/QuestionListPage/components/user_card/UserCard.jsx @@ -0,0 +1,125 @@ +import styled from "styled-components"; +import { ReactComponent as MessagesSVG } from "../../../../assets/icon/Messages.svg"; + + +const UserCardBox = styled.div` + display: flex; + width: 220px; + height: 187px; + padding: 20px; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + border-radius: 16px; + border: 1px solid var(--Grayscale-40, #818181); + background: var(--Grayscale-10, #fff); + @media (max-width: 950px) { + width: 186px; + height: 187px; + padding: 20px; + justify-content: space-between; + align-items: flex-start; + }; + @media (max-width: 768px) { + display: flex; + width: 220px; + height: 187px; + padding: 20px; + justify-content: space-between; + align-items: flex-start; + }; + @media (max-width:390px) { + display: flex; + width: 155.5px; + padding: 16px; + justify-content: space-between; + align-items: flex-start; + align-self: stretch; + }; +`; + +// Profile // Photo 부분 - 이미지 이클립스 wrapper 의 부모 로 존재 +const ProfileImage = styled.div``; + +// 이미지 이클립스, 동그랗게만드는 wrapper +const ImageWrapper = styled.div` + // background-color: #d9d9d9; + background-color: lightgray; + background: url(${({ $imageUrl }) => $imageUrl}) no-repeat; + width: 60px; + height: 60px; + background-size: cover; /* 배경 이미지 크기를 조정 */ + background-position: 50%; + background-position: center; /* 이미지가 중앙에 오도록 위치 조정 */ + border-radius: 60px; +`; + +// 아초는 고양이 - text에 대한 스타일 +const NickName = styled.div` + color: var(--Grayscale-60, #000); + font-feature-settings: "liga" off, "clig" off; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 25px; /* 125% */ +`; + +// 프레임 51 카드 하단의 데이터를 보여주는 박스 ( 아이콘, 받은질문 , 질문개수 를 포함함) +const CardInfoBox = styled.div` + display: flex; + height: 22px; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + align-self: stretch; +`; + +// 프레임 46 , 받은질문과 아이콘을 포함하는 박스 , svg 와 text 를 포함함 +const GetQuestionBox = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +//logo 스타일 박스 +const LogoStyledBox = styled(MessagesSVG)` + width: 18px; + height: 18px; + color: var(--Grayscale-40, #818181); +`; + +// 받은질문과 숫자를 모두 사용가능한 text +const QuestionText = styled.div` + color: var(--Grayscale-40, #818181); + font-feature-settings: "liga" off, "clig" off; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 137.5% */ +`; + +const CombinedUserCard = ({ id, name, imageSource, questionCount }) => { + return ( + <> + <div> + <UserCardBox> + <ProfileImage> + <ImageWrapper $imageUrl={imageSource} /> + </ProfileImage> + <NickName>{name}</NickName> + <CardInfoBox> + <GetQuestionBox> + <LogoStyledBox /> + <QuestionText>받은 질문</QuestionText> + </GetQuestionBox> + <QuestionText>{questionCount}</QuestionText> + </CardInfoBox> + </UserCardBox> + </div> + </> + ); +}; + +export default CombinedUserCard; diff --git a/src/styles/global.css b/src/styles/global.css index 5edaabb..a0bd9d3 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,21 +1,20 @@ -@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css'); - +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css"); :root { /* Gray scale */ --gray-60: #000000; - --gray-50: #2515151; - --gray-40: #454c53; - --gray-30: #818181; - --gray-20: #CFCFCF; - --gray-10: #FFFFFF; + --gray-50: #515151; + --gray-40: #818181; + --gray-30: #cfcfcf; + --gray-20: #f9f9f9; + --gray-10: #ffffff; /* Brown */ --brown-50: #341909; --brown-40: #542F1A; --brown-30: #C7BBB5; --brown-20: #E4D5C9; - --bronw-10: #F5F1EE; + --brown-10: #F5F1EE; /* Blue */ --blue-50:#1877F2; @@ -24,27 +23,24 @@ /* Red */ --red-50:#B93333; - /* shadow */ - --box-shadow-1px: 0px 4px 4px 0px rgba(140, 140, 140, 0.25); - --box-shadow-2px: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); - --box-shadow-3px: 0px 16px 20px 0px rgba(48, 48, 48, 0.62); + + /* shadow */ + --box-shadow-1px: 0px 4px 4px 0px rgba(140, 140, 140, 0.25); + --box-shadow-2px: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); + --box-shadow-3px: 0px 16px 20px 0px rgba(48, 48, 48, 0.62); } * { margin: 0; padding: 0; box-sizing: border-box; - background:#F9F9F9; + text-decoration: none; } html { - word-break: keep-all; - font-family: Pretendard, sans-serif; - + word-break: keep-all; + font-family: Pretendard, sans-serif; } body { - margin: 0; - background: #F9F9F9; - + margin: 0; } -