Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: read
jobs:
storybook:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
outputs:
status: ${{ job.status }}
steps:
Expand Down
22 changes: 14 additions & 8 deletions src/app/provider/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthStore {
accessToken: string | null;
refreshToken: string | null;
setAccessToken: (token: string | null) => void;
setRefreshToken: (token: string | null) => void;
isLoggedIn: boolean;
login: () => void;
logout: () => void;

name: string | null;
setName: (name: string) => void;

isModalOpen: boolean;
openModal: () => void;
Expand All @@ -15,13 +17,17 @@ interface AuthStore {
export const useAuthStore = create<AuthStore>()(
persist(
(set) => ({
accessToken: null,
refreshToken: null,
setAccessToken: (token) => set({ accessToken: token }),
setRefreshToken: (token) => set({ refreshToken: token }),
isLoggedIn: false,
isModalOpen: false,

openModal: () => set({ isModalOpen: true }),
closeModal: () => set({ isModalOpen: false }),

login: () => set({ isLoggedIn: true }),
logout: () => set({ isLoggedIn: false, name: null }),

name: null,
setName: (name) => set({ name }),
}), { name: 'auth-storage' }
)
);
Expand Down
4 changes: 4 additions & 0 deletions src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import TicketOptionCreatePage from '../../pages/dashboard/ui/ticket/TicketOption
import ResponseManagementPage from '../../pages/dashboard/ui/ResponsesManagementPage';
import TicketOptionResponsePage from '../../pages/dashboard/ui/ticket/TicketOptionResponsePage';
import TicketOptionAttachPage from '../../pages/dashboard/ui/ticket/TicketOptionAttachPage';
import AuthCallback from '../../pages/join/AuthCallback';
import LogoutPage from '../../pages/join/LogoutPage';

const mainRoutes = [
{ path: MAIN_ROUTES.main, element: <MainPage />, requiresAuth: false },
Expand All @@ -48,6 +50,7 @@ const mainRoutes = [
const joinRoutes = [
{ path: JOIN_ROUTES.agreement, element: <AgreementPage />, requiresAuth: false },
{ path: JOIN_ROUTES.infoInput, element: <InfoInputPage />, requiresAuth: false },
{ path: JOIN_ROUTES.authCallback, element: <AuthCallback />, requiresAuth: false },
];

const menuRoutes = [
Expand All @@ -57,6 +60,7 @@ const menuRoutes = [
{ path: MENU_ROUTES.hostEdit, element: <HostEditPage />, requiresAuth: false },
{ path: MENU_ROUTES.myPage, element: <MyPage />, requiresAuth: false },
{ path: MENU_ROUTES.hostInfo, element: <HostInfoPage />, requiresAuth: false },
{ path: MENU_ROUTES.logout, element: <LogoutPage />, requiresAuth: false },
];

const dashboardRoutes = [
Expand Down
2 changes: 2 additions & 0 deletions src/app/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const JOIN_ROUTES = {
// 회원가입 관련 페이지
agreement: '/join/agreement', // 이용약관 페이지
infoInput: '/join/info-input', // 정보입력 페이지
authCallback: '/authCallback'
};

export const MENU_ROUTES = {
Expand All @@ -27,6 +28,7 @@ export const MENU_ROUTES = {
hostEdit: `${MAIN_ROUTES.menu}/hostEdit/:id`,
myPage: `${MAIN_ROUTES.menu}/myPage`,
hostInfo: `${MAIN_ROUTES.menu}/hostInfo/:id`,
logout: `${MAIN_ROUTES.menu}/logout`,
};

export const DASHBOARD_ROUTES = {
Expand Down
12 changes: 12 additions & 0 deletions src/features/join/api/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { axiosClient } from "../../../shared/types/api/http-client"
import { UserInfoRequest, UserInfoResponse } from "../model/userInformation";

export const readUser = async (): Promise<UserInfoResponse> => {
const response = await axiosClient.get<{ result: UserInfoResponse }>('/users');
return response.data.result;
}

export const updateUser = async(data: UserInfoRequest): Promise<UserInfoResponse> => {
const response = await axiosClient.put<UserInfoResponse>('/users', data);
return response.data;
}
16 changes: 16 additions & 0 deletions src/features/join/hooks/useUserHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { readUser, updateUser } from '../api/user';
import { UserInfoRequest, UserInfoResponse } from '../model/userInformation';

export const useUserInfo = () => {
return useQuery<UserInfoResponse>({
queryKey: ['userInfo'],
queryFn: readUser,
});
};

export const useUserUpdate = () => {
return useMutation<UserInfoResponse, Error, UserInfoRequest>({
mutationFn: updateUser,
});
}
13 changes: 13 additions & 0 deletions src/features/join/model/userInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface UserInfoResponse {
id: number;
name: string;
phoneNumber: string;
email: string;
}

export interface UserInfoRequest {
id: number;
name: string;
phoneNumber: string;
email: string;
}
2 changes: 1 addition & 1 deletion src/pages/event/ui/EventDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const EventDetailsPage = () => {
const [clickedLike, setClickedLike] = useState(false);

const [event, setEvent] = useState<ReadEvent | null>(null);
const eventId = 1; //수정 필요
const eventId = 21; //수정 필요
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

하드코딩된 이벤트 ID에 대한 수정 필요

현재 이벤트 ID가 하드코딩되어 있고 주석에도 '수정 필요'라고 표시되어 있습니다. 실제 프로덕션 환경에서는 URL 파라미터나 상태 관리를 통해 동적으로 eventId를 받아오는 것이 좋습니다.

다음과 같이 URL에서 동적으로 ID를 받아오는 방식을 고려해보세요:

- const eventId = 21; //수정 필요
+ const { eventId } = useParams<{ eventId: string }>();
+ const numericEventId = eventId ? parseInt(eventId, 10) : undefined;

이 경우 Router 설정도 수정해야 합니다. 또한 API 호출 시 eventId가 undefined일 경우의 예외 처리도 필요합니다.


const handleShareClick = (title: string) => {
setTitle(title);
Expand Down
7 changes: 3 additions & 4 deletions src/pages/home/ui/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ const MainPage = () => {
{ img: secondPage, link: 'https://example.com/page2' },
{ img: thirdPage, link: 'https://example.com/page3' },
];

const [latestStartIndex, setLatestStartIndex] = useState<number>(0);
const [trendingStartIndex, setTrendingStartIndex] = useState<number>(0);
const [closingStartIndex, setClosingStartIndex] = useState<number>(0);
const maxCardsToShow = 2;
const navigate = useNavigate();
const { isModalOpen, openModal, closeModal} = useAuthStore();
const { isModalOpen, openModal, closeModal, isLoggedIn, name} = useAuthStore();

type SetStartIndex = Dispatch<SetStateAction<number>>;

Expand All @@ -43,7 +42,7 @@ const MainPage = () => {
const handlePrev = (setStartIndex: SetStartIndex, currentIndex: number, eventsLength: number): void => {
setStartIndex((currentIndex - 1 + eventsLength) % eventsLength);
};

return (
<div className="flex flex-col items-center pb-24">
<Header
Expand All @@ -58,7 +57,7 @@ const MainPage = () => {
leftButtonClassName="sm:text-lg md:text-xl lg:text-2xl font-extrabold font-nexon"
leftButtonClick={() => { }}
leftButtonLabel="같이가요"
rightContent={<SecondaryButton size="large" color="black" label="로그인" onClick={openModal} />}
rightContent={<SecondaryButton size="large" color="black" label={isLoggedIn ? `${name}님` : '로그인'} onClick={isLoggedIn ? closeModal : openModal} />}
/>
<AnimatePresence>{isModalOpen && <LoginModal onClose={closeModal} />}</AnimatePresence>

Expand Down
37 changes: 37 additions & 0 deletions src/pages/join/AuthCallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useNavigate, useSearchParams } from "react-router-dom";
import useAuthStore from "../../app/provider/authStore";
import { useEffect } from "react";
import { useUserInfo } from "../../features/join/hooks/useUserHook";

const AuthCallback = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const status = searchParams.get('status'); // 'new' or 'existing'
const { login, setName, isModalOpen } = useAuthStore();
const { data } = useUserInfo();

useEffect(() => {
const handleAuth = async () => {
try {
if (status === 'new') {
navigate('/join/agreement');
} else {
login();
setName(data?.name || "사용자");
console.log(isModalOpen);
navigate('/');
}
} catch (error) {
console.error('인증 처리 실패', error);
navigate('/');
}
};
handleAuth();
}, [data, navigate, login, status, setName, isModalOpen]);

return <div className="text-center mt-32 text-lg font-bold">로그인 중입니다...</div>;
};

export default AuthCallback;


74 changes: 36 additions & 38 deletions src/pages/join/InfoInputPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,50 @@ import Button from '../../../design-system/ui/Button';
import UnderlineTextField from '../../../design-system/ui/textFields/UnderlineTextField';
import { useNavigate } from 'react-router-dom';
import { FormData, zodValidation } from '../../shared/lib/formValidation';

const existingNames = ['김원영']; // 중복 이름 예시
const existingPhones = ['01012345678']; // 중복 연락처 예시
const existingEmails = ['example@example.com']; // 중복 이메일 예시
import { useUserInfo, useUserUpdate } from '../../features/join/hooks/useUserHook';
import useAuthStore from '../../app/provider/authStore';

const InfoInputPage = () => {
const { data, isLoading } = useUserInfo();
const { login, setName} = useAuthStore();
const { mutate: updateUser } = useUserUpdate();
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
} = useForm<FormData>({
mode: 'onChange',
...zodValidation,
});
const navigate = useNavigate();

const nameValue = watch('name');
const phoneValue = watch('phone');
const emailValue = watch('email');

// 버튼 활성화 조건
const isButtonEnabled = nameValue && phoneValue && emailValue && isValid;
const nameValue = data?.name;
const emailValue = data?.email;

const onSubmit: SubmitHandler<FormData> = data => {
console.log('제출 데이터:', data);
alert('정보 입력 완료!');
const onSubmit: SubmitHandler<FormData> = formData => {
const updatedData = {
id: data?.id || 0,
name: data?.name || "",
email: data?.email || "",
phoneNumber: formData.phone,
};
updateUser(updatedData, {
onSuccess: () => {
login();
setName(data?.name || "사용자");
alert('정보가 성공적으로 업데이트되었습니다.');
navigate('/');
},
onError: (err) => {
alert('정보 업데이트에 실패했습니다. 다시 시도해주세요.');
console.error(err);
},
});
};

// 디버깅 추가
console.log('name:', nameValue, errors.name?.message);
console.log('phone:', phoneValue, errors.phone?.message);
console.log('email:', emailValue, errors.email?.message);
console.log('isValid:', isValid);
console.log('isButtonEnabled:', isButtonEnabled);

if (isLoading) {
return <div>로딩 중...</div>;
}
return (
<div className="relative flex flex-col w-full h-screen border ">
<Header
Expand All @@ -54,49 +62,39 @@ const InfoInputPage = () => {
<UnderlineTextField
label="이름"
placeholder="이름"
value={nameValue}
errorMessage={errors.name?.message}
className="text-xl"
{...register('name', {
validate: {
notDuplicate: value => !existingNames.includes(value) || '이미 존재하는 이름입니다.',
},
})}
{...register('name')}
/>

{/* 연락처 필드 */}
<UnderlineTextField
label="연락처"
placeholder={`"-" 없이 번호만 입력해주세요`}
placeholder={"전화번호를 010-1234-5678 형식으로 입력해주세요."}
type="tel"
errorMessage={errors.phone?.message}
className="text-xl"
{...register('phone', {
validate: {
notDuplicate: value => !existingPhones.includes(value) || '이미 존재하는 연락처입니다.',
},
})}
{...register('phone')}
/>

{/* 이메일 필드 */}
<UnderlineTextField
label="이메일"
placeholder="이메일"
value={emailValue}
type="email"
errorMessage={errors.email?.message}
className="text-xl"
{...register('email', {
validate: {
notDuplicate: value => !existingEmails.includes(value) || '이미 존재하는 이메일입니다.',
},
})}
{...register('email')}
/>
</form>
<div className="flex flex-grow" />
<div className="w-full px-5 py-10">
<Button
label="시작하기"
onClick={handleSubmit(onSubmit)}
disabled={!isButtonEnabled}
disabled={!isValid}
className="w-full h-12 rounded-full"
/>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/pages/join/LogoutPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { axiosClient } from '../../shared/types/api/http-client';

const LogoutPage = () => {
const navigate = useNavigate();

useEffect(() => {
const logout = async () => {
try {
await axiosClient.post('/oauth/logout');
navigate('/');
} catch (error) {
console.error('로그아웃 실패:', error);
alert('로그아웃에 실패했습니다. 다시 시도해주세요.');
}
};
logout();
}, [navigate]);

return <div>로그아웃 중...</div>;
};

export default LogoutPage;
Loading