Refactor RegisterForm component

Splits the RegisterForm component into smaller, more manageable components to improve code readability and maintainability.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-15 16:18:10 +00:00
parent 7024c6423f
commit 5d1ff46c3e
4 changed files with 283 additions and 193 deletions

View File

@@ -0,0 +1,55 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { Mail, InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
interface EmailConfirmationProps {
email: string;
onBackToForm: () => void;
}
const EmailConfirmation: React.FC<EmailConfirmationProps> = ({ email, onBackToForm }) => {
const navigate = useNavigate();
return (
<div className="neuro-flat p-8 mb-6">
<div className="text-center space-y-6">
<Mail className="w-16 h-16 mx-auto text-neuro-income" />
<h2 className="text-2xl font-bold"> </h2>
<p className="text-gray-600">
<strong>{email}</strong> .
.
</p>
<Alert className="bg-blue-50 border-blue-200 my-6">
<InfoIcon className="h-5 w-5 text-blue-600" />
<AlertTitle className="text-blue-700"> ?</AlertTitle>
<AlertDescription className="text-blue-600">
. .
</AlertDescription>
</Alert>
<div className="space-y-4 pt-4">
<Button
onClick={() => navigate("/login")}
variant="outline"
className="w-full"
>
</Button>
<Button
onClick={onBackToForm}
variant="ghost"
className="w-full"
>
</Button>
</div>
</div>
</div>
);
};
export default EmailConfirmation;

View File

@@ -1,34 +1,18 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { ArrowRight, Mail, KeyRound, User, Eye, EyeOff } from "lucide-react";
import { ArrowRight } from "lucide-react";
import { useToast } from "@/hooks/useToast.wrapper";
import { verifyServerConnection } from "@/contexts/auth/auth.utils";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { InfoIcon } from "lucide-react";
import { ServerStatus, SignUpResponse } from "./types";
import EmailConfirmation from "./EmailConfirmation";
import RegisterFormFields from "./RegisterFormFields";
interface RegisterFormProps {
signUp: (email: string, password: string, username: string) => Promise<{
error: any;
user: any;
redirectToSettings?: boolean;
emailConfirmationRequired?: boolean;
}>;
serverStatus: {
checked: boolean;
connected: boolean;
message: string;
};
setServerStatus: React.Dispatch<
React.SetStateAction<{
checked: boolean;
connected: boolean;
message: string;
}>
>;
signUp: (email: string, password: string, username: string) => Promise<SignUpResponse>;
serverStatus: ServerStatus;
setServerStatus: React.Dispatch<React.SetStateAction<ServerStatus>>;
setRegisterError: React.Dispatch<React.SetStateAction<string | null>>;
}
@@ -49,10 +33,50 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
const { toast } = useToast();
const navigate = useNavigate();
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
setRegisterError(null);
const validateForm = (): boolean => {
if (!username || !email || !password || !confirmPassword) {
toast({
title: "입력 오류",
description: "모든 필드를 입력해주세요.",
variant: "destructive",
});
return false;
}
if (password !== confirmPassword) {
toast({
title: "비밀번호 불일치",
description: "비밀번호와 비밀번호 확인이 일치하지 않습니다.",
variant: "destructive",
});
return false;
}
// 비밀번호 강도 검사
if (password.length < 8) {
toast({
title: "비밀번호 강도 부족",
description: "비밀번호는 최소 8자 이상이어야 합니다.",
variant: "destructive",
});
return false;
}
// 이메일 형식 검사
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
toast({
title: "이메일 형식 오류",
description: "유효한 이메일 주소를 입력해주세요.",
variant: "destructive",
});
return false;
}
return true;
};
const checkServerConnectivity = async (): Promise<boolean> => {
// 서버 연결 상태 재확인
if (!serverStatus.connected) {
try {
@@ -69,7 +93,7 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
description: "서버에 연결할 수 없습니다. 네트워크 또는 서버 상태를 확인하세요.",
variant: "destructive"
});
return;
return false;
}
} catch (error: any) {
toast({
@@ -77,48 +101,22 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
description: error.message || "서버 연결 확인 중 오류가 발생했습니다.",
variant: "destructive"
});
return;
return false;
}
}
return true;
};
if (!username || !email || !password || !confirmPassword) {
toast({
title: "입력 오류",
description: "모든 필드를 입력해주세요.",
variant: "destructive",
});
return;
}
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
setRegisterError(null);
if (password !== confirmPassword) {
toast({
title: "비밀번호 불일치",
description: "비밀번호와 비밀번호 확인이 일치하지 않습니다.",
variant: "destructive",
});
return;
}
// 서버 연결 확인
const isServerConnected = await checkServerConnectivity();
if (!isServerConnected) return;
// 비밀번호 강도 검사
if (password.length < 8) {
toast({
title: "비밀번호 강도 부족",
description: "비밀번호는 최소 8자 이상이어야 합니다.",
variant: "destructive",
});
return;
}
// 이메일 형식 검사
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
toast({
title: "이메일 형식 오류",
description: "유효한 이메일 주소를 입력해주세요.",
variant: "destructive",
});
return;
}
// 폼 유효성 검사
if (!validateForm()) return;
setIsLoading(true);
@@ -203,140 +201,36 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
// 이메일 인증 안내 화면 (인증 메일이 발송된 경우)
if (emailConfirmationSent) {
return (
<div className="neuro-flat p-8 mb-6">
<div className="text-center space-y-6">
<Mail className="w-16 h-16 mx-auto text-neuro-income" />
<h2 className="text-2xl font-bold"> </h2>
<p className="text-gray-600">
<strong>{email}</strong> .
.
</p>
<Alert className="bg-blue-50 border-blue-200 my-6">
<InfoIcon className="h-5 w-5 text-blue-600" />
<AlertTitle className="text-blue-700"> ?</AlertTitle>
<AlertDescription className="text-blue-600">
. .
</AlertDescription>
</Alert>
<div className="space-y-4 pt-4">
<Button
onClick={() => navigate("/login")}
variant="outline"
className="w-full"
>
</Button>
<Button
onClick={() => setEmailConfirmationSent(false)}
variant="ghost"
className="w-full"
>
</Button>
</div>
</div>
</div>
);
return <EmailConfirmation
email={email}
onBackToForm={() => setEmailConfirmationSent(false)}
/>;
}
// 일반 회원가입 양식
return (
<div className="neuro-flat p-8 mb-6">
<form onSubmit={handleRegister}>
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="username" className="text-base"></Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="username"
type="text"
placeholder="홍길동"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
</div>
<RegisterFormFields
username={username}
setUsername={setUsername}
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
confirmPassword={confirmPassword}
setConfirmPassword={setConfirmPassword}
showPassword={showPassword}
setShowPassword={setShowPassword}
/>
<div className="space-y-2">
<Label htmlFor="email" className="text-base"></Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="email"
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-base"></Label>
<div className="relative">
<KeyRound className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10 neuro-pressed"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500"
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
{password && password.length > 0 && password.length < 8 && (
<p className="text-xs text-red-500 mt-1"> 8 .</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword" className="text-base"> </Label>
<div className="relative">
<KeyRound className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="confirmPassword"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
{confirmPassword && password !== confirmPassword && (
<p className="text-xs text-red-500 mt-1"> .</p>
)}
</div>
<Alert className="bg-amber-50 border-amber-200">
<InfoIcon className="h-5 w-5 text-amber-600" />
<AlertTitle className="text-amber-700"> </AlertTitle>
<AlertDescription className="text-amber-600">
.
.
</AlertDescription>
</Alert>
<Button
type="submit"
className="w-full bg-neuro-income hover:bg-neuro-income/80 text-white py-6 h-auto"
disabled={isLoading || (!serverStatus.connected && serverStatus.checked)}
>
{isLoading ? "가입 중..." : "회원가입"} {!isLoading && <ArrowRight className="ml-2 h-5 w-5" />}
</Button>
</div>
<Button
type="submit"
className="w-full bg-neuro-income hover:bg-neuro-income/80 text-white py-6 h-auto mt-6"
disabled={isLoading || (!serverStatus.connected && serverStatus.checked)}
>
{isLoading ? "가입 중..." : "회원가입"} {!isLoading && <ArrowRight className="ml-2 h-5 w-5" />}
</Button>
</form>
</div>
);

View File

@@ -0,0 +1,120 @@
import React, { useState } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { User, Mail, KeyRound, Eye, EyeOff, InfoIcon } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
interface RegisterFormFieldsProps {
username: string;
setUsername: (value: string) => void;
email: string;
setEmail: (value: string) => void;
password: string;
setPassword: (value: string) => void;
confirmPassword: string;
setConfirmPassword: (value: string) => void;
showPassword: boolean;
setShowPassword: (value: boolean) => void;
}
const RegisterFormFields: React.FC<RegisterFormFieldsProps> = ({
username,
setUsername,
email,
setEmail,
password,
setPassword,
confirmPassword,
setConfirmPassword,
showPassword,
setShowPassword
}) => {
return (
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="username" className="text-base"></Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="username"
type="text"
placeholder="홍길동"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="text-base"></Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="email"
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-base"></Label>
<div className="relative">
<KeyRound className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10 neuro-pressed"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500"
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
{password && password.length > 0 && password.length < 8 && (
<p className="text-xs text-red-500 mt-1"> 8 .</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword" className="text-base"> </Label>
<div className="relative">
<KeyRound className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<Input
id="confirmPassword"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10 neuro-pressed"
/>
</div>
{confirmPassword && password !== confirmPassword && (
<p className="text-xs text-red-500 mt-1"> .</p>
)}
</div>
<Alert className="bg-amber-50 border-amber-200">
<InfoIcon className="h-5 w-5 text-amber-600" />
<AlertTitle className="text-amber-700"> </AlertTitle>
<AlertDescription className="text-amber-600">
.
.
</AlertDescription>
</Alert>
</div>
);
};
export default RegisterFormFields;

View File

@@ -0,0 +1,21 @@
// 서버 연결 상태 타입
export interface ServerStatus {
checked: boolean;
connected: boolean;
message: string;
}
// 회원가입 응답 타입
export interface SignUpResponse {
error: any;
user: any;
redirectToSettings?: boolean;
emailConfirmationRequired?: boolean;
}
// 회원가입 폼 공통 props
export interface RegisterFormCommonProps {
serverStatus: ServerStatus;
setRegisterError: React.Dispatch<React.SetStateAction<string | null>>;
}