diff --git a/package.json b/package.json index 1bf2185..8e1148f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "check:types": "tsc", "check:format": "biome check .", "check:unused": "knip", - "check-all": "yarn check:types && yarn check:format && yarn check:unused" + "check-all": "yarn check:types && yarn check:format && yarn check:unused", + "fix": "yarn biome check --write ." }, "dependencies": { "@radix-ui/react-alert-dialog": "^1.1.15", diff --git a/src/layouts/RootLayout.tsx b/src/layouts/RootLayout.tsx index 4f55f61..50c5bec 100644 --- a/src/layouts/RootLayout.tsx +++ b/src/layouts/RootLayout.tsx @@ -7,7 +7,7 @@ export default function RootLayout() {
@@ -272,7 +271,7 @@ export default function Event() { navigate('participants')} + onClick={() => navigate('guests')} className="text-base font-bold text-black p-0 h-auto" > 더보기 diff --git a/src/routes/Guests.tsx b/src/routes/Guests.tsx new file mode 100644 index 0000000..099832b --- /dev/null +++ b/src/routes/Guests.tsx @@ -0,0 +1,191 @@ +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router'; +import { toast } from 'sonner'; + +// shadcn UI 컴포넌트 +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; + +// SVG 아이콘 컴포넌트 +const IconChevronLeft = () => ( + + + +); + +interface GuestResponse { + registration_id: number; + name: string; + email: string; + profile_image: string | null; +} + +export default function Guests() { + // API로 불러올 registrations id + // const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + + 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: '이메일@example.com', + 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 = (regId: number, name: string) => { + // 삭제 API 필요 + setGuests((prev) => prev.filter((g) => g.registration_id !== regId)); + toast.success(`${name} 님의 참여가 취소되었습니다.`); + }; + + return ( + + {/* 1. 상단 네비게이션 */} + + + navigate(-1)} + className="rounded-full" + > + + + + 참여자 명단({guests.length}) + + + + + {/* 2. 참여자 리스트 */} + + {guests.map((guest) => ( + + + + + + {guest.name.slice(0, 2)} + + + + + {guest.name} + + {guest.email} + + + + {/* 강제취소 버튼 */} + + + + 강제취소 + + + + + + {guest.name} 님의 신청을 취소하시겠습니까? + + + 취소 후 원복이 어렵습니다. 취소 메일이 참여자에게 + 전송됩니다. + + + + 신청 유지하기 + + handleCancelGuest(guest.registration_id, guest.name) + } + className="bg-red-600 hover:bg-red-700" + > + 취소하기 + + + + + + ))} + + + ); +} diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx index 9769160..9aeec8d 100644 --- a/src/routes/Login.tsx +++ b/src/routes/Login.tsx @@ -14,104 +14,106 @@ export default function Login() { }; return ( - - - 로그인 - - 계정이 없으신가요?{' '} - - 회원가입하러 가기 - - - + + + + 로그인 + + 계정이 없으신가요?{' '} + + 회원가입하러 가기 + + + - - - - setUsername(e.target.value)} - /> + + + + setUsername(e.target.value)} + /> + + + setPassword(e.target.value)} + /> + + - setPassword(e.target.value)} - /> + + 로그인하기 + - + - - - 로그인하기 - - - - - - - - - - - - 또는 소셜 계정으로 로그인 - + + + + + + + + 또는 소셜 계정으로 로그인 + + - - - - - - - - - - + + + + + + + + + - - - - - + + + + + + diff --git a/src/routes/RegisterChoice.tsx b/src/routes/RegisterChoice.tsx index 2bc5244..e7ec36f 100644 --- a/src/routes/RegisterChoice.tsx +++ b/src/routes/RegisterChoice.tsx @@ -5,71 +5,75 @@ export default function RegisterChoice() { const navigate = useNavigate(); return ( - - - - 회원가입 - - - 모이샤와 함께 모임 활동을 시작해 보세요! - - - - - - - - - - - - + + + + + 회원가입 + + + 모이샤와 함께 모임 활동을 시작해 보세요! + + - - - - - - + + + + + + + + + - - - + + + + + - - 또는 + + + + + + + + 또는 + + - - navigate('/register/email')} - className="w-full py-3.5 px-4 rounded-md bg-blue-600 text-white hover:bg-blue-700 font-bold shadow-md transition-all active:scale-[0.98]" - > - 이메일로 가입하기 - + navigate('/register/email')} + className="w-full py-3.5 px-4 rounded-md bg-blue-600 text-white hover:bg-blue-700 font-bold shadow-md transition-all active:scale-[0.98]" + > + 이메일로 가입하기 + + ); } diff --git a/src/routes/RegisterForm.tsx b/src/routes/RegisterForm.tsx index 4a6db63..1e5f39f 100644 --- a/src/routes/RegisterForm.tsx +++ b/src/routes/RegisterForm.tsx @@ -82,164 +82,166 @@ export default function RegisterForm() { const errorTextStyle = 'mt-1 text-xs text-red-500 font-medium'; return ( - - - 회원 정보 입력 - - - - - - {previewUrl ? ( - + + + 회원 정보 입력 + + + + + + {previewUrl ? ( + + ) : ( + 사진 없음 + )} + + + 사진 선택 + + setPhoto(e.target.files ? e.target.files[0] : null) + } /> - ) : ( - 사진 없음 + + + + + + 이름 + + setName(e.target.value)} + placeholder="이름을 입력하세요" + /> + + {showErrors && !name && ( + 이름을 입력해주세요. + )} + + + + + 이메일 + + 0 && !validations.isEmailValid + ? 'border-red-400 focus:ring-red-100' + : 'border-gray-300 focus:ring-blue-500' + }`} + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="example@moisha.com" + /> + + {showErrors && !email && ( + 이메일을 입력해주세요. + )} + {email.length > 0 && !validations.isEmailValid && ( + + 유효한 이메일 형식을 입력해주세요. + + )} + + + + + 비밀번호 + + 0 && !isPasswordValid + ? 'border-red-400 focus:ring-red-100' + : 'border-gray-300 focus:ring-blue-500' + }`} + value={password} + onChange={(e) => setPassword(e.target.value)} + placeholder="8자 이상, 숫자, 특수문자 포함" + /> + + {showErrors && !password && ( + 비밀번호를 입력해주세요. + )} + {password.length > 0 && ( + + + {validations.password.isLongEnough ? '✓' : '○'} 8자 이상 + + + {validations.password.hasNumber ? '✓' : '○'} 숫자 포함 + + + {validations.password.hasSpecial ? '✓' : '○'} 특수문자 포함 + + )} - - 사진 선택 + + + + 비밀번호 확인 + - setPhoto(e.target.files ? e.target.files[0] : null) - } + ref={confirmPasswordRef} + type="password" + className={`w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none transition-all ${ + confirmPassword.length > 0 && !validations.isPasswordMatch + ? 'border-red-400 focus:ring-red-100' + : 'border-gray-300 focus:ring-blue-500' + }`} + value={confirmPassword} + onChange={(e) => setConfirmPassword(e.target.value)} + placeholder="비밀번호를 확인해주세요" /> - - - - - - 이름 - - setName(e.target.value)} - placeholder="이름을 입력하세요" - /> - - {showErrors && !name && ( - 이름을 입력해주세요. - )} - - - - - 이메일 - - 0 && !validations.isEmailValid - ? 'border-red-400 focus:ring-red-100' - : 'border-gray-300 focus:ring-blue-500' - }`} - value={email} - onChange={(e) => setEmail(e.target.value)} - placeholder="example@moisha.com" - /> - - {showErrors && !email && ( - 이메일을 입력해주세요. - )} - {email.length > 0 && !validations.isEmailValid && ( - - 유효한 이메일 형식을 입력해주세요. - - )} - - - - - 비밀번호 - - 0 && !isPasswordValid - ? 'border-red-400 focus:ring-red-100' - : 'border-gray-300 focus:ring-blue-500' - }`} - value={password} - onChange={(e) => setPassword(e.target.value)} - placeholder="8자 이상, 숫자, 특수문자 포함" - /> - - {showErrors && !password && ( - 비밀번호를 입력해주세요. - )} - {password.length > 0 && ( - - - {validations.password.isLongEnough ? '✓' : '○'} 8자 이상 - - - {validations.password.hasNumber ? '✓' : '○'} 숫자 포함 - - - {validations.password.hasSpecial ? '✓' : '○'} 특수문자 포함 - - - )} - - - - - 비밀번호 확인 - - 0 && !validations.isPasswordMatch - ? 'border-red-400 focus:ring-red-100' - : 'border-gray-300 focus:ring-blue-500' - }`} - value={confirmPassword} - onChange={(e) => setConfirmPassword(e.target.value)} - placeholder="비밀번호를 확인해주세요" - /> - - {showErrors && !confirmPassword && ( - 비밀번호를 확인해주세요. - )} - {confirmPassword.length > 0 && !validations.isPasswordMatch && ( - 비밀번호가 일치하지 않습니다. - )} - - - - - 회원가입 - - navigate(-1)} - className="w-full py-2 text-sm text-gray-500 hover:text-gray-700 font-medium transition-colors" - > - 이전 단계로 - - - + + {showErrors && !confirmPassword && ( + 비밀번호를 확인해주세요. + )} + {confirmPassword.length > 0 && !validations.isPasswordMatch && ( + 비밀번호가 일치하지 않습니다. + )} + + + + + 회원가입 + + navigate(-1)} + className="w-full py-2 text-sm text-gray-500 hover:text-gray-700 font-medium transition-colors" + > + 이전 단계로 + + + + ); }
- 계정이 없으신가요?{' '} - - 회원가입하러 가기 - -
+ 계정이 없으신가요?{' '} + + 회원가입하러 가기 + +
- 모이샤와 함께 모임 활동을 시작해 보세요! -
+ 모이샤와 함께 모임 활동을 시작해 보세요! +
이름을 입력해주세요.
이메일을 입력해주세요.
+ 유효한 이메일 형식을 입력해주세요. +
비밀번호를 입력해주세요.
- 유효한 이메일 형식을 입력해주세요. -
비밀번호를 확인해주세요.
비밀번호가 일치하지 않습니다.