로그인 UI 추가
This commit is contained in:
@@ -620,7 +620,7 @@ model CouponRedemption {
|
||||
// 제휴업체(위치 기반)
|
||||
model Partner {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
name String @unique
|
||||
category String
|
||||
latitude Float
|
||||
longitude Float
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 781 KiB After Width: | Height: | Size: 781 KiB |
@@ -4,6 +4,23 @@ import prisma from "@/lib/prisma";
|
||||
import { verifyPassword } from "@/lib/password";
|
||||
import { getClientKey, isRateLimited } from "@/lib/ratelimit";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const cookieHeader = req.headers.get("cookie") || "";
|
||||
const uid = cookieHeader
|
||||
.split(";")
|
||||
.map((s) => s.trim())
|
||||
.find((pair) => pair.startsWith("uid="))
|
||||
?.split("=")[1];
|
||||
if (!uid) return NextResponse.json({ ok: false }, { status: 200 });
|
||||
const user = await prisma.user.findUnique({ where: { userId: decodeURIComponent(uid) } });
|
||||
if (!user) return NextResponse.json({ ok: false }, { status: 200 });
|
||||
return NextResponse.json({ ok: true, user: { userId: user.userId, nickname: user.nickname } });
|
||||
} catch {
|
||||
return NextResponse.json({ ok: false }, { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const key = getClientKey(req, "login");
|
||||
if (isRateLimited(key, 5, 60_000)) {
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
"use client";
|
||||
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
||||
import { SearchBar } from "@/app/components/SearchBar";
|
||||
import { Button } from "@/app/components/ui/Button";
|
||||
import React from "react";
|
||||
|
||||
export function AppHeader() {
|
||||
const [user, setUser] = React.useState<{ nickname: string } | null>(null);
|
||||
React.useEffect(() => {
|
||||
fetch("/api/auth/session")
|
||||
.then((r) => r.json())
|
||||
.then((d) => setUser(d?.ok ? d.user : null))
|
||||
.catch(() => setUser(null));
|
||||
}, []);
|
||||
const onLogout = async () => {
|
||||
await fetch("/api/auth/session", { method: "DELETE" });
|
||||
setUser(null);
|
||||
location.reload();
|
||||
};
|
||||
return (
|
||||
<header style={{ display: "flex", justifyContent: "space-between", padding: 12 }}>
|
||||
<div>msg App</div>
|
||||
@@ -10,6 +25,14 @@ export function AppHeader() {
|
||||
<a href="/boards">게시판</a>
|
||||
<SearchBar />
|
||||
<ThemeToggle />
|
||||
{user ? (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<span>{user.nickname}님</span>
|
||||
<Button variant="ghost" onClick={onLogout}>로그아웃</Button>
|
||||
</div>
|
||||
) : (
|
||||
<a href="/login">로그인</a>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
53
src/app/login/page.tsx
Normal file
53
src/app/login/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { Button } from "@/app/components/ui/Button";
|
||||
import { useToast } from "@/app/components/ui/ToastProvider";
|
||||
|
||||
export default function LoginPage() {
|
||||
const { show } = useToast();
|
||||
const [nickname, setNickname] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const onSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ nickname, password }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data?.error || "로그인 실패");
|
||||
show("로그인되었습니다");
|
||||
location.href = "/";
|
||||
} catch (err: any) {
|
||||
show(err.message || "로그인 실패");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div style={{ maxWidth: 420, margin: "40px auto" }}>
|
||||
<h1>로그인</h1>
|
||||
<form onSubmit={onSubmit} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<input
|
||||
placeholder="닉네임"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
style={{ padding: 8, border: "1px solid #ddd", borderRadius: 6 }}
|
||||
/>
|
||||
<input
|
||||
placeholder="비밀번호"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
style={{ padding: 8, border: "1px solid #ddd", borderRadius: 6 }}
|
||||
/>
|
||||
<Button type="submit" disabled={loading}>{loading ? "로그인 중..." : "로그인"}</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user