129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useState, useEffect } from "react";
|
||
|
|
import Link from "next/link";
|
||
|
|
import {
|
||
|
|
getAiAssistantAuth,
|
||
|
|
setAiAssistantAuth,
|
||
|
|
loginAiAssistant,
|
||
|
|
} from "@/lib/api/aiAssistant";
|
||
|
|
import { Button } from "@/components/ui/button";
|
||
|
|
import { Input } from "@/components/ui/input";
|
||
|
|
import { Label } from "@/components/ui/label";
|
||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||
|
|
|
||
|
|
export default function AIAssistantLayout({
|
||
|
|
children,
|
||
|
|
}: {
|
||
|
|
children: React.ReactNode;
|
||
|
|
}) {
|
||
|
|
const [mounted, setMounted] = useState(false);
|
||
|
|
const [auth, setAuth] = useState<ReturnType<typeof getAiAssistantAuth>>(null);
|
||
|
|
const [email, setEmail] = useState("");
|
||
|
|
const [password, setPassword] = useState("");
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [error, setError] = useState("");
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
setAuth(getAiAssistantAuth());
|
||
|
|
setMounted(true);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const handleLogin = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
setError("");
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
await loginAiAssistant(email, password);
|
||
|
|
setAuth(getAiAssistantAuth());
|
||
|
|
} catch (err: unknown) {
|
||
|
|
const msg =
|
||
|
|
err && typeof err === "object" && "response" in err
|
||
|
|
? (err as { response?: { data?: { error?: { message?: string } } } }).response?.data
|
||
|
|
?.error?.message
|
||
|
|
: null;
|
||
|
|
setError(msg || "로그인에 실패했습니다.");
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleLogout = () => {
|
||
|
|
setAiAssistantAuth(null);
|
||
|
|
setAuth(null);
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!mounted) {
|
||
|
|
return (
|
||
|
|
<div className="flex min-h-[40vh] items-center justify-center">
|
||
|
|
<p className="text-muted-foreground text-sm">로딩 중...</p>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!auth) {
|
||
|
|
return (
|
||
|
|
<div className="mx-auto flex min-h-[60vh] max-w-sm flex-col justify-center p-6">
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>AI 어시스턴트 로그인</CardTitle>
|
||
|
|
<CardDescription>
|
||
|
|
AI 서비스(API 키, 사용량, 채팅 등)를 사용하려면 로그인하세요.
|
||
|
|
</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<form onSubmit={handleLogin} className="space-y-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="email">이메일</Label>
|
||
|
|
<Input
|
||
|
|
id="email"
|
||
|
|
type="email"
|
||
|
|
value={email}
|
||
|
|
onChange={(e) => setEmail(e.target.value)}
|
||
|
|
placeholder="admin@admin.com"
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="password">비밀번호</Label>
|
||
|
|
<Input
|
||
|
|
id="password"
|
||
|
|
type="password"
|
||
|
|
value={password}
|
||
|
|
onChange={(e) => setPassword(e.target.value)}
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
{error && (
|
||
|
|
<p className="text-destructive text-sm">{error}</p>
|
||
|
|
)}
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<Button type="submit" className="flex-1" disabled={loading}>
|
||
|
|
{loading ? "로그인 중..." : "로그인"}
|
||
|
|
</Button>
|
||
|
|
<Button asChild variant="outline">
|
||
|
|
<Link href="/admin">취소</Link>
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-4">
|
||
|
|
<div className="flex items-center justify-end gap-2 border-b pb-2 text-sm">
|
||
|
|
<span className="text-muted-foreground">
|
||
|
|
{auth.user?.name || auth.user?.email} (AI 서비스)
|
||
|
|
</span>
|
||
|
|
<Button variant="ghost" size="sm" onClick={handleLogout}>
|
||
|
|
로그아웃
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|