diff --git a/umc-master/index.html b/umc-master/index.html index e76b095..14b710e 100644 --- a/umc-master/index.html +++ b/umc-master/index.html @@ -11,7 +11,7 @@ /> 홈마스터 - +
diff --git a/umc-master/src/apis/authApi.ts b/umc-master/src/apis/authApi.ts index 55d9c5c..0052887 100644 --- a/umc-master/src/apis/authApi.ts +++ b/umc-master/src/apis/authApi.ts @@ -1,38 +1,18 @@ -// import axiosInstance from '@apis/axios-instance'; +import axiosInstance from '@apis/axios-instance'; -// interface UserSignup { -// email: string; -// password: string; -// nickname: string; -// hashtags: string[]; -// } +interface UserSignup { + email: string; + password: string; + nickname: string; + hashtags: string[]; +} -// 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); - } +export const postSignup = async ({ email, password, nickname, hashtags }: UserSignup) => { + const { data } = await axiosInstance.post(`/signup`, { + email, + password, + nickname, + hashtags, + }); + return data; }; diff --git a/umc-master/src/apis/axios-instance.ts b/umc-master/src/apis/axios-instance.ts index 6afb7ac..5add23a 100644 --- a/umc-master/src/apis/axios-instance.ts +++ b/umc-master/src/apis/axios-instance.ts @@ -1,11 +1,12 @@ -import RoutePaths from '@router/routePaths'; import { useTokenStore } from '@store/tokenStore'; import axios, { AxiosInstance } from 'axios'; +const { accessToken } = useTokenStore.getState(); + const axiosInstance: AxiosInstance = axios.create({ headers: { accept: 'application/json', - Authorization: `Bearer ${import.meta.env.VITE_ACCESS_TOKEN}`, + Authorization: `Bearer ${accessToken}`, }, baseURL: import.meta.env.VITE_BASE_URL, }); @@ -69,11 +70,6 @@ axiosInstance.interceptors.response.use( console.error('토큰 갱신 실패:', refreshError); useTokenStore.getState().clearTokens(); - // 로그인 페이지로 리다이렉트 - if (window.location.pathname !== RoutePaths.LOGIN) { - window.location.href = RoutePaths.LOGIN; - } - return Promise.reject(refreshError); } } diff --git a/umc-master/src/components/Auth/AuthWrapper.tsx b/umc-master/src/components/Auth/AuthWrapper.tsx new file mode 100644 index 0000000..933ab93 --- /dev/null +++ b/umc-master/src/components/Auth/AuthWrapper.tsx @@ -0,0 +1,30 @@ +import { useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useTokenStore } from '@store/tokenStore'; +import RoutePaths from '@router/routePaths'; + +interface AuthWrapperProps { + children: React.ReactNode; +} + +const AuthWrapper = ({ children }: AuthWrapperProps) => { + const navigate = useNavigate(); + const location = useLocation(); + const { accessToken } = useTokenStore.getState(); + + useEffect(() => { + // 로그인 페이지가 아니고 토큰이 없는 경우 리다이렉트 + if (!accessToken && location.pathname !== RoutePaths.LANDING) { + navigate(RoutePaths.LOGIN, { replace: true }); + } + }, [accessToken, location.pathname, navigate]); + + // 토큰이 없고 로그인 페이지가 아닌 경우 아무것도 렌더링하지 않음 + if (!accessToken && location.pathname !== RoutePaths.LOGIN) { + return null; + } + + return <>{children}; +}; + +export default AuthWrapper; diff --git a/umc-master/src/components/Modal/profile.tsx b/umc-master/src/components/Modal/profile.tsx index 0c0d5ed..5f4d3d2 100644 --- a/umc-master/src/components/Modal/profile.tsx +++ b/umc-master/src/components/Modal/profile.tsx @@ -14,16 +14,17 @@ import { getUsers } from '@apis/profileApi'; interface ProfileModalProps { isOpen: boolean; onClose: () => void; + profileImage: string; } -const ProfileModal: React.FC = ({ isOpen, onClose }) => { +const ProfileModal: React.FC = ({ isOpen, onClose, profileImage }) => { if (!isOpen) return null; const navigate = useNavigate(); const { clearAuth } = useAuthStore(); const { user, fetchUser } = useUserStore(); useEffect(() => { - fetchUser(); // 컴포넌트 마운트 시 사용자 정보 가져오기 + fetchUser(); }, []); getUsers(); @@ -39,7 +40,7 @@ const ProfileModal: React.FC = ({ isOpen, onClose }) => { - + {user?.nickname} 님 @@ -109,7 +110,7 @@ const ProfileWrapper = styled.div` padding: 20px 0; `; -const ProfileImage = styled.div` +const ProfileImage = styled.img` width: 60px; height: 60px; border-radius: 50%; @@ -138,7 +139,6 @@ const MenuItem = styled.li` } `; -// "마이페이지" 링크 전용 컴포넌트 const MenuItemLink = styled(Link)` display: flex; align-items: center; diff --git a/umc-master/src/components/NavigationBar/NavigationBar.tsx b/umc-master/src/components/NavigationBar/NavigationBar.tsx index 9846272..698f53a 100644 --- a/umc-master/src/components/NavigationBar/NavigationBar.tsx +++ b/umc-master/src/components/NavigationBar/NavigationBar.tsx @@ -26,6 +26,10 @@ const NavigationBar: React.FC = ({ login }) => { }, []); getUsers(); + const handleNavClick = () => { + window.scrollTo(0, 0); + }; + const toggleAlarmModal = () => setIsAlarmModalOpen((prev) => !prev); const toggleProfileModal = () => setIsProfileModalOpen((prev) => !prev); @@ -33,15 +37,23 @@ const NavigationBar: React.FC = ({ login }) => { - + ); }; diff --git a/umc-master/src/pages/auth/KakaoCallback.tsx b/umc-master/src/pages/auth/KakaoCallback.tsx index ff9e035..918e89d 100644 --- a/umc-master/src/pages/auth/KakaoCallback.tsx +++ b/umc-master/src/pages/auth/KakaoCallback.tsx @@ -1,54 +1,42 @@ import React, { useEffect } from 'react'; -import axios from 'axios'; import { useNavigate } from 'react-router-dom'; +import axiosInstance from '@apis/axios-instance'; +import { useTokenStore } from '@store/tokenStore'; +import { useAuthStore } from '@store/authStore'; const KakaoCallback: React.FC = () => { - + const { setAuth } = useAuthStore(); + const { setTokens } = useTokenStore(); const navigate = useNavigate(); useEffect(() => { - const getAccessToken = async () => { - const queryParams = new URLSearchParams(window.location.search); - const code = queryParams.get('code'); - - if (!code) { - console.error('Authorization code not found'); - return; - } - - try { - // 카카오에서 Access Token 요청 - const response = await axios.post('https://kauth.kakao.com/oauth/token', { - grant_type: 'authorization_code', - client_id: import.meta.env.VITE_KAKAO_API_KEY, - redirect_uri: 'http://localhost:3000/oauth/kakao/callback', - code, - }); - - const kakaoAccessToken = response.data.access_token; - - // 백엔드로 카카오 Access Token 전송 - const backendResponse = await axios.post('http://localhost:3000/login/kakao', { - kakaoAccessToken, + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + + if (code) { + console.log('✅ 카카오 로그인 코드 확인:', code); + + axiosInstance + .post('/login/kakao', { code }) + .then((response) => { + const { accessToken, refreshToken } = response.data.result; + setTokens({ accessToken, refreshToken }); + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', refreshToken); + setAuth(true); + navigate('/main'); + }) + .catch(() => { + // console.error('❌ 카카오 로그인 실패:', error.response?.data || error.message); + // alert(error.response?.data?.message || '카카오 로그인에 실패했습니다.'); + navigate('/'); }); - - const { accessToken, refreshToken } = backendResponse.data.result; - - // Access Token 및 Refresh Token 저장 (예: 로컬 스토리지) - localStorage.setItem('accessToken', accessToken); - localStorage.setItem('refreshToken', refreshToken); - - // 홈 또는 사용자 대시보드로 이동 - navigate('/'); - } catch (error) { - console.error('Failed to login with Kakao:', error); - } - }; - - getAccessToken(); + } else { + console.warn('⚠️ 카카오 로그인 코드 없음'); + } }, [navigate]); - return
카카오 로그인 중…
; + return <>; }; export default KakaoCallback; diff --git a/umc-master/src/pages/auth/Login_components/InputForm.tsx b/umc-master/src/pages/auth/Login_components/InputForm.tsx index 2204ddb..5fafebb 100644 --- a/umc-master/src/pages/auth/Login_components/InputForm.tsx +++ b/umc-master/src/pages/auth/Login_components/InputForm.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import Input from '@components/Input/Input'; import useInput from '@hooks/useInput'; import { validateEmailFormat, validatePasswordFormat } from '@utils/validation'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Button from '@components/Button/Button'; import Kakao_Image from '@assets/kakao_login/kakao_login_large_wide.png'; import { useNavigate } from 'react-router-dom'; @@ -12,14 +12,49 @@ import axiosInstance from '@apis/axios-instance'; import { useAuthStore } from '@store/authStore'; import { useTokenStore } from '@store/tokenStore'; +const KAKAO_REDIRECT_URI = + import.meta.env.MODE === 'development' + ? 'http://localhost:5173/oauth/kakao/callback' + : 'https://www.hmaster.shop/oauth/kakao/callback'; + const InputForm: React.FC = () => { const { setAuth } = useAuthStore(); - const { setTokens } = useTokenStore.getState(); + const { setTokens } = useTokenStore(); + const navigate = useNavigate(); + + useEffect(() => { + if (!window.Kakao) { + console.warn('⚠️ window.Kakao가 없음, SDK 로드 시작'); + + const script = document.createElement('script'); + script.src = 'https://developers.kakao.com/sdk/js/kakao.js'; + script.async = true; + script.onload = () => { + console.log('✅ 카카오 SDK 로드 완료:', window.Kakao); + if (window.Kakao && !window.Kakao.isInitialized()) { + console.error('❌ window.Kakao는 있지만 초기화되지 않음! init() 필요'); + window.Kakao.init(import.meta.env.VITE_JAVASCRIPT_KEY); + console.log('✅ 카카오 SDK 강제 초기화 완료'); + } + }; + document.head.appendChild(script); + } else { + console.log('✅ window.Kakao 이미 로드됨'); + if (!window.Kakao.isInitialized()) { + console.error('❌ window.Kakao는 있지만 초기화되지 않음! init() 필요'); + window.Kakao.init(import.meta.env.VITE_JAVASCRIPT_KEY); + console.log('✅ 카카오 SDK 강제 초기화 완료'); + } + } + }, []); const handleKakaoLogin = () => { - const KAKAO_API_KEY = import.meta.env.VITE_KAKAO_API_KEY; - const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_API_KEY}&redirect_uri=http://localhost:3000/oauth/kakao/callback&response_type=code`; - window.location.href = kakaoAuthUrl; + const clientId = import.meta.env.VITE_JAVASCRIPT_KEY; + const redirectUri = encodeURIComponent(KAKAO_REDIRECT_URI); + const fallbackUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code`; + + console.log('✅ fallback URL로 리디렉션:', fallbackUrl); + window.location.href = fallbackUrl; }; const handleEmailLogin = async () => { @@ -72,13 +107,12 @@ const InputForm: React.FC = () => { e.preventDefault(); setIsSubmitted(true); - // 이메일 및 비밀번호가 비어있는지 체크하고 오류 메시지 표시 if (!email) { handleEmailError('이메일을 입력해주세요.'); } else { const emailError = validateEmailFormat(email); if (emailError) { - handleEmailError(emailError); // 이메일 오류 처리 + handleEmailError(emailError); } } @@ -87,7 +121,7 @@ const InputForm: React.FC = () => { } else { const passwordError = validatePasswordFormat(password); if (passwordError) { - handlePasswordError(passwordError); // 비밀번호 오류 처리 + handlePasswordError(passwordError); } } @@ -110,8 +144,6 @@ const InputForm: React.FC = () => { await handleEmailLogin(); }; - const navigate = useNavigate(); // 추가 - return ( @@ -132,7 +164,7 @@ const InputForm: React.FC = () => { - diff --git a/umc-master/src/pages/auth/SignUpPage.tsx b/umc-master/src/pages/auth/SignUpPage.tsx index 48f542f..0036ff2 100644 --- a/umc-master/src/pages/auth/SignUpPage.tsx +++ b/umc-master/src/pages/auth/SignUpPage.tsx @@ -43,15 +43,17 @@ const handleNicknameChange = (nickname: string) => { }; const handleHashtagChange = (hashtags: string[]) => { - setHashtag(hashtags); + const hashtagStrings = hashtags.map(hashtag => hashtag.toString()); + setHashtag(hashtagStrings); }; const handleSignUpComplete = async () => { try { const userSignupData = { email, password, nickname, hashtags }; console.log("회원가입 확인:", userSignupData) - await postSignup(); - navigate("/main"); + await postSignup(userSignupData); + alert("회원가입 성공!"); + navigate("/login"); } catch (error) { console.error("회원가입 오류:", error); alert("회원가입에 실패했습니다. 다시 시도해주세요."); diff --git a/umc-master/src/pages/auth/Signup_components/AgreementForm.tsx b/umc-master/src/pages/auth/Signup_components/AgreementForm.tsx index f0eff45..96c3573 100644 --- a/umc-master/src/pages/auth/Signup_components/AgreementForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/AgreementForm.tsx @@ -3,7 +3,7 @@ import Typography from "@components/common/typography"; import styled, { useTheme } from "styled-components"; import ImgAdd from "@assets/add.svg"; import ImgRemove from "@assets/remove.svg"; -import { useEffect, useState } from "react"; +import { useState } from "react"; interface AgreementItemProps { isRequired: boolean; // 필수 여부 @@ -67,10 +67,14 @@ const AgreementForm: React.FC = ({ onCheckRequired }) => { }); // 전체 동의 체크박스 - const handleAllAgreeChange = (checked: boolean) => { - setIsAllAgreed(checked); + const handleAllAgreeChange = () => { + const newCheckedState = !isAllAgreed; + setIsAllAgreed(newCheckedState); const updatedItems = { - terms: checked, privacy: checked, thirdinfo: checked, marketing: checked, + terms: newCheckedState, + privacy: newCheckedState, + thirdinfo: newCheckedState, + marketing: newCheckedState, }; setCheckedItems(updatedItems); onCheckRequired(updatedItems.terms && updatedItems.privacy); @@ -79,22 +83,13 @@ const AgreementForm: React.FC = ({ onCheckRequired }) => { const handleCheckboxChange = (key: string, checked: boolean) => { setCheckedItems((prevState) => { const updatedItems = { ...prevState, [key]: checked }; + const allChecked = Object.values(updatedItems).every(Boolean); + setIsAllAgreed(allChecked); onCheckRequired(updatedItems.terms && updatedItems.privacy); return updatedItems; }); }; - useEffect(() => { - if (isAllAgreed) { - setCheckedItems({ - terms: true, privacy: true, thirdinfo: true, marketing: true, - }); - onCheckRequired(true); - } else { - onCheckRequired(checkedItems.terms && checkedItems.privacy); - } - }, [isAllAgreed, checkedItems, onCheckRequired]); - return ( (false); const [verified, setVerified] = useState(false); const [timer, setTimer] = useState(180); - const [retryEnabled, setRetryEnabled] = useState(false); + const [isEmailDuplicate, setIsEmailDuplicate] = useState(false); const fullEmail = localPart && domain ? `${localPart}@${domain}` : ""; @@ -49,9 +49,7 @@ const EmailForm: React.FC<{ timerInterval = setInterval(() => { setTimer((prev) => prev - 1); }, 1000); - } else if (timer === 0) { - setRetryEnabled(true); - } + } return () => { if (timerInterval) { clearInterval(timerInterval); @@ -103,6 +101,8 @@ const EmailForm: React.FC<{ onEmailChange(updatedFullEmail); console.log("도메인 변경:", updatedFullEmail); + + setIsEmailDuplicate(false); // 중복 확인 버튼을 다시 활성화 }; const handleEmailChange = (e: React.ChangeEvent) => { @@ -113,8 +113,26 @@ const EmailForm: React.FC<{ onEmailChange(updatedFullEmail); console.log("이메일 입력:", updatedFullEmail); + + setIsEmailDuplicate(false); // 중복 확인 버튼을 다시 활성화 }; + // 이메일 중복 체크 + const checkEmailDuplicate = async () => { + if (!fullEmail) { + alert("이메일을 입력해주세요."); + return; + } + try { + const response = await axiosInstance.post("/check-email", { email: fullEmail }); + if (response.data.isSuccess) { + setIsEmailDuplicate(true); + alert("사용 가능한 이메일입니다."); + } + } catch (error) { + alert("이메일이 이미 존재합니다."); + } + }; const handleRetry = () => { setLocalPart(""); @@ -123,7 +141,6 @@ const EmailForm: React.FC<{ setEmailSent(false); setVerified(false); setTimer(180); - setRetryEnabled(false); }; return ( @@ -147,7 +164,7 @@ const EmailForm: React.FC<{ {emails.map((email) => ( ))} - @@ -175,7 +200,7 @@ const EmailForm: React.FC<{ variant="titleXxxSmall" style={{color: theme.colors.red[500]}} >남은 시간: {Math.floor(timer / 60)}분 {timer % 60}초 - diff --git a/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx b/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx index 0de5716..01ebcf3 100644 --- a/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx +++ b/umc-master/src/pages/auth/Signup_components/PasswordForm.tsx @@ -1,6 +1,8 @@ /* eslint-disable react/prop-types */ import Typography from "@components/common/typography"; import Input from "@components/Input/Input"; +import useInput from "@hooks/useInput"; +import { validatePasswordFormat } from "@utils/validation"; import { useEffect, useState } from "react"; import { styled, useTheme } from "styled-components"; @@ -13,11 +15,40 @@ const PasswordForm: React.FC<{ const theme = useTheme(); - const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); + const [confirmPasswordErrorMessage, setConfirmPasswordErrorMessage] = useState(''); + const [isPasswordValid, setIsPasswordValid] = useState(false); + + // 비밀번호 상태 검증 및 에러메세지 + const { + input: password, + errorMessage: passwordInputErrorMessage, + changeHandler: passwordChangeHandler, + } = useInput({ + initialValue: "", + validate: async (value) => { + const error = validatePasswordFormat(value); + setIsPasswordValid(!error); // 형식이 맞으면 true, 아니면 false + return error; + } + }); + + // 비밀번호 확인 오류 메시지 실시간 변동 + useEffect(() => { + if (confirmPassword && password !== confirmPassword) { + setConfirmPasswordErrorMessage("비밀번호가 일치하지 않습니다."); + } else { + setConfirmPasswordErrorMessage(""); + } + }, [confirmPassword, password]); + + useEffect(() => { + setPasswordErrorMessage(passwordInputErrorMessage || ""); + }, [passwordInputErrorMessage]); useEffect(() => { - if (password && confirmPassword && password === confirmPassword) { + if (isPasswordValid && confirmPassword && password === confirmPassword) { onCheckRequired(true); } else { onCheckRequired(false); @@ -26,8 +57,8 @@ const PasswordForm: React.FC<{ const handlePasswordChange = (e: React.ChangeEvent) => { const newPassword = e.target.value; - setPassword(newPassword); onPasswordChange(newPassword); // 상위 컴포넌트에 전달 + passwordChangeHandler(e); console.log("비밀번호: ", newPassword); }; @@ -38,14 +69,14 @@ const PasswordForm: React.FC<{ style={{color: theme.colors.primary[700]}} >비밀번호 입력 (필수) * void; }> = ({ onCheckRequired, onNicknameChange }) => { + const theme = useTheme(); + + const [profileImageUrl, setProfileImageUrlLocal] = useState(gray_character); const [selectedCity, setSelectedCity] = useState("default"); const [districts, setDistricts] = useState([]); - const [nickname, setNickname] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); // 닉네임 입력값을 업데이트하는 함수 const handleNicknameChange = (e: React.ChangeEvent) => { const newNickname = e.target.value; setNickname(newNickname); - onNicknameChange(newNickname); // 상위 컴포넌트로 업데이트 - console.log("닉네임: ", newNickname); - // 닉네임이 0글자 이상일 때만 "다음" 버튼을 활성화 - onCheckRequired(newNickname.length > 0); + onNicknameChange(newNickname); + const nicknameRegex = /^[a-zA-Z0-9ㄱ-ㅎ가-힣._]+$/; + + if (newNickname.length === 0) { + setErrorMessage("닉네임을 입력해주세요."); + onCheckRequired(false); + } else if (newNickname.length > 11) { + setErrorMessage("닉네임을 최대 10자까지 입력 가능합니다."); + onCheckRequired(false); + } else if (!nicknameRegex.test(newNickname)) { + setErrorMessage("닉네임은 한글, 영문, 숫자, '.', '_' 만 사용할 수 있습니다."); + onCheckRequired(false); + } else { + setErrorMessage(""); + onCheckRequired(true); + } }; // 도시 선택시 구 목록을 업데이트하는 함수 @@ -90,17 +106,34 @@ const PrivacyForm: React.FC<{ break; } }; - - 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); // 로컬 상태 업데이트 + // api 연결 + }; + reader.readAsDataURL(file); + } + }; + return ( - 프로필 사진 (선택) - - + + document.getElementById('fileInput')?.click()} + /> + @@ -109,6 +142,7 @@ const PrivacyForm: React.FC<{ style={{color: theme.colors.primary[700]}} >닉네임 (필수) * = ({ sectionCount }) => { {step} @@ -51,7 +52,7 @@ const Sequence = styled.div` position: relative; display: flex; align-items: center; - gap: 160px; + gap: 130px; ` const Step = styled.div` @@ -85,7 +86,7 @@ const Num = styled.div<{ isActive: boolean }>` const Line = styled.div` position: absolute; top: 30px; - left: 5px; + left: 15px; width: 99%; height: 1px; border: 1px dotted ${({ theme }) => theme.colors.text.lightGray}; diff --git a/umc-master/src/pages/challenge/compoents/ChallengeHeader.tsx b/umc-master/src/pages/challenge/compoents/ChallengeHeader.tsx index e027f53..c61eae5 100644 --- a/umc-master/src/pages/challenge/compoents/ChallengeHeader.tsx +++ b/umc-master/src/pages/challenge/compoents/ChallengeHeader.tsx @@ -16,9 +16,9 @@ const ChallengeHeader: React.FC = ({ onSortChange, }) => { const categories = [ + { id: 'cleaning', label: '청소' }, { id: 'season', label: '계절' }, { id: 'fashion', label: '패션' }, - { id: 'cleaning', label: '청소' }, { id: 'cooking', label: '요리/사계절' }, { id: 'games', label: '게임형/분리하기' }, { id: 'etc', label: '후기' }, diff --git a/umc-master/src/pages/challenge/compoents/TipSection.tsx b/umc-master/src/pages/challenge/compoents/TipSection.tsx index e23d758..e8267f4 100644 --- a/umc-master/src/pages/challenge/compoents/TipSection.tsx +++ b/umc-master/src/pages/challenge/compoents/TipSection.tsx @@ -1,10 +1,10 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import ChallengeHeader from './ChallengeHeader'; import NumberCard from '@components/Card/NumberCard'; -import dummyImage from '@assets/dummyImage/clean.png'; import styled from 'styled-components'; import SkeletonCard from '@components/Skeleton/SkeletonCard'; import { useNavigate } from 'react-router-dom'; +import { dummyImage, dummyText } from '@pages/mychallenge/dummydata'; interface Tip { id: number; @@ -18,16 +18,15 @@ interface Tip { const PAGE_SIZE = 10; -/* 임시 더미 데이터 생성 함수 */ -const generateDummyData = (count: number, startId: number): Tip[] => { +const generateDummyData = (count: number, startId: number, images: string[], texts: string[]): Tip[] => { return Array.from({ length: count }, (_, index) => { const randomDaysAgo = Math.floor(Math.random() * 30); const date = new Date(); date.setDate(date.getDate() - randomDaysAgo); return { id: startId + index, - image: dummyImage, - text: `청소메이킹가이드 ${startId + index}`, + image: images[Math.floor(Math.random() * images.length)], // 랜덤 이미지 + text: texts[Math.floor(Math.random() * texts.length)], // 랜덤 텍스트 likes: Math.floor(Math.random() * 5000), bookmarks: Math.floor(Math.random() * 5000), date: date.toISOString().slice(0, 10), @@ -38,7 +37,7 @@ const generateDummyData = (count: number, startId: number): Tip[] => { const TipSection = () => { const [sortBy, setSortBy] = useState<'users' | 'latest'>('users'); - const [selectedCategory, setSelectedCategory] = useState('season'); + const [selectedCategory, setSelectedCategory] = useState('cleaning'); const [allData, setAllData] = useState([]); const [loadedCount, setLoadedCount] = useState(PAGE_SIZE); const [isLoading, setIsLoading] = useState(false); @@ -51,7 +50,7 @@ const TipSection = () => { setIsLoading(true); const timer = setTimeout(() => { - const dummyData = generateDummyData(100, 1); + const dummyData = generateDummyData(100, 1, dummyImage, dummyText); setAllData(dummyData); setIsLoading(false); }, 1000); @@ -126,7 +125,7 @@ const TipSection = () => { ))} ) : ( - + )} {hasMore && !isLoading &&
} diff --git a/umc-master/src/pages/community/components/Banner.tsx b/umc-master/src/pages/community/components/Banner.tsx index fa9a931..ab7e77f 100644 --- a/umc-master/src/pages/community/components/Banner.tsx +++ b/umc-master/src/pages/community/components/Banner.tsx @@ -38,4 +38,5 @@ const TipCreateBTN = styled.button` border-radius: 50px; margin-top: 24px; color: ${({ theme }) => theme.colors.text['black']}; + cursor: pointer; `; diff --git a/umc-master/src/pages/magazine/MagazinePage.tsx b/umc-master/src/pages/magazine/MagazinePage.tsx index 3f4983b..42c9c13 100644 --- a/umc-master/src/pages/magazine/MagazinePage.tsx +++ b/umc-master/src/pages/magazine/MagazinePage.tsx @@ -5,17 +5,43 @@ import styled from 'styled-components'; import Typography from '@components/common/typography'; import MindMap from './components/mindMap'; import CardGrid, { CardGridData } from './components/cardGrid'; -import dummyImg from '@assets/dummyImage/dummy.jpeg'; import { usePolicies } from '@apis/queries/usePolicyQueries'; +const IMAGES = [ + 'https://i.pinimg.com/736x/06/e6/e5/06e6e5415db0dc7aa3aaba1b93f03b20.jpg', + 'https://i.pinimg.com/1200x/7c/6f/7c/7c6f7c596654678b7400ef2c07c086ad.jpg', + 'https://i.pinimg.com/1200x/ad/24/04/ad24045a4a3ae15c6d765b3b711f61c1.jpg', + 'https://i.pinimg.com/1200x/7d/78/fa/7d78faa3c633bf5fde231e1b446bc4fb.jpg', + 'https://i.pinimg.com/1200x/12/76/a2/1276a2dc2749275436a49fb3561770ba.jpg', + 'https://i.pinimg.com/1200x/d8/0d/a9/d80da9b244d7de383b319457de475e89.jpg', + 'https://i.pinimg.com/1200x/cf/93/b1/cf93b11fac04e6790fab2f83e60b70cf.jpg', + 'https://i.pinimg.com/1200x/60/39/77/60397711931a8bbbdcb2d25493556a27.jpg', + 'https://i.pinimg.com/736x/62/6f/e8/626fe850e3fd8c62c9ad4126571112bf.jpg', +] as const; + +const TEXTS = [ + '세탁 꿀팁 모음', + '욕실 청소 가이드', + '냉장고 정리 꿀팁', + '주방 유리창 청소법', + '강아지 털 청소 방법', + '빨래 냄새 없애는 법', + '전자레인지 찌든때 제거', + '청소 동선 최적화 팁', + '원룸 미니멀 정리법', +] as const; + const generateDummyData = (): CardGridData[] => { + const randomDaysAgo = Math.floor(Math.random() * 30); + const date = new Date(); + date.setDate(date.getDate() - randomDaysAgo); return Array.from({ length: 9 }, (_, index) => ({ id: String(index + 1), - image: dummyImg, - text: `더미 데이터 ${index + 1}`, + image: IMAGES[index % IMAGES.length], + text: TEXTS[index % TEXTS.length], likes: Math.floor(Math.random() * 2000) + 1000, bookmarks: Math.floor(Math.random() * 2000) + 1000, - date: '2025.01.18', + date: date.toISOString().slice(0, 10), })); }; @@ -23,6 +49,15 @@ const MagazinePage = () => { const { data: policiesData } = usePolicies({ locationId: 17 }); const influencerData = generateDummyData(); + const influencerPolicies: PolicyData[] = influencerData.map((c) => ({ + id: Number(c.id), + title: c.text, + imageUrl: c.image, + likeCount: c.likes, + bookmarkCount: c.bookmarks, + createAt: c.date, + })); + useEffect(() => { window.scrollTo(0, 0); }, []); @@ -40,7 +75,7 @@ const MagazinePage = () => { <Typography variant="headingXxSmall">인플루언서 꿀팁</Typography> - +
); diff --git a/umc-master/src/pages/magazine/components/cardGrid.tsx b/umc-master/src/pages/magazine/components/cardGrid.tsx index 18c53e9..de19350 100644 --- a/umc-master/src/pages/magazine/components/cardGrid.tsx +++ b/umc-master/src/pages/magazine/components/cardGrid.tsx @@ -2,7 +2,6 @@ import React from 'react'; import styled from 'styled-components'; import { useNavigate } from 'react-router-dom'; import CardInfo from '@components/Card/CardInfo'; -import dummyImg from '@assets/dummyImage/dummy.jpeg'; export interface PolicyData { id: number; @@ -34,7 +33,7 @@ const transformPolicies = (policies: PolicyData[] | undefined): CardGridData[] = if (!policies) return []; return policies.map((policy) => ({ id: policy.id.toString(), - image: policy.imageUrl || dummyImg, // 이미지 없을 경우 기본값 + image: policy.imageUrl || 'https://i.pinimg.com/736x/af/a0/76/afa07695334ef42a7864f09a0099a679.jpg', // 이미지 없을 경우 기본값 text: policy.title, likes: policy.likeCount ?? 0, // undefined 방지 bookmarks: policy.bookmarkCount ?? 0, // undefined 방지 diff --git a/umc-master/src/pages/main/components/TipsSection.tsx b/umc-master/src/pages/main/components/TipsSection.tsx index 61eebba..99a2b6d 100644 --- a/umc-master/src/pages/main/components/TipsSection.tsx +++ b/umc-master/src/pages/main/components/TipsSection.tsx @@ -12,7 +12,7 @@ import { AnimatePresence, motion } from 'framer-motion'; import { useSearchList } from '@apis/queries/useSearchList'; import BigCard from '@components/Card/BigCard'; import SkeletonBigCard from '@components/Skeleton/SkeletonBigCard'; - +import { recentStore } from '@store/recentStore'; interface TipsSectionProps { title?: string; showArrows?: boolean; @@ -146,8 +146,11 @@ const TipsSection: React.FC = ({ if (isError) return
Something went wrong...
; // 에러 발생 시 표시 - const handleCardClick = (id: number) => { - navigate(`/save-tip/${id}`); + const { addRecentTip } = recentStore(); // Zustand 상태 가져오기 + + const handleCardClick = (tip: TipItem) => { + addRecentTip(tip); // 최근 본 팁으로 저장 + navigate(`/save-tip/${tip.tipId}`); // 상세 페이지로 이동 }; const handleSlide = (direction: number) => { @@ -212,7 +215,7 @@ const TipsSection: React.FC = ({ likes={item.likesCount || 0} bookmarks={item.savesCount || 0} date={item.createdAt?.slice(0, 10) || ''} - onClick={() => handleCardClick(item.tipId)} + onClick={() => handleCardClick(item)} /> ))} diff --git a/umc-master/src/pages/mychallenge/components/TipSection.tsx b/umc-master/src/pages/mychallenge/components/TipSection.tsx index adaae57..78e6378 100644 --- a/umc-master/src/pages/mychallenge/components/TipSection.tsx +++ b/umc-master/src/pages/mychallenge/components/TipSection.tsx @@ -2,8 +2,8 @@ import { useState, useEffect, useMemo } from 'react'; import styled from 'styled-components'; import NumberCard from '@components/Card/NumberCard'; import SkeletonCard from '@components/Skeleton/SkeletonCard'; -import dummyImage from '@assets/dummyImage/clean.png'; import { useNavigate } from 'react-router-dom'; +import { dummyImage, dummyText } from '../dummydata'; interface Tip { id: number; @@ -16,15 +16,15 @@ interface Tip { } // 더미 데이터 생성 함수 (필요에 따라 유지) -const generateDummyData = (count: number, startId: number): Tip[] => { +const generateDummyData = (count: number, startId: number, images: string[], texts: string[]): Tip[] => { return Array.from({ length: count }, (_, index) => { const randomDaysAgo = Math.floor(Math.random() * 30); const date = new Date(); date.setDate(date.getDate() - randomDaysAgo); return { id: startId + index, - image: dummyImage, - text: `청소메이킹가이드 ${startId + index}`, + image: images[Math.floor(Math.random() * images.length)], // 랜덤 이미지 + text: texts[Math.floor(Math.random() * texts.length)], // 랜덤 텍스트 likes: Math.floor(Math.random() * 5000), bookmarks: Math.floor(Math.random() * 5000), date: date.toISOString().slice(0, 10), @@ -44,7 +44,7 @@ const TipSection = () => { // 1초 뒤에 더미 데이터를 설정 const timer = setTimeout(() => { - const dummyData = generateDummyData(100, 1); + const dummyData = generateDummyData(100, 1, dummyImage, dummyText); setAllData(dummyData); setIsLoading(false); }, 1000); @@ -83,7 +83,7 @@ const TipSection = () => { ))} ) : ( - + )} ); diff --git a/umc-master/src/pages/mychallenge/dummydata.ts b/umc-master/src/pages/mychallenge/dummydata.ts new file mode 100644 index 0000000..2b254b4 --- /dev/null +++ b/umc-master/src/pages/mychallenge/dummydata.ts @@ -0,0 +1,137 @@ +export const dummyText = [ + '세탁 효율 업그레이드 7일 챌린지', + '욕실 몰드 제로 프로젝트', + '냉장고 전체 리셋 정리 미션', + '주방 유리창 반짝이게 도전', + '반려동물 털 완전 제거 챌린지', + '빨래 냄새 잡기 3단계 루틴', + '전자레인지 찌든때 제거 미션', + '집 안 청소 동선 줄이기 챌린지', + '원룸 미니멀라이프 실천 프로젝트', + '가구 먼지 쓸어내기 데이', + '베이킹소다 청소 실험 미션', + '싱크대 물때 박멸 챌린지', + '욕실 곰팡이 퇴치 7일 도전', + '거울 얼룩 제로 스팟 미션', + '나만의 청소 루틴 만들기 챌린지', + '옷장 공간 확보 대작전', + '청소기 필터 새것처럼 챌린지', + '유리창 무자국 광택 미션', + '카펫 머리카락 제거 도전', + '주방 냄새 제로 프로젝트', + '먼지 없는 집 만들기 실천 챌린지', + '집안 공기 정화 플랜트 챌린지', + '설거지 속도 2배 미션', + '빨래 건조 뽀송하게 성공하기', + '쓰레기통 탈취 미션', + '이불 속 먼지 제거 캠페인', + '전자제품 먼지 쓸기 루틴', + '에어컨 필터 청소 미션', + '베란다 홈카페 정리 프로젝트', + '신발 냄새 제거 3일 도전', + '기름때 없는 주방 만들기 챌린지', + '반려동물 털 클린 집 도전', + '냉장고 유통기한 정리 챌린지', + '바닥 얼룩 제거 레벨업 미션', + '컵라면 설거지 1분 챌린지', + '걸레 없이 청소 도전', + '카펫 냄새 OUT 챌린지', + '매트리스 청소 D-Day', + '세탁 실패 복구 미션', + '빨래 색상 분류 정복 챌린지', + '화장대 미니멀 정리 미션', + '창문 방풍테이프 붙이기 도전', + '욕실 줄눈 살리기 프로젝트', + '도마 살균 실전 챌린지', + 'TV 화면 먼지 제로 미션', + '친환경 물티슈 만들기 도전', + '수납함으로 공간 최적화 챌린지', + '빨래 냄새 방지 3일 루틴', + '수세미 위생 관리 미션', + '싱크대 물자국 지우기 스팟 미션', + '전자레인지 냄새 제거 프로젝트', + '베란다 빨래건조 최적화 챌린지', + '옷장 곰팡이 예방 챌린지', + '커튼 세탁 실행 미션', + '섬유유연제 없이 부드럽게 챌린지', + '리모컨 살균 미션', + '세탁기 내부 청소 프로젝트', + '바닥 타입별 청소도구 실험 챌린지', + '양말 빨리 말리기 트릭 도전', + '프라이팬 눌어붙음 제거 미션', + '칫솔 살균 5분 챌린지', + '현관 신발 정리 리셋 미션', + '침구 털어내기 모닝 루틴', + '변기 물때 박멸 실전 챌린지', + '주방 수납 재배치 프로젝트', + '청소 시간 반으로 줄이기 미션', + '정리 전 버리기 기준 설정 챌린지', + '천장 먼지 닦기 미션', + '곰팡이 스프레이 DIY 챌린지', + '노트북 키보드 청소 도전', + '마스크 보관 위생 챌린지', + '장난감 살균 미션', + '반려동물 밥그릇 세척 루틴', + '책장 먼지 OUT 챌린지', + '고무장갑 오래 쓰기 실험', + '냄비 탄 자국 지우기 미션', + '케이블 정리 깔끔하게 챌린지', + '화장솜 대신 청소도구 활용 챌린지', + '집 청소 체크리스트 실전 미션', + '바닥 물걸레 순서 최적화 챌린지', + '청소 스트레스 줄이기 루틴', + '창문 미세먼지 차단 도전', + '버리기부터 시작하는 정리 챌린지', + '밀폐용기 얼룩 제거 실험', + '손때 많은 곳 집중 청소 미션', + '신발장 냄새 박멸 프로젝트', + '안 입는 옷 비우기 챌린지', + '생활 미니멀 수납 도전', + '기름 오염 옷 세탁 응급 미션', + '냉장고 냄새 원인 추적 챌린지', + '요리 후 기름 연기 제거 미션', + '주방 쓰레기 줄이기 프로젝트', + '아기 옷 안전 세탁 루틴', + '발자국 없는 바닥 만들기 챌린지', + '재사용 청소도구 실험 미션', + '5분 정리 습관 들이기 챌린지', + '침대 밑 먼지 쓸기 미션', + '선반 정리 색상 정렬 프로젝트', + '천연 세제 직접 만들기 도전', + '청소하면서 듣는 플레이리스트 챌린지', + '주말 30분 청소 미션', + '분리수거 헷갈림 해결 프로젝트', + '욕실 유리 물때 제거 챌린지', +]; + +export const dummyImage = [ + 'https://i.pinimg.com/736x/06/e6/e5/06e6e5415db0dc7aa3aaba1b93f03b20.jpg', + 'https://i.pinimg.com/1200x/7c/6f/7c/7c6f7c596654678b7400ef2c07c086ad.jpg', + 'https://i.pinimg.com/1200x/ad/24/04/ad24045a4a3ae15c6d765b3b711f61c1.jpg', + 'https://i.pinimg.com/1200x/7d/78/fa/7d78faa3c633bf5fde231e1b446bc4fb.jpg', + 'https://i.pinimg.com/1200x/12/76/a2/1276a2dc2749275436a49fb3561770ba.jpg', + 'https://i.pinimg.com/1200x/d8/0d/a9/d80da9b244d7de383b319457de475e89.jpg', + 'https://i.pinimg.com/1200x/cf/93/b1/cf93b11fac04e6790fab2f83e60b70cf.jpg', + 'https://i.pinimg.com/1200x/60/39/77/60397711931a8bbbdcb2d25493556a27.jpg', + 'https://i.pinimg.com/736x/62/6f/e8/626fe850e3fd8c62c9ad4126571112bf.jpg', + 'https://i.pinimg.com/736x/d2/61/35/d26135aa8f817737682d3d3589b3fa75.jpg', + 'https://i.pinimg.com/736x/be/0c/da/be0cdab6740eaa94e4bf65311188c464.jpg', + 'https://i.pinimg.com/736x/79/22/33/79223349ad21e1b39062ef2f6a456426.jpg', + 'https://i.pinimg.com/736x/18/6c/89/186c89559f3af60441287c50951221ee.jpg', + 'https://i.pinimg.com/1200x/6d/5d/e9/6d5de95a547745c7d7bd89dce36f1a21.jpg', + 'https://i.pinimg.com/1200x/e0/4f/8d/e04f8d546a74e3bfe597c3a2a21f47f1.jpg', + 'https://i.pinimg.com/1200x/a8/91/c9/a891c976daeb28498dbb45a4c82bea2c.jpg', + 'https://i.pinimg.com/1200x/33/3d/60/333d600f6fe8ade83159cfbab72ca404.jpg', + 'https://i.pinimg.com/736x/f4/40/db/f440db7021262be430ea92bb071d266b.jpg', + 'https://i.pinimg.com/736x/d4/b5/bb/d4b5bbba4b70be6f56bb5d35d548e4b3.jpg', + 'https://i.pinimg.com/1200x/f9/4e/4d/f94e4d3e4eadea9cb3d9cd8bc5f2f0cd.jpg', + 'https://i.pinimg.com/1200x/6c/14/d4/6c14d4ff5ce77a9b0ad6ffb2fed4ad71.jpg', + 'https://i.pinimg.com/736x/09/4a/d6/094ad6976c03c08f1b324c9972172b6f.jpg', + 'https://i.pinimg.com/1200x/09/70/8e/09708e37bea71d1daa2c1ff70b0c0901.jpg', + 'https://i.pinimg.com/736x/ed/38/66/ed38663596cc2b9472742b68c40b455b.jpg', + 'https://i.pinimg.com/1200x/da/8d/38/da8d3856b00b300c3edb466600a3ba9f.jpg', + 'https://i.pinimg.com/1200x/dd/28/20/dd282050b43ae6c2a4a789e7bec82fea.jpg', + 'https://i.pinimg.com/736x/23/72/fb/2372fbd717eb1ccb447830a774ca14dd.jpg', + 'https://i.pinimg.com/736x/14/5c/11/145c110257c3746aecd2f0b03349cb97.jpg', + 'https://i.pinimg.com/736x/37/a1/e9/37a1e93a266525cc3eb71effbc6f7d72.jpg', +]; diff --git a/umc-master/src/pages/mypage/components/RecentTips.tsx b/umc-master/src/pages/mypage/components/RecentTips.tsx index 59a56c6..9f974ea 100644 --- a/umc-master/src/pages/mypage/components/RecentTips.tsx +++ b/umc-master/src/pages/mypage/components/RecentTips.tsx @@ -4,6 +4,7 @@ import Typography from '@components/common/typography'; import { useNavigate } from 'react-router-dom'; import { recentStore } from '@store/recentStore'; import { useEffect } from 'react'; +import dummyImage from '@assets/dummyImage/dummy.jpeg'; const RecentTips: React.FC = () => { const theme = useTheme(); @@ -33,11 +34,11 @@ const RecentTips: React.FC = () => { {recentTips.map((item) => ( handleCardClick(String(item.tipId))} /> ))} diff --git a/umc-master/src/pages/saveTip/SaveTipPage.tsx b/umc-master/src/pages/saveTip/SaveTipPage.tsx index 2641d3b..a877484 100644 --- a/umc-master/src/pages/saveTip/SaveTipPage.tsx +++ b/umc-master/src/pages/saveTip/SaveTipPage.tsx @@ -80,8 +80,8 @@ const SaveTipPage: React.FC = () => { : placeholderImg } text={item.title} - likes={item.likes ?? 0} - bookmarks={item.bookmarks ?? 0} + likes={item.likeCount ?? 0} + bookmarks={item.saveCount ?? 0} date={item.createdAt.slice(0, 10)} onClick={() => handleCardClick(item.tipId)} /> diff --git a/umc-master/src/pages/saveTip/components/PostDetail.tsx b/umc-master/src/pages/saveTip/components/PostDetail.tsx index c9e1460..6af68ab 100644 --- a/umc-master/src/pages/saveTip/components/PostDetail.tsx +++ b/umc-master/src/pages/saveTip/components/PostDetail.tsx @@ -45,7 +45,19 @@ const PostDetail: React.FC = ({ detail }) => { return ( - {detail.media.length > 0 && 게시물 이미지} + {detail.media.length > 0 && ( + + {detail.media.map((item, index) => ( + + ))} + + )} {detail.title} @@ -98,14 +110,78 @@ const PostView = styled.div` align-self: stretch; `; -const Img = styled.img` +const ImageGrid = styled.div<{ count: number }>` + display: grid; width: 80vw; - height: 360px; + gap: 10px; + + ${({ count }) => + count === 1 + ? `grid-template-columns: 1fr; + grid-auto-rows: 360px;` + : count === 2 + ? `grid-template-columns: 1fr 1fr; + grid-auto-rows: 360px;` + : count === 3 + ? `grid-template-columns: repeat(3, 1fr);` + : count === 4 + ? ` + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: auto auto; + grid-template-areas: + "left top right" + "left bottom right"; + ` + : count === 5 + ? ` + display: grid; + grid-template-columns: 1fr 1fr 1fr; + & > :nth-child(4), & > :nth-child(5) { + grid-column: span 1; + } + ` + : `grid-template-columns: repeat(3, 1fr);`} + + grid-auto-rows: minmax(180px, auto); +`; + +const ImageItem = styled.img<{ index: number; count: number }>` + width: 100%; border-radius: 20px; object-fit: cover; - background: #d9d9d9; -`; + ${({ count, index }) => + count === 1 + ? `height: 360px;` + : count === 4 + ? index === 0 + ? `grid-area: left; grid-row: span 2; height: 360px;` + : index === 1 + ? `grid-area: top; height: 175px;` + : index === 2 + ? `grid-area: bottom; height: 175px;` + : index === 3 + ? `grid-area: right; grid-row: span 2; height: 360px;` + : '' + : // TODO: 수정 + // count === 5 + // ? index === 0 + // ? `grid-area: left; grid-row: span 2; height: 360px;` + // : index === 1 + // ? `grid-area: top; height: 175px;` + // : index === 2 + // ? `grid-area: bottom-left; grid-column: span 1; height: 175px;` + // : index === 3 + // ? `grid-area: bottom-right; grid-column: span 1; height: 175px;` + // : index === 4 + // ? `grid-area: right; grid-row: span 2; height: 360px;` + // : '' + count === 5 + ? index < 3 + ? `grid-column: span 1; height: 360px;` + : `grid-column: span 2; height: 360px;` + : `aspect-ratio: 16/9;`} +`; const PostInfo = styled.div` display: flex; justify-content: space-between; diff --git a/umc-master/src/router/routes.tsx b/umc-master/src/router/routes.tsx index d87d993..854b969 100644 --- a/umc-master/src/router/routes.tsx +++ b/umc-master/src/router/routes.tsx @@ -20,6 +20,39 @@ import ChatPage from '@pages/chat/ChatPage'; import ErrorPage from '@pages/error/ErrorPage'; import MyChallengePage from '@pages/mychallenge/MyChallenge'; import ChallengeDetailPage from '@pages/challenge/ChallengeDetailPage'; +import AuthWrapper from '@components/Auth/AuthWrapper'; + +// 인증이 필요한 라우트들을 배열로 정의 +const protectedRoutes = [ + { path: RoutePaths.MYPAGE, element: }, + { path: RoutePaths.MAIN, element: }, + { path: RoutePaths.SAVE_TIP, element: }, + { path: RoutePaths.SEARCH, element: }, + { path: RoutePaths.SAVE_TIP_DETAIL, element: }, + { path: RoutePaths.CREATE_POST, element: }, + { path: RoutePaths.COMMUNITY, element: }, + { path: RoutePaths.MAGAZINE, element: }, + { path: RoutePaths.MAGAZINE_DETAIL, element: }, + { path: RoutePaths.CHALLENGE, element: }, + { path: RoutePaths.MYCHALLENGE, element: }, + { path: RoutePaths.CHALLENGE_DETAIL, element: }, + { path: RoutePaths.CHAT, element: }, +]; + +// 인증이 필요없는 public 라우트들 +const publicRoutes = [ + { index: true, element: }, + { path: RoutePaths.LOGIN, element: }, + { path: RoutePaths.SIGNUP, element: }, + { path: RoutePaths.FINDPRIVACY, element: }, + { path: RoutePaths.KAKAO_CALLBACK, element: }, +]; + +// protected 라우트들을 AuthWrapper로 감싸기 +const wrappedProtectedRoutes = protectedRoutes.map((route) => ({ + ...route, + element: {route.element}, +})); const router = createBrowserRouter([ { @@ -28,26 +61,7 @@ const router = createBrowserRouter([ children: [ { errorElement: , - children: [ - { index: true, element: }, - { path: RoutePaths.LOGIN, element: }, - { path: RoutePaths.SIGNUP, element: }, - { path: RoutePaths.FINDPRIVACY, element: }, - { path: RoutePaths.MYPAGE, element: }, - { path: RoutePaths.MAIN, element: }, - { path: RoutePaths.SEARCH, element: }, - { path: RoutePaths.SAVE_TIP, element: }, - { path: RoutePaths.SAVE_TIP_DETAIL, element: }, - { path: RoutePaths.CREATE_POST, element: }, - { path: RoutePaths.COMMUNITY, element: }, - { path: RoutePaths.MAGAZINE, element: }, - { path: RoutePaths.MAGAZINE_DETAIL, element: }, - { path: RoutePaths.KAKAO_CALLBACK, element: }, - { path: RoutePaths.CHALLENGE, element: }, - { path: RoutePaths.MYCHALLENGE, element: }, - { path: RoutePaths.CHALLENGE_DETAIL, element: }, - { path: RoutePaths.CHAT, element: }, - ], + children: [...publicRoutes, ...wrappedProtectedRoutes], }, ], }, diff --git a/umc-master/src/store/tokenStore.ts b/umc-master/src/store/tokenStore.ts index 3f894cc..b06adaf 100644 --- a/umc-master/src/store/tokenStore.ts +++ b/umc-master/src/store/tokenStore.ts @@ -9,7 +9,7 @@ interface TokenStore { export const useTokenStore = create((set) => ({ accessToken: null, - refreshToken: `${import.meta.env.VITE_REFRESH_TOKEN}` /* 로그인 기능 완성 후 수정 예정임 */, + refreshToken: null /* 로그인 기능 완성 후 수정 예정임 */, setTokens: (tokens) => { // console.log('Setting Tokens:', tokens); set(() => ({