From a36094e03b3fe8daedb80dddf2e877a33210bb8a Mon Sep 17 00:00:00 2001 From: rael Date: Fri, 14 Feb 2025 02:00:31 +0900 Subject: [PATCH 01/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=88=98=EC=A0=95=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/mypage/MyPage.tsx | 26 ++++++++++++++++--- .../mypage/components/ProfileSection.tsx | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/umc-master/src/pages/mypage/MyPage.tsx b/umc-master/src/pages/mypage/MyPage.tsx index 6a9757c..6ad6225 100644 --- a/umc-master/src/pages/mypage/MyPage.tsx +++ b/umc-master/src/pages/mypage/MyPage.tsx @@ -40,10 +40,19 @@ const MyPageForm = styled.div` display: flex; flex-direction: column; align-items: center; - width: 1280px; + width: 1440px; gap: 48px; - padding-top: 80px; - padding-bottom: 100px; + padding: 80px 0px 100px; + + @media (max-width: 1024px) { + gap: 32px; + padding: 60px 16px 80px; + } + + @media (max-width: 768px) { + gap: 24px; + padding: 40px 12px 60px; + } ` const ProfileCard = styled.div` @@ -51,4 +60,15 @@ const ProfileCard = styled.div` flex-direction: row; gap: 30px; flex-shrink: 0; + width: 100%; + + @media (max-width: 1024px) { + gap: 20px; + } + + @media (max-width: 768px) { + flex-direction: column; + align-items: center; + gap: 16px; + } ` \ No newline at end of file diff --git a/umc-master/src/pages/mypage/components/ProfileSection.tsx b/umc-master/src/pages/mypage/components/ProfileSection.tsx index 83c676b..652a28f 100644 --- a/umc-master/src/pages/mypage/components/ProfileSection.tsx +++ b/umc-master/src/pages/mypage/components/ProfileSection.tsx @@ -76,7 +76,7 @@ const Card = styled.div` display: flex; justify-content: space-between; align-items: center; - width: 1100px; + width: 1240px; height: 140px; padding: 18px 56px 20px 56px; gap: 10px; From 92155d2335254b3aedb69d287082c356f91c2540 Mon Sep 17 00:00:00 2001 From: rael Date: Fri, 14 Feb 2025 03:41:22 +0900 Subject: [PATCH 02/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95=20api=20?= =?UTF-8?q?=ED=95=84=EC=9A=94)=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/assets/gray-character.png | Bin 0 -> 53869 bytes .../NavigationBar/NavigationBar.tsx | 19 +++++-- .../mypage/components/ProfileSection.tsx | 49 ++++++++++++++++-- umc-master/src/store/userStore.ts | 8 +++ 4 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 umc-master/src/assets/gray-character.png diff --git a/umc-master/src/assets/gray-character.png b/umc-master/src/assets/gray-character.png new file mode 100644 index 0000000000000000000000000000000000000000..0a67ba97925f0e24a8ebbea8744ab3fb09e44eff GIT binary patch literal 53869 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

Hrmz*cxC}QO1iNY8+0wl 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 = ({ login }) => { const [isAlarmModalOpen, setIsAlarmModalOpen] = useState(false); const [isProfileModalOpen, setIsProfileModalOpen] = useState(false); + const { user, fetchUser } = useUserStore(); + + useEffect(() => { + fetchUser(); // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + }, []); + getUsers(); const toggleAlarmModal = () => setIsAlarmModalOpen((prev) => !prev); const toggleProfileModal = () => setIsProfileModalOpen((prev) => !prev); @@ -45,7 +54,11 @@ const NavigationBar: React.FC = ({ login }) => { {login ? ( - + ) : ( @@ -113,7 +126,7 @@ const UserSection = styled.div` cursor: pointer; `; -const ProfileImg = styled.div` +const ProfileImg = styled.img` width: 40px; height: 40px; background: #e0e0e0; /** TODO: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€ */ diff --git a/umc-master/src/pages/mypage/components/ProfileSection.tsx b/umc-master/src/pages/mypage/components/ProfileSection.tsx index 652a28f..7063d58 100644 --- a/umc-master/src/pages/mypage/components/ProfileSection.tsx +++ b/umc-master/src/pages/mypage/components/ProfileSection.tsx @@ -1,20 +1,53 @@ import Typography from '@components/common/typography'; import styled, { useTheme } from 'styled-components'; import CameraImg from '@assets/icons/cameraImg.svg' -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import ProfileEditModal from '../modal/ProfileEditModal'; +import { useUserStore } from '@store/userStore'; +import { getUsers } from '@apis/profileApi'; +import gray_character from '@assets/gray-character.png'; const ProfileSection: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); + const { user, fetchUser, setProfileImageUrl } = useUserStore(); + const [profileImageUrl, setProfileImageUrlLocal] = useState(user?.profile_image_url || gray_character); + + useEffect(() => { + fetchUser(); // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + }, []); + getUsers(); const theme = useTheme(); + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + const imageUrl = reader.result as string; + setProfileImageUrlLocal(imageUrl); // ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + setProfileImageUrl(imageUrl); // ์ „์—ญ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + }; + reader.readAsDataURL(file); + } + }; + return ( - - + + document.getElementById('fileInput')?.click()} + /> + @@ -22,7 +55,7 @@ const ProfileSection: React.FC = () => { ์• ๋‹ˆ + >{user?.nickname} Promise; clearUser: () => void; + setProfileImageUrl: (imageUrl: string) => void; } export const useUserStore = create((set) => ({ @@ -46,4 +47,11 @@ export const useUserStore = create((set) => ({ }, clearUser: () => set({ user: null }), + + setProfileImageUrl: (imageUrl: string) => set((state) => { + if (state.user) { + state.user.profile_image_url = imageUrl; + } + return state + }) })); From 84943c3d67296f2041a50909b2eb53333bd4c5d2 Mon Sep 17 00:00:00 2001 From: rael Date: Fri, 14 Feb 2025 04:32:03 +0900 Subject: [PATCH 03/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EB=B3=B8=20=EA=BF=80=ED=8C=81=20=EA=B8=B0=EB=8A=A5=20(?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=90=9C=20=EA=BF=80=ED=8C=81=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EA=B9=8C=EC=A7=80=EB=A7=8C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EB=90=A8/=EB=A9=94=EC=9D=B8,=20=EB=A7=A4=EA=B1=B0?= =?UTF-8?q?=EC=A7=84=20=EB=93=B1=EB=93=B1=20handleCardClick=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=88=98=EC=A0=95=20=ED=95=B4=EC=95=BC=ED=95=A8.)=20#?= =?UTF-8?q?76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/mypage/MyPage.tsx | 4 +- .../pages/mypage/components/RecentTips.tsx | 32 +++++------- umc-master/src/pages/saveTip/SaveTipPage.tsx | 8 +++ umc-master/src/store/recentStore.ts | 51 +++++++++++++++++++ 4 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 umc-master/src/store/recentStore.ts diff --git a/umc-master/src/pages/mypage/MyPage.tsx b/umc-master/src/pages/mypage/MyPage.tsx index 6ad6225..4deb5e6 100644 --- a/umc-master/src/pages/mypage/MyPage.tsx +++ b/umc-master/src/pages/mypage/MyPage.tsx @@ -2,7 +2,7 @@ import styled, { useTheme } from "styled-components"; import ProfileSection from "./components/ProfileSection"; import RecentTips from "./components/RecentTips"; import BestInterest from "./components/BestInterest"; -import { dummyData, dummyInterests } from "./dummyData/dummyData"; +import { dummyInterests } from "./dummyData/dummyData"; import Typography from "@components/common/typography"; const MyPage: React.FC = () => { @@ -17,7 +17,7 @@ const MyPage: React.FC = () => { >๋งˆ์ดํŽ˜์ด์ง€ - + diff --git a/umc-master/src/pages/mypage/components/RecentTips.tsx b/umc-master/src/pages/mypage/components/RecentTips.tsx index a1c2940..a9c6066 100644 --- a/umc-master/src/pages/mypage/components/RecentTips.tsx +++ b/umc-master/src/pages/mypage/components/RecentTips.tsx @@ -1,43 +1,38 @@ -/* eslint-disable react/prop-types */ import styled, { useTheme } from 'styled-components'; import Card from '@components/Card/Card'; import Typography from '@components/common/typography'; import { useNavigate } from 'react-router-dom'; +import { recentStore } from '@store/recentStore'; +import { useEffect } from 'react'; -interface TipCardItem { - id: string; - image: string; - text: string; - likes?: number; - bookmarks?: number; - date?: string; -} - -interface RecentTipsProps { - items: TipCardItem[]; -} - -const RecentTips: React.FC = ({ items }) => { +const RecentTips: React.FC = () => { const theme = useTheme(); - const navigate = useNavigate(); // ์ถ”๊ฐ€ + const navigate = useNavigate(); + // zustand ์ƒํƒœ์—์„œ ์ตœ๊ทผ ํŒ ๊ฐ€์ ธ์˜ค๊ธฐ + const { recentTips } = recentStore(); + const handleCardClick = (id: string) => { navigate(`/save-tip/${id}`); // ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ }; + useEffect(() => { + // ์ฒ˜์Œ ๋ Œ๋”๋ง ์‹œ์— ์ตœ๊ทผ ๋ณธ ํŒ์ด ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— ์žˆ์œผ๋ฉด ๋ณต์›๋ฉ๋‹ˆ๋‹ค. + }, []); + return ( ์ตœ๊ทผ์— ๋ณธ ๊ฟ€ํŒ - {items.length === 0 ? ( + {recentTips.length === 0 ? ( ์ตœ๊ทผ ๋ณธ ๊ฟ€ํŒ์ด ์—†์Šต๋‹ˆ๋‹ค. ) : ( - {items.map((item) => ( + {recentTips.map((item) => ( { const theme = useTheme(); + const { addRecentTip } = recentStore(); const [data, setData] = useState(initialData.slice(0, PAGE_SIZE * 6)); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(initialData.length > PAGE_SIZE); @@ -49,6 +51,12 @@ const SaveTipPage: React.FC = () => { const navigate = useNavigate(); // ์ถ”๊ฐ€ const handleCardClick = (id: string) => { + // ํด๋ฆญ ์‹œ ํŒ์„ ์ตœ๊ทผ์— ๋ณธ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ + const clickedTip = data.find((item) => item.id === id); + if (clickedTip) { + addRecentTip(clickedTip); + } + navigate(`/save-tip/${id}`); // ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ }; diff --git a/umc-master/src/store/recentStore.ts b/umc-master/src/store/recentStore.ts new file mode 100644 index 0000000..0df219c --- /dev/null +++ b/umc-master/src/store/recentStore.ts @@ -0,0 +1,51 @@ +import { create } from 'zustand'; +import { createJSONStorage, persist } from 'zustand/middleware'; + +interface Tip { + id: string; + image: string; + text: string; + likes?: number; + bookmarks?: number; + date?: string; +} + +interface UserState { + recentTips: Tip[]; + addRecentTip: (tip: Tip) => void; + clearRecentTips: () => void; +} + +export const recentStore = create()( + persist( + (set) => ({ + recentTips: [], + addRecentTip: (tip) => { + set((state) => { + const newTips = [...state.recentTips]; + const existingTipIndex = newTips.findIndex((t) => t.id === tip.id); + + if (existingTipIndex !== -1) { + // ์ด๋ฏธ ์žˆ๋Š” ํŒ์€ ์ตœ์‹ ์œผ๋กœ ์—…๋ฐ์ดํŠธ + newTips[existingTipIndex] = tip; + } else { + // ์ƒˆ๋กœ์šด ํŒ์€ ๋ฆฌ์ŠคํŠธ์˜ ๋งจ ์•ž์— ์ถ”๊ฐ€ + newTips.unshift(tip); + } + + // ์ตœ๋Œ€ 3๊ฐœ๋งŒ ์ €์žฅ + if (newTips.length > 3) { + newTips.pop(); + } + + return { recentTips: newTips }; + }); + }, + clearRecentTips: () => set({ recentTips: [] }), + }), + { + name: 'recent-tips-storage', // ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ํ‚ค ์ด๋ฆ„ + storage: createJSONStorage(() => localStorage), + } + ) +); From 91ccfc384825799fe09b218d25dca729117d510a Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 01:24:03 +0900 Subject: [PATCH 04/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/axios-instance.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/umc-master/src/apis/axios-instance.ts b/umc-master/src/apis/axios-instance.ts index bd20a2f..6afb7ac 100644 --- a/umc-master/src/apis/axios-instance.ts +++ b/umc-master/src/apis/axios-instance.ts @@ -70,7 +70,9 @@ axiosInstance.interceptors.response.use( useTokenStore.getState().clearTokens(); // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ - window.location.href = RoutePaths.LOGIN; + if (window.location.pathname !== RoutePaths.LOGIN) { + window.location.href = RoutePaths.LOGIN; + } return Promise.reject(refreshError); } From 6a72cf232a542ca452c7d3969bae30c50972dd0a Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 04:22:25 +0900 Subject: [PATCH 05/40] =?UTF-8?q?=F0=9F=93=A6=20package:=20InfiniteQuery?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/package.json | 4 ++-- umc-master/src/App.tsx | 13 +++++++++---- umc-master/yarn.lock | 26 +++++++++++++------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/umc-master/package.json b/umc-master/package.json index 5c0fb6a..c9d54a0 100644 --- a/umc-master/package.json +++ b/umc-master/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.7.2", - "@tanstack/react-query": "^5.65.0", - "@tanstack/react-query-devtools": "^5.65.0", + "@tanstack/react-query": "^5.66.3", + "@tanstack/react-query-devtools": "^5.66.3", "@types/node": "^22.10.5", "@types/styled-components": "^5.1.34", "axios": "^1.7.9", diff --git a/umc-master/src/App.tsx b/umc-master/src/App.tsx index 5874d65..571df18 100644 --- a/umc-master/src/App.tsx +++ b/umc-master/src/App.tsx @@ -2,13 +2,18 @@ import { ThemeProvider } from 'styled-components'; import GlobalStyle from '@styles/globalStyle.ts'; import theme from '@styles/theme.ts'; import Router from './router/routes.tsx'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient(); function App() { return ( - - - - + + + + + + ); } diff --git a/umc-master/yarn.lock b/umc-master/yarn.lock index a4868e1..cd60ad8 100644 --- a/umc-master/yarn.lock +++ b/umc-master/yarn.lock @@ -705,29 +705,29 @@ dependencies: "@swc/counter" "^0.1.3" -"@tanstack/query-core@5.65.0": - version "5.65.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.65.0.tgz#6b7c7087a36867361535b613ff39b633808052fd" - integrity sha512-Bnnq/1axf00r2grRT6gUyIkZRKzhHs+p4DijrCQ3wMlA3D3TTT71gtaSLtqnzGddj73/7X5JDGyjiSLdjvQN4w== +"@tanstack/query-core@5.66.3": + version "5.66.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.66.3.tgz#3ab0daa49477cfae38c45c02b8dc0bc39ec9cc5d" + integrity sha512-+2iDxH7UFdtwcry766aJszGmbByQDIzTltJ3oQAZF9bhCxHCIN3yDwHa6qDCZxcpMGvUphCRx/RYJvLbM8mucQ== "@tanstack/query-devtools@5.65.0": version "5.65.0" resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.65.0.tgz#37da5e911543b4f6d98b9a04369eab0de6044ba1" integrity sha512-g5y7zc07U9D3esMdqUfTEVu9kMHoIaVBsD0+M3LPdAdD710RpTcLiNvJY1JkYXqkq9+NV+CQoemVNpQPBXVsJg== -"@tanstack/react-query-devtools@^5.65.0": - version "5.65.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.65.0.tgz#951e8ddbe08b13ba0452c52f3a49af8764b4c2dd" - integrity sha512-xKoeWpHs6DcPqYmydIl+juWmZ2j4e4DaQEA/ju9PylhLI/X5eV5JG4IsI0ZrjtGwAEb4+lJv3SFwU/m6cmrljA== +"@tanstack/react-query-devtools@^5.66.3": + version "5.66.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.66.3.tgz#86ededd9567b92ef71dd060b66580960da88fdb2" + integrity sha512-ycICgTVQ2V6EEAXShOei8Ekxf+6IT6EQmwUgzEnJInZRTJZIcokOGB2Shp60Ky7sTAe1oeZD3tuky7gZg0gvyw== dependencies: "@tanstack/query-devtools" "5.65.0" -"@tanstack/react-query@^5.65.0": - version "5.65.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.65.0.tgz#741b124ff78dd7c27cdb80cd3bd13f3972f819c1" - integrity sha512-qXdHj3SCT2xkFxgrBIe6y9Lkowlwm+tGcV++PBLFtyvEJR5Q+biTnzm5p0tdVwqA603xlju9mtV2Kd/2brobgA== +"@tanstack/react-query@^5.66.3": + version "5.66.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.66.3.tgz#287d37e2079291302b86ce2c5898f6a1a4aaeea9" + integrity sha512-sWMvxZ5VugPDgD1CzP7f0s9yFvjcXP3FXO5IVV2ndXlYqUCwykU8U69Kk05Qn5UvGRqB/gtj4J7vcTC6vtLHtQ== dependencies: - "@tanstack/query-core" "5.65.0" + "@tanstack/query-core" "5.66.3" "@types/cookie@^0.6.0": version "0.6.0" From 0811ba9e898d7e67edecac136d59c88e3ce85588 Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 04:22:58 +0900 Subject: [PATCH 06/40] =?UTF-8?q?=E2=9C=A8=20feat:=20getSavedTips=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(=EC=95=84=EC=A7=81=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0X)=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/tipApi.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/umc-master/src/apis/tipApi.ts b/umc-master/src/apis/tipApi.ts index bef2112..d5a94e6 100644 --- a/umc-master/src/apis/tipApi.ts +++ b/umc-master/src/apis/tipApi.ts @@ -14,6 +14,10 @@ export interface NewPost { imageUrls: File[]; } +export interface GetSavedParams { + page: number; +} + export const getTips = async ({ pageParam, sorted }: GetTipsParams) => { const { data } = await axiosInstance.get(`/tips/sorted?page=${pageParam}&limit=5&sort=${sorted}`); return data; @@ -50,4 +54,16 @@ export const createPost = async (newPost: NewPost): Promise => { } }; +export const getSavedTips = async () => { + try { + const { data } = await axiosInstance.get(`/users/bookmarks`); + console.log("์ €์žฅ๋œ ๊ฟ€ํŒ API ์‘๋‹ต:", data); + return data; + } catch (error: any) { + console.error("์ €์žฅ๋œ ๊ฟ€ํŒ API ์—๋Ÿฌ ๋ฐœ์ƒ:", error.response?.status, error.response?.data); + throw new Error(`์ €์žฅ๋œ ๊ฟ€ํŒ API ์š”์ฒญ ์‹คํŒจ: ${error.response?.status}`); + } +}; + + // ๋‹ค๋ฅธ Tips ๊ด€๋ จ API๋“ค... From ffd10df5c6c3127d4a3331abc21f79714ea47b4f Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 04:23:21 +0900 Subject: [PATCH 07/40] =?UTF-8?q?=E2=9C=A8=20feat:=20useSaveTipList=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=9D=B4=EC=9A=A9=ED=95=B4=EC=84=9C=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=8B=9C=EB=8F=84=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/queries/useSaveTipQueries.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 umc-master/src/apis/queries/useSaveTipQueries.ts diff --git a/umc-master/src/apis/queries/useSaveTipQueries.ts b/umc-master/src/apis/queries/useSaveTipQueries.ts new file mode 100644 index 0000000..da538a1 --- /dev/null +++ b/umc-master/src/apis/queries/useSaveTipQueries.ts @@ -0,0 +1,13 @@ +import { useInfiniteQuery } from "@tanstack/react-query"; +import { getSavedTips } from "@apis/tipApi"; + +export const useSaveTipList = () => { + return useInfiniteQuery({ + queryKey: ["savedTips"], + queryFn: () => getSavedTips(), + initialPageParam: 1, + getNextPageParam: (lastPage, allPages) => { + return lastPage.hasMore ? allPages.length + 1 : undefined; + }, + }); +}; From 28835fc169c21ccd79b7bd17ae97a397fb62ada7 Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 04:23:41 +0900 Subject: [PATCH 08/40] =?UTF-8?q?=E2=9C=A8=20feat:=20api=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EC=A4=91=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/saveTip/SaveTipPage.tsx | 50 +++++++++----------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/umc-master/src/pages/saveTip/SaveTipPage.tsx b/umc-master/src/pages/saveTip/SaveTipPage.tsx index 7bf1b9d..eda9a96 100644 --- a/umc-master/src/pages/saveTip/SaveTipPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipPage.tsx @@ -1,58 +1,54 @@ import Card from "@components/Card/Card"; import Typography from "@components/common/typography"; import styled, { useTheme } from "styled-components"; -import { dummyData as initialData } from "./dummydata/dummydata"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef } from "react"; import SkeletonCard from "@components/Skeleton/SkeletonCard"; import { useNavigate } from "react-router-dom"; import { recentStore } from "@store/recentStore"; +import { useSaveTipList } from "@apis/queries/useSaveTipQueries"; const PAGE_SIZE = 5; const SaveTipPage: React.FC = () => { const theme = useTheme(); - const { addRecentTip } = recentStore(); - const [data, setData] = useState(initialData.slice(0, PAGE_SIZE * 6)); - const [isLoading, setIsLoading] = useState(false); - const [hasMore, setHasMore] = useState(initialData.length > PAGE_SIZE); + const navigate = useNavigate(); const observerRef = useRef(null); const lastElementRef = useRef(null); - const loadMoreData = useCallback(() => { - if (isLoading || !hasMore) return; + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useSaveTipList(); - setIsLoading(true); - setTimeout(() => { - const nextData = initialData.slice(data.length, data.length + PAGE_SIZE); - setData((prevData) => [...prevData, ...nextData]); - setHasMore(data.length + PAGE_SIZE < initialData.length); - setIsLoading(false); - }, 1000); - }, [isLoading, hasMore, data.length]); + const loadMoredata = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); useEffect(() => { - if (isLoading || !hasMore) return; + if (!hasNextPage) return; - if (observerRef.current) observerRef.current.disconnect(); + if(observerRef.current) observerRef.current.disconnect(); observerRef.current = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { - loadMoreData(); + loadMoredata(); } }); if (lastElementRef.current) observerRef.current.observe(lastElementRef.current); return () => observerRef.current?.disconnect(); - }, [isLoading, hasMore, loadMoreData]); - - const navigate = useNavigate(); // ์ถ”๊ฐ€ + }, [hasNextPage, loadMoredata]); const handleCardClick = (id: string) => { // ํด๋ฆญ ์‹œ ํŒ์„ ์ตœ๊ทผ์— ๋ณธ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ - const clickedTip = data.find((item) => item.id === id); + const clickedTip = data?.pages.flatMap((page) => page.tips).find((item) => item.id === id); if (clickedTip) { addRecentTip(clickedTip); } @@ -67,11 +63,11 @@ const SaveTipPage: React.FC = () => { variant="headingXxSmall" style={{color: theme.colors.primary[900]}} >์ €์žฅํ•œ ๊ฟ€ํŒ - {data.length === 0 && !isLoading ? ( + {data?.pages.length === 0 && !isFetchingNextPage ? ( ์ตœ๊ทผ ๋ณธ ๊ฟ€ํŒ์ด ์—†์Šต๋‹ˆ๋‹ค. ) : ( - {data.map((item) => ( + {data?.pages.flatMap((page) => page.tips).map((item) => ( { ))} {/* ๋งˆ์ง€๋ง‰ ์š”์†Œ ๊ฐ์ง€์šฉ div */} - {hasMore && !isLoading &&

} + {hasNextPage && !isFetchingNextPage &&
} {/* ์Šค์ผˆ๋ ˆํ†ค UI */} - {isLoading && + {isFetchingNextPage && Array.from({ length: PAGE_SIZE }).map((_, index) => ( )) From 50fd8e3a0d2b85d37838d605bd946f41f43ebfcc Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 04:24:09 +0900 Subject: [PATCH 09/40] =?UTF-8?q?=E2=9C=A8=20feat:=20tip=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=ED=86=B5=EC=9D=BC=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/store/recentStore.ts | 33 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/umc-master/src/store/recentStore.ts b/umc-master/src/store/recentStore.ts index 0df219c..1430737 100644 --- a/umc-master/src/store/recentStore.ts +++ b/umc-master/src/store/recentStore.ts @@ -1,13 +1,32 @@ import { create } from 'zustand'; import { createJSONStorage, persist } from 'zustand/middleware'; +interface Hashtag { + hashtagId: number; + name: string; +} + +interface Image { + media_url: string; + media_type: string; +} +interface Author { + userId: number; + nickname: string; + profileImageUrl: string | null; +} + interface Tip { - id: string; - image: string; - text: string; - likes?: number; - bookmarks?: number; - date?: string; + tipId: number; + title: string; + content: string; + createdAt: string; + updatedAt: string; + hashtags: Hashtag[]; + imageUrls: Image[]; + likesCount: number; + savesCount: number; + author: Author; } interface UserState { @@ -23,7 +42,7 @@ export const recentStore = create()( addRecentTip: (tip) => { set((state) => { const newTips = [...state.recentTips]; - const existingTipIndex = newTips.findIndex((t) => t.id === tip.id); + const existingTipIndex = newTips.findIndex((t) => t.tipId === tip.tipId); if (existingTipIndex !== -1) { // ์ด๋ฏธ ์žˆ๋Š” ํŒ์€ ์ตœ์‹ ์œผ๋กœ ์—…๋ฐ์ดํŠธ From 592e461c0eaf2bf0373c7021a24bfa0d3f60d9c2 Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 05:26:20 +0900 Subject: [PATCH 10/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/index.html | 1 + .../src/pages/saveTip/SaveTipDetailPage.tsx | 83 ++++++++++++++----- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/umc-master/index.html b/umc-master/index.html index 046e329..98aeab9 100644 --- a/umc-master/index.html +++ b/umc-master/index.html @@ -11,6 +11,7 @@ /> Vite + React + TS +
diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index df88406..2a5b6a4 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import styled from "styled-components"; import PostDetail from "./DetailPage_componenets/PostDetail"; import CommentView from "./DetailPage_componenets/CommentView"; @@ -10,7 +11,7 @@ import Typography from "@components/common/typography"; import { dummyData } from "./dummydata/dummydata"; import { useParams } from "react-router-dom"; import theme from "@styles/theme"; -import { useState } from "react"; +import { useEffect, useState } from "react"; const SaveTipDetailPage: React.FC = () => { @@ -24,28 +25,72 @@ const SaveTipDetailPage: React.FC = () => { const [likes, setLikes] = useState(detail.likes); const [liked, setLiked] = useState(false); + const [saves, setSaves] = useState(detail.bookmarks); + const [saved, setSaved] = useState(false); const handleLikeClick = () => { - if (!liked) { - setLikes(likes + 1); // ์ข‹์•„์š” ์ˆ˜ ์ฆ๊ฐ€ - setLiked(true); // ์ข‹์•„์š” ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ - } else { - setLikes(likes - 1); // ์ข‹์•„์š” ์ทจ์†Œ ์‹œ ์ˆ˜ ๊ฐ์†Œ - setLiked(false); // ์ข‹์•„์š” ์ทจ์†Œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ - } + setLikes((prevLikes) => prevLikes + (liked ? -1 : 1)); + setLiked(!liked); }; - const [saves, setSaves] = useState(detail.bookmarks); - const [saved, setSaved] = useState(false); - const handleSaveClick = () => { - if (!saved) { - setSaves(saves + 1); // ์ข‹์•„์š” ์ˆ˜ ์ฆ๊ฐ€ - setSaved(true); // ์ข‹์•„์š” ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ - } else { - setSaves(saves - 1); // ์ข‹์•„์š” ์ทจ์†Œ ์‹œ ์ˆ˜ ๊ฐ์†Œ - setSaved(false); // ์ข‹์•„์š” ์ทจ์†Œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ - } + setSaves((prevSaves) => prevSaves + (saved ? -1 : 1)); + setSaved(!saved); + }; + + const Kakao = (window as any).Kakao; + // const realUrl = "https://umc-master-frontend.vercel.app"; // ์‹ค์ œ URL์„ ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜์„ธ์š” + const realUrl = window.location.href; // ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์˜ URL + + + const loadKakaoSDK = () => { + const script = document.createElement("script"); + script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js"; + script.integrity = import.meta.env.VITE_INTEGRITY_VALUE; // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ + script.crossOrigin = "anonymous"; + script.onload = () => { + console.log("Kakao SDK ๋กœ๋“œ ์™„๋ฃŒ"); + }; + document.head.appendChild(script); + }; + + loadKakaoSDK(); + + + useEffect(() => { + if (!Kakao) return; + if (!Kakao.isInitialized()) { + Kakao.init(`${import.meta.env.VITE_JAVASCRIPT_KEY}`); // ์—ฌ๊ธฐ์— ์นด์นด์˜ค ์•ฑ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š” + } + }, []); + + const shareKakao = () => { + + if (!Kakao) { + console.error("Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); + return; + } + + Kakao.Share.sendDefault({ + objectType: "feed", + content: { + title: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ", + description: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?", + imageUrl: + "https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg", + link: { + mobileWebUrl: realUrl, + }, + }, + buttons: [ + { + title: "๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ", + link: { + mobileWebUrl: realUrl, + }, + }, + ], + }); }; return ( @@ -71,7 +116,7 @@ const SaveTipDetailPage: React.FC = () => { >{saves} - ๊ณต์œ ํ•˜๊ธฐ + ๊ณต์œ ํ•˜๊ธฐ Date: Mon, 17 Feb 2025 06:18:27 +0900 Subject: [PATCH 11/40] =?UTF-8?q?=E2=9C=A8=20feat:=20getTipDetail=20api,?= =?UTF-8?q?=20useTipDetail=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/queries/useTipDetailQuery.ts | 10 ++++++++++ umc-master/src/apis/tipApi.ts | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 umc-master/src/apis/queries/useTipDetailQuery.ts diff --git a/umc-master/src/apis/queries/useTipDetailQuery.ts b/umc-master/src/apis/queries/useTipDetailQuery.ts new file mode 100644 index 0000000..6c0b00a --- /dev/null +++ b/umc-master/src/apis/queries/useTipDetailQuery.ts @@ -0,0 +1,10 @@ +import { getTipDetail } from "@apis/tipApi"; +import { useQuery } from "@tanstack/react-query"; + +export const useTipDetail = (tipId: string) => { + return useQuery({ + queryKey: ["tipDetail", tipId], + queryFn: () => getTipDetail(tipId), + enabled: !!tipId, // tipId๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰ + }); +}; diff --git a/umc-master/src/apis/tipApi.ts b/umc-master/src/apis/tipApi.ts index d5a94e6..b0a23e3 100644 --- a/umc-master/src/apis/tipApi.ts +++ b/umc-master/src/apis/tipApi.ts @@ -65,5 +65,10 @@ export const getSavedTips = async () => { } }; +export const getTipDetail = async (tipId: string) => { + const { data } = await axiosInstance.get(`/tips/${tipId}`); + return data; +}; + // ๋‹ค๋ฅธ Tips ๊ด€๋ จ API๋“ค... From b10555dec66faf1721717dea99deb74d5a82656c Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 06:19:14 +0900 Subject: [PATCH 12/40] =?UTF-8?q?=E2=9C=A8=20feat:=20dummydata=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=ED=9B=84=20api=20=EC=97=B0=EA=B2=B0=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailPage_componenets/PostDetail.tsx | 61 ++++++++++++------- .../src/pages/saveTip/SaveTipDetailPage.tsx | 51 ++++++++++++---- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx b/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx index e522288..fd8ec9f 100644 --- a/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx +++ b/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx @@ -1,24 +1,47 @@ +/* eslint-disable react/prop-types */ import Typography from "@components/common/typography"; import Tag from "@components/Tag/Tag"; import { useEffect } from "react"; -import { useParams } from "react-router-dom"; import styled, { useTheme } from "styled-components"; -import { saveTipDetailPageDataList } from "../dummydata/dummydata"; - -const PostDetail: React.FC = () => { - - const { tipId } = useParams<{ tipId: string }>(); +interface Hashtag { + hashtagId: number; + name: string; +} + +interface Image { + media_url: string; + media_type: string; +} +interface Author { + userId: number; + nickname: string; + profileImageUrl: string | null; +} + +interface TipItem { + tipId: number; + title: string; + content: string; + createdAt: string; + updatedAt: string; + hashtags: Hashtag[]; + imageUrls: Image[]; + likesCount: number; + savesCount: number; + author: Author; +} + +interface PostDetailProps { + detail: TipItem; // detail์˜ ํƒ€์ž…์„ TipItem์œผ๋กœ ์ •์˜ +} + +const PostDetail: React.FC = ({ detail }) => { useEffect(() => { window.scrollTo(0, 0); }, []); - // TODO: ์ถ”ํ›„ API ์—ฐ๋™ ์‹œ, ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ์ˆ˜์ • - const detail = saveTipDetailPageDataList.find((item) => item.id === tipId); - console.log(tipId); - - if (!detail) { return ํ•ด๋‹น ํฌ์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.; } @@ -39,7 +62,7 @@ const PostDetail: React.FC = () => { {detail.author} + >{detail.author.nickname} { {detail.bestnum}ํšŒ + >num ํšŒ - {detail.tags.map((tag, index) => ( - + {detail.hashtags.map((tag, index) => ( + ))} @@ -62,17 +85,13 @@ const PostDetail: React.FC = () => { {detail.date} - {detail.time} + >{detail.createdAt} {detail.description} + >{detail.content} ); }; diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index 2a5b6a4..07017fe 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -8,24 +8,53 @@ import Saves from "@assets/savetipdetail/saves.svg"; import Saved from "@assets/savetipdetail/saved.svg"; import Link from "@assets/savetipdetail/link.svg"; import Typography from "@components/common/typography"; -import { dummyData } from "./dummydata/dummydata"; import { useParams } from "react-router-dom"; import theme from "@styles/theme"; import { useEffect, useState } from "react"; - -const SaveTipDetailPage: React.FC = () => { +import { useTipDetail } from "@apis/queries/useTipDetailQuery"; + +interface Hashtag { + hashtagId: number; + name: string; +} + +interface Image { + media_url: string; + media_type: string; +} +interface Author { + userId: number; + nickname: string; + profileImageUrl: string | null; +} + +interface TipItem { + tipId: number; + title: string; + content: string; + createdAt: string; + updatedAt: string; + hashtags: Hashtag[]; + imageUrls: Image[]; + likesCount: number; + savesCount: number; + author: Author; +} + +const SaveTipDetailPage: React.FC = () => { const { tipId } = useParams<{ tipId: string }>(); - const detail = dummyData.find((item) => item.id === tipId); + // ์ƒ์„ธ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + const { data: detail, isLoading, error } = useTipDetail(tipId!); - if (!detail) { - return
๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
; - } + if (isLoading) return
๋กœ๋”ฉ ์ค‘...
; + if (error) return
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
; + if (!detail) return
๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
; - const [likes, setLikes] = useState(detail.likes); + const [likes, setLikes] = useState(detail.likesCount); const [liked, setLiked] = useState(false); - const [saves, setSaves] = useState(detail.bookmarks); + const [saves, setSaves] = useState(detail.savesCount); const [saved, setSaved] = useState(false); const handleLikeClick = () => { @@ -96,9 +125,9 @@ const SaveTipDetailPage: React.FC = () => { return ( - + - + From 1b1477da9bd2517f4cdf5075b853c69bc93ec91d Mon Sep 17 00:00:00 2001 From: rael Date: Mon, 17 Feb 2025 06:22:30 +0900 Subject: [PATCH 13/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=20comment=20get,=20p?= =?UTF-8?q?ost,=20put,=20delete=20api=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/commentApi.ts | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 umc-master/src/apis/commentApi.ts diff --git a/umc-master/src/apis/commentApi.ts b/umc-master/src/apis/commentApi.ts new file mode 100644 index 0000000..e77b49a --- /dev/null +++ b/umc-master/src/apis/commentApi.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import axiosInstance from "./axios-instance"; + +export const getComments = async (tipId: string, page: number = 1, limit: number = 10) => { + try { + const { data } = await axiosInstance.get(`/tips/${tipId}/comments`, { + params: { page, limit }, + }); + return data; + } catch (error: any) { + console.error('๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ:', error); + throw new Error('๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + } +}; + +export const addComment = async (tipId: string, comment: string) => { + try { + const { data } = await axiosInstance.post(`/tips/${tipId}/comments`, { + comment, + }); + return data; + } catch (error: any) { + console.error('๋Œ“๊ธ€ ์ถ”๊ฐ€ ์‹คํŒจ:', error); + throw new Error('๋Œ“๊ธ€ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + } +}; + +export const editComment = async (tipId: string, commentId: string, newComment: string) => { + try { + const { data } = await axiosInstance.put(`/tips/${tipId}/comments/${commentId}`, { + comment: newComment, + }); + return data; + } catch (error: any) { + console.error('๋Œ“๊ธ€ ์ˆ˜์ • ์‹คํŒจ:', error); + throw new Error('๋Œ“๊ธ€ ์ˆ˜์ •์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + } +}; + +export const deleteComment = async (tipId: string, commentId: string) => { + try { + const { data } = await axiosInstance.delete(`/tips/${tipId}/comments/${commentId}`); + return data; + } catch (error: any) { + console.error('๋Œ“๊ธ€ ์‚ญ์ œ ์‹คํŒจ:', error); + throw new Error('๋Œ“๊ธ€ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + } +}; From 606228014d3265172aaba4947b7fd74220818486 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 02:57:50 +0900 Subject: [PATCH 14/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=EC=99=80=20=EB=B6=81=EB=A7=88=ED=81=AC=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20api=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/tipApi.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/umc-master/src/apis/tipApi.ts b/umc-master/src/apis/tipApi.ts index b0a23e3..a2c1102 100644 --- a/umc-master/src/apis/tipApi.ts +++ b/umc-master/src/apis/tipApi.ts @@ -56,19 +56,26 @@ export const createPost = async (newPost: NewPost): Promise => { export const getSavedTips = async () => { try { - const { data } = await axiosInstance.get(`/users/bookmarks`); - console.log("์ €์žฅ๋œ ๊ฟ€ํŒ API ์‘๋‹ต:", data); + const { data } = await axiosInstance.get(`/users/saved-tips`); + console.log('์ €์žฅ๋œ ๊ฟ€ํŒ API ์‘๋‹ต:', data); return data; } catch (error: any) { - console.error("์ €์žฅ๋œ ๊ฟ€ํŒ API ์—๋Ÿฌ ๋ฐœ์ƒ:", error.response?.status, error.response?.data); + console.error('์ €์žฅ๋œ ๊ฟ€ํŒ API ์—๋Ÿฌ ๋ฐœ์ƒ:', error.response?.status, error.response?.data); throw new Error(`์ €์žฅ๋œ ๊ฟ€ํŒ API ์š”์ฒญ ์‹คํŒจ: ${error.response?.status}`); } }; -export const getTipDetail = async (tipId: string) => { +export const getTipDetail = async (tipId: number) => { const { data } = await axiosInstance.get(`/tips/${tipId}`); - return data; + return data.result; }; +export const toggleLike = async (tipId: number) => { + const response = await axiosInstance.post(`/tips/${tipId}/like`); + return response.data; +}; -// ๋‹ค๋ฅธ Tips ๊ด€๋ จ API๋“ค... +export const toggleBookmark = async (tipId: number) => { + const response = await axiosInstance.post(`/tips/${tipId}/bookmark`); + return response.data; +}; From 2c85a8e9da843a674a3f902c78bed59b8b4925f5 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:00:01 +0900 Subject: [PATCH 15/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=EC=99=80=20=EB=B6=81=EB=A7=88=ED=81=AC=20mutation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/apis/queries/useTipDetailMutations.ts | 32 +++++++++++++++++++ .../src/apis/queries/useTipDetailQuery.ts | 8 ++--- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 umc-master/src/apis/queries/useTipDetailMutations.ts diff --git a/umc-master/src/apis/queries/useTipDetailMutations.ts b/umc-master/src/apis/queries/useTipDetailMutations.ts new file mode 100644 index 0000000..d7d4230 --- /dev/null +++ b/umc-master/src/apis/queries/useTipDetailMutations.ts @@ -0,0 +1,32 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { toggleLike, toggleBookmark } from '@apis/tipApi'; + +export const useToggleLike = (tipId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => toggleLike(tipId), + onSuccess: (data) => { + console.log(data.message); + queryClient.invalidateQueries({ queryKey: ['tipDetail', tipId] }); + }, + onError: (error) => { + console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error); + }, + }); +}; + +export const useToggleBookmark = (tipId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => toggleBookmark(tipId), + onSuccess: (data) => { + console.log(data.message); + queryClient.invalidateQueries({ queryKey: ['tipDetail', tipId] }); + }, + onError: (error) => { + console.error('๋ถ๋งˆํฌ ํ† ๊ธ€ ์˜ค๋ฅ˜:', error); + }, + }); +}; diff --git a/umc-master/src/apis/queries/useTipDetailQuery.ts b/umc-master/src/apis/queries/useTipDetailQuery.ts index 6c0b00a..66f644f 100644 --- a/umc-master/src/apis/queries/useTipDetailQuery.ts +++ b/umc-master/src/apis/queries/useTipDetailQuery.ts @@ -1,9 +1,9 @@ -import { getTipDetail } from "@apis/tipApi"; -import { useQuery } from "@tanstack/react-query"; +import { getTipDetail } from '@apis/tipApi'; +import { useQuery } from '@tanstack/react-query'; -export const useTipDetail = (tipId: string) => { +export const useTipDetail = (tipId: number) => { return useQuery({ - queryKey: ["tipDetail", tipId], + queryKey: ['tipDetail', tipId], queryFn: () => getTipDetail(tipId), enabled: !!tipId, // tipId๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰ }); From 4d8e9c75c7466d32a9147913fc4496fa6ddf0217 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:00:39 +0900 Subject: [PATCH 16/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20=ED=86=A0=EA=B8=80?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=EB=93=A4=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=ED=99=94=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saveTip/components/FloatingToggleBtn.tsx | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx diff --git a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx new file mode 100644 index 0000000..fd2fa4f --- /dev/null +++ b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx @@ -0,0 +1,117 @@ +/* eslint-disable react/prop-types */ +import { useState } from 'react'; +import styled from 'styled-components'; +import theme from '@styles/theme'; +import Typography from '@components/common/typography'; +import Likes from '@assets/savetipdetail/Likes.svg'; +import Liked from '@assets/savetipdetail/liked.svg'; +import Saves from '@assets/savetipdetail/saves.svg'; +import Saved from '@assets/savetipdetail/saved.svg'; +import Link from '@assets/savetipdetail/link.svg'; +import { useToggleLike, useToggleBookmark } from '@apis/queries/useTipDetailMutations'; + +interface FloatingToggleBtnProps { + tipId: number; + initialLikes: number; + initialSaves: number; + userLiked: boolean; + userSaved: boolean; +} + +const FloatingToggleBtn: React.FC = ({ tipId, initialLikes, initialSaves }) => { + //TODO: ์œ ์ € ์ถ”๊ฐ€ ์—ฌ๋ถ€ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ์ฃผ๋ฉด ์ถ”๊ฐ€ ์˜ˆ์ • + const [likes, setLikes] = useState(initialLikes); + const [liked, setLiked] = useState(false); + const [saves, setSaves] = useState(initialSaves); + const [saved, setSaved] = useState(false); + + const { mutate: toggleLike } = useToggleLike(tipId); + const { mutate: toggleBookmark } = useToggleBookmark(tipId); + + const handleLikeClick = () => { + toggleLike(); + setLikes((prevLikes) => prevLikes + (liked ? -1 : 1)); + setLiked(!liked); + }; + + const handleSaveClick = () => { + toggleBookmark(); + setSaves((prevSaves) => prevSaves + (saved ? -1 : 1)); + setSaved(!saved); + }; + + const shareKakao = () => { + const Kakao = (window as any).Kakao; + if (!Kakao) { + console.error('Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.'); + return; + } + + Kakao.Share.sendDefault({ + objectType: 'feed', + content: { + title: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ', + description: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?', + imageUrl: 'https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg', + link: { + mobileWebUrl: window.location.href, + }, + }, + buttons: [ + { + title: '๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ', + link: { + mobileWebUrl: window.location.href, + }, + }, + ], + }); + }; + + return ( + + + + + {likes} + + + + + + {saves} + + + + + + ๊ณต์œ ํ•˜๊ธฐ + + + + ); +}; + +export default FloatingToggleBtn; + +const BtnContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + position: fixed; + right: 2%; + bottom: 5%; + gap: 26px; +`; + +const InteractionBtn = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 7px; +`; + +const BtnImg = styled.img` + object-fit: cover; + cursor: pointer; +`; From b3a8c2b9d50a21b90f5797dffe87b9611dc08027 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:01:15 +0900 Subject: [PATCH 17/40] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=ED=99=94=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/saveTip/SaveTipDetailPage.tsx | 208 +++--------------- 1 file changed, 30 insertions(+), 178 deletions(-) diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index 07017fe..aa71ec0 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -1,157 +1,34 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import styled from "styled-components"; -import PostDetail from "./DetailPage_componenets/PostDetail"; -import CommentView from "./DetailPage_componenets/CommentView"; -import Likes from "@assets/savetipdetail/Likes.svg"; -import Liked from "@assets/savetipdetail/liked.svg"; -import Saves from "@assets/savetipdetail/saves.svg"; -import Saved from "@assets/savetipdetail/saved.svg"; -import Link from "@assets/savetipdetail/link.svg"; -import Typography from "@components/common/typography"; -import { useParams } from "react-router-dom"; -import theme from "@styles/theme"; -import { useEffect, useState } from "react"; -import { useTipDetail } from "@apis/queries/useTipDetailQuery"; - -interface Hashtag { - hashtagId: number; - name: string; -} - -interface Image { - media_url: string; - media_type: string; -} -interface Author { - userId: number; - nickname: string; - profileImageUrl: string | null; -} - -interface TipItem { - tipId: number; - title: string; - content: string; - createdAt: string; - updatedAt: string; - hashtags: Hashtag[]; - imageUrls: Image[]; - likesCount: number; - savesCount: number; - author: Author; -} - -const SaveTipDetailPage: React.FC = () => { - +import styled from 'styled-components'; +import { useParams } from 'react-router-dom'; +import PostDetail from './components/PostDetail'; +import CommentView from './components/CommentView'; +import FloatingToggleBtn from './components/FloatingToggleBtn'; +import { useTipDetail } from '@apis/queries/useTipDetailQuery'; + +const SaveTipDetailPage: React.FC = () => { const { tipId } = useParams<{ tipId: string }>(); - - // ์ƒ์„ธ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ - const { data: detail, isLoading, error } = useTipDetail(tipId!); + const { data: detail, isLoading, error } = useTipDetail(Number(tipId)); + console.log('๊ฟ€ํŒ ์ƒ์„ธ', detail); if (isLoading) return
๋กœ๋”ฉ ์ค‘...
; if (error) return
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
; if (!detail) return
๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
; - - const [likes, setLikes] = useState(detail.likesCount); - const [liked, setLiked] = useState(false); - const [saves, setSaves] = useState(detail.savesCount); - const [saved, setSaved] = useState(false); - - const handleLikeClick = () => { - setLikes((prevLikes) => prevLikes + (liked ? -1 : 1)); - setLiked(!liked); - }; - - const handleSaveClick = () => { - setSaves((prevSaves) => prevSaves + (saved ? -1 : 1)); - setSaved(!saved); - }; - - const Kakao = (window as any).Kakao; - // const realUrl = "https://umc-master-frontend.vercel.app"; // ์‹ค์ œ URL์„ ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜์„ธ์š” - const realUrl = window.location.href; // ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์˜ URL - - - const loadKakaoSDK = () => { - const script = document.createElement("script"); - script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js"; - script.integrity = import.meta.env.VITE_INTEGRITY_VALUE; // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ - script.crossOrigin = "anonymous"; - script.onload = () => { - console.log("Kakao SDK ๋กœ๋“œ ์™„๋ฃŒ"); - }; - document.head.appendChild(script); - }; - - loadKakaoSDK(); - - useEffect(() => { - if (!Kakao) return; - if (!Kakao.isInitialized()) { - Kakao.init(`${import.meta.env.VITE_JAVASCRIPT_KEY}`); // ์—ฌ๊ธฐ์— ์นด์นด์˜ค ์•ฑ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š” - } - }, []); - - const shareKakao = () => { - - if (!Kakao) { - console.error("Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); - return; - } - - Kakao.Share.sendDefault({ - objectType: "feed", - content: { - title: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ", - description: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?", - imageUrl: - "https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg", - link: { - mobileWebUrl: realUrl, - }, - }, - buttons: [ - { - title: "๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ", - link: { - mobileWebUrl: realUrl, - }, - }, - ], - }); - }; - return ( - - - - - - - - ์ข‹์•„์š” - {likes} - - - ์ €์žฅํ•˜๊ธฐ - {saves} - - - ๊ณต์œ ํ•˜๊ธฐ - ๊ณต์œ ํ•˜๊ธฐ - - + + + + + + {/* TODO: ์œ ์ € ํ† ๊ธ€ ์—ฌ๋ถ€ - ์ผ๋‹จ false๋กœ ์ „๋‹ฌ*/} + ); }; @@ -166,45 +43,20 @@ const Container = styled.div` align-items: center; padding-top: 80px; padding-bottom: 100px; - background: #FFF; -` + background: #fff; +`; -const SaveTipDatail = styled.div` +const Content = styled.div` display: flex; - width: 1280px; + width: 80vw; flex-direction: column; justify-content: space-between; align-items: flex-start; gap: 60px; -` +`; const Line = styled.div` - width: 1280px; + width: 80vw; height: 1px; border: 1px solid ${({ theme }) => theme.colors.primary[800]}; -` - -const InteractionButtons = styled.div` - position: fixed; - right: 168px; - top: 610px; - width: 72px; - height: 366px; - display: flex; - flex-direction: column; - align-items: center; - gap: 36px; -` - -const Interaction = styled.div` - display: flex; - width: 72px; - flex-direction: column; - align-items: center; - gap: 7px; -` - -const Img = styled.img` - object-fit: cover; - cursor: pointer; -` \ No newline at end of file +`; From 150b1878fff21839caa5eac4608340df7e810746 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:02:11 +0900 Subject: [PATCH 18/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20=EA=BF=80=ED=8C=81?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailPage_componenets/PostDetail.tsx | 167 ----------------- .../pages/saveTip/components/PostDetail.tsx | 172 ++++++++++++++++++ 2 files changed, 172 insertions(+), 167 deletions(-) delete mode 100644 umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx create mode 100644 umc-master/src/pages/saveTip/components/PostDetail.tsx diff --git a/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx b/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx deleted file mode 100644 index fd8ec9f..0000000 --- a/umc-master/src/pages/saveTip/DetailPage_componenets/PostDetail.tsx +++ /dev/null @@ -1,167 +0,0 @@ -/* eslint-disable react/prop-types */ -import Typography from "@components/common/typography"; -import Tag from "@components/Tag/Tag"; -import { useEffect } from "react"; -import styled, { useTheme } from "styled-components"; - -interface Hashtag { - hashtagId: number; - name: string; -} - -interface Image { - media_url: string; - media_type: string; -} -interface Author { - userId: number; - nickname: string; - profileImageUrl: string | null; -} - -interface TipItem { - tipId: number; - title: string; - content: string; - createdAt: string; - updatedAt: string; - hashtags: Hashtag[]; - imageUrls: Image[]; - likesCount: number; - savesCount: number; - author: Author; -} - -interface PostDetailProps { - detail: TipItem; // detail์˜ ํƒ€์ž…์„ TipItem์œผ๋กœ ์ •์˜ -} - -const PostDetail: React.FC = ({ detail }) => { - - useEffect(() => { - window.scrollTo(0, 0); - }, []); - - if (!detail) { - return ํ•ด๋‹น ํฌ์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.; - } - - const theme = useTheme(); - return ( - - - {detail.title} - - - - - - {detail.author.nickname} - - BEST ๊ฟ€ํŒ ์„ ์ • ํšŸ์ˆ˜ - num ํšŒ - - - - - {detail.hashtags.map((tag, index) => ( - - ))} - - - - {detail.createdAt} - - - {detail.content} - - ); -}; - -export default PostDetail; - -const PostView = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 32px; - align-self: stretch; -` - -const Img = styled.div` - width: 1280px; - height: 360px; - border-radius: 20px; - background: #D9D9D9; -` - -const PostInfo = styled.div` - display: flex; - justify-content: space-between; - align-items: flex-start; - align-self: stretch; -` - -const InfoDetail = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 32px; -` - -const Author = styled.div` - display: flex; - justify-content: center; - align-items: center; - gap: 19px; -` - -const ProfileImg = styled.div` - width: 80px; - height: 80px; - border-radius: 50px; - background: #D9D9D9; -` - -const AuthorInfo = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 2px; -` - -const Bestnum = styled.div` - display: flex; - align-items: center; - gap: 4px; -` - -const Tags = styled.div` - display: flex; - align-items: center; - gap: 12px; -` - -const PostDate = styled.div` - display: flex; - align-items: center; - gap: 8px; -` \ No newline at end of file diff --git a/umc-master/src/pages/saveTip/components/PostDetail.tsx b/umc-master/src/pages/saveTip/components/PostDetail.tsx new file mode 100644 index 0000000..b719a3b --- /dev/null +++ b/umc-master/src/pages/saveTip/components/PostDetail.tsx @@ -0,0 +1,172 @@ +/* eslint-disable react/prop-types */ +import { useEffect } from 'react'; +import styled from 'styled-components'; +import theme from '@styles/theme'; +import Typography from '@components/common/typography'; +import Tag from '@components/Tag/Tag'; +import ProfileDefault from '@assets/gray-character.png'; + +interface Hashtag { + hashtag_id: number; + name: string; +} + +interface Media { + media_url: string; + media_type: string; +} + +interface User { + user_id: number; + nickname: string; + profile_image_url: string | null; +} + +export interface TipItem { + tips_id: number; + title: string; + content: string; + created_at: string; + media: Media[]; + hashtags: { hashtag: Hashtag }[]; + user: User; + _count: { + likes: number; + saves: number; + }; +} + +interface PostDetailProps { + detail: TipItem; +} + +const PostDetail: React.FC = ({ detail }) => { + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + if (!detail) { + return ํ•ด๋‹น ํฌ์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.; + } + + return ( + + {detail.media.length > 0 && ๊ฒŒ์‹œ๋ฌผ ์ด๋ฏธ์ง€} + + {detail.title} + + + + + + + + {detail.user.nickname} + + + + BEST ๊ฟ€ํŒ ์„ ์ • ํšŸ์ˆ˜ + + + 0 ํšŒ + + + + + + {detail.hashtags.map((tag, index) => ( + + ))} + + + + + {detail.created_at.slice(0, 10)} + + + + + + {detail.content} + + + + ); +}; + +export default PostDetail; + +const PostView = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 32px; + align-self: stretch; +`; + +const Img = styled.img` + width: 80vw; + height: 360px; + border-radius: 20px; + object-fit: cover; + background: #d9d9d9; +`; + +const PostInfo = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-start; + align-self: stretch; +`; + +const InfoDetail = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 32px; +`; + +const Author = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 19px; +`; + +const ProfileImg = styled.img` + width: 80px; + height: 80px; + border-radius: 50px; + object-fit: cover; + background: #d9d9d9; +`; + +const AuthorInfo = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; +`; + +const Bestnum = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const Tags = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +const PostDate = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const ContentWrapper = styled.div` + white-space: pre-wrap; + word-wrap: break-word; +`; From 001a38fc034a1793456e73ecbc25b0e27bf28221 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:02:39 +0900 Subject: [PATCH 19/40] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{DetailPage_componenets => components}/CommentView.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename umc-master/src/pages/saveTip/{DetailPage_componenets => components}/CommentView.tsx (100%) diff --git a/umc-master/src/pages/saveTip/DetailPage_componenets/CommentView.tsx b/umc-master/src/pages/saveTip/components/CommentView.tsx similarity index 100% rename from umc-master/src/pages/saveTip/DetailPage_componenets/CommentView.tsx rename to umc-master/src/pages/saveTip/components/CommentView.tsx From e7660d942564de13b3dc3b1ff3328d68f91b7797 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:34:00 +0900 Subject: [PATCH 20/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=BF=80=ED=8C=81=20=EB=B0=98=ED=99=98=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/tipApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/umc-master/src/apis/tipApi.ts b/umc-master/src/apis/tipApi.ts index a2c1102..41bef19 100644 --- a/umc-master/src/apis/tipApi.ts +++ b/umc-master/src/apis/tipApi.ts @@ -57,8 +57,8 @@ export const createPost = async (newPost: NewPost): Promise => { export const getSavedTips = async () => { try { const { data } = await axiosInstance.get(`/users/saved-tips`); - console.log('์ €์žฅ๋œ ๊ฟ€ํŒ API ์‘๋‹ต:', data); - return data; + console.log('์ €์žฅ๋œ ๊ฟ€ํŒ API ์‘๋‹ต:', data.result); + return data.result; } catch (error: any) { console.error('์ €์žฅ๋œ ๊ฟ€ํŒ API ์—๋Ÿฌ ๋ฐœ์ƒ:', error.response?.status, error.response?.data); throw new Error(`์ €์žฅ๋œ ๊ฟ€ํŒ API ์š”์ฒญ ์‹คํŒจ: ${error.response?.status}`); From fd3eab590028dc2fb2a013491667de0b397408e9 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:34:40 +0900 Subject: [PATCH 21/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20modify=20width=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/components/Card/Card.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/umc-master/src/components/Card/Card.tsx b/umc-master/src/components/Card/Card.tsx index 5bc0e33..f99add1 100644 --- a/umc-master/src/components/Card/Card.tsx +++ b/umc-master/src/components/Card/Card.tsx @@ -40,8 +40,7 @@ const CardImageWrapper = styled.div` `; const CardImage = styled.img` - min-width: 240px; - width: 100%; + width: 240px; height: 200px; object-fit: cover; `; From c028fe95e4e277add465ecec02817f36c876e56a Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:35:08 +0900 Subject: [PATCH 22/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20remove=20text=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/saveTip/SaveTipDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index aa71ec0..7c09902 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -10,7 +10,7 @@ const SaveTipDetailPage: React.FC = () => { const { data: detail, isLoading, error } = useTipDetail(Number(tipId)); console.log('๊ฟ€ํŒ ์ƒ์„ธ', detail); - if (isLoading) return
๋กœ๋”ฉ ์ค‘...
; + if (isLoading) return; if (error) return
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
; if (!detail) return
๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
; From 374cd73ca5ae9ea217360a8026d4262167a9af54 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Tue, 18 Feb 2025 03:35:32 +0900 Subject: [PATCH 23/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=9C=20=EA=BF=80=ED=8C=81=20api=20=EC=97=B0=EA=B2=B0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/saveTip/SaveTipPage.tsx | 106 ++++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/umc-master/src/pages/saveTip/SaveTipPage.tsx b/umc-master/src/pages/saveTip/SaveTipPage.tsx index eda9a96..2641d3b 100644 --- a/umc-master/src/pages/saveTip/SaveTipPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipPage.tsx @@ -1,30 +1,25 @@ -import Card from "@components/Card/Card"; -import Typography from "@components/common/typography"; -import styled, { useTheme } from "styled-components"; -import { useCallback, useEffect, useRef } from "react"; -import SkeletonCard from "@components/Skeleton/SkeletonCard"; -import { useNavigate } from "react-router-dom"; -import { recentStore } from "@store/recentStore"; -import { useSaveTipList } from "@apis/queries/useSaveTipQueries"; +import Card from '@components/Card/Card'; +import Typography from '@components/common/typography'; +import styled, { useTheme } from 'styled-components'; +import { useCallback, useEffect, useRef } from 'react'; +import SkeletonCard from '@components/Skeleton/SkeletonCard'; +import { useNavigate } from 'react-router-dom'; +import { recentStore } from '@store/recentStore'; +import { useSaveTipList } from '@apis/queries/useSaveTipQueries'; const PAGE_SIZE = 5; +const placeholderImg = 'https://via.placeholder.com/150'; const SaveTipPage: React.FC = () => { - const theme = useTheme(); const { addRecentTip } = recentStore(); const navigate = useNavigate(); const observerRef = useRef(null); const lastElementRef = useRef(null); - const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - } = useSaveTipList(); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useSaveTipList(); - const loadMoredata = useCallback(() => { + const loadMoreData = useCallback(() => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); } @@ -33,61 +28,72 @@ const SaveTipPage: React.FC = () => { useEffect(() => { if (!hasNextPage) return; - if(observerRef.current) observerRef.current.disconnect(); + if (observerRef.current) observerRef.current.disconnect(); observerRef.current = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { - loadMoredata(); + loadMoreData(); } }); if (lastElementRef.current) observerRef.current.observe(lastElementRef.current); return () => observerRef.current?.disconnect(); - }, [hasNextPage, loadMoredata]); + }, [hasNextPage, loadMoreData]); + + const handleCardClick = (tipId: number) => { + console.log('๐Ÿ–ฑ๏ธ ํด๋ฆญํ•œ tipId:', tipId); + const clickedTip = data?.pages.flatMap((page) => page).find((item) => item.tipId === tipId); + console.log('๐Ÿ” ์ฐพ์€ ํŒ ๋ฐ์ดํ„ฐ:', clickedTip); - const handleCardClick = (id: string) => { - // ํด๋ฆญ ์‹œ ํŒ์„ ์ตœ๊ทผ์— ๋ณธ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ - const clickedTip = data?.pages.flatMap((page) => page.tips).find((item) => item.id === id); if (clickedTip) { addRecentTip(clickedTip); } - - navigate(`/save-tip/${id}`); // ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + navigate(`/save-tip/${tipId}`); }; + if (isLoading) { + return; + } + return ( - ์ €์žฅํ•œ ๊ฟ€ํŒ + + ์ €์žฅํ•œ ๊ฟ€ํŒ + {data?.pages.length === 0 && !isFetchingNextPage ? ( ์ตœ๊ทผ ๋ณธ ๊ฟ€ํŒ์ด ์—†์Šต๋‹ˆ๋‹ค. ) : ( - {data?.pages.flatMap((page) => page.tips).map((item) => ( - handleCardClick(item.id)} - /> - ))} - + {data?.pages + .flatMap((page) => page) + .filter(Boolean) + .map((item) => { + console.log('๐Ÿ” ๊ฐœ๋ณ„ ์•„์ดํ…œ ํ™•์ธ:', item); + return ( + 0 + ? item.imageUrls[0]?.media_url + : placeholderImg + } + text={item.title} + likes={item.likes ?? 0} + bookmarks={item.bookmarks ?? 0} + date={item.createdAt.slice(0, 10)} + onClick={() => handleCardClick(item.tipId)} + /> + ); + })} + {/* ๋งˆ์ง€๋ง‰ ์š”์†Œ ๊ฐ์ง€์šฉ div */} - {hasNextPage && !isFetchingNextPage &&
} + {hasNextPage && !isFetchingNextPage &&
} {/* ์Šค์ผˆ๋ ˆํ†ค UI */} - {isFetchingNextPage && - Array.from({ length: PAGE_SIZE }).map((_, index) => ( - - )) - } + {isFetchingNextPage && + Array.from({ length: PAGE_SIZE }).map((_, index) => )} )} @@ -105,8 +111,8 @@ const Container = styled.div` align-items: center; padding-top: 80px; padding-bottom: 100px; - background: #FFF; -` + background: #fff; +`; const SavedTips = styled.div` display: flex; @@ -114,7 +120,7 @@ const SavedTips = styled.div` flex-direction: column; align-items: center; gap: 40px; -` +`; const TipCardList = styled.div` display: flex; @@ -123,4 +129,4 @@ const TipCardList = styled.div` align-self: stretch; flex-wrap: wrap; cursor: pointer; -` \ No newline at end of file +`; From 016b03153783479e4a8862952c82bd6f89533144 Mon Sep 17 00:00:00 2001 From: rael Date: Wed, 19 Feb 2025 04:57:12 +0900 Subject: [PATCH 24/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20[#76]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saveTip/components/FloatingToggleBtn.tsx | 71 ++++++++++++------- umc-master/src/types/global.d.ts | 8 +++ 2 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 umc-master/src/types/global.d.ts diff --git a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx index fd2fa4f..65eaf47 100644 --- a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx +++ b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/prop-types */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; import theme from '@styles/theme'; import Typography from '@components/common/typography'; @@ -40,32 +40,53 @@ const FloatingToggleBtn: React.FC = ({ tipId, initialLik setSaved(!saved); }; + const realUrl = "https://umc-master-frontend.vercel.app"; // ์‹ค์ œ URL์„ ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜์„ธ์š” + // const realUrl = window.location.href; // ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์˜ URL + const loadKakaoSDK = () => { + const script = document.createElement("script"); + script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js"; + script.integrity = import.meta.env.VITE_INTEGRITY_VALUE; // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ + script.crossOrigin = "anonymous"; + script.onload = () => { + console.log("Kakao SDK ๋กœ๋“œ ์™„๋ฃŒ"); + }; + document.head.appendChild(script); + }; + + loadKakaoSDK(); + + useEffect(() => { + if (!window.Kakao) return; + if (!window.Kakao.isInitialized()) { + window.Kakao.init(`${import.meta.env.VITE_JAVASCRIPT_KEY}`); // ์—ฌ๊ธฐ์— ์นด์นด์˜ค ์•ฑ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š” + } + }, []); const shareKakao = () => { - const Kakao = (window as any).Kakao; - if (!Kakao) { - console.error('Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.'); - return; - } - - Kakao.Share.sendDefault({ - objectType: 'feed', - content: { - title: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ', - description: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?', - imageUrl: 'https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg', - link: { - mobileWebUrl: window.location.href, - }, - }, - buttons: [ - { - title: '๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ', - link: { - mobileWebUrl: window.location.href, + + if (!window.Kakao) { + console.error("Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); + return; + } + window.Kakao.Share.sendDefault({ + objectType: "feed", + content: { + title: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ", + description: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?", + imageUrl: + "https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg", + link: { + mobileWebUrl: realUrl, + }, }, - }, - ], - }); + buttons: [ + { + title: "๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ", + link: { + mobileWebUrl: realUrl, + }, + }, + ], + }); }; return ( diff --git a/umc-master/src/types/global.d.ts b/umc-master/src/types/global.d.ts new file mode 100644 index 0000000..47b4956 --- /dev/null +++ b/umc-master/src/types/global.d.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare global { + interface Window { + Kakao: any; + } + } + export {}; + \ No newline at end of file From 2da2c199c86e70018627938b83b1f927a73dcdab Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 11:41:19 +0900 Subject: [PATCH 25/40] =?UTF-8?q?=F0=9F=92=84=20UI:=20cursor=20pointer=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20[#76]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/auth/Login_components/InputForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/umc-master/src/pages/auth/Login_components/InputForm.tsx b/umc-master/src/pages/auth/Login_components/InputForm.tsx index 0d6d7f1..2204ddb 100644 --- a/umc-master/src/pages/auth/Login_components/InputForm.tsx +++ b/umc-master/src/pages/auth/Login_components/InputForm.tsx @@ -195,6 +195,7 @@ const StyledCheckbox = styled.input` const StyledTypography = styled(Typography)` color: ${({ theme }) => theme.colors.text.gray}; + cursor: pointer; `; const Options = styled.div` From 6ccb4d10dbf2ba5c30bb2e1bc278ecd1a8610393 Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 17:14:05 +0900 Subject: [PATCH 26/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=9E=85=EB=A0=A5=20=ED=8F=BC=20api=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/authApi.ts | 19 ++++ .../auth/Signup_components/EmailForm.tsx | 101 ++++++++++++++++-- 2 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 umc-master/src/apis/authApi.ts diff --git a/umc-master/src/apis/authApi.ts b/umc-master/src/apis/authApi.ts new file mode 100644 index 0000000..706f449 --- /dev/null +++ b/umc-master/src/apis/authApi.ts @@ -0,0 +1,19 @@ +import axiosInstance from '@apis/axios-instance'; + +interface UserSignup { + email: string; + password: string; + nickname: string; + hashtag: string[]; +} + +export const postEmail = async ({ email, password, nickname, hashtag } : UserSignup) => { + const { data } = await axiosInstance.post(`/signup`, { + email, + password, + nickname, + hashtag, + }); + return data; +}; + diff --git a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx index 113d0b2..0cb9822 100644 --- a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx @@ -1,4 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable react/prop-types */ +import axiosInstance from "@apis/axios-instance"; import Button from "@components/Button/Button"; import Typography from "@components/common/typography"; import Input from "@components/Input/Input"; @@ -6,6 +8,7 @@ import { useEffect, useState } from "react"; import styled, { useTheme } from "styled-components"; const emails = [ + { value: "", label: "์„ ํƒ" }, { value: "naver.com", label: "naver.com" }, { value: "daum.net", label: "daum.net" }, { value: "gmail.com", label: "gmail.com" }, @@ -14,21 +17,77 @@ const emails = [ { value: "outlook.com", label: "outlook.com" }, ]; -const EmailForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = ({ onCheckRequired }) => { +const EmailForm: React.FC<{ + onCheckRequired: (isValid: boolean) => void, + onEmailChange: (email: string) => void +}> = ({ onCheckRequired, onEmailChange }) => { const theme = useTheme(); - const [email, setEmail] = useState(''); const [authCode, setAuthCode] = useState(''); + const [localPart, setLocalPart] = useState(""); + const [domain, setDomain] = useState(""); + const [emailSent, setEmailSent] = useState(false); + const [verified, setVerified] = useState(false); + const [timer, setTimer] = useState(180); + const [retryEnabled, setRetryEnabled] = useState(false); + + const fullEmail = localPart && domain ? `${localPart}@${domain}` : ""; // ์ด๋ฉ”์ผ๊ณผ ์ธ์ฆ๋ฒˆํ˜ธ ์ž…๋ ฅ์ด ๋ชจ๋‘ ์ฑ„์›Œ์กŒ๋Š”์ง€ ํ™•์ธํ•˜๋Š” useEffect useEffect(() => { - if (email && authCode) { + if (verified) { onCheckRequired(true); // ์œ ํšจ์„ฑ ์ฒดํฌ } else { onCheckRequired(false); // ์œ ํšจ์„ฑ ์ฒดํฌ } - }, [email, authCode, onCheckRequired]); + }, [verified, onCheckRequired]); + + useEffect(() => { + let timerInterval: NodeJS.Timeout | undefined; + if (emailSent && timer > 0) { + timerInterval = setInterval(() => { + setTimer((prev) => prev - 1); + }, 1000); + } else if (timer === 0) { + setRetryEnabled(true); + } + return () => { + if (timerInterval) { + clearInterval(timerInterval); + } + }; + }, [emailSent, timer]); + + const handleEmailRequest = async () => { + if (!localPart || !domain) { + alert("์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + return; + } + try { + await axiosInstance.post("/auth/send-verification-email", { email: fullEmail }); + setEmailSent(true); + alert("์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋ฐœ์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + } catch (error) { + alert("์ด๋ฉ”์ผ ์ธ์ฆ ์š”์ฒญ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + }; + + const handleEmailChange = (e: React.ChangeEvent) => { + const email = e.target.value; + setLocalPart(email); + onEmailChange(email); + }; + + const handleRetry = () => { + setLocalPart(""); + setDomain(""); + setAuthCode(""); + setEmailSent(false); + setVerified(false); + setTimer(180); + setRetryEnabled(false); + }; return ( @@ -38,24 +97,30 @@ const EmailForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = ({ >์ด๋ฉ”์ผ ์ž…๋ ฅ (ํ•„์ˆ˜) * setEmail(e.target.value)} + value={localPart} + onChange={handleEmailChange} + disabled={emailSent} /> @ - + setDomain(e.target.value)} + disabled={emailSent} + > {emails.map((email) => ( ))} - + void }> = ({ /> + {emailSent && ( + +

๋‚จ์€ ์‹œ๊ฐ„: {Math.floor(timer / 60)}๋ถ„ {timer % 60}์ดˆ

+ +
+ )}
); }; @@ -119,4 +192,12 @@ const EmailSelect = styled.select` background-color: ${({ theme }) => theme.colors.text.white}; color: ${({ theme }) => theme.colors.text.black}; } +` + +const Timer = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 275px; + margin-top: 10px; ` \ No newline at end of file From 949fb71dff55bd7ef690606bd7ed98e81ae7bc26 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 17:29:52 +0900 Subject: [PATCH 27/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EB=B0=8F=20=EC=A0=80=EC=9E=A5=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/saveTip/SaveTipDetailPage.tsx | 8 +- .../saveTip/components/FloatingToggleBtn.tsx | 80 ++++++++++--------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index 7c09902..a8f5522 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -24,10 +24,10 @@ const SaveTipDetailPage: React.FC = () => { {/* TODO: ์œ ์ € ํ† ๊ธ€ ์—ฌ๋ถ€ - ์ผ๋‹จ false๋กœ ์ „๋‹ฌ*/} ); diff --git a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx index 65eaf47..0a415f3 100644 --- a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx +++ b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx @@ -18,12 +18,18 @@ interface FloatingToggleBtnProps { userSaved: boolean; } -const FloatingToggleBtn: React.FC = ({ tipId, initialLikes, initialSaves }) => { +const FloatingToggleBtn: React.FC = ({ + tipId, + initialLikes, + initialSaves, + userLiked, + userSaved, +}) => { //TODO: ์œ ์ € ์ถ”๊ฐ€ ์—ฌ๋ถ€ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ์ฃผ๋ฉด ์ถ”๊ฐ€ ์˜ˆ์ • const [likes, setLikes] = useState(initialLikes); - const [liked, setLiked] = useState(false); + const [liked, setLiked] = useState(userLiked); const [saves, setSaves] = useState(initialSaves); - const [saved, setSaved] = useState(false); + const [saved, setSaved] = useState(userSaved); const { mutate: toggleLike } = useToggleLike(tipId); const { mutate: toggleBookmark } = useToggleBookmark(tipId); @@ -40,53 +46,51 @@ const FloatingToggleBtn: React.FC = ({ tipId, initialLik setSaved(!saved); }; - const realUrl = "https://umc-master-frontend.vercel.app"; // ์‹ค์ œ URL์„ ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜์„ธ์š” + const realUrl = 'https://umc-master-frontend.vercel.app'; // ์‹ค์ œ URL์„ ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜์„ธ์š” // const realUrl = window.location.href; // ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์˜ URL const loadKakaoSDK = () => { - const script = document.createElement("script"); - script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js"; + const script = document.createElement('script'); + script.src = 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js'; script.integrity = import.meta.env.VITE_INTEGRITY_VALUE; // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ - script.crossOrigin = "anonymous"; + script.crossOrigin = 'anonymous'; script.onload = () => { - console.log("Kakao SDK ๋กœ๋“œ ์™„๋ฃŒ"); + console.log('Kakao SDK ๋กœ๋“œ ์™„๋ฃŒ'); }; document.head.appendChild(script); }; - + loadKakaoSDK(); - + useEffect(() => { - if (!window.Kakao) return; - if (!window.Kakao.isInitialized()) { - window.Kakao.init(`${import.meta.env.VITE_JAVASCRIPT_KEY}`); // ์—ฌ๊ธฐ์— ์นด์นด์˜ค ์•ฑ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š” - } + if (!window.Kakao) return; + if (!window.Kakao.isInitialized()) { + window.Kakao.init(`${import.meta.env.VITE_JAVASCRIPT_KEY}`); // ์—ฌ๊ธฐ์— ์นด์นด์˜ค ์•ฑ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š” + } }, []); const shareKakao = () => { - - if (!window.Kakao) { - console.error("Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); - return; - } - window.Kakao.Share.sendDefault({ - objectType: "feed", - content: { - title: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ", - description: "์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?", - imageUrl: - "https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg", - link: { - mobileWebUrl: realUrl, - }, + if (!window.Kakao) { + console.error('Kakao SDK๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.'); + return; + } + window.Kakao.Share.sendDefault({ + objectType: 'feed', + content: { + title: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ', + description: '์˜ค๋Š˜์˜ ๊ฟ€ํŒ์„ ๋ณด๋Ÿฌ ๊ฐˆ๊นŒ์š”?', + imageUrl: 'https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg', + link: { + mobileWebUrl: realUrl, + }, + }, + buttons: [ + { + title: '๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ', + link: { + mobileWebUrl: realUrl, }, - buttons: [ - { - title: "๋‚˜๋„ ๊ฟ€ํŒ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ", - link: { - mobileWebUrl: realUrl, - }, - }, - ], - }); + }, + ], + }); }; return ( From 6d237ce96fa656a1942fdd3197d117cf950d95b7 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 17:30:39 +0900 Subject: [PATCH 28/40] =?UTF-8?q?=F0=9F=92=AC=20comment:=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=82=AD=EC=A0=9C=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/saveTip/SaveTipDetailPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx index a8f5522..bd694ca 100644 --- a/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipDetailPage.tsx @@ -21,7 +21,6 @@ const SaveTipDetailPage: React.FC = () => { - {/* TODO: ์œ ์ € ํ† ๊ธ€ ์—ฌ๋ถ€ - ์ผ๋‹จ false๋กœ ์ „๋‹ฌ*/} Date: Thu, 20 Feb 2025 18:16:52 +0900 Subject: [PATCH 29/40] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20DTO=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/saveTip/components/PostDetail.tsx | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/umc-master/src/pages/saveTip/components/PostDetail.tsx b/umc-master/src/pages/saveTip/components/PostDetail.tsx index b719a3b..c9e1460 100644 --- a/umc-master/src/pages/saveTip/components/PostDetail.tsx +++ b/umc-master/src/pages/saveTip/components/PostDetail.tsx @@ -5,35 +5,29 @@ import theme from '@styles/theme'; import Typography from '@components/common/typography'; import Tag from '@components/Tag/Tag'; import ProfileDefault from '@assets/gray-character.png'; - -interface Hashtag { - hashtag_id: number; - name: string; -} - interface Media { - media_url: string; - media_type: string; + mediaUrl: string; + mediaType: string; } interface User { - user_id: number; + userId: number; nickname: string; - profile_image_url: string | null; + profileImageUrl: string | null; } export interface TipItem { - tips_id: number; + tipId: number; title: string; content: string; - created_at: string; + createdAt: string; media: Media[]; - hashtags: { hashtag: Hashtag }[]; + hashtags: []; user: User; - _count: { - likes: number; - saves: number; - }; + likesCount: number; + savesCount: number; + isLiked: boolean; + isBookmarked: boolean; } interface PostDetailProps { @@ -51,14 +45,14 @@ const PostDetail: React.FC = ({ detail }) => { return ( - {detail.media.length > 0 && ๊ฒŒ์‹œ๋ฌผ ์ด๋ฏธ์ง€} + {detail.media.length > 0 && ๊ฒŒ์‹œ๋ฌผ ์ด๋ฏธ์ง€} {detail.title} - + {detail.user.nickname} @@ -75,13 +69,13 @@ const PostDetail: React.FC = ({ detail }) => { {detail.hashtags.map((tag, index) => ( - + ))} - {detail.created_at.slice(0, 10)} + {detail.createdAt.slice(0, 10)} From d53bceb82c1b21377a3aed1dbf98e1029d3a5ca0 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 20:08:32 +0900 Subject: [PATCH 30/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?api=20=EC=97=B0=EA=B2=B0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/commentApi.ts | 10 ++-- .../src/apis/queries/useCommentMutations.ts | 49 +++++++++++++++++++ .../src/apis/queries/useCommentQueries.ts | 9 ++++ 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 umc-master/src/apis/queries/useCommentMutations.ts create mode 100644 umc-master/src/apis/queries/useCommentQueries.ts diff --git a/umc-master/src/apis/commentApi.ts b/umc-master/src/apis/commentApi.ts index e77b49a..4de38d8 100644 --- a/umc-master/src/apis/commentApi.ts +++ b/umc-master/src/apis/commentApi.ts @@ -1,12 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import axiosInstance from "./axios-instance"; +import axiosInstance from './axios-instance'; -export const getComments = async (tipId: string, page: number = 1, limit: number = 10) => { +export const getComments = async (tipId: number) => { try { - const { data } = await axiosInstance.get(`/tips/${tipId}/comments`, { - params: { page, limit }, - }); - return data; + const { data } = await axiosInstance.get('/comments'); + return data.result.filter((comment: any) => comment.tips_id === tipId); } catch (error: any) { console.error('๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ:', error); throw new Error('๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); diff --git a/umc-master/src/apis/queries/useCommentMutations.ts b/umc-master/src/apis/queries/useCommentMutations.ts new file mode 100644 index 0000000..a464174 --- /dev/null +++ b/umc-master/src/apis/queries/useCommentMutations.ts @@ -0,0 +1,49 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { getComments, addComment, editComment, deleteComment } from '@apis/commentApi'; +import { useUserStore } from '@store/userStore'; + +export const useAddComment = (tipId: number) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (comment: string) => addComment(tipId.toString(), comment), + onSuccess: () => { + queryClient.invalidateQueries(['comments', tipId]); + }, + }); +}; + +export const useDeleteComment = (tipId: number) => { + const queryClient = useQueryClient(); + const { user } = useUserStore(); + return useMutation({ + mutationFn: async (commentId: number) => { + const comments = await getComments(tipId); + const comment = comments.find((c: any) => c.comment_id === commentId); + if (comment?.user.user_id !== user?.user_id) { + throw new Error('๋ณธ์ธ์˜ ๋Œ“๊ธ€๋งŒ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'); + } + return deleteComment(tipId.toString(), commentId.toString()); + }, + onSuccess: () => { + queryClient.invalidateQueries(['comments', tipId]); + }, + }); +}; + +export const useUpdateComment = (tipId: number) => { + const queryClient = useQueryClient(); + const { user } = useUserStore(); + return useMutation({ + mutationFn: async ({ commentId, newComment }: { commentId: number; newComment: string }) => { + const comments = await getComments(tipId); + const comment = comments.find((c: any) => c.comment_id === commentId); + if (comment?.user.user_id !== user?.user_id) { + throw new Error('๋ณธ์ธ์˜ ๋Œ“๊ธ€๋งŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'); + } + return editComment(tipId.toString(), commentId.toString(), newComment); + }, + onSuccess: () => { + queryClient.invalidateQueries(['comments', tipId]); + }, + }); +}; diff --git a/umc-master/src/apis/queries/useCommentQueries.ts b/umc-master/src/apis/queries/useCommentQueries.ts new file mode 100644 index 0000000..120b530 --- /dev/null +++ b/umc-master/src/apis/queries/useCommentQueries.ts @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query'; +import { getComments } from '@apis/commentApi'; + +export const useComments = (tipId: number) => { + return useQuery({ + queryKey: ['comments', tipId], + queryFn: () => getComments(tipId), + }); +}; From 738c8537e9e47b3cdff7f73c690b27e164aff8ba Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 20:09:08 +0900 Subject: [PATCH 31/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/saveTip/components/CommentView.tsx | 325 ++++++++---------- 1 file changed, 136 insertions(+), 189 deletions(-) diff --git a/umc-master/src/pages/saveTip/components/CommentView.tsx b/umc-master/src/pages/saveTip/components/CommentView.tsx index 6c99704..8e09ab7 100644 --- a/umc-master/src/pages/saveTip/components/CommentView.tsx +++ b/umc-master/src/pages/saveTip/components/CommentView.tsx @@ -1,257 +1,204 @@ -/* eslint-disable react/prop-types */ -import Typography from "@components/common/typography"; -import { useCallback, useEffect, useRef, useState } from "react"; -// import { useParams } from "react-router-dom"; -import styled, { useTheme } from "styled-components"; -import { generateComments } from "../dummydata/dummydata"; -import SkeletonComment from "@components/Skeleton/SkeletonComment"; +import { useState, useRef, useEffect, useCallback } from 'react'; +import { useParams } from 'react-router-dom'; +import { useTheme } from 'styled-components'; +import { useComments } from '@apis/queries/useCommentQueries'; +import { useAddComment, useDeleteComment, useUpdateComment } from '@apis/queries/useCommentMutations'; +import { useUserStore } from '@store/userStore'; +import Typography from '@components/common/typography'; +import SkeletonComment from '@components/Skeleton/SkeletonComment'; +import styled from 'styled-components'; +import ProfileDefault from '@assets/gray-character.png'; - -const commentCount = 1000; // ์‹ค์ œ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ฌ ๊ฐ’ -const formattedNumber = new Intl.NumberFormat().format(commentCount); - -const MAX_LENGTH = 100; const COMMENTS_PER_LOAD = 3; -const CommentText: React.FC<{ text: string }> = ({ text }) => { - const [isExpanded, setIsExpanded] = useState(false); - const shouldShowMore = text.length > MAX_LENGTH; - const theme = useTheme(); - - return ( -
- - {isExpanded ? text : `${text.slice(0, MAX_LENGTH)}`} - - {shouldShowMore && ( - - )} -
- ); - }; - const CommentView: React.FC = () => { - const theme = useTheme(); - // const { tipId } = useParams<{ tipId: string }>(); + const { tipId } = useParams<{ tipId: string }>(); + const { user } = useUserStore(); + + const { data: comments, isLoading, error } = useComments(Number(tipId)); + const addCommentMutation = useAddComment(Number(tipId)); + const deleteCommentMutation = useDeleteComment(Number(tipId)); + const updateCommentMutation = useUpdateComment(Number(tipId)); + + const [inputValue, setInputValue] = useState(''); + const [visibleComments, setVisibleComments] = useState(COMMENTS_PER_LOAD); + const [editMode, setEditMode] = useState<{ [key: number]: boolean }>({}); + const [editedComment, setEditedComment] = useState<{ [key: number]: string }>({}); - const comment = generateComments(1300); - - const [comments, setComments] = useState<{ author: string; date: string; time: string; comment: string }[]>(comment.slice(0, COMMENTS_PER_LOAD * 2)); - const [inputValue, setInputValue] = useState(""); - const [isLoading, setIsLoading] = useState(false); // ๋กœ๋”ฉ ์ƒํƒœ ๊ด€๋ฆฌ - const [hasMore, setHasMore] = useState(comment.length > COMMENTS_PER_LOAD); const observerRef = useRef(null); const lastElementRef = useRef(null); const handleAddComment = () => { if (inputValue.trim().length === 0) return; - setComments((prevComments) => [ - { - author: "๋‚ด์ด๋ฆ„", // ์˜ˆ์‹œ๋กœ ์ƒˆ ๋Œ“๊ธ€ ์ž‘์„ฑ์ž ์ง€์ • - date: "2025.02.02", // ์˜ˆ์‹œ๋กœ ์ƒˆ ๋Œ“๊ธ€ ์ž‘์„ฑ ๋‚ ์งœ ์ง€์ • - time: "2:43", // ์˜ˆ์‹œ๋กœ ์ƒˆ ๋Œ“๊ธ€ ์ž‘์„ฑ ์‹œ๊ฐ„ ์ง€์ • - comment: inputValue, // ์ƒˆ ๋Œ“๊ธ€ ๋‚ด์šฉ - }, - ...prevComments, - ]); - setInputValue(""); + addCommentMutation.mutate(inputValue, { + onSuccess: () => setInputValue(''), + }); }; - const loadMoreData = useCallback(() => { - if (isLoading || !hasMore) return; + const handleDeleteComment = (commentId: number) => { + deleteCommentMutation.mutate(commentId); + }; - setIsLoading(true); - setTimeout(() => { - const nextData = comment.slice(comments.length, comments.length + COMMENTS_PER_LOAD); - setComments((prevData) => { - const updatedComments = [...prevData, ...nextData]; - setHasMore(updatedComments.length < comment.length); // ์—ฌ๊ธฐ์„œ updatedComments.length ์‚ฌ์šฉ! - return updatedComments; - }); - setIsLoading(false); - }, 1000); - }, [isLoading, hasMore, comments.length]); + const handleEditToggle = (commentId: number, commentText: string) => { + setEditMode((prev) => ({ ...prev, [commentId]: true })); + setEditedComment((prev) => ({ ...prev, [commentId]: commentText })); + }; - useEffect(() => { - if (isLoading || !hasMore) return; + const handleEditSubmit = (commentId: number) => { + updateCommentMutation.mutate({ commentId, newComment: editedComment[commentId] }); + setEditMode((prev) => ({ ...prev, [commentId]: false })); + }; + + const loadMoreData = useCallback(() => { + setVisibleComments((prev) => prev + COMMENTS_PER_LOAD); + }, []); + useEffect(() => { + if (!comments || comments.length <= visibleComments) return; if (observerRef.current) observerRef.current.disconnect(); - observerRef.current = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting) { - loadMoreData(); - } - }, { threshold: 1.0 }); + observerRef.current = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + loadMoreData(); + } + }, + { threshold: 1.0 } + ); if (lastElementRef.current) observerRef.current.observe(lastElementRef.current); return () => observerRef.current?.disconnect(); - }, [isLoading, hasMore, loadMoreData]); + }, [comments, visibleComments, loadMoreData]); + + if (error) return ๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.; return ( - + - - <Typography - variant="headingXxxSmall" - style={{color: theme.colors.text.black}} - >๋Œ“๊ธ€</Typography> - <Typography - variant="titleXxxSmall" - style={{color: theme.colors.text.gray}} - >({formattedNumber})</Typography> - - ๋Œ“๊ธ€ ({comments?.length || 0}) + setInputValue(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleAddComment()} + onKeyDown={(e) => e.key === 'Enter' && handleAddComment()} /> - {comments.length === 0 && !isLoading ? ( - ์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. - ) : ( - - {comments.map((cmt, index) => ( - - - - - - {cmt.author} - - - - {cmt.date} - + + {isLoading + ? Array.from({ length: COMMENTS_PER_LOAD }).map((_, index) => ) + : comments.slice(0, visibleComments).map((cmt) => ( + + + + + {cmt.user.nickname} - {cmt.time} + {new Date(cmt.created_at).toLocaleDateString()}{' '} + {new Date(cmt.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - - - - - - ))} - - {/* ๋งˆ์ง€๋ง‰ ์š”์†Œ ๊ฐ์ง€์šฉ div */} - {hasMore && !isLoading &&
} - - {/* ์Šค์ผˆ๋ ˆํ†ค UI */} - {isLoading && ( - - {Array.from({ length: COMMENTS_PER_LOAD }).map((_, index) => ( - - ))} - - )} - + + {user?.user_id === cmt.user.user_id && ( + + handleEditToggle(cmt.comment_id, cmt.comment)}>์ˆ˜์ • + handleDeleteComment(cmt.comment_id)}>์‚ญ์ œ + + )} + + {editMode[cmt.comment_id] ? ( + setEditedComment({ ...editedComment, [cmt.comment_id]: e.target.value })} + onKeyDown={(e) => e.key === 'Enter' && handleEditSubmit(cmt.comment_id)} + /> + ) : ( + + {cmt.comment} + + )} + + ))} + {comments?.length > visibleComments &&
} - )} - + ); }; export default CommentView; -const Comment = styled.div` +const CommentContainer = styled.div` display: flex; + width: 100%; flex-direction: column; - align-items: flex-start; gap: 60px; - align-self: stretch; -` +`; const CommentAdd = styled.div` display: flex; flex-direction: column; - align-items: flex-start; - gap: 30px; - align-self: stretch; -` - -const Title = styled.div` - display: flex; - align-items: center; - align-self: stretch; - gap: 10px; -` + gap: 40px; +`; const StyledInput = styled.input` - display: flex; - height: 72px; - padding: 23px 32px; - align-items: center; - align-self: stretch; + padding: 26px 32px; + border: 1px solid ${({ theme }) => theme.colors.primary[400]}; border-radius: 20px; - border: 2px solid ${({ theme }) => theme.colors.primary[400]}; - background: ${({ theme }) => theme.colors.text.white}; - - color: ${({ theme }) => theme.colors.text.gray}; - - font-family: ${({ theme }) => theme.fontFamily.regular}; - font-size: ${({ theme }) => theme.typography.body.small.size}; - font-weight: ${({ theme }) => theme.typography.body.small.weight}; - line-height: ${({ theme }) => theme.typography.body.small.lineHeight}; - letter-spacing: -0.48px; - - &:focus { - outline: none; - border-color: ${({ theme }) => theme.colors.primary[500]}; - } -` + font-size: 14px; +`; const CommentList = styled.div` display: flex; flex-direction: column; - align-items: flex-start; gap: 40px; - align-self: stretch; -` +`; const CommentCard = styled.div` display: flex; flex-direction: column; - align-items: flex-start; - gap: 32px; - align-self: stretch; -` + padding-bottom: 10px; +`; -const Author = styled.div` +const CommentHeader = styled.div` display: flex; align-items: center; gap: 24px; -` +`; -const ProfileImg = styled.div` +const ProfileImg = styled.img` width: 60px; height: 60px; - border-radius: 30px; - background: #D9D9D9; -` + border-radius: 50%; +`; -const AuthorInfo = styled.div` +const CommentInfo = styled.div` display: flex; - width: 125px; flex-direction: column; - align-items: flex-start; - gap: 8px; -` +`; -const CommentDate = styled.div` +const EditDelete = styled.div` display: flex; - align-items: center; - gap: 8px; - align-self: stretch; -` - -const SkeletonWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 56px; -` \ No newline at end of file + gap: 5px; + margin-left: auto; +`; + +const EditText = styled.span` + cursor: pointer; + font-size: 12px; +`; + +const DeleteText = styled.span` + cursor: pointer; + font-size: 12px; +`; + +const StyledEditInput = styled.input` + margin-top: 16px; + padding: 10px; + width: 100%; + border: 1px solid ${({ theme }) => theme.colors.primary[400]}; + border-radius: 5px; + background: #f7f7f7; +`; From 2ceb0532f8b88ced5d16fb5416282623174b2558 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 20:48:53 +0900 Subject: [PATCH 32/40] =?UTF-8?q?=F0=9F=90=9B=20bug:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EA=B3=B5=EC=9C=A0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/saveTip/components/FloatingToggleBtn.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx index 0a415f3..e554dc0 100644 --- a/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx +++ b/umc-master/src/pages/saveTip/components/FloatingToggleBtn.tsx @@ -25,7 +25,6 @@ const FloatingToggleBtn: React.FC = ({ userLiked, userSaved, }) => { - //TODO: ์œ ์ € ์ถ”๊ฐ€ ์—ฌ๋ถ€ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ์ฃผ๋ฉด ์ถ”๊ฐ€ ์˜ˆ์ • const [likes, setLikes] = useState(initialLikes); const [liked, setLiked] = useState(userLiked); const [saves, setSaves] = useState(initialSaves); @@ -107,8 +106,8 @@ const FloatingToggleBtn: React.FC = ({ {saves} - - + + ๊ณต์œ ํ•˜๊ธฐ From 2aee74ed10ce84247e52a228b131c104aebd5691 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 21:01:42 +0900 Subject: [PATCH 33/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=86=8D=EC=84=B1=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/mypage/components/RecentTips.tsx | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/umc-master/src/pages/mypage/components/RecentTips.tsx b/umc-master/src/pages/mypage/components/RecentTips.tsx index a9c6066..59a56c6 100644 --- a/umc-master/src/pages/mypage/components/RecentTips.tsx +++ b/umc-master/src/pages/mypage/components/RecentTips.tsx @@ -6,44 +6,42 @@ import { recentStore } from '@store/recentStore'; import { useEffect } from 'react'; const RecentTips: React.FC = () => { - const theme = useTheme(); const navigate = useNavigate(); // zustand ์ƒํƒœ์—์„œ ์ตœ๊ทผ ํŒ ๊ฐ€์ ธ์˜ค๊ธฐ const { recentTips } = recentStore(); - + const handleCardClick = (id: string) => { navigate(`/save-tip/${id}`); // ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ }; useEffect(() => { // ์ฒ˜์Œ ๋ Œ๋”๋ง ์‹œ์— ์ตœ๊ทผ ๋ณธ ํŒ์ด ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— ์žˆ์œผ๋ฉด ๋ณต์›๋ฉ๋‹ˆ๋‹ค. - }, []); + }, []); return ( - ์ตœ๊ทผ์— ๋ณธ ๊ฟ€ํŒ + + ์ตœ๊ทผ์— ๋ณธ ๊ฟ€ํŒ + {recentTips.length === 0 ? ( ์ตœ๊ทผ ๋ณธ ๊ฟ€ํŒ์ด ์—†์Šต๋‹ˆ๋‹ค. ) : ( {recentTips.map((item) => ( - handleCardClick(item.id)} + handleCardClick(String(item.tipId))} /> ))} - + )} ); @@ -59,11 +57,11 @@ const RecentGoodTip = styled.div` align-items: flex-start; gap: 20px; flex-shrink: 0; -` +`; const TipCardList = styled.div` display: flex; align-items: center; gap: 30px; align-self: stretch; -` \ No newline at end of file +`; From 26027e78e178fa7c289b9d0aae7623fc6b262b84 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 21:04:47 +0900 Subject: [PATCH 34/40] =?UTF-8?q?=F0=9F=90=9B=20bug:=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/apis/queries/useCommentMutations.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/umc-master/src/apis/queries/useCommentMutations.ts b/umc-master/src/apis/queries/useCommentMutations.ts index a464174..09a8b18 100644 --- a/umc-master/src/apis/queries/useCommentMutations.ts +++ b/umc-master/src/apis/queries/useCommentMutations.ts @@ -2,12 +2,20 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { getComments, addComment, editComment, deleteComment } from '@apis/commentApi'; import { useUserStore } from '@store/userStore'; +interface Comment { + comment_id: number; + user: { + user_id: number; + }; + comment: string; +} + export const useAddComment = (tipId: number) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (comment: string) => addComment(tipId.toString(), comment), onSuccess: () => { - queryClient.invalidateQueries(['comments', tipId]); + queryClient.invalidateQueries({ queryKey: ['comments', tipId] }); }, }); }; @@ -17,15 +25,15 @@ export const useDeleteComment = (tipId: number) => { const { user } = useUserStore(); return useMutation({ mutationFn: async (commentId: number) => { - const comments = await getComments(tipId); - const comment = comments.find((c: any) => c.comment_id === commentId); + const comments: Comment[] = await getComments(tipId); + const comment = comments.find((c) => c.comment_id === commentId); if (comment?.user.user_id !== user?.user_id) { throw new Error('๋ณธ์ธ์˜ ๋Œ“๊ธ€๋งŒ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'); } return deleteComment(tipId.toString(), commentId.toString()); }, onSuccess: () => { - queryClient.invalidateQueries(['comments', tipId]); + queryClient.invalidateQueries({ queryKey: ['comments', tipId] }); }, }); }; @@ -35,15 +43,15 @@ export const useUpdateComment = (tipId: number) => { const { user } = useUserStore(); return useMutation({ mutationFn: async ({ commentId, newComment }: { commentId: number; newComment: string }) => { - const comments = await getComments(tipId); - const comment = comments.find((c: any) => c.comment_id === commentId); + const comments: Comment[] = await getComments(tipId); + const comment = comments.find((c) => c.comment_id === commentId); if (comment?.user.user_id !== user?.user_id) { throw new Error('๋ณธ์ธ์˜ ๋Œ“๊ธ€๋งŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'); } return editComment(tipId.toString(), commentId.toString(), newComment); }, onSuccess: () => { - queryClient.invalidateQueries(['comments', tipId]); + queryClient.invalidateQueries({ queryKey: ['comments', tipId] }); }, }); }; From 7805e5cfade82d734f5c8426454a0f8b6666cf96 Mon Sep 17 00:00:00 2001 From: Minji Kim Date: Thu, 20 Feb 2025 21:10:50 +0900 Subject: [PATCH 35/40] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=81=EC=9A=A9=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/queries/useCommentMutations.ts | 6 ++++-- umc-master/src/pages/saveTip/components/CommentView.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/umc-master/src/apis/queries/useCommentMutations.ts b/umc-master/src/apis/queries/useCommentMutations.ts index 09a8b18..e123d98 100644 --- a/umc-master/src/apis/queries/useCommentMutations.ts +++ b/umc-master/src/apis/queries/useCommentMutations.ts @@ -1,13 +1,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { getComments, addComment, editComment, deleteComment } from '@apis/commentApi'; import { useUserStore } from '@store/userStore'; - -interface Comment { +export interface Comment { comment_id: number; user: { user_id: number; + nickname: string; + profileImageUrl?: string | null; }; comment: string; + created_at: string; } export const useAddComment = (tipId: number) => { diff --git a/umc-master/src/pages/saveTip/components/CommentView.tsx b/umc-master/src/pages/saveTip/components/CommentView.tsx index 8e09ab7..20521d4 100644 --- a/umc-master/src/pages/saveTip/components/CommentView.tsx +++ b/umc-master/src/pages/saveTip/components/CommentView.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { useTheme } from 'styled-components'; import { useComments } from '@apis/queries/useCommentQueries'; -import { useAddComment, useDeleteComment, useUpdateComment } from '@apis/queries/useCommentMutations'; +import { Comment, useAddComment, useDeleteComment, useUpdateComment } from '@apis/queries/useCommentMutations'; import { useUserStore } from '@store/userStore'; import Typography from '@components/common/typography'; import SkeletonComment from '@components/Skeleton/SkeletonComment'; @@ -89,7 +89,7 @@ const CommentView: React.FC = () => { {isLoading ? Array.from({ length: COMMENTS_PER_LOAD }).map((_, index) => ) - : comments.slice(0, visibleComments).map((cmt) => ( + : comments.slice(0, visibleComments).map((cmt: Comment) => ( From ea55d650b3d397e92838aa7683169899584999b4 Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 21:44:08 +0900 Subject: [PATCH 36/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20api=20=EC=97=B0=EA=B2=B0=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/pages/auth/SignUpPage.tsx | 67 ++++++++++++------ .../auth/Signup_components/EmailForm.tsx | 1 + .../Signup_components/InterestForm copy.tsx | 69 +++++++++++++++++++ .../auth/Signup_components/InterestForm.tsx | 5 +- .../auth/Signup_components/PasswordForm.tsx | 15 +++- .../auth/Signup_components/PrivacyForm.tsx | 8 ++- 6 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx diff --git a/umc-master/src/pages/auth/SignUpPage.tsx b/umc-master/src/pages/auth/SignUpPage.tsx index e650b2e..06ee54c 100644 --- a/umc-master/src/pages/auth/SignUpPage.tsx +++ b/umc-master/src/pages/auth/SignUpPage.tsx @@ -9,44 +9,71 @@ import PrivacyForm from "./Signup_components/PrivacyForm"; import InterestForm from "./Signup_components/InterestForm"; import Button from "@components/Button/Button"; import { useNavigate } from "react-router-dom"; - +import { postEmail } from "@apis/authApi"; const SignUpPage: React.FC = () => { - const theme = useTheme(); - const navigate = useNavigate(); - const [sectionCount, setSectionCount] = useState(0); - const [isNextButtonEnabled, setIsNextButtonEnabled] = useState(false); +const theme = useTheme(); +const navigate = useNavigate(); +const [sectionCount, setSectionCount] = useState(0); +const [isNextButtonEnabled, setIsNextButtonEnabled] = useState(false); +const [email, setEmail] = useState(""); +const [password, setPassword] = useState(""); +const [nickname, setNickname] = useState(""); +const [hashtag, setHashtag] = useState([]); + +useEffect(() => { + setIsNextButtonEnabled(false); // ์„น์…˜์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” +}, [sectionCount]); + +const handleCheckRequired = (areRequiredChecked: boolean) => { + setIsNextButtonEnabled(areRequiredChecked); +} + +const handleEmailChange = (email: string) => { + setEmail(email); +}; + +const handlePasswordChange = (password: string) => { + setPassword(password); +}; + +const handleNicknameChange = (nickname: string) => { + setNickname(nickname); +}; - useEffect(() => { - setIsNextButtonEnabled(false); // ์„น์…˜์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” - }, [sectionCount]); - - const handleCheckRequired = (areRequiredChecked: boolean) => { - setIsNextButtonEnabled(areRequiredChecked); +const handleHashtagChange = (hashtags: string[]) => { + setHashtag(hashtags); +}; + +const handleSignUpComplete = async () => { + try { + const userSignupData = { email, password, nickname, hashtag }; + await postEmail(userSignupData); + navigate("/main"); + } catch (error) { + console.error("ํšŒ์›๊ฐ€์ž… ์˜ค๋ฅ˜:", error); + alert("ํšŒ์›๊ฐ€์ž…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”."); } +}; const renderSection = () => { switch (sectionCount) { case 0: return ; case 1: - return ; + return ; case 2: - return ; + return ; case 3: - return ; + return ; case 4: - return ; + return ; default: - return ; + return ; } }; - const handleSignUpComplete = () => { - navigate("/main"); - }; - return ( diff --git a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx index 0cb9822..5df18be 100644 --- a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx @@ -77,6 +77,7 @@ const EmailForm: React.FC<{ const email = e.target.value; setLocalPart(email); onEmailChange(email); + console.log("์ด๋ฉ”์ผ: ", email); }; const handleRetry = () => { diff --git a/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx b/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx new file mode 100644 index 0000000..3d69070 --- /dev/null +++ b/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx @@ -0,0 +1,69 @@ +/* eslint-disable react/prop-types */ +import Typography from "@components/common/typography"; +import CategoryInputSection from "@pages/main/components/CategoriesInputSection"; +import { useState } from "react"; +import { styled, useTheme } from "styled-components"; + + +const dummyCategories = [ + { section: '๊ณ„์ ˆ', tags: ['๋ด„', '์—ฌ๋ฆ„', '๊ฐ€์„', '๊ฒจ์šธ'] }, + { section: 'ํŒจ์…˜', tags: ['ํŒจ์…˜', '๋งจํˆฌ๋งจ', '๋‹ˆํŠธ', '๋ฐ”์ง€', '์น˜๋งˆ', '๋ธ”๋ผ์šฐ์Šค', '์ž์ผ“'] }, + { section: '์ฒญ์†Œ', tags: ['์ฒญ์†Œ', '๋ฐฉ', '์ •๋ฆฌ', '์ธํ…Œ๋ฆฌ์–ด', '๊ฐ€๊ตฌ', '์ฒญ์†Œ๋„๊ตฌ'] }, + { + section: '์š”๋ฆฌ / ์‹์žฌ๋ฃŒ', + tags: ['์š”๋ฆฌ', '์Œ์‹', '๋ณด๊ด€', '๋ƒ‰์žฅ', '๋ƒ‰๋™', '๋ฉด', '๋ฐฅ', '์ˆ ', '๋ฐ˜์ฐฌ', '๋ ˆ์‹œํ”ผ', '๋ƒ‰์žฅ๊ณ '], + }, + { section: '์žฌํ™œ์šฉ / ๋ถ„๋ฆฌ์ˆ˜๊ฑฐ', tags: ['์žฌํ™œ์šฉ', '๋ถ„๋ฆฌ์ˆ˜๊ฑฐ', '๋ฆฌํผ', 'ํ”Œ๋ผ์Šคํ‹ฑ', '์Šคํ‹ฐ๋กœํผ', '์ข…์ด', '์œ ๋ฆฌ'] }, + { section: '์ฃผ๊ฑฐ', tags: ['์ฃผํƒ', '์›๋ฃธ', '๋นŒ๋ผ', '์•„ํŒŒํŠธ', '๊ธฐ์ˆ™์‚ฌ'] }, +]; + +const InterestForm: React.FC<{ onHashtagChange: (hashtags: string[]) => void }> = ({ onHashtagChange }) => { + + const [selectedTags, setSelectedTags] = useState([]); + + const handleTagClick = (tag: string) => { + const isSelected = selectedTags.includes(tag); + const updatedTags = isSelected ? selectedTags.filter((t) => t !== tag) : [...selectedTags, tag]; + setSelectedTags(updatedTags); + onHashtagChange(updatedTags); // ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์—…๋ฐ์ดํŠธ + console.log("ํ•ด์‹œํƒœ๊ทธ: ", updatedTags); + }; + + const theme = useTheme(); + return ( + + ๊ด€์‹ฌ์‚ฌ (ํ•„์ˆ˜, 10๊ฐœ ์ด๋‚ด) * + + + + + ); +}; + +export default InterestForm; + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 30px; + align-self: stretch; +` + +const InterestEditForm = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 28px; + align-self: stretch; +` \ No newline at end of file diff --git a/umc-master/src/pages/auth/Signup_components/InterestForm.tsx b/umc-master/src/pages/auth/Signup_components/InterestForm.tsx index 24c23da..3d69070 100644 --- a/umc-master/src/pages/auth/Signup_components/InterestForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/InterestForm.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ import Typography from "@components/common/typography"; import CategoryInputSection from "@pages/main/components/CategoriesInputSection"; import { useState } from "react"; @@ -16,7 +17,7 @@ const dummyCategories = [ { section: '์ฃผ๊ฑฐ', tags: ['์ฃผํƒ', '์›๋ฃธ', '๋นŒ๋ผ', '์•„ํŒŒํŠธ', '๊ธฐ์ˆ™์‚ฌ'] }, ]; -const InterestForm: React.FC = () => { +const InterestForm: React.FC<{ onHashtagChange: (hashtags: string[]) => void }> = ({ onHashtagChange }) => { const [selectedTags, setSelectedTags] = useState([]); @@ -24,6 +25,8 @@ const InterestForm: React.FC = () => { const isSelected = selectedTags.includes(tag); const updatedTags = isSelected ? selectedTags.filter((t) => t !== tag) : [...selectedTags, tag]; setSelectedTags(updatedTags); + onHashtagChange(updatedTags); // ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์—…๋ฐ์ดํŠธ + console.log("ํ•ด์‹œํƒœ๊ทธ: ", updatedTags); }; const theme = useTheme(); diff --git a/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx b/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx index 3fd0414..0de5716 100644 --- a/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx @@ -5,7 +5,11 @@ import { useEffect, useState } from "react"; import { styled, useTheme } from "styled-components"; -const PasswordForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = ({ onCheckRequired }) => { +const PasswordForm: React.FC<{ + onCheckRequired: (isValid: boolean) => void; + onPasswordChange: (password: string) => void; +}> = ({ onCheckRequired, onPasswordChange }) => { + const theme = useTheme(); @@ -19,6 +23,13 @@ const PasswordForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = onCheckRequired(false); } }, [password, confirmPassword]); + + const handlePasswordChange = (e: React.ChangeEvent) => { + const newPassword = e.target.value; + setPassword(newPassword); + onPasswordChange(newPassword); // ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ + console.log("๋น„๋ฐ€๋ฒˆํ˜ธ: ", newPassword); + }; return ( @@ -31,7 +42,7 @@ const PasswordForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = type={'password'} placeholder={'๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ (์ˆซ์ž, ์˜๋ฌธ์ž, ๋ฌธ์ž ํฌํ•จ 8~15์ž ์ด๋‚ด)'} value={password} - onChange={(e) => setPassword(e.target.value)} + onChange={handlePasswordChange} /> void }> = ({ onCheckRequired }) => { +const PrivacyForm: React.FC<{ + onCheckRequired: (isValid: boolean) => void; + onNicknameChange: (nickname: string) => void; +}> = ({ onCheckRequired, onNicknameChange }) => { const [selectedCity, setSelectedCity] = useState("default"); const [districts, setDistricts] = useState([]); @@ -22,7 +25,8 @@ const PrivacyForm: React.FC<{ onCheckRequired: (isValid: boolean) => void }> = ( const handleNicknameChange = (e: React.ChangeEvent) => { const newNickname = e.target.value; setNickname(newNickname); - + onNicknameChange(newNickname); // ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์—…๋ฐ์ดํŠธ + console.log("๋‹‰๋„ค์ž„: ", newNickname); // ๋‹‰๋„ค์ž„์ด 0๊ธ€์ž ์ด์ƒ์ผ ๋•Œ๋งŒ "๋‹ค์Œ" ๋ฒ„ํŠผ์„ ํ™œ์„ฑํ™” onCheckRequired(newNickname.length > 0); }; From 13991e367d4bb154ec7422525267f536948da648 Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 21:44:45 +0900 Subject: [PATCH 37/40] =?UTF-8?q?=E2=9C=A8=20feat:=20userStore=20updatePro?= =?UTF-8?q?file=20=EC=B6=94=EA=B0=80=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/store/userStore.ts | 50 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/umc-master/src/store/userStore.ts b/umc-master/src/store/userStore.ts index 4039586..97b5625 100644 --- a/umc-master/src/store/userStore.ts +++ b/umc-master/src/store/userStore.ts @@ -1,28 +1,36 @@ import { create } from 'zustand'; import { getUsers } from '@apis/profileApi'; +import axiosInstance from '@apis/axios-instance'; interface User { - user_id: number; - email: string; - nickname: string; - city: string | null; - district: string | null; - profile_image_url: string | null; - provider: string; - providerId: string; - role: string; - status: string; - created_at: string; - updated_at: string; - last_login: string | null; - location_id: string | null; - } + user_id: number; + email: string; + nickname: string; + city: string | null; + district: string | null; + profile_image_url: string | null; + provider: string; + providerId: string; + role: string; + status: string; + created_at: string; + updated_at: string; + last_login: string | null; + location_id: string | null; + hashtags: string[]; +} +interface ProfileUpdateData { + nickname?: string; + city?: string; + district?: string; + hashtags?: string[]; +} interface UserState { user: User | null; fetchUser: () => Promise; + updateProfile: (profileData: ProfileUpdateData) => Promise; clearUser: () => void; - setProfileImageUrl: (imageUrl: string) => void; } export const useUserStore = create((set) => ({ @@ -46,6 +54,16 @@ export const useUserStore = create((set) => ({ } }, + updateProfile: async (profileData: { nickname?: string; city?: string; district?: string; hashtags?: string[] }) => { + try { + const response = await axiosInstance.put('/profile', profileData); + set({ user: response.data }); + } catch (error) { + console.error('Failed to update profile:', error); + throw error; + } + }, + clearUser: () => set({ user: null }), setProfileImageUrl: (imageUrl: string) => set((state) => { From 86fe45416dbeecfb26e115132d93e960ae64c841 Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 21:50:07 +0900 Subject: [PATCH 38/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/store/userStore.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/umc-master/src/store/userStore.ts b/umc-master/src/store/userStore.ts index 11c8616..069e578 100644 --- a/umc-master/src/store/userStore.ts +++ b/umc-master/src/store/userStore.ts @@ -28,6 +28,8 @@ interface ProfileUpdateData { interface UserState { user: User | null; + profileImageUrl: string; + setProfileImageUrl: (url: string) => void; fetchUser: () => Promise; updateProfile: (profileData: ProfileUpdateData) => Promise; clearUser: () => void; @@ -66,10 +68,6 @@ export const useUserStore = create((set) => ({ clearUser: () => set({ user: null }), - setProfileImageUrl: (imageUrl: string) => set((state) => { - if (state.user) { - state.user.profile_image_url = imageUrl; - } - return state - }) + profileImageUrl: "", + setProfileImageUrl: (url) => set({ profileImageUrl: url }), })); From b72562b8947c38179d0cbabb27478dba481932e6 Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 22:31:35 +0900 Subject: [PATCH 39/40] =?UTF-8?q?=EB=B3=B5=EC=A0=9C=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Signup_components/InterestForm copy.tsx | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx diff --git a/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx b/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx deleted file mode 100644 index 3d69070..0000000 --- a/umc-master/src/pages/auth/Signup_components/InterestForm copy.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable react/prop-types */ -import Typography from "@components/common/typography"; -import CategoryInputSection from "@pages/main/components/CategoriesInputSection"; -import { useState } from "react"; -import { styled, useTheme } from "styled-components"; - - -const dummyCategories = [ - { section: '๊ณ„์ ˆ', tags: ['๋ด„', '์—ฌ๋ฆ„', '๊ฐ€์„', '๊ฒจ์šธ'] }, - { section: 'ํŒจ์…˜', tags: ['ํŒจ์…˜', '๋งจํˆฌ๋งจ', '๋‹ˆํŠธ', '๋ฐ”์ง€', '์น˜๋งˆ', '๋ธ”๋ผ์šฐ์Šค', '์ž์ผ“'] }, - { section: '์ฒญ์†Œ', tags: ['์ฒญ์†Œ', '๋ฐฉ', '์ •๋ฆฌ', '์ธํ…Œ๋ฆฌ์–ด', '๊ฐ€๊ตฌ', '์ฒญ์†Œ๋„๊ตฌ'] }, - { - section: '์š”๋ฆฌ / ์‹์žฌ๋ฃŒ', - tags: ['์š”๋ฆฌ', '์Œ์‹', '๋ณด๊ด€', '๋ƒ‰์žฅ', '๋ƒ‰๋™', '๋ฉด', '๋ฐฅ', '์ˆ ', '๋ฐ˜์ฐฌ', '๋ ˆ์‹œํ”ผ', '๋ƒ‰์žฅ๊ณ '], - }, - { section: '์žฌํ™œ์šฉ / ๋ถ„๋ฆฌ์ˆ˜๊ฑฐ', tags: ['์žฌํ™œ์šฉ', '๋ถ„๋ฆฌ์ˆ˜๊ฑฐ', '๋ฆฌํผ', 'ํ”Œ๋ผ์Šคํ‹ฑ', '์Šคํ‹ฐ๋กœํผ', '์ข…์ด', '์œ ๋ฆฌ'] }, - { section: '์ฃผ๊ฑฐ', tags: ['์ฃผํƒ', '์›๋ฃธ', '๋นŒ๋ผ', '์•„ํŒŒํŠธ', '๊ธฐ์ˆ™์‚ฌ'] }, -]; - -const InterestForm: React.FC<{ onHashtagChange: (hashtags: string[]) => void }> = ({ onHashtagChange }) => { - - const [selectedTags, setSelectedTags] = useState([]); - - const handleTagClick = (tag: string) => { - const isSelected = selectedTags.includes(tag); - const updatedTags = isSelected ? selectedTags.filter((t) => t !== tag) : [...selectedTags, tag]; - setSelectedTags(updatedTags); - onHashtagChange(updatedTags); // ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์—…๋ฐ์ดํŠธ - console.log("ํ•ด์‹œํƒœ๊ทธ: ", updatedTags); - }; - - const theme = useTheme(); - return ( - - ๊ด€์‹ฌ์‚ฌ (ํ•„์ˆ˜, 10๊ฐœ ์ด๋‚ด) * - - - - - ); -}; - -export default InterestForm; - -const Container = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 30px; - align-self: stretch; -` - -const InterestEditForm = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 28px; - align-self: stretch; -` \ No newline at end of file From 3230c75a05a277a8a7da19d64fe1171d3be1c6fc Mon Sep 17 00:00:00 2001 From: rael Date: Thu, 20 Feb 2025 23:18:36 +0900 Subject: [PATCH 40/40] =?UTF-8?q?=E2=9C=A8=20feat:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=A4=91=20=20#76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umc-master/src/apis/authApi.ts | 51 +++++++++++----- umc-master/src/pages/auth/SignUpPage.tsx | 9 +-- .../auth/Signup_components/EmailForm.tsx | 58 ++++++++++++++++--- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/umc-master/src/apis/authApi.ts b/umc-master/src/apis/authApi.ts index 706f449..55d9c5c 100644 --- a/umc-master/src/apis/authApi.ts +++ b/umc-master/src/apis/authApi.ts @@ -1,19 +1,38 @@ -import axiosInstance from '@apis/axios-instance'; +// import axiosInstance from '@apis/axios-instance'; -interface UserSignup { - email: string; - password: string; - nickname: string; - hashtag: string[]; -} +// interface UserSignup { +// email: string; +// password: string; +// nickname: string; +// hashtags: string[]; +// } -export const postEmail = async ({ email, password, nickname, hashtag } : UserSignup) => { - const { data } = await axiosInstance.post(`/signup`, { - email, - password, - nickname, - hashtag, - }); - return data; -}; +// export const postSignup = async ({ email, password, nickname, hashtags } : UserSignup) => { +// const { data } = await axiosInstance.post(`/signup`, { +// email, +// password, +// nickname, +// hashtags, +// }); +// return data; +// }; + +import axios from 'axios'; +export const postSignup = async () => { + try { + const response = await axios.post('https://api.hmaster.shop/api/v1/signup', { + email: 'ekos555@naver.com', + password: 'asfa1234!@', + nickname: 'rael', + hashtags: ['๋ด„', 'ํŒจ์…˜', '์ฒญ์†Œ', '์š”๋ฆฌ', '์žฌํ™œ์šฉ', '์ฃผํƒ'] + }, { + headers: { + 'Content-Type': 'application/json' + } + }); + console.log('ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต:', response.data); + } catch (error) { + console.error('ํšŒ์›๊ฐ€์ž… ์˜ค๋ฅ˜:', error); + } +}; diff --git a/umc-master/src/pages/auth/SignUpPage.tsx b/umc-master/src/pages/auth/SignUpPage.tsx index 06ee54c..48f542f 100644 --- a/umc-master/src/pages/auth/SignUpPage.tsx +++ b/umc-master/src/pages/auth/SignUpPage.tsx @@ -9,7 +9,7 @@ import PrivacyForm from "./Signup_components/PrivacyForm"; import InterestForm from "./Signup_components/InterestForm"; import Button from "@components/Button/Button"; import { useNavigate } from "react-router-dom"; -import { postEmail } from "@apis/authApi"; +import { postSignup } from "@apis/authApi"; const SignUpPage: React.FC = () => { @@ -20,7 +20,7 @@ const [isNextButtonEnabled, setIsNextButtonEnabled] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [nickname, setNickname] = useState(""); -const [hashtag, setHashtag] = useState([]); +const [hashtags, setHashtag] = useState([]); useEffect(() => { setIsNextButtonEnabled(false); // ์„น์…˜์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” @@ -48,8 +48,9 @@ const handleHashtagChange = (hashtags: string[]) => { const handleSignUpComplete = async () => { try { - const userSignupData = { email, password, nickname, hashtag }; - await postEmail(userSignupData); + const userSignupData = { email, password, nickname, hashtags }; + console.log("ํšŒ์›๊ฐ€์ž… ํ™•์ธ:", userSignupData) + await postSignup(); navigate("/main"); } catch (error) { console.error("ํšŒ์›๊ฐ€์ž… ์˜ค๋ฅ˜:", error); diff --git a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx index 5df18be..647b5c9 100644 --- a/umc-master/src/pages/auth/Signup_components/EmailForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/EmailForm.tsx @@ -73,12 +73,48 @@ const EmailForm: React.FC<{ } }; + const handleVerifyCode = async () => { + if (!authCode) { + alert("์ธ์ฆ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + return; + } + try { + const response = await axiosInstance.post("/auth/verify-email-code", { + email: fullEmail, + code: authCode, + }); + console.log("์„œ๋ฒ„ ์‘๋‹ต:", response.data); + if (response.data) { + alert("์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + setVerified(true); + } else { + alert("์ธ์ฆ๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); + } + } catch (error) { + alert("์ธ์ฆ๋ฒˆํ˜ธ ํ™•์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + }; + + const handleDomainChange = (e: React.ChangeEvent) => { + const updatedDomain = e.target.value; + setDomain(updatedDomain); + + const updatedFullEmail = `${localPart}@${updatedDomain}`; + onEmailChange(updatedFullEmail); + + console.log("๋„๋ฉ”์ธ ๋ณ€๊ฒฝ:", updatedFullEmail); + }; + const handleEmailChange = (e: React.ChangeEvent) => { const email = e.target.value; setLocalPart(email); - onEmailChange(email); - console.log("์ด๋ฉ”์ผ: ", email); - }; + + const updatedFullEmail = `${email}@${domain}`; // ์ตœ์‹  ๋„๋ฉ”์ธ ์‚ฌ์šฉ + onEmailChange(updatedFullEmail); + + console.log("์ด๋ฉ”์ผ ์ž…๋ ฅ:", updatedFullEmail); + }; + const handleRetry = () => { setLocalPart(""); @@ -110,7 +146,7 @@ const EmailForm: React.FC<{ >@ setDomain(e.target.value)} + onChange={handleDomainChange} disabled={emailSent} > {emails.map((email) => ( @@ -131,12 +167,15 @@ const EmailForm: React.FC<{ value={authCode} onChange={(e) => setAuthCode(e.target.value)} /> - + {emailSent && ( -

๋‚จ์€ ์‹œ๊ฐ„: {Math.floor(timer / 60)}๋ถ„ {timer % 60}์ดˆ

-
@@ -199,6 +238,9 @@ const Timer = styled.div` display: flex; justify-content: center; align-items: center; - gap: 275px; + flex-direction: column; + gap: 10px; margin-top: 10px; + width: 100%; /* ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ค‘์•™ ์ •๋ ฌ */ + align-self: center; ` \ No newline at end of file