From 670d2864593f40146eee431b0ba7f2aef7a4ddf1 Mon Sep 17 00:00:00 2001 From: Park Junyoung Date: Sat, 17 Jan 2026 13:58:46 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=8F=AA=20Revert=20":wrench:=20Fix=20API?= =?UTF-8?q?=20setting=20&=20co.=20(#33)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-dev.yml | 2 -- src/api/apiClient.ts | 2 +- src/api/event.ts | 2 +- src/api/users/me.ts | 2 +- src/components/EventCardView.tsx | 2 +- src/components/EventDetailContent.tsx | 2 +- src/components/GuestSummaryList.tsx | 2 +- src/components/ProfileButton.tsx | 2 +- src/hooks/useAuthStore.ts | 2 +- src/hooks/useEventDetail.ts | 3 +-- src/routes/EventRegister.tsx | 2 +- src/types/auth.ts | 2 +- src/types/{schemas.ts => event.ts} | 24 +++++++++++++++++------- src/types/events.ts | 18 ------------------ src/types/user.ts | 6 ++++++ src/types/users.ts | 2 +- 16 files changed, 35 insertions(+), 40 deletions(-) rename src/types/{schemas.ts => event.ts} (60%) delete mode 100644 src/types/events.ts create mode 100644 src/types/user.ts diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index f5f4b9a..9462202 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -20,8 +20,6 @@ jobs: node-version: '20.11.1' - name: Build & Export - env: - VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }} run: | yarn install yarn build diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index b55bdbc..45e9e62 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -3,7 +3,7 @@ import useAuthStore from '../hooks/useAuthStore'; // 공통 설정을 가진 axios 인스턴스 생성 const apiClient = axios.create({ - baseURL: `{import.meta.env.VITE_API_BASE_URL}/api/v1`, + baseURL: 'http://3.35.89.220/api/v1', // 모든 요청의 기본 경로 timeout: 5000, headers: { 'Content-Type': 'application/json' }, }); diff --git a/src/api/event.ts b/src/api/event.ts index dea5a2f..5550243 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -3,7 +3,7 @@ import type { JoinEventRequest, JoinEventResponse, RegistrationListResponse, -} from '@/types/events'; +} from '../types/event'; import apiClient from './apiClient'; // 일정 상세 응답 (GET /api/events/:id) diff --git a/src/api/users/me.ts b/src/api/users/me.ts index e957589..5dd0935 100644 --- a/src/api/users/me.ts +++ b/src/api/users/me.ts @@ -2,6 +2,6 @@ import apiClient from '@/api/apiClient'; import type { GetMeResponse } from '@/types/users'; export default async function getMe(): Promise { - const response = await apiClient.get('/users/me'); + const response = await apiClient.get('/auth/me'); return response.data; } diff --git a/src/components/EventCardView.tsx b/src/components/EventCardView.tsx index 1a8827e..17d3929 100644 --- a/src/components/EventCardView.tsx +++ b/src/components/EventCardView.tsx @@ -7,7 +7,7 @@ import { ItemMedia, ItemTitle, } from '@/components/ui/item'; -import type { Event } from '@/types/schemas'; +import type { Event } from '@/types/event'; import { getShortenedDate } from '@/utils/date'; import { ChevronRightIcon, Plus } from 'lucide-react'; import { Link } from 'react-router'; diff --git a/src/components/EventDetailContent.tsx b/src/components/EventDetailContent.tsx index 1cb910e..720a98a 100644 --- a/src/components/EventDetailContent.tsx +++ b/src/components/EventDetailContent.tsx @@ -1,6 +1,6 @@ import { ScrollArea } from '@/components/ui/scroll-area'; -import type { Event } from '@/types/schemas'; import { useNavigate } from 'react-router'; +import type { Event } from '../types/event'; import { formatEventDate } from '../utils/date'; interface EventDetailContentProps { diff --git a/src/components/GuestSummaryList.tsx b/src/components/GuestSummaryList.tsx index da7388d..682e222 100644 --- a/src/components/GuestSummaryList.tsx +++ b/src/components/GuestSummaryList.tsx @@ -1,6 +1,6 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; -import type { Guest } from '@/types/schemas'; +import type { Guest } from '@/types/event'; import { useNavigate } from 'react-router'; interface Props { diff --git a/src/components/ProfileButton.tsx b/src/components/ProfileButton.tsx index de2f3f7..50f70c7 100644 --- a/src/components/ProfileButton.tsx +++ b/src/components/ProfileButton.tsx @@ -8,7 +8,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import type { User } from '@/types/schemas'; +import type { User } from '@/types/user'; interface ProfileButtonProps { user: User; diff --git a/src/hooks/useAuthStore.ts b/src/hooks/useAuthStore.ts index 11aebb1..755f1b2 100644 --- a/src/hooks/useAuthStore.ts +++ b/src/hooks/useAuthStore.ts @@ -1,4 +1,4 @@ -import type { User } from '@/types/schemas'; +import type { User } from '@/types/user'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; diff --git a/src/hooks/useEventDetail.ts b/src/hooks/useEventDetail.ts index b353cfc..e9ba199 100644 --- a/src/hooks/useEventDetail.ts +++ b/src/hooks/useEventDetail.ts @@ -1,9 +1,8 @@ -import type { JoinEventRequest } from '@/types/events'; -import type { Event, Guest } from '@/types/schemas'; import { isAxiosError } from 'axios'; import { useCallback, useState } from 'react'; import { toast } from 'sonner'; import { getEventDetail, getRegistrations, registerEvent } from '../api/event'; +import type { Event, Guest, JoinEventRequest } from '../types/event'; type EventFetchStatus = | 'GUEST' diff --git a/src/routes/EventRegister.tsx b/src/routes/EventRegister.tsx index d2d5e85..78572c3 100644 --- a/src/routes/EventRegister.tsx +++ b/src/routes/EventRegister.tsx @@ -1,11 +1,11 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import type { JoinEventRequest } from '@/types/events'; import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import { GOOGLE_AUTH_URL, KAKAO_AUTH_URL } from '../constants/auth'; import useEventDetail from '../hooks/useEventDetail'; +import type { JoinEventRequest } from '../types/event'; import { formatEventDate } from '../utils/date'; // 아이콘 컴포넌트 diff --git a/src/types/auth.ts b/src/types/auth.ts index 8b89f12..23ec30f 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -1,4 +1,4 @@ -import type { User } from '@/types/schemas'; +import type { User } from '@/types/user'; // ---------- /singup ---------- diff --git a/src/types/schemas.ts b/src/types/event.ts similarity index 60% rename from src/types/schemas.ts rename to src/types/event.ts index 5184f9b..d7ebcbd 100644 --- a/src/types/schemas.ts +++ b/src/types/event.ts @@ -1,10 +1,3 @@ -export interface User { - id: number; - email: string; - name: string; - profileImage?: string; -} - export interface Event { id: number; title: string; @@ -29,3 +22,20 @@ export interface Guest { status: 'CONFIRMED' | 'WAITING' | 'CANCELED'; createdAt: number; // 신청 일시 } + +// 일정 상세 응답 (GET /api/events/:id) +export type EventDetailResponse = Event; + +// 참여자 명단 (GET /api/events/:id/registrations) +export type RegistrationListResponse = Guest[]; + +// 참여 신청 (POST /api/events/:id/registrations) +export interface JoinEventRequest { + guestName: string | null; + guestEmail: string | null; +} + +export interface JoinEventResponse { + registration: Guest; + cancelToken: string; // 신청 취소용 토큰 +} diff --git a/src/types/events.ts b/src/types/events.ts deleted file mode 100644 index 60e960b..0000000 --- a/src/types/events.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Event, Guest } from '@/types/schemas'; - -// 일정 상세 응답 (GET /api/events/:id) -export type EventDetailResponse = Event; - -// 참여자 명단 (GET /api/events/:id/registrations) -export type RegistrationListResponse = Guest[]; - -// 참여 신청 (POST /api/events/:id/registrations) -export interface JoinEventRequest { - guestName: string | null; - guestEmail: string | null; -} - -export interface JoinEventResponse { - registration: Guest; - cancelToken: string; // 신청 취소용 토큰 -} diff --git a/src/types/user.ts b/src/types/user.ts new file mode 100644 index 0000000..2867e3c --- /dev/null +++ b/src/types/user.ts @@ -0,0 +1,6 @@ +export interface User { + id: number; + email: string; + name: string; + profileImage?: string; +} diff --git a/src/types/users.ts b/src/types/users.ts index d432963..ab8666a 100644 --- a/src/types/users.ts +++ b/src/types/users.ts @@ -1,4 +1,4 @@ -import type { User } from '@/types/schemas'; +import type { User } from '@/types/user'; // ---------- /me ---------- export type GetMeResponse = User; From d5fc7082f1573518344ba2aa73413aba5c273f95 Mon Sep 17 00:00:00 2001 From: Park Junyoung Date: Sat, 17 Jan 2026 13:59:17 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=8F=AA=20Revert=20"=F0=9F=91=BD=20Use=20?= =?UTF-8?q?the=20API=20for=20authorisation=20&=20co.=20(#32)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth/login.ts | 10 +++--- src/api/auth/logout.ts | 7 ---- src/api/auth/me.ts | 7 ++++ src/api/auth/signup.ts | 20 +++++------ src/api/auth/social.ts | 9 ++--- src/api/users/me.ts | 7 ---- src/components/EventCardView.tsx | 23 +++++++----- src/components/Header.tsx | 15 ++++++-- src/components/ProfileButton.tsx | 2 +- src/hooks/useAuth.ts | 34 ++++++++++-------- src/hooks/useAuthStore.ts | 2 +- src/routes/Home.tsx | 44 ++++++++++++++++------- src/routes/Login.tsx | 12 +++---- src/routes/RegisterForm.tsx | 16 ++++----- src/types/auth.ts | 58 +++++++++++++++--------------- src/types/event.ts | 12 +++---- src/types/schema.ts | 60 ++++++++++++++++++++++++++++++++ src/types/user.ts | 6 ---- src/types/users.ts | 4 --- 19 files changed, 209 insertions(+), 139 deletions(-) delete mode 100644 src/api/auth/logout.ts create mode 100644 src/api/auth/me.ts delete mode 100644 src/api/users/me.ts create mode 100644 src/types/schema.ts delete mode 100644 src/types/user.ts delete mode 100644 src/types/users.ts diff --git a/src/api/auth/login.ts b/src/api/auth/login.ts index abd9520..dd86a82 100644 --- a/src/api/auth/login.ts +++ b/src/api/auth/login.ts @@ -1,9 +1,7 @@ -import apiClient from '@/api/apiClient'; -import type { LoginRequest, LoginResponse } from '@/types/auth'; +import type { AuthResponse, LoginRequest } from '../../types/auth'; +import apiClient from '../apiClient'; -export default async function login( - data: LoginRequest -): Promise { - const response = await apiClient.post('/auth/login', data); +export default async function login(data: LoginRequest): Promise { + const response = await apiClient.post('/auth/login', data); return response.data; } diff --git a/src/api/auth/logout.ts b/src/api/auth/logout.ts deleted file mode 100644 index 50ff457..0000000 --- a/src/api/auth/logout.ts +++ /dev/null @@ -1,7 +0,0 @@ -import apiClient from '@/api/apiClient'; -import type { LogoutResponse } from '@/types/auth'; - -export default async function logout(): Promise { - const response = await apiClient.post('/auth/logout'); - return response.data; -} diff --git a/src/api/auth/me.ts b/src/api/auth/me.ts new file mode 100644 index 0000000..62b5902 --- /dev/null +++ b/src/api/auth/me.ts @@ -0,0 +1,7 @@ +import type { User, UserGroup } from '../../types/auth'; +import apiClient from '../apiClient'; + +export default async function getMe(): Promise { + const response = await apiClient.get('/auth/me'); + return response.data; +} diff --git a/src/api/auth/signup.ts b/src/api/auth/signup.ts index 8ed8fab..94b341f 100644 --- a/src/api/auth/signup.ts +++ b/src/api/auth/signup.ts @@ -1,23 +1,19 @@ -import apiClient from '@/api/apiClient'; -import type { SignUpRequest, SignUpResponse } from '@/types/auth'; +import type { AuthResponse, SignUpRequest } from '../../types/auth'; +import apiClient from '../apiClient'; export default async function signup( data: SignUpRequest -): Promise { +): Promise { const formData = new FormData(); - formData.append('email', data.email); - formData.append('name', data.name); + formData.append('username', data.username); formData.append('password', data.password); + formData.append('name', data.name); - if (data.profileImage) { - formData.append('profileImage', data.profileImage); + if (data.photo) { + formData.append('photo', data.photo); // 백엔드 필드명과 'photo' 일치시키기 } - const response = await apiClient.post( - '/auth/signup', - formData - ); - + const response = await apiClient.post('/auth/signup', formData); return response.data; } diff --git a/src/api/auth/social.ts b/src/api/auth/social.ts index e5826e9..d9468e0 100644 --- a/src/api/auth/social.ts +++ b/src/api/auth/social.ts @@ -1,12 +1,9 @@ -import type { SocialLoginRequest, SocialLoginResponse } from '../../types/auth'; +import type { AuthResponse, SocialLoginRequest } from '../../types/auth'; import apiClient from '../apiClient'; export default async function social( data: SocialLoginRequest -): Promise { - const response = await apiClient.post( - '/auth/social', - data - ); +): Promise { + const response = await apiClient.post('/auth/social', data); return response.data; } diff --git a/src/api/users/me.ts b/src/api/users/me.ts deleted file mode 100644 index 5dd0935..0000000 --- a/src/api/users/me.ts +++ /dev/null @@ -1,7 +0,0 @@ -import apiClient from '@/api/apiClient'; -import type { GetMeResponse } from '@/types/users'; - -export default async function getMe(): Promise { - const response = await apiClient.get('/auth/me'); - return response.data; -} diff --git a/src/components/EventCardView.tsx b/src/components/EventCardView.tsx index 17d3929..50745ef 100644 --- a/src/components/EventCardView.tsx +++ b/src/components/EventCardView.tsx @@ -7,29 +7,31 @@ import { ItemMedia, ItemTitle, } from '@/components/ui/item'; -import type { Event } from '@/types/event'; +import type { Events } from '@/types/schema'; import { getShortenedDate } from '@/utils/date'; import { ChevronRightIcon, Plus } from 'lucide-react'; import { Link } from 'react-router'; import { Button } from './ui/button'; +type Event = Events & { guests: number; registration_start: string | null }; + function getDateLabel( - startEpoch: number | undefined, - endEpoch: number | undefined + startDateString: string | null, + endDateString: string | null ) { - if (!endEpoch) { + if (!endDateString) { return '상시 모집 중'; } const now = new Date(); - const endDate = new Date(endEpoch); + const endDate = new Date(endDateString!); - if (startEpoch && now < new Date(startEpoch)) { - return `${getShortenedDate(startEpoch)}부터 모집`; + if (startDateString && now < new Date(startDateString)) { + return `${getShortenedDate(startDateString)}부터 모집`; } if (now <= endDate) { - return `${getShortenedDate(endEpoch)}까지 모집`; + return `${getShortenedDate(endDateString)}까지 모집`; } return '모집 마감'; @@ -82,7 +84,10 @@ export default function EventCardView({ events }: { events: Event[] }) { - {getDateLabel(undefined, event.registrationDeadline)} + {getDateLabel( + event.registration_start, + event.registration_deadline + )} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 58c250c..cf36306 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -2,8 +2,17 @@ import ProfileButton from '@/components/ProfileButton'; import useAuth from '@/hooks/useAuth'; import { Link, NavLink } from 'react-router'; +const userExample = { + id: 1, + email: 'user@example.com', + name: '박준영', + profileImage: 'https://github.com/shadcn.png', + createdAt: '2023-01-01T00:00:00.000Z', + updatedAt: '2023-01-01T00:00:00.000Z', +}; + export default function Header() { - const { user, isLoggedIn, handleLogout } = useAuth(); + const { isLoggedIn, handleLogout } = useAuth(); const linkClassName = (isActive: boolean) => ` h-[42px] items-center px-4 py-2 rounded-md text-black font-semibold @@ -17,7 +26,7 @@ export default function Header() { 모이샤
- {!isLoggedIn || !user ? ( + {!isLoggedIn ? ( <> ) : ( - + )}
diff --git a/src/components/ProfileButton.tsx b/src/components/ProfileButton.tsx index 50f70c7..9632e6e 100644 --- a/src/components/ProfileButton.tsx +++ b/src/components/ProfileButton.tsx @@ -8,7 +8,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import type { User } from '@/types/user'; +import type { User } from '@/types/auth'; interface ProfileButtonProps { user: User; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 1a967be..d55a543 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,15 +1,14 @@ -import loginApi from '@/api/auth/login'; -import logoutApi from '@/api/auth/logout'; -import signupApi from '@/api/auth/signup'; -import socialApi from '@/api/auth/social'; -import getMeApi from '@/api/users/me'; -import useAuthStore from '@/hooks/useAuthStore'; +import { useNavigate } from 'react-router'; +import loginApi from '../api/auth/login'; +import getMeApi from '../api/auth/me'; +import signupApi from '../api/auth/signup'; +import socialApi from '../api/auth/social'; import type { LoginRequest, SignUpRequest, SocialLoginRequest, -} from '@/types/auth'; -import { useNavigate } from 'react-router'; +} from '../types/auth'; +import useAuthStore from './useAuthStore'; export default function useAuth() { const navigate = useNavigate(); @@ -21,8 +20,7 @@ export default function useAuth() { // 1. 이메일 로그인 로직 const handleLogin = async (data: LoginRequest) => { try { - const { token } = await loginApi(data); - const user = await getMeApi(); + const { user, token } = await loginApi(data); login(user, token); // Zustand 스토어 업데이트 navigate('/'); // 메인 페이지로 이동 } catch (error) { @@ -34,8 +32,9 @@ export default function useAuth() { // 2. 이메일 회원가입 로직 const handleSignUp = async (data: SignUpRequest) => { try { - await signupApi(data); - navigate('/login'); + const { user, token } = await signupApi(data); + login(user, token); + navigate('/'); } catch (error) { console.error('Signup failed:', error); alert('회원가입 중 오류가 발생했습니다.'); @@ -45,7 +44,7 @@ export default function useAuth() { // 3. 소셜 로그인 로직 const handleSocialLogin = async (data: SocialLoginRequest) => { try { - const { token, user } = await socialApi(data); + const { user, token } = await socialApi(data); login(user, token); navigate('/'); } catch (error) { @@ -69,12 +68,16 @@ export default function useAuth() { }; // 5. 로그아웃 로직 - const handleLogout = async () => { - await logoutApi(); + const handleLogout = () => { logout(); // Zustand 상태 초기화 navigate('/login'); }; + // 6. 관리자 확인 + const isAdmin = (ownerId: number | undefined) => { + return isLoggedIn && user?.id === ownerId; + }; + return { user, isLoggedIn, @@ -83,5 +86,6 @@ export default function useAuth() { handleSocialLogin, refreshUser, handleLogout, + isAdmin, }; } diff --git a/src/hooks/useAuthStore.ts b/src/hooks/useAuthStore.ts index 755f1b2..05d00db 100644 --- a/src/hooks/useAuthStore.ts +++ b/src/hooks/useAuthStore.ts @@ -1,6 +1,6 @@ -import type { User } from '@/types/user'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; +import type { User } from '../types/auth'; interface AuthState { user: User | null; diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index e87d199..24bd6ec 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -5,18 +5,38 @@ import { Link } from 'react-router'; const events = [ { - id: 0, - title: 'string', - description: 'string', - location: 'string', - startAt: 0, - endAt: 0, - capacity: 0, - waitlistEnabled: true, - registrationDeadline: 0, - createdBy: 0, - createdAt: 0, - updatedAt: 0, + id: 1, + title: '제2회 기획 세미나', + description: + '일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 ...', + location: '서울대', + start_at: '2026-02-02T18:00:00Z', + end_at: '2026-02-02T20:00:00Z', + guests: 12, + capacity: 10, + waitlist_enabled: true, + registration_start: '2026-01-20T09:00:00Z', + registration_deadline: '2026-02-02T17:00:00Z', + created_by: 123, + created_at: '2026-01-14T00:00:00Z', + updated_at: '2026-01-14T00:00:00Z', + }, + { + id: 2, + title: '2026 와커톤', + description: + '그동안 각자의 위치에서 몰두해 온 여러분들이 한자리에 모여 뜨거운 에너지를 나누고, 무박 2일간 개발에 집중하며 즐길 수 있는 행사입니다.', + location: '성수 엘리스랩 라운지', + start_at: '2026-01-01T18:00:00Z', + end_at: '2026-02-13T20:00:00Z', + guests: 6, + capacity: 10, + waitlist_enabled: true, + registration_start: '2025-02-01T09:00:00Z', + registration_deadline: '2026-02-02T17:00:00Z', + created_by: 124, + created_at: '2026-01-14T00:00:00Z', + updated_at: '2026-01-14T00:00:00Z', }, ]; diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index 00d2252..9aeec8d 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -1,16 +1,16 @@ -import { GOOGLE_AUTH_URL, KAKAO_AUTH_URL } from '@/constants/auth'; -import useAuth from '@/hooks/useAuth'; import { useState } from 'react'; import { Link } from 'react-router'; +import { GOOGLE_AUTH_URL, KAKAO_AUTH_URL } from '../constants/auth'; +import useAuth from '../hooks/useAuth'; export default function Login() { - const [email, setEmail] = useState(''); + const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const { handleLogin } = useAuth(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - handleLogin({ email, password }); + handleLogin({ username, password }); }; return ( @@ -37,8 +37,8 @@ export default function Login() { required className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="이메일 (아이디)" - value={email} - onChange={(e) => setEmail(e.target.value)} + value={username} + onChange={(e) => setUsername(e.target.value)} />
diff --git a/src/routes/RegisterForm.tsx b/src/routes/RegisterForm.tsx index a954f86..1e5f39f 100644 --- a/src/routes/RegisterForm.tsx +++ b/src/routes/RegisterForm.tsx @@ -8,7 +8,7 @@ export default function RegisterForm() { const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [photo, setPhoto] = useState(null); - const [profileImage, setProfileImage] = useState(null); + const [previewUrl, setPreviewUrl] = useState(null); const [showErrors, setShowErrors] = useState(false); const emailRef = useRef(null); @@ -22,12 +22,12 @@ export default function RegisterForm() { // 사진이 선택될 때마다 미리보기 URL 생성 useEffect(() => { if (!photo) { - setProfileImage(null); + setPreviewUrl(null); return; } const objectUrl = URL.createObjectURL(photo); - setProfileImage(objectUrl); + setPreviewUrl(objectUrl); // 컴포넌트 언마운트 시 메모리 누수 방지를 위해 URL 해제 return () => URL.revokeObjectURL(objectUrl); @@ -76,11 +76,7 @@ export default function RegisterForm() { return; } - if (profileImage) { - handleSignUp({ email, name, password, profileImage }); - } else { - handleSignUp({ email, name, password }); - } + handleSignUp({ username: email, password, name, photo }); }; const errorTextStyle = 'mt-1 text-xs text-red-500 font-medium'; @@ -95,9 +91,9 @@ export default function RegisterForm() {
- {profileImage ? ( + {previewUrl ? ( Preview diff --git a/src/types/auth.ts b/src/types/auth.ts index 23ec30f..d6e0718 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -1,43 +1,45 @@ -import type { User } from '@/types/user'; - -// ---------- /singup ---------- - -export interface SignUpRequest { +// 유저 기본 정보 +export interface User { + id: number; email: string; name: string; - password: string; - profileImage?: string; + profileImage: string; + createdAt: string; + updatedAt: string; } -export type SignUpResponse = User; - -// ---------- /logout ---------- +// 멤버 권한 타입 +type MemberRole = 'leader' | 'co-leader' | 'admin' | 'member'; -export interface LogoutResponse { - message: string; +// 내 정보 조회 시 함께 내려올 그룹 정보 +export interface UserGroup { + groupId: number; // group id + groupName: string; // group name + role: MemberRole; // member type } -// ---------- /login ---------- +// API 응답 공통 규격 +export interface AuthResponse { + user: User; + token: string; +} +// 로그인 요청 데이터 export interface LoginRequest { - email: string; - password: string; + username: string; // 이메일 + password: string; // 비밀번호 } -export interface LoginResponse { - token: string; +// 회원가입 요청 데이터 +export interface SignUpRequest { + username: string; + password: string; + name: string; + photo: File | null; // 회원가입 시에는 선택 사항일 수 있음 } -// ---------- /social ---------- - -type AuthProvider = 'google' | 'kakao'; - +// 소셜 로그인 요청 데이터 export interface SocialLoginRequest { - provider: AuthProvider; - code: string; -} - -export interface SocialLoginResponse { - token: string; - user: User; + provider: 'google' | 'kakao'; + code: string; // 플랫폼으로부터 받은 인가 코드 } diff --git a/src/types/event.ts b/src/types/event.ts index d7ebcbd..4915bcd 100644 --- a/src/types/event.ts +++ b/src/types/event.ts @@ -1,13 +1,13 @@ export interface Event { id: number; title: string; - description?: string; - location?: string; - startAt?: number; // epoch milliseconds - endAt?: number; // epoch milliseconds - capacity?: number; + description: string | null; + location: string | null; + startAt: number; // epoch milliseconds + endAt: number; // epoch milliseconds + capacity: number; waitlistEnabled: boolean; - registrationDeadline?: number; // epoch milliseconds + registrationDeadline: number; // epoch milliseconds createdBy: number; // 작성자 ID createdAt?: number; updatedAt?: number; diff --git a/src/types/schema.ts b/src/types/schema.ts new file mode 100644 index 0000000..dd9d9c7 --- /dev/null +++ b/src/types/schema.ts @@ -0,0 +1,60 @@ +// export interface UserIdentities { +// id: number; // PK / BIGINT +// user_id: number; // FK / BIGINT +// provider: string; // VARCHAR(50) +// provider_user_id: string; // VARCHAR(191) +// created_at: string; // DATETIME(6) +// } + +export interface Events { + id: number | null; // PK / BIGINT + title: string; // VARCHAR(255) + description: string | null; // TEXT + location: string | null; // VARCHAR(255) + start_at: string | null; // DATETIME(6) + end_at: string | null; // DATETIME(6) + capacity: number | null; // INT + waitlist_enabled: boolean; // BOOLEAN + registration_deadline: string | null; // DATETIME(6) + created_by: number; // FK / BIGINT + created_at: string; // DATETIME(6) + updated_at: string; // DATETIME(6) +} + +// export interface PendingUsers { +// id: number; // PK / BIGINT +// email: string; // VARCHAR(255) +// name: string; // VARCHAR(100) +// password_hash: string; // VARCHAR(255) +// verification_code: string; // VARCHAR(255) +// created_at: string; // TIMESTAMP +// expires_at: string; // TIMESTAMP +// } + +// export interface Users { +// id: number; // PK / BIGINT +// email: string; // VARCHAR(255) +// name: string; // VARCHAR(100) +// password_hash: string | null; // VARCHAR(255) +// profile_image: string | null; // VARCHAR(255) +// created_at: string; // DATETIME(6) +// updated_at: string; // DATETIME(6) +// } + +// export interface RegistrationTokens { +// id: number | null; // PK / BIGINT +// registration_id: number | null; // FK / BIGINT +// token_hash: string; // CHAR(64) +// purpose: string; // ENUM('CANCEL','CHANGE_VOTE') +// created_at: string; // DATETIME(6) +// } + +// export interface Registrations { +// id: number | null; // PK / BIGINT +// user_id: number; // FK / BIGINT +// event_id: number | null; // FK / BIGINT +// guest_name: string | null; // VARCHAR(100) +// guest_email: string | null; // VARCHAR(255) +// status: string; // ENUM('CONFIRMED','WAITING','CANCELED') +// created_at: string; // DATETIME(6) +// } diff --git a/src/types/user.ts b/src/types/user.ts deleted file mode 100644 index 2867e3c..0000000 --- a/src/types/user.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface User { - id: number; - email: string; - name: string; - profileImage?: string; -} diff --git a/src/types/users.ts b/src/types/users.ts deleted file mode 100644 index ab8666a..0000000 --- a/src/types/users.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { User } from '@/types/user'; - -// ---------- /me ---------- -export type GetMeResponse = User; From b2a21c3c584ed7f7e00d7182be76e99f67c30e62 Mon Sep 17 00:00:00 2001 From: Park Junyoung Date: Sat, 17 Jan 2026 13:59:49 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E2=8F=AA=20Revert=20"=F0=9F=91=BD=EF=B8=8F?= =?UTF-8?q?=20Use=20the=20API=20for=20event-related=20pages=20(#30)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/apiClient.ts | 3 +- src/api/event.ts | 50 -------- src/components/EventDetailContent.tsx | 22 ++-- src/components/GuestSummaryList.tsx | 12 +- src/hooks/useEventDetail.ts | 97 -------------- src/routes/Event.tsx | 118 +++++++---------- src/routes/EventRegister.tsx | 72 +++++------ src/routes/EventRegisterSuccess.tsx | 44 ++++--- src/routes/Guests.tsx | 176 ++++++++++++-------------- src/routes/JoinEvent.tsx | 124 +++++++----------- src/types/event.ts | 41 ------ src/utils/date.ts | 8 +- 12 files changed, 248 insertions(+), 519 deletions(-) delete mode 100644 src/api/event.ts delete mode 100644 src/hooks/useEventDetail.ts delete mode 100644 src/types/event.ts diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index 45e9e62..bfa312d 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -3,9 +3,8 @@ import useAuthStore from '../hooks/useAuthStore'; // 공통 설정을 가진 axios 인스턴스 생성 const apiClient = axios.create({ - baseURL: 'http://3.35.89.220/api/v1', // 모든 요청의 기본 경로 + baseURL: '/api', // 모든 요청의 기본 경로 timeout: 5000, - headers: { 'Content-Type': 'application/json' }, }); // 요청 인터셉터: 모든 요청 직전에 실행됨 diff --git a/src/api/event.ts b/src/api/event.ts deleted file mode 100644 index 5550243..0000000 --- a/src/api/event.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { - EventDetailResponse, - JoinEventRequest, - JoinEventResponse, - RegistrationListResponse, -} from '../types/event'; -import apiClient from './apiClient'; - -// 일정 상세 응답 (GET /api/events/:id) -export const getEventDetail = async (id: string) => { - const response = await apiClient.get(`/events/${id}`); - return { - data: response.data, - status: response.status, - }; -}; - -// 참여자 명단 (GET /api/events/:id/registrations) -export const getRegistrations = async (id: string) => { - const response = await apiClient.get( - `/events/${id}/registrations` - ); - return response.data; -}; - -// 참여 신청 (POST /api/events/:id/registrations) -export const registerEvent = async (id: string, data: JoinEventRequest) => { - const response = await apiClient.post( - `/events/${id}/registrations`, - data - ); - return { - data: response.data, - status: response.status, - }; -}; - -// 이벤트 신청 취소(POST /api/v1/events/{eventId}/registrations/{registrationId}/cancel) -// export const cancelRegistration = async ( -// eventId: string, -// registrationId: number, -// token: string -// ) => { -// const response = await apiClient.post( -// `/events/${eventId}/registrations/${registrationId}/cancel`, -// null, // body는 비우고 query parameter로 전달 -// { params: { token } } -// ); -// return response.data; -// }; diff --git a/src/components/EventDetailContent.tsx b/src/components/EventDetailContent.tsx index 720a98a..6fba772 100644 --- a/src/components/EventDetailContent.tsx +++ b/src/components/EventDetailContent.tsx @@ -1,11 +1,10 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { useNavigate } from 'react-router'; -import type { Event } from '../types/event'; +import type { Events } from '../types/schema'; import { formatEventDate } from '../utils/date'; -interface EventDetailContentProps { - schedule: Event; - currentParticipants: number; +interface Props { + schedule: Events; } // 아이콘 @@ -24,10 +23,7 @@ const IconChevronLeft = () => ( ); -export default function EventDetailContent({ - schedule, - currentParticipants, -}: EventDetailContentProps) { +export default function EventDetailContent({ schedule }: Props) { const navigate = useNavigate(); return ( @@ -35,7 +31,7 @@ export default function EventDetailContent({ {/* 1. 일시 및 장소 */}

- 일시 {formatEventDate(schedule.startAt)} + 일시 {formatEventDate(schedule.start_at)}

장소 {schedule.location || '미정'} @@ -44,12 +40,12 @@ export default function EventDetailContent({ {/* 2. 신청 현황 버튼 */}

- {event.title} + {schedule.title}

@@ -223,10 +204,7 @@ export default function Event() { {/* 2. 메인 콘텐츠*/}
{/* 일정 정보 */} - + {/* 모집 마감 및 링크 블록 */}
@@ -251,9 +229,9 @@ export default function Event() { {/* 참여자 명단 섹션 */}
diff --git a/src/routes/EventRegister.tsx b/src/routes/EventRegister.tsx index 78572c3..98cc963 100644 --- a/src/routes/EventRegister.tsx +++ b/src/routes/EventRegister.tsx @@ -4,8 +4,7 @@ import { Label } from '@/components/ui/label'; import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import { GOOGLE_AUTH_URL, KAKAO_AUTH_URL } from '../constants/auth'; -import useEventDetail from '../hooks/useEventDetail'; -import type { JoinEventRequest } from '../types/event'; +import type { Events } from '../types/schema'; import { formatEventDate } from '../utils/date'; // 아이콘 컴포넌트 @@ -27,8 +26,9 @@ const IconChevronLeft = () => ( export default function EventRegister() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const { loading, event, confirmedCount, handleFetchDetail, handleJoinEvent } = - useEventDetail(); + + // 일정 데이터 상태 + const [schedule, setSchedule] = useState(null); // 폼 상태 관리 const [formData, setFormData] = useState({ @@ -37,40 +37,34 @@ export default function EventRegister() { }); useEffect(() => { - if (id) { - handleFetchDetail(id); - } - }, [id, handleFetchDetail]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!id) return; + // Event.tsx와 동일한 구성의 Mock 데이터 + const mockEvent: Events = { + id: Number(id) || 1, + title: '제2회 기획 세미나', + description: '일정 설명...', + location: '서울대', + start_at: '2026-02-02T18:00:00Z', + end_at: '2026-02-02T20:00:00Z', + capacity: 10, + waitlist_enabled: true, + registration_deadline: '2026-02-02T17:00:00Z', + created_by: 123, + created_at: '2026-01-14T00:00:00Z', + updated_at: '2026-01-14T00:00:00Z', + }; - // 간단한 유효성 검사 - if (!formData.name.trim() || !formData.email.trim()) { - alert('성함과 이메일을 모두 입력해주세요.'); - return; - } + setSchedule(mockEvent); + }, [id]); - const requestData: JoinEventRequest = { - guestName: formData.name, - guestEmail: formData.email, - }; + const handleJoin = (e: React.FormEvent) => { + e.preventDefault(); + console.info('신청 시도:', { eventId: id, ...formData }); - const success = await handleJoinEvent(id, requestData); - if (success) { - // 신청 성공 시 성공 페이지로 이동 - navigate(`/join/${id}/success`); - } + // 신청 완료 페이지로 이동 + navigate(`/join/${id}/success`); }; - if (loading || !event) { - return ( -
-
-
- ); - } + if (!schedule) return null; return (
@@ -86,7 +80,7 @@ export default function EventRegister() {

- {event.title} + {schedule.title}

@@ -96,10 +90,10 @@ export default function EventRegister() { {/* 일정 정보 */}

- 일시 {formatEventDate(event.startAt)} + 일시 {formatEventDate(schedule.start_at)}

- 장소 {event.location || '미정'} + 장소 {schedule.location || '미정'}

@@ -108,9 +102,9 @@ export default function EventRegister() { onClick={() => navigate('guests')} className="flex items-center text-lg font-bold group hover:opacity-70 transition-opacity" > - {event.capacity}명 중{' '} + {schedule.capacity}명 중{' '} - {confirmedCount}명 신청 + {/* 신청 인원 필드 필요 */} 8명 신청
@@ -120,7 +114,7 @@ export default function EventRegister() {
{/* 신청 폼 섹션 */} - +

예약자 정보를 입력해 주세요 diff --git a/src/routes/EventRegisterSuccess.tsx b/src/routes/EventRegisterSuccess.tsx index 402461a..67a5fe7 100644 --- a/src/routes/EventRegisterSuccess.tsx +++ b/src/routes/EventRegisterSuccess.tsx @@ -1,8 +1,8 @@ import { Button } from '@/components/ui/button'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import EventDetailContent from '../components/EventDetailContent'; -import useEventDetail from '../hooks/useEventDetail'; +import type { Events } from '../types/schema'; // shadcn UI 컴포넌트 import { @@ -36,22 +36,29 @@ const IconChevronLeft = () => ( export default function EventRegisterSuccess() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const { loading, event, confirmedCount, handleFetchDetail } = - useEventDetail(); + const [schedule, setSchedule] = useState(null); useEffect(() => { - if (id) { - handleFetchDetail(id); - } - }, [id, handleFetchDetail]); + // Event.tsx와 동일한 구성의 Mock 데이터 + const mockEvent: Events = { + id: Number(id) || 1, + title: '제2회 기획 세미나', + description: + '일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 ...', + location: '서울대', + start_at: '2026-02-02T18:00:00Z', + end_at: '2026-02-02T20:00:00Z', + capacity: 10, + waitlist_enabled: true, + registration_deadline: '2026-02-02T17:00:00Z', + created_by: 123, + created_at: '2026-01-14T00:00:00Z', + updated_at: '2026-01-14T00:00:00Z', + }; + setSchedule(mockEvent); + }, [id]); - if (loading || !event) { - return ( -
-
-
- ); - } + if (!schedule) return null; return (
@@ -77,15 +84,12 @@ export default function EventRegisterSuccess() { {/* 일정 정보 (왼쪽 정렬) */}

- {event.title} + {schedule.title}

{/* 일정 정보 */} - + {/* 취소 버튼 */} diff --git a/src/routes/Guests.tsx b/src/routes/Guests.tsx index b5763c1..8bb22f5 100644 --- a/src/routes/Guests.tsx +++ b/src/routes/Guests.tsx @@ -1,7 +1,6 @@ -import { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; import { toast } from 'sonner'; -import useEventDetail from '../hooks/useEventDetail'; // shadcn UI 컴포넌트 import { @@ -34,96 +33,82 @@ const IconChevronLeft = () => ( ); -// interface GuestResponse { -// registration_id: number; -// name: string; -// email: string | null; -// profile_image: string | null; -// } +interface GuestResponse { + registration_id: number; + name: string; + email: string | null; + profile_image: string | null; +} export default function Guests() { - // const [guests, setGuests] = useState([]); - - // useEffect(() => { - // // 데이터 하드코딩 - // const mockGuests: GuestResponse[] = [ - // { - // registration_id: 1, - // name: '이준엽', - // email: 'jun411@snu.ac.kr', - // profile_image: 'https://github.com/shadcn.png', - // }, - // { - // registration_id: 2, - // name: '이름2', - // email: '이메일@example.com', - // profile_image: null, - // }, - // { - // registration_id: 3, - // name: '이름3', - // email: '이메일@example.com', - // profile_image: null, - // }, - // { - // registration_id: 4, - // name: '이름4', - // email: '이메일@example.com', - // profile_image: null, - // }, - // { - // registration_id: 5, - // name: '이름5', - // email: null, - // profile_image: null, - // }, - // { - // registration_id: 6, - // name: '이름6', - // email: '이메일@example.com', - // profile_image: null, - // }, - // { - // registration_id: 7, - // name: '이름7', - // email: '이메일@example.com', - // profile_image: null, - // }, - // { - // registration_id: 8, - // name: '이름8', - // email: '이메일@example.com', - // profile_image: null, - // }, - // ]; - - // setGuests(mockGuests); - // }, []); - - const { id } = useParams<{ id: string }>(); + // API로 불러올 registrations id + // const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const { loading, registrations, handleFetchRegistrations } = useEventDetail(); + + const [guests, setGuests] = useState([]); useEffect(() => { - if (id) { - handleFetchRegistrations(id); - } - }, [id, handleFetchRegistrations]); + // 데이터 하드코딩 + const mockGuests: GuestResponse[] = [ + { + registration_id: 1, + name: '이준엽', + email: 'jun411@snu.ac.kr', + profile_image: 'https://github.com/shadcn.png', + }, + { + registration_id: 2, + name: '이름2', + email: '이메일@example.com', + profile_image: null, + }, + { + registration_id: 3, + name: '이름3', + email: '이메일@example.com', + profile_image: null, + }, + { + registration_id: 4, + name: '이름4', + email: '이메일@example.com', + profile_image: null, + }, + { + registration_id: 5, + name: '이름5', + email: null, + profile_image: null, + }, + { + registration_id: 6, + name: '이름6', + email: '이메일@example.com', + profile_image: null, + }, + { + registration_id: 7, + name: '이름7', + email: '이메일@example.com', + profile_image: null, + }, + { + registration_id: 8, + name: '이름8', + email: '이메일@example.com', + profile_image: null, + }, + ]; + + setGuests(mockGuests); + }, []); - const handleCancelGuest = (name: string | null) => { + const handleCancelGuest = (regId: number, name: string) => { // 삭제 API 필요 + setGuests((prev) => prev.filter((g) => g.registration_id !== regId)); toast.success(`${name} 님의 참여가 취소되었습니다.`); }; - // 로딩 중이거나 데이터가 아직 없을 때 (리다이렉트 판단 전) 스피너나 빈 화면 표시 - if (loading || !registrations) { - return ( -
-
-
- ); - } - return (
{/* 1. 상단 네비게이션 */} @@ -138,33 +123,31 @@ export default function Guests() {

- 참여자 명단({registrations.length}) + 참여자 명단({guests.length})

{/* 2. 참여자 리스트 */}
- {registrations.map((registration) => ( + {guests.map((guest) => (
- + - {registration.guestName?.slice(0, 2)} + {guest.name.slice(0, 2)}
- {registration.guestName} + {guest.name} - {registration.guestEmail ? ( - - {registration.guestEmail} - + {guest.email ? ( + {guest.email} ) : null}
@@ -182,8 +165,7 @@ export default function Guests() { - {registration.guestName} 님의 신청을 - 취소하시겠습니까? + {guest.name} 님의 신청을 취소하시겠습니까? 취소 후 원복이 어렵습니다. 취소 메일이 참여자에게 @@ -193,7 +175,9 @@ export default function Guests() { 신청 유지하기 handleCancelGuest(registration.guestName)} + onClick={() => + handleCancelGuest(guest.registration_id, guest.name) + } className="bg-red-600 hover:bg-red-700" > 취소하기 diff --git a/src/routes/JoinEvent.tsx b/src/routes/JoinEvent.tsx index aa90944..beafed0 100644 --- a/src/routes/JoinEvent.tsx +++ b/src/routes/JoinEvent.tsx @@ -1,10 +1,9 @@ import { Button } from '@/components/ui/button'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import EventDetailContent from '../components/EventDetailContent'; import GuestSummaryList from '../components/GuestSummaryList'; -import useAuth from '../hooks/useAuth'; -import useEventDetail from '../hooks/useEventDetail'; +import type { Events } from '../types/schema'; // SVG 아이콘 컴포넌트 const IconChevronLeft = () => ( @@ -25,77 +24,47 @@ const IconChevronLeft = () => ( export default function JoinEvent() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const { isLoggedIn } = useAuth(); - const { - loading, - event, - registrations, - confirmedCount, - handleFetchDetail, - handleJoinEvent, - } = useEventDetail(); + const [schedule, setSchedule] = useState(null); + const [displayGuests, setDisplayGuests] = useState< + { name: string; img: string | undefined }[] + >([]); - // 초기 데이터 로드 및 리다이렉션 로직 useEffect(() => { - if (!id) return; - - const init = async () => { - const status = await handleFetchDetail(id); - - // 백엔드 상태 코드 기반 분기 처리 - switch (status) { - case 'ADMIN': - // 관리자라면 관리자 전용 상세 페이지로 리다이렉트 - navigate(`/event/${id}`, { replace: true }); - break; - case 'REGISTERED': - // 이미 신청한 사람이라면 성공 페이지로 리다이렉트 - navigate(`/join/${id}/success`, { replace: true }); - break; - case 'NOT_FOUND': - navigate('/', { replace: true }); - break; - case 'ERROR': - // 에러 시 알림은 훅에서 toast로 처리하므로 메인으로만 이동 - navigate('/'); - break; - default: - // 'GUEST' (200 OK)인 경우에만 페이지에 머무름 - break; - } + // 데이터 하드코딩 + const mockEvent: Events = { + id: Number(id) || 1, + title: '제2회 기획 세미나', + description: + '일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 일정설명 ...', + location: '서울대', + start_at: '2026-02-02T18:00:00Z', + end_at: '2026-02-02T20:00:00Z', + capacity: 10, + waitlist_enabled: true, + registration_deadline: '2026-02-02T17:00:00Z', + created_by: 123, // 관리자 ID + created_at: '2026-01-14T00:00:00Z', + updated_at: '2026-01-14T00:00:00Z', }; - init(); - }, [id, handleFetchDetail, navigate]); - - // 신청하기 버튼 핸들러 - const onJoinClick = async () => { - if (!id) return; + // 관리자 확인 로직 + // if (!isLoggedIn || !isAdmin(mockData.ownerName)) { + // alert('접근 권한이 없습니다. 관리자만 접근 가능합니다.'); + // navigate('/'); // 혹은 로그인 페이지로 이동 + // return; + // } - // 로그인 되어 있지 않으면 참여 신청 페이지(폼 입력)로 이동 - if (!isLoggedIn) { - return navigate(`/join/${id}/register`); - } + setSchedule(mockEvent); - // 로그인 되어 있으면 즉시 API 호출 - const success = await handleJoinEvent(id, { - guestName: null, - guestEmail: null, - }); + setDisplayGuests([ + { name: '이름1', img: undefined }, // profile_image가 null인 경우 + { name: '이름2', img: 'https://via.placeholder.com/40' }, + { name: '이름3', img: undefined }, + { name: '이름4', img: undefined }, + ]); + }, [id]); - if (success) { - navigate(`/join/${id}/success`); - } - }; - - // 로딩 중이거나 데이터가 아직 없을 때 (리다이렉트 판단 전) 스피너나 빈 화면 표시 - if (loading || !event) { - return ( -
-
-
- ); - } + if (!schedule) return null; return (
@@ -111,7 +80,7 @@ export default function JoinEvent() {

- {event.title} + {schedule.title}

@@ -119,25 +88,18 @@ export default function JoinEvent() { {/* 메인 콘텐츠 */}
{/* 일정 정보 */} - + {/* 신청하기 버튼*/} - {/* 참여자 명단 섹션 */}
diff --git a/src/types/event.ts b/src/types/event.ts deleted file mode 100644 index 4915bcd..0000000 --- a/src/types/event.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface Event { - id: number; - title: string; - description: string | null; - location: string | null; - startAt: number; // epoch milliseconds - endAt: number; // epoch milliseconds - capacity: number; - waitlistEnabled: boolean; - registrationDeadline: number; // epoch milliseconds - createdBy: number; // 작성자 ID - createdAt?: number; - updatedAt?: number; -} - -export interface Guest { - id: number; - userId: number | null; // 회원인 경우 ID, 비회원이면 null - eventId: number; - guestName: string | null; // 비회원 이름 - guestEmail: string | null; // 비회원 이메일 - status: 'CONFIRMED' | 'WAITING' | 'CANCELED'; - createdAt: number; // 신청 일시 -} - -// 일정 상세 응답 (GET /api/events/:id) -export type EventDetailResponse = Event; - -// 참여자 명단 (GET /api/events/:id/registrations) -export type RegistrationListResponse = Guest[]; - -// 참여 신청 (POST /api/events/:id/registrations) -export interface JoinEventRequest { - guestName: string | null; - guestEmail: string | null; -} - -export interface JoinEventResponse { - registration: Guest; - cancelToken: string; // 신청 취소용 토큰 -} diff --git a/src/utils/date.ts b/src/utils/date.ts index 21f6a4c..3a8b1cb 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -1,14 +1,11 @@ export const formatEventDate = ( - dateString: number | string | null | undefined, // number 타입 추가 + dateString: string | null | undefined, fallback: string = '미정' ): string => { if (!dateString) return fallback; const date = new Date(dateString); - // 유효하지 않은 날짜 체크 - if (isNaN(date.getTime())) return fallback; - const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); @@ -23,9 +20,8 @@ export const formatEventDate = ( return `${year}/${month}/${day}(${weekDay}) ${hours}:${minutes}`; }; -export const getShortenedDate = (dateString: number | string): string => { +export const getShortenedDate = (dateString: string): string => { const date = new Date(dateString); - if (isNaN(date.getTime())) return '미정'; const month = String(date.getMonth() + 1); const day = String(date.getDate()); From c306addfbe6acad978e0b0f0c06acb8110e91618 Mon Sep 17 00:00:00 2001 From: Park Junyoung Date: Sat, 17 Jan 2026 14:14:46 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=91=B7=20Add=20a=20mock=20CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-temp.yml | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/deploy-temp.yml diff --git a/.github/workflows/deploy-temp.yml b/.github/workflows/deploy-temp.yml new file mode 100644 index 0000000..1a60ce2 --- /dev/null +++ b/.github/workflows/deploy-temp.yml @@ -0,0 +1,34 @@ +name: deploy-temp + +on: + push: + branches: + - revert-api + workflow_dispatch: + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20.11.1' + + - name: Build & Export + run: | + yarn install + yarn build + + - name: Deploy to S3 and Invalidate Cloudfront + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ap-northeast-2 + run: | + aws s3 sync ./dist s3://waffle-team4-web-temp --delete + aws cloudfront create-invalidation --distribution-id E2HDLBJVBNM8HH --paths "/*" \ No newline at end of file From f8ea4d70dd4b590f84fdfc06dcf81e9f6dc0fc3b Mon Sep 17 00:00:00 2001 From: Park Junyoung Date: Sat, 17 Jan 2026 14:17:09 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=92=AC=20Update=20the=20app=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 5cb5702..fb47cdf 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - 모이샤 dev + 모이샤 Alpha