Refactor Login component

Splits the Login component into smaller, more manageable parts and extracts related logic into hooks to improve code organization and readability.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-15 12:39:09 +00:00
parent 511a5bb2da
commit e687047401
8 changed files with 453 additions and 326 deletions

View File

@@ -1,26 +1,30 @@
import React, { useState, useEffect } from "react";
import { Link, 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, Eye, EyeOff, RefreshCw, Shield } from "lucide-react";
import { useToast } from "@/hooks/useToast.wrapper";
import { useAuth } from "@/contexts/auth";
import { testSupabaseConnection } from "@/lib/supabase";
import LoginForm from "@/components/auth/LoginForm";
import TestConnectionSection from "@/components/auth/TestConnectionSection";
import SupabaseConnectionStatus from "@/components/auth/SupabaseConnectionStatus";
import { useLogin } from "@/hooks/useLogin";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [testLoading, setTestLoading] = useState(false);
const [testResults, setTestResults] = useState<any>(null);
const [loginError, setLoginError] = useState<string | null>(null);
const { toast } = useToast();
const navigate = useNavigate();
const { signIn, user } = useAuth();
const {
email,
setEmail,
password,
setPassword,
showPassword,
setShowPassword,
isLoading,
loginError,
setLoginError,
handleLogin
} = useLogin();
const [testResults, setTestResults] = useState<any>(null);
const navigate = useNavigate();
const { user } = useAuth();
// 이미 로그인된 경우 메인 페이지로 리다이렉트
useEffect(() => {
if (user) {
@@ -28,145 +32,25 @@ const Login = () => {
}
}, [user, navigate]);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoginError(null);
if (!email || !password) {
toast({
title: "입력 오류",
description: "이메일과 비밀번호를 모두 입력해주세요.",
variant: "destructive"
});
return;
}
setIsLoading(true);
try {
const { error } = await signIn(email, password);
if (error) {
console.error("로그인 실패:", error);
let errorMessage = "로그인에 실패했습니다.";
// Supabase 오류 메시지 처리
if (error.message) {
if (error.message.includes("Invalid login credentials")) {
errorMessage = "이메일 또는 비밀번호가 올바르지 않습니다.";
} else if (error.message.includes("Email not confirmed")) {
errorMessage = "이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요.";
} else {
errorMessage = `오류: ${error.message}`;
}
}
setLoginError(errorMessage);
toast({
title: "로그인 실패",
description: errorMessage,
variant: "destructive"
});
} else {
navigate("/");
}
} catch (err: any) {
console.error("로그인 과정에서 예외 발생:", err);
const errorMessage = err.message || "알 수 없는 오류가 발생했습니다.";
setLoginError(errorMessage);
toast({
title: "로그인 오류",
description: errorMessage,
variant: "destructive"
});
} finally {
setIsLoading(false);
}
};
// Supabase 연결 테스트
const runConnectionTest = async () => {
setTestLoading(true);
setLoginError(null);
try {
const results = await testSupabaseConnection();
setTestResults(results);
if (results.errors.length === 0) {
toast({
title: "연결 테스트 성공",
description: "Supabase 서버 연결이 정상입니다.",
});
} else {
toast({
title: "연결 테스트 실패",
description: `오류 발생: ${results.errors[0]}`,
variant: "destructive"
});
}
} catch (error: any) {
console.error("연결 테스트 중 오류:", error);
setLoginError(error.message || "테스트 실행 중 예상치 못한 오류가 발생했습니다.");
toast({
title: "연결 테스트 오류",
description: "테스트 실행 중 예상치 못한 오류가 발생했습니다.",
variant: "destructive"
});
} finally {
setTestLoading(false);
}
};
return <div className="min-h-screen flex flex-col items-center justify-center p-6 bg-neuro-background">
return (
<div className="min-h-screen flex flex-col items-center justify-center p-6 bg-neuro-background">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-neuro-income mb-2"> </h1>
<p className="text-gray-500"> </p>
</div>
<div className="neuro-flat p-8 mb-6">
<form onSubmit={handleLogin}>
<div className="space-y-6">
<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>
</div>
{loginError && (
<div className="p-3 bg-red-50 text-red-600 rounded-md text-sm">
<p>{loginError}</p>
</div>
)}
<div className="text-right">
<Link to="/forgot-password" className="text-sm text-neuro-income hover:underline">
?
</Link>
</div>
<Button type="submit" disabled={isLoading} className="w-full hover:bg-neuro-income/80 text-white py-6 h-auto bg-neuro-income">
{isLoading ? "로그인 중..." : "로그인"} {!isLoading && <ArrowRight className="ml-2 h-5 w-5" />}
</Button>
</div>
</form>
</div>
<LoginForm
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
showPassword={showPassword}
setShowPassword={setShowPassword}
isLoading={isLoading}
loginError={loginError}
handleLogin={handleLogin}
/>
<div className="text-center mb-4">
<p className="text-gray-500">
@@ -177,98 +61,15 @@ const Login = () => {
</p>
</div>
{/* 연결 테스트 섹션 */}
<div className="neuro-card p-4 mb-6">
<h3 className="text-sm font-medium mb-2">Supabase </h3>
<div className="flex justify-between items-center">
<button
onClick={runConnectionTest}
disabled={testLoading}
className="text-xs flex items-center text-neuro-income hover:underline"
>
{testLoading ? (
<>
<RefreshCw className="mr-1 h-3 w-3 animate-spin" />
...
</>
) : (
<>
<RefreshCw className="mr-1 h-3 w-3" />
</>
)}
</button>
{testResults && (
<div className="text-xs">
<span className={`inline-block rounded-full w-2 h-2 mr-1 ${testResults.restApi ? 'bg-green-500' : 'bg-red-500'}`}></span>
API
<span className={`inline-block rounded-full w-2 h-2 mx-1 ${testResults.auth ? 'bg-green-500' : 'bg-red-500'}`}></span>
<span className={`inline-block rounded-full w-2 h-2 mx-1 ${testResults.database ? 'bg-green-500' : 'bg-red-500'}`}></span>
DB
</div>
)}
</div>
{testResults && testResults.errors.length > 0 && (
<div className="mt-2 text-xs text-red-500 bg-red-50 p-2 rounded-md overflow-auto max-h-24">
{testResults.errors.map((error: string, index: number) => (
<div key={index} className="mb-1">{error}</div>
))}
</div>
)}
</div>
<TestConnectionSection
setLoginError={setLoginError}
setTestResults={setTestResults}
/>
{/* 고급 진단 정보 */}
<details className="neuro-flat p-3 text-xs mb-4">
<summary className="cursor-pointer text-gray-500 font-medium"> </summary>
<div className="mt-2 space-y-2">
<div>
<p className="font-medium">Supabase URL:</p>
<p className="break-all bg-gray-100 p-1 rounded">{testResults?.url || '알 수 없음'}</p>
</div>
{testResults?.usingProxy && (
<div>
<p className="font-medium">CORS :</p>
<div className="flex items-center gap-1">
<Shield className="h-3 w-3 text-blue-500" />
<p className="break-all text-blue-500"> - {testResults.proxyUrl}</p>
</div>
</div>
)}
<div>
<p className="font-medium"> :</p>
<p>{testResults?.client ? '성공' : '실패'}</p>
</div>
<div>
<p className="font-medium"> :</p>
<p className="break-all">{navigator.userAgent}</p>
</div>
<div>
<p className="font-medium"> :</p>
<p>1.0.0</p>
</div>
{testResults && (
<div className="border-t pt-2 mt-2">
<p>REST API: {testResults.restApi ? '✅' : '❌'}</p>
<p>: {testResults.auth ? '✅' : '❌'}</p>
<p>: {testResults.database ? '✅' : '❌'}</p>
</div>
)}
<div className="text-right pt-1">
<Link
to="/supabase-settings"
className="inline-flex items-center text-neuro-income hover:underline"
>
<span>Supabase </span>
<ArrowRight className="h-3 w-3 ml-1" />
</Link>
</div>
</div>
</details>
<SupabaseConnectionStatus testResults={testResults} />
</div>
</div>;
</div>
);
};
export default Login;