diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 00000000..9e2190b8 --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,23 @@ +name: GitHub Actions Vercel Preview Deployment +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +on: + pull_request: + types: + - opened +jobs: + Deploy-Preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Vercel CLI + run: npm install --global vercel@canary + - name: Install pnpm + run: npm install -g pnpm + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + - name: Build Project Artifacts + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} diff --git a/README.md b/README.md index 5166a8dc..52cde81a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ ## ๐Ÿ“ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ + ์—ฌ์ •๊ณต์œ  ์—ฌํ–‰ํ”Œ๋žซํผ ## ๐Ÿ“– Commit convention + - Feat : ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - Fix : ๋ฒ„๊ทธ ์ˆ˜์ • - Env : ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ด€๋ จ ์„ค์ • @@ -12,5 +14,5 @@ - Docs : ๋‚ด๋ถ€ ๋ฌธ์„œ ์ถ”๊ฐ€/์ˆ˜์ • - Test : ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€/์ˆ˜์ • - Chore : ๋นŒ๋“œ ๊ด€๋ จ ์ฝ”๋“œ ์ˆ˜์ • -- Rename : ํŒŒ์ผ ๋ฐ ํด๋”๋ช… ์ˆ˜์ • -- Remove : ํŒŒ์ผ ์‚ญ์ œ +- Rename : ํŒŒ์ผ ๋ฐ ํด๋”๋ช… ์ˆ˜์ •. +- Remove : ํŒŒ์ผ ์‚ญ์ œ. diff --git a/index.html b/index.html index 3ba384e2..6a7807f4 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,17 @@ + + + + + TenTen + - - - - - TenTen - - - -
- - - - \ No newline at end of file + +
+ + + + diff --git a/package-lock.json b/package-lock.json index e9c713ff..8dbc2df4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,24 +9,27 @@ "version": "0.0.0", "dependencies": { "@pnpm-monorepo/shared": "^1.0.0", - "@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3", "@svgr/rollup": "^8.1.0", "@tanstack/react-query": "^5.14.6", "@tanstack/react-query-devtools": "^5.14.6", "axios": "^1.6.2", - "clsx": "^2.0.0", "msw": "0.36.3", "path": "^0.12.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-infinite-scroller": "^1.2.6", + "react-kakao-maps-sdk": "^1.1.24", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", - "styled-components": "^6.1.3" + "styled-components": "^6.1.3", + "uuid": "^9.0.1" }, "devDependencies": { "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", + "@types/react-infinite-scroller": "^1.2.5", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", @@ -2580,29 +2583,6 @@ "@babel/runtime": "^7.13.10" } }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.0.3.tgz", - "integrity": "sha512-fXR5kbMan9oQqMuacfzlGG/SQMcmMlZ4wrvpckv8SgUulD0MMpspxJrxg/Gp/ISV3JfV1AeSWTYK9GvxA4ySwA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-collapsible": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz", @@ -3406,6 +3386,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-infinite-scroller": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.5.tgz", + "integrity": "sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -3439,6 +3428,12 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.16.0.tgz", @@ -4297,14 +4292,6 @@ "node": ">=0.8" } }, - "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5797,6 +5784,11 @@ "node": ">=6" } }, + "node_modules/kakao.maps.d.ts": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/kakao.maps.d.ts/-/kakao.maps.d.ts-0.1.39.tgz", + "integrity": "sha512-KXENJ8hHYtjb5G+0vf8TXx/PwWW4j5ndDiQTSMvGtF7EFWu2P3N/+Zivcj9/UKn3j29Iz/sIUaA7WL8Ug3IDGQ==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6256,7 +6248,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6826,6 +6817,16 @@ "node": ">= 0.6.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6883,6 +6884,35 @@ "react": "^18.2.0" } }, + "node_modules/react-infinite-scroller": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", + "integrity": "sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-kakao-maps-sdk": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/react-kakao-maps-sdk/-/react-kakao-maps-sdk-1.1.24.tgz", + "integrity": "sha512-leLbFwBj6zbTdDg6A9U7EwYT2oq0+2F+NHZSVTyCmmvyc4yt2zpRvUmcAt8I6h2SDUdgHbpvKAV1sZoRIxD4JQ==", + "dependencies": { + "@babel/runtime": "^7.22.15", + "kakao.maps.d.ts": "^0.1.39" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -7798,6 +7828,18 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", @@ -9688,15 +9730,6 @@ "@babel/runtime": "^7.13.10" } }, - "@radix-ui/react-aspect-ratio": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.0.3.tgz", - "integrity": "sha512-fXR5kbMan9oQqMuacfzlGG/SQMcmMlZ4wrvpckv8SgUulD0MMpspxJrxg/Gp/ISV3JfV1AeSWTYK9GvxA4ySwA==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - } - }, "@radix-ui/react-collapsible": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz", @@ -10154,6 +10187,15 @@ "@types/react": "*" } }, + "@types/react-infinite-scroller": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.5.tgz", + "integrity": "sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -10187,6 +10229,12 @@ "@types/node": "*" } }, + "@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.16.0.tgz", @@ -10731,11 +10779,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" }, - "clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -11806,6 +11849,11 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, + "kakao.maps.d.ts": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/kakao.maps.d.ts/-/kakao.maps.d.ts-0.1.39.tgz", + "integrity": "sha512-KXENJ8hHYtjb5G+0vf8TXx/PwWW4j5ndDiQTSMvGtF7EFWu2P3N/+Zivcj9/UKn3j29Iz/sIUaA7WL8Ug3IDGQ==" + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12141,8 +12189,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-hash": { "version": "3.0.0", @@ -12468,6 +12515,16 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -12502,6 +12559,28 @@ "scheduler": "^0.23.0" } }, + "react-infinite-scroller": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", + "integrity": "sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==", + "requires": { + "prop-types": "^15.5.8" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-kakao-maps-sdk": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/react-kakao-maps-sdk/-/react-kakao-maps-sdk-1.1.24.tgz", + "integrity": "sha512-leLbFwBj6zbTdDg6A9U7EwYT2oq0+2F+NHZSVTyCmmvyc4yt2zpRvUmcAt8I6h2SDUdgHbpvKAV1sZoRIxD4JQ==", + "requires": { + "@babel/runtime": "^7.22.15", + "kakao.maps.d.ts": "^0.1.39" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -13141,6 +13220,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, "vite": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", diff --git a/package.json b/package.json index 8bfcda9e..b5aabc9f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@pnpm-monorepo/shared": "^1.0.0", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@svgr/rollup": "^8.1.0", "@tanstack/react-query": "^5.14.6", "@tanstack/react-query-devtools": "^5.14.6", @@ -21,13 +22,18 @@ "path": "^0.12.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-infinite-scroller": "^1.2.6", + "react-kakao-maps-sdk": "^1.1.24", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", - "styled-components": "^6.1.3" + "styled-components": "^6.1.3", + "uuid": "^9.0.1" }, "devDependencies": { "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", + "@types/react-infinite-scroller": "^1.2.5", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", @@ -39,6 +45,7 @@ "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.9", "tailwindcss": "^3.4.0", + "terser": "^5.26.0", "typescript": "^5.2.2", "vite": "^5.0.8" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30980d54..77bc46cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle-group': + specifier: ^1.0.4 + version: 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) '@svgr/rollup': specifier: ^8.1.0 version: 8.1.0(typescript@5.3.3) @@ -38,6 +41,12 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-infinite-scroller: + specifier: ^1.2.6 + version: 1.2.6(react@18.2.0) + react-kakao-maps-sdk: + specifier: ^1.1.24 + version: 1.1.24(react-dom@18.2.0)(react@18.2.0) react-router-dom: specifier: ^6.21.1 version: 6.21.1(react-dom@18.2.0)(react@18.2.0) @@ -47,6 +56,9 @@ dependencies: styled-components: specifier: ^6.1.3 version: 6.1.4(react-dom@18.2.0)(react@18.2.0) + uuid: + specifier: ^9.0.1 + version: 9.0.1 devDependencies: '@types/react': @@ -55,6 +67,12 @@ devDependencies: '@types/react-dom': specifier: ^18.2.17 version: 18.2.18 + '@types/react-infinite-scroller': + specifier: ^1.2.5 + version: 1.2.5 + '@types/uuid': + specifier: ^9.0.7 + version: 9.0.7 '@typescript-eslint/eslint-plugin': specifier: ^6.14.0 version: 6.16.0(@typescript-eslint/parser@6.16.0)(eslint@8.56.0)(typescript@5.3.3) @@ -88,12 +106,15 @@ devDependencies: tailwindcss: specifier: ^3.4.0 version: 3.4.0 + terser: + specifier: ^5.26.0 + version: 5.26.0 typescript: specifier: ^5.2.2 version: 5.3.3 vite: specifier: ^5.0.8 - version: 5.0.10 + version: 5.0.10(terser@5.26.0) packages: @@ -1701,6 +1722,13 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} @@ -1998,6 +2026,56 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-toggle@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -2451,6 +2529,12 @@ packages: dependencies: '@types/react': 18.2.45 + /@types/react-infinite-scroller@1.2.5: + resolution: {integrity: sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==} + dependencies: + '@types/react': 18.2.45 + dev: true + /@types/react@18.2.45: resolution: {integrity: sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==} dependencies: @@ -2481,6 +2565,10 @@ packages: '@types/node': 20.10.5 dev: false + /@types/uuid@9.0.7: + resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} + dev: true + /@typescript-eslint/eslint-plugin@6.16.0(@typescript-eslint/parser@6.16.0)(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-O5f7Kv5o4dLWQtPX4ywPPa+v9G+1q1x8mz0Kr0pXUtKsevo+gIJHLkGc8RxaZWtP8RrhwhSNIWThnW42K9/0rQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2628,7 +2716,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.6) '@types/babel__core': 7.20.5 react-refresh: 0.14.0 - vite: 5.0.10 + vite: 5.0.10(terser@5.26.0) transitivePeerDependencies: - supports-color dev: true @@ -2836,6 +2924,10 @@ packages: node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: @@ -2960,6 +3052,10 @@ packages: delayed-stream: 1.0.0 dev: false + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -3752,6 +3848,10 @@ packages: engines: {node: '>=6'} hasBin: true + /kakao.maps.d.ts@0.1.39: + resolution: {integrity: sha512-KXENJ8hHYtjb5G+0vf8TXx/PwWW4j5ndDiQTSMvGtF7EFWu2P3N/+Zivcj9/UKn3j29Iz/sIUaA7WL8Ug3IDGQ==} + dev: false + /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -3986,7 +4086,6 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -4276,6 +4375,14 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -4299,6 +4406,31 @@ packages: scheduler: 0.23.0 dev: false + /react-infinite-scroller@1.2.6(react@18.2.0): + resolution: {integrity: sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==} + peerDependencies: + react: ^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false + + /react-kakao-maps-sdk@1.1.24(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-leLbFwBj6zbTdDg6A9U7EwYT2oq0+2F+NHZSVTyCmmvyc4yt2zpRvUmcAt8I6h2SDUdgHbpvKAV1sZoRIxD4JQ==} + peerDependencies: + react: ^16.8 || ^17 || ^18 + react-dom: ^16.8 || ^17 || ^18 + dependencies: + '@babel/runtime': 7.23.6 + kakao.maps.d.ts: 0.1.39 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -4558,6 +4690,18 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -4713,6 +4857,17 @@ packages: - ts-node dev: true + /terser@5.26.0: + resolution: {integrity: sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.2 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -4855,7 +5010,12 @@ packages: inherits: 2.0.3 dev: false - /vite@5.0.10: + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + + /vite@5.0.10(terser@5.26.0): resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4886,6 +5046,7 @@ packages: esbuild: 0.19.10 postcss: 8.4.32 rollup: 4.9.1 + terser: 5.26.0 optionalDependencies: fsevents: 2.3.3 dev: true diff --git a/src/@types/detail.types.ts b/src/@types/detail.types.ts new file mode 100644 index 00000000..8c65e79c --- /dev/null +++ b/src/@types/detail.types.ts @@ -0,0 +1,11 @@ +interface tourDetail { + id: number; + title: string; + liked: boolean; + fullAddress: string; + zipcode: string; + longitude: string; + latitude: string; + tel: string; + originalThumbnailUrl: string; +} diff --git a/src/@types/tours.types.ts b/src/@types/tours.types.ts new file mode 100644 index 00000000..c819462e --- /dev/null +++ b/src/@types/tours.types.ts @@ -0,0 +1,38 @@ +export type TourKeywordInfo = { + keywordId: number; + content: string; + type: string; + keywordCount: number; +}; + +export interface RegionTypes { + areaCode?: number; + subAreaCode?: number; + name: string; +} + +export interface ToursCategoryItemProps extends RegionTypes { + isSelected: boolean; + onSelect: (name: string) => void; +} + +export interface ToursListProps { + selectedRegion: string; +} + +export interface ToursCategoryProps extends ToursListProps { + setSelectedRegion: (region: string) => void; +} + +export interface TourType { + id: number; + title: string; + liked: boolean; + likedCount: number; + ratingAverage: number; + reviewCount: number; + smallThumbnailUrl: string; + tourAddress?: string; + longitude?: string; + latitude?: string; +} diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355df..00000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index 35a48aa4..bfa01682 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import React, { Suspense } from 'react'; import { ThemeProvider } from 'styled-components'; import { GlobalStyle } from '@styles/globalStyles'; import { theme } from '@styles/theme'; diff --git a/src/api/region.ts b/src/api/region.ts index e64e8515..7b5db167 100644 --- a/src/api/region.ts +++ b/src/api/region.ts @@ -3,13 +3,17 @@ import client from './client'; // ์ง€์—ญ ๊ด€๋ จ API // ์ „์ฒด ์ง€์—ญ ์กฐํšŒ -export const getTours = async (areaCode: number) => { +export const getRegion = async (areaCode: number) => { const res = await client.get(`region?areaCode=${areaCode}`); return res; }; -// ์ธ๊ธฐ ์ง€์—ญ ์กฐํšŒ -export const getPopularTours = async () => { - const res = await client.get(`region/popular`); - return res; +// ์ธ๊ธฐ ์ง€์—ญ ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํšŒ +export const getPopularRegion = async () => { + try { + const res = await client.get(`region/popular`); + return res; + } catch (e) { + console.error(e); + } }; diff --git a/src/api/tours.ts b/src/api/tours.ts index 5b9c7837..7f838378 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -3,35 +3,67 @@ import client from './client'; // ์—ฌํ–‰์ง€ ๊ด€๋ จ API // ์ธ๊ธฐ ์—ฌํ–‰์ง€ ์กฐํšŒ -export const getTours = async (region: number, page: number, size: number) => { - const res = await client.get( - `tours?region=${region}&page=${page}&size=${size}`, - ); - return res; +export const getTours = async ( + region?: string, + page?: number, + size?: number, +) => { + try { + const res = await client.get( + `tours?${ + region !== '์ „์ฒด' && `region=${region}` + }&page=${page}&size=${size}`, + ); + return res; + } catch (e) { + console.error(e); + } }; // ์—ฌํ–‰์ง€ ์ƒ์„ธ ์กฐํšŒ export const getDetailTours = async (tourItemId: number) => { - const res = await client.get(`tours/${tourItemId}`); - return res; + try { + const { + data: { data }, + } = await client.get(`tours/${tourItemId}`); + return data; + } catch (e) { + console.error(e); + } }; // ์—ฌํ–‰ ์ƒํ’ˆ ๋ฆฌ๋ทฐ ์กฐํšŒ export const getToursReviews = async (tourItemId: number) => { const res = await client.get(`tours/${tourItemId}/reviews`); + console.log('res', res); + console.log('res.data.data.reviewInfos', res.data.data.reviewInfos); return res; }; // ์—ฌํ–‰์ง€ ๊ฒ€์ƒ‰ -export const getToursSearch = async ( - region: number, - category: number, - searchWord: string, - page: number, - size: number, -) => { - const res = await client.get( - `tours/search?region=${region}&category=${category}&searchWord=${searchWord}&page=${page}&size=${size}}`, - ); +export const getToursSearch = async (options: { + region?: string; + searchWord: string; + category?: string; + page?: number; + size?: number; +}) => { + const { region, searchWord, category, page = 0, size } = options; + + let query = `tours/search?searchWord=${searchWord}`; + + if (region) { + query += `®ion=${region}`; + } + + if (category) { + query += `&category=${category}`; + } + query += `&page=${page}`; + + if (size) { + query += `&size=${size}`; + } + const res = await client.get(query); return res; }; diff --git a/src/assets/images/Star.svg b/src/assets/images/Star.svg index 833ebb39..5c27ea56 100644 --- a/src/assets/images/Star.svg +++ b/src/assets/images/Star.svg @@ -5,4 +5,4 @@ fill="current" xmlns="http://www.w3.org/2000/svg"> - + \ No newline at end of file diff --git a/src/components/DetailSectionBottom/DetailReview.tsx b/src/components/DetailSectionBottom/DetailReview.tsx new file mode 100644 index 00000000..b68f9377 --- /dev/null +++ b/src/components/DetailSectionBottom/DetailReview.tsx @@ -0,0 +1,85 @@ +// import { getToursReviews } from '@api/tours'; +// import { useEffect, useState } from 'react'; +// import InfiniteScroll from 'react-infinite-scroller'; +// import { useInfiniteQuery } from '@tanstack/react-query'; +// import ReviewItem from './ReviewItem'; +// import { StarIcon } from '@components/common/icons/Icons'; + +// export default function DetailReview() { +// const [reviewDataLength, setReviewDataLength] = useState(0); +// const tourItemId = 1; + +// const { +// data: toursReviews, +// fetchNextPage, +// hasNextPage, +// } = useInfiniteQuery({ +// queryKey: ['toursReviews'], +// queryFn: ({ pageParam }) => getToursReviews(tourItemId), +// initialPageParam: 0, +// getNextPageParam: (lastPage, allPages, lastPageParam) => { +// const lastData = lastPage?.data?.data?.reviewInfos; +// return lastData && lastData.length === 4 ? lastPageParam + 1 : undefined; +// }, +// }); + +// useEffect(() => { +// if (toursReviews) { +// const totalCount = toursReviews.pages.reduce( +// (accumulator, page) => +// accumulator + (page?.data?.data?.reviewInfos?.length || 0), +// 0, +// ); +// setReviewDataLength(totalCount); +// } +// }, [toursReviews]); + +// return ( +// <> +//
+// ๋ฆฌ๋ทฐ {reviewDataLength} +//
+// {reviewDataLength > 0 && ( +// fetchNextPage()} +// initialLoad={false}> +// {toursReviews?.pages?.map((page, pageIndex) => ( +//
+// {page?.data?.data?.reviewInfos?.map( +// (item: any, index: number) => ( +// +// ), +// )} +//
+// ))} +//
+// )} +// {reviewDataLength == 0 && ( +//
+//
+// {Array.from({ length: 5 }, (_, index) => ( +// +// ))} +//
+//
์ฒซ๋ฒˆ์งธ ๋ฆฌ๋ทฐ๋ฅผ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!
+//
+// )} +// +// ); +// } diff --git a/src/components/DetailSectionBottom/DetailReviewStats.tsx b/src/components/DetailSectionBottom/DetailReviewStats.tsx new file mode 100644 index 00000000..61a80623 --- /dev/null +++ b/src/components/DetailSectionBottom/DetailReviewStats.tsx @@ -0,0 +1,62 @@ +import { useState } from 'react'; +import { useGetToursReviews } from '@hooks/useReviewStats'; +import { v4 as uuidv4 } from 'uuid'; +import { DownIcon } from '@components/common/icons/Icons'; +import useReviewStatsCalculator from '@hooks/useReviewStatsCalculator'; +import { getEmoji } from '@utils/utils'; + +const DetailReviewStats = () => { + const { reviewStats } = useGetToursReviews(); + const { calculateWidth, getColor } = useReviewStatsCalculator(reviewStats); + const [showAll, setShowAll] = useState(false); + + return ( + <> +
์ด๋Ÿฐ ์ ์ด ์ข‹์•˜์–ด์š”
+ {reviewStats && reviewStats.length > 0 ? ( + reviewStats.slice(0, showAll ? reviewStats.length : 3).map((data) => ( +
+
+
+
+
+

{getEmoji(data.content)}

+

+ {data.content} +

+
+

{data.keywordCount}

+
+
+
+ )) + ) : ( +
+

+ ์ฒซ๋ฒˆ์งธ ๋ฆฌ๋ทฐ๋ฅผ ๋‚จ๊ฒจ์ฃผ์„ธ์š”! +

+
+ )} + {reviewStats && reviewStats.length > 3 && ( +
+
setShowAll(!showAll)} + className="cursor-pointer transition-transform duration-300" + style={{ + transform: showAll ? 'rotate(180deg)' : 'rotate(0deg)', + }}> + +
+
+ )} + + ); +}; + +export default DetailReviewStats; diff --git a/src/components/DetailSectionBottom/DetailSectionBottom.tsx b/src/components/DetailSectionBottom/DetailSectionBottom.tsx new file mode 100644 index 00000000..9455ba17 --- /dev/null +++ b/src/components/DetailSectionBottom/DetailSectionBottom.tsx @@ -0,0 +1,11 @@ +import { DetailReviewStats } from '.'; + +// ๋‹ด๋‹น ์ปดํฌ๋„ŒํŠธ๋“ค ํ˜ธ์ถœํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ(๋ถ„์—… ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€ ๋Š๋‚Œ์œผ๋กœ ๋‚˜๋ˆ ๋ดค์Šต๋‹ˆ๋‹ค), API ํ˜ธ์ถœ ์ปดํฌ๋„ŒํŠธ, +export default function DetailSectionBottom() { + return ( + <> + + {/* */} + + ); +} diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx new file mode 100644 index 00000000..ae09f613 --- /dev/null +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -0,0 +1,96 @@ +import { useEffect } from 'react'; +import { StarIcon, ChatIcon } from '@components/common/icons/Icons'; + +interface Keyword { + keywordId: number; + content: string; + type: string; +} + +interface ItemProps { + authorNickname: string; + authorProfileImageUrl: string; + rating: number; + createdTime: any; + content: string; + keywords: Keyword[]; // keywordId, content, type + commentCount: number; +} + +const Item: React.FC = (props: ItemProps) => { + const { + authorNickname, + // authorProfileImageUrl, + rating, + createdTime, + content, + keywords, + commentCount, + } = props; + + const formatCreatedTime = (timeString: string): string => { + const date = new Date(timeString); + const formattedDate = new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(date); + + return formattedDate; + }; + useEffect(() => { + console.log('commentCount', commentCount); + }, []); + return ( +
+
+ {/* {authorProfileImageUrl} */} +
+ ์œ ์ € ํ”„๋กœํ•„ +
+
+
{authorNickname}
+
+ {Array.from({ length: 5 }, (_, index) => ( + + ))} +
+
+
+ {formatCreatedTime(createdTime)} +
+
+
{content}
+
+
+ {keywords.map((keyword, idx) => { + return ( +
+ {keyword.content} +
+ ); + })} +
+
+ +
{commentCount}
+
+
+
+ ); +}; + +export default Item; diff --git a/src/components/DetailSectionBottom/index.tsx b/src/components/DetailSectionBottom/index.tsx new file mode 100644 index 00000000..d1ca1b41 --- /dev/null +++ b/src/components/DetailSectionBottom/index.tsx @@ -0,0 +1,3 @@ +import DetailReviewStats from './DetailReviewStats'; + +export { DetailReviewStats }; diff --git a/src/components/DetailSectionTop/DetailSectionTop.tsx b/src/components/DetailSectionTop/DetailSectionTop.tsx index 08443ac9..34eb2cbf 100644 --- a/src/components/DetailSectionTop/DetailSectionTop.tsx +++ b/src/components/DetailSectionTop/DetailSectionTop.tsx @@ -1,18 +1,38 @@ +// import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; + +import { getDetailTours, getToursReviews } from '@api/tours'; + import { DetailToursInfo, DetailToursRating, DetailToursMap, - DetailTourButtons, + DetailToursButtons, } from '.'; -// ๋‹ด๋‹น ์ปดํฌ๋„ŒํŠธ๋“ค ํ˜ธ์ถœํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ(๋ถ„์—… ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€ ๋Š๋‚Œ์œผ๋กœ ๋‚˜๋ˆ ๋ดค์Šต๋‹ˆ๋‹ค), API ํ˜ธ์ถœ ์ปดํฌ๋„ŒํŠธ, export default function DetailSectionTop() { - return ( - <> - - - - - - ); + const params = useParams(); + const tourId = Number(params.id); + + const detailQuery = useQuery({ + queryKey: ['details', tourId], + queryFn: () => getDetailTours(tourId), + }); + + const reviewQuery = useQuery({ + queryKey: ['reviews', tourId], + queryFn: () => getToursReviews(tourId), + }); + + if (detailQuery.error || reviewQuery.error) console.log('error - ์˜ˆ์™ธ ์ฒ˜๋ฆฌ'); + + return detailQuery.data && reviewQuery.data?.data.data ? ( +
+ + + + +
+ ) : null; } diff --git a/src/components/DetailSectionTop/DetailTourButtons.tsx b/src/components/DetailSectionTop/DetailTourButtons.tsx deleted file mode 100644 index a8900595..00000000 --- a/src/components/DetailSectionTop/DetailTourButtons.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; -import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; - -export default function DetailTourButtons() { - return ( -
- - -
- ); -} diff --git a/src/components/DetailSectionTop/DetailToursButtons.tsx b/src/components/DetailSectionTop/DetailToursButtons.tsx new file mode 100644 index 00000000..05e79202 --- /dev/null +++ b/src/components/DetailSectionTop/DetailToursButtons.tsx @@ -0,0 +1,17 @@ +import { ReactComponent as PenIcon } from '@assets/images/Pen.svg'; +import { ReactComponent as CalendarIcon } from '@assets/images/Calendar.svg'; + +export default function DetailTourButtons() { + return ( +
+ + +
+ ); +} diff --git a/src/components/DetailSectionTop/DetailToursInfo.tsx b/src/components/DetailSectionTop/DetailToursInfo.tsx index 8e5194bb..4c906c1a 100644 --- a/src/components/DetailSectionTop/DetailToursInfo.tsx +++ b/src/components/DetailSectionTop/DetailToursInfo.tsx @@ -1,20 +1,32 @@ -import { ReactComponent as HeartIcon } from '../../assets/images/Heart.svg'; +import { ReactComponent as HeartIcon } from '@assets/images/Heart.svg'; + +interface DetailToursInfoProps { + infoData: tourDetail; +} + +export default function DetailToursInfo({ infoData }: DetailToursInfoProps) { + const { title, liked, originalThumbnailUrl } = infoData; -export default function DetailToursInfo() { return ( <>
tour-image
-

๊ฐ•๋ฆ‰ ์„ธ์ธํŠธ์กด์Šค ํ˜ธํ…”

-
- -
+

{title}

+ {liked ? ( +
+ +
+ ) : ( +
+ +
+ )}
); diff --git a/src/components/DetailSectionTop/DetailToursMap.tsx b/src/components/DetailSectionTop/DetailToursMap.tsx new file mode 100644 index 00000000..90719757 --- /dev/null +++ b/src/components/DetailSectionTop/DetailToursMap.tsx @@ -0,0 +1,69 @@ +import { ReactComponent as MapIcon } from '@assets/images/Map.svg'; +import { ReactComponent as CheckIcon } from '@assets/images/Check.svg'; +import { ReactComponent as PhoneIcon } from '@assets/images/Phone.svg'; + +import { Map, MapMarker } from 'react-kakao-maps-sdk'; +import { useState } from 'react'; + +interface DetailToursMapProps { + mapData: tourDetail; +} + +export default function DetailToursMap({ mapData }: DetailToursMapProps) { + const { fullAddress, longitude, latitude, tel } = mapData; + const [isMapVisible, setIsMapVisible] = useState(false); + + const MapStyle = { + width: '100%', + height: isMapVisible ? 0 : '180px', + marginTop: isMapVisible ? '15px' : '15px', + marginBottom: isMapVisible ? '15px' : '15px', + transition: 'height 0.3s ease-in-out', + }; + + const closeMap = () => { + setIsMapVisible((prev) => !prev); + }; + + return ( +
+
+
+ + {fullAddress} +
+ +
+
+ + + +
+
+ + {tel} +
+
+ ); +} diff --git a/src/components/DetailSectionTop/DetailToursRating.tsx b/src/components/DetailSectionTop/DetailToursRating.tsx index e3ccc13b..db9a9f5b 100644 --- a/src/components/DetailSectionTop/DetailToursRating.tsx +++ b/src/components/DetailSectionTop/DetailToursRating.tsx @@ -1,15 +1,72 @@ -import { ReactComponent as StarIcon } from '../../assets/images/Star.svg'; +import { useEffect, useState } from 'react'; + +interface ReviewData { + ratingAverage: number; + reviewTotalCount: number; +} + +interface DetailToursRatingProps { + reviewData: ReviewData; +} + +export default function DetailToursRating({ + reviewData, +}: DetailToursRatingProps) { + const { reviewTotalCount, ratingAverage } = reviewData; + + const STAR_IDX_ARR = ['1', '2', '3', '4', '5']; + const [ratedStarArr, setRatedStarArr] = useState([0, 0, 0, 0, 0]); + + const calculateRates = (average: number) => { + let tempRatedStarArr = [0, 0, 0, 0, 0]; + let exchangedStarScore = (average * 80) / 5; + let idx = 0; + while (exchangedStarScore > 16) { + tempRatedStarArr[idx] = 16; + idx += 1; + exchangedStarScore -= 16; + } + tempRatedStarArr[idx] = Number(exchangedStarScore.toFixed(1)); + return tempRatedStarArr; + }; + + useEffect(() => { + setRatedStarArr(calculateRates(ratingAverage)); + }, []); -export default function DetailToursRating() { return ( -
- - - - - - -

(4)

+
+ {STAR_IDX_ARR.map((item, idx) => { + return ( + + + + + + + + + + ); + })} + +
+

+ {reviewTotalCount} +

+
); } diff --git a/src/components/DetailSectionTop/DetailToutsMap.tsx b/src/components/DetailSectionTop/DetailToutsMap.tsx deleted file mode 100644 index 40730658..00000000 --- a/src/components/DetailSectionTop/DetailToutsMap.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ReactComponent as MapIcon } from '../../assets/images/Map.svg'; -import { ReactComponent as CheckIcon } from '../../assets/images/Check.svg'; -import { ReactComponent as PhoneIcon } from '../../assets/images/Phone.svg'; - -export default function DetailToursMap() { - return ( -
-
-
- - ๊ฐ•์› ๊ฐ•๋ฆ‰์‹œ ์ฐฝํ•ด๋กœ 307 -
- -
-
์ง€๋„ ์ •๋ณด
-
- - 064-743-0703 -
-
- ); -} diff --git a/src/components/DetailSectionTop/index.tsx b/src/components/DetailSectionTop/index.tsx index cc88b9e9..150b6ffd 100644 --- a/src/components/DetailSectionTop/index.tsx +++ b/src/components/DetailSectionTop/index.tsx @@ -1,11 +1,12 @@ import DetailToursInfo from './DetailToursInfo'; import DetailToursRating from './DetailToursRating'; -import DetailToursMap from './DetailToutsMap'; -import DetailTourButtons from './DetailTourButtons'; +import DetailToursMap from './DetailToursMap'; +import DetailToursButtons from './DetailToursButtons'; + export { DetailToursInfo, DetailToursRating, DetailToursMap, - DetailTourButtons, + DetailToursButtons, }; diff --git a/src/components/Review/Review.tsx b/src/components/Review/Review.tsx new file mode 100644 index 00000000..6114bb50 --- /dev/null +++ b/src/components/Review/Review.tsx @@ -0,0 +1,15 @@ +import ReviewButton from './ReviewButton'; +import ReviewKeyword from './ReviewKeyword'; +import ReviewPosting from './ReviewPosting'; +import ReviewRating from './ReviewRating'; + +export default function Review() { + return ( + <> + + + + + + ); +} diff --git a/src/components/Review/ReviewButton.tsx b/src/components/Review/ReviewButton.tsx new file mode 100644 index 00000000..eaf9c5a4 --- /dev/null +++ b/src/components/Review/ReviewButton.tsx @@ -0,0 +1,5 @@ +import { ButtonPrimary } from '@components/common/button/Button'; + +export default function ReviewButton() { + return {}}>์™„๋ฃŒ; +} diff --git a/src/components/Review/ReviewKeyword.tsx b/src/components/Review/ReviewKeyword.tsx new file mode 100644 index 00000000..79bb0f5e --- /dev/null +++ b/src/components/Review/ReviewKeyword.tsx @@ -0,0 +1,47 @@ +export default function ReviewKeyword() { + return ( +
+
์–ด๋–ค ์ ์ด ์ข‹์•˜๋‚˜์š”?
+
+
+
+ ๊นจ๋—ํ•ด์š” +
+
+ ์นœ์ ˆํ•ด์š” +
+
+ ๋ทฐ๊ฐ€ ์ข‹์•„์š” +
+
+
+
+ ์นจ๊ตฌ๊ฐ€ ์ข‹์•„์š” +
+
+ ์ฃผ์ฐจํ•˜๊ธฐ ํŽธํ•ด์š” +
+
+ ๋ƒ‰๋‚œ๋ฐฉ์ด ์ž˜๋ผ์š” +
+
+
+
+ ๋Œ€์ค‘๊ตํ†ต์ด ํŽธํ•ด์š” +
+
+ ํ˜ธ์บ‰์Šคํ•˜๊ธฐ ์ข‹์•„์š” +
+
+
+
+ ์กฐ์‹์ด ๋ง›์žˆ์–ด์š” +
+
+ ์‚ฌ์ง„ ์ฐ๊ธฐ ์ข‹์•„์š” +
+
+
+
+ ); +} diff --git a/src/components/Review/ReviewPosting.tsx b/src/components/Review/ReviewPosting.tsx new file mode 100644 index 00000000..e0a37d4a --- /dev/null +++ b/src/components/Review/ReviewPosting.tsx @@ -0,0 +1,26 @@ +import { useState, ChangeEvent } from 'react'; + +export default function ReviewPosting() { + const [textLength, setTextLength] = useState(0); + + const handleTextChange = (event: ChangeEvent) => { + const inputText = event.target.value; + setTextLength(inputText.length); + }; + + return ( +
+
๋ฆฌ๋ทฐ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”
+
+