Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ Thumbs.db
.idea/
.vscode/settings.json
*~

.claude
.codex
253 changes: 89 additions & 164 deletions frontend/src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { useId, useState } from "react";
import { useRouter } from "next/navigation";
import { Heart, Mail, Lock, Loader2 } from "lucide-react";
import { Mail, Lock, Loader2 } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { AuthShell, AuthCard, AuthTitle, GoogleAuthButton } from "@/components/auth";
import { signIn } from "@/lib/auth-client";

export default function LoginPage() {
Expand Down Expand Up @@ -63,175 +63,100 @@ export default function LoginPage() {
};

return (
<div className="min-h-screen flex flex-col">
{/* Header */}
<header className="p-4 bg-card/50 backdrop-blur-sm border-b border-border">
<div className="max-w-6xl mx-auto flex items-center justify-center">
<Link href="/" className="flex items-center gap-2 hover:opacity-80 transition-opacity">
<Heart className="w-6 h-6 text-primary fill-primary" />
<span className="font-semibold text-foreground">恋ai</span>
</Link>
</div>
</header>

{/* Main Content */}
<main className="flex-1 flex items-center justify-center p-4 sm:p-6">
<div className="w-full max-w-md space-y-6">
{/* Title */}
<div className="text-center space-y-2">
<h1 className="text-3xl sm:text-4xl font-bold text-foreground">
ログイン
</h1>
<p className="text-muted-foreground">
アカウントにログインして練習を始めましょう
</p>
<AuthShell>
<AuthTitle
title="ログイン"
description="アカウントにログインして練習を始めましょう"
/>

<AuthCard>
<div className="space-y-6">
<GoogleAuthButton
onClick={handleGoogleLogin}
isLoading={isLoading}
label="Googleでログイン"
/>

<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-card text-muted-foreground">または</span>
</div>
</div>

{/* Login Card */}
<Card className="p-6 sm:p-8 border-2">
<div className="space-y-6">
{/* Google Login */}
<Button
onClick={handleGoogleLogin}
disabled={isLoading}
variant="outline"
size="lg"
className="w-full rounded-full border-2"
>
{isLoading ? (
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
) : (
<svg
className="w-5 h-5 mr-2"
viewBox="0 0 24 24"
aria-hidden="true"
focusable="false"
>
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
)}
Googleでログイン
</Button>

{/* Divider */}
<form onSubmit={handleEmailLogin} className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor={emailInputId}>
メールアドレス
</label>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-card text-muted-foreground">または</span>
</div>
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id={emailInputId}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="example@email.com"
required
className="w-full pl-10 pr-4 py-2 rounded-lg border-2 border-border bg-background text-foreground focus:outline-none focus:border-primary"
/>
</div>
</div>

{/* Email/Password Login */}
<form onSubmit={handleEmailLogin} className="space-y-4">
{/* Email Input */}
<div className="space-y-2">
<label
className="text-sm font-medium text-foreground"
htmlFor={emailInputId}
>
メールアドレス
</label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id={emailInputId}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="example@email.com"
required
className="w-full pl-10 pr-4 py-2 rounded-lg border-2 border-border bg-background text-foreground focus:outline-none focus:border-primary"
/>
</div>
</div>

{/* Password Input */}
<div className="space-y-2">
<label
className="text-sm font-medium text-foreground"
htmlFor={passwordInputId}
>
パスワード
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id={passwordInputId}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
className="w-full pl-10 pr-4 py-2 rounded-lg border-2 border-border bg-background text-foreground focus:outline-none focus:border-primary"
/>
</div>
</div>

{/* Error Message */}
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}

{/* Login Button */}
<Button
type="submit"
disabled={isLoading}
size="lg"
className="w-full rounded-full"
>
{isLoading ? (
<>
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
ログイン中...
</>
) : (
"ログイン"
)}
</Button>
</form>

{/* Forgot Password */}
<div className="text-center">
<Link
href="/reset-password"
className="text-sm text-primary hover:underline"
>
パスワードを忘れた方
</Link>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor={passwordInputId}>
パスワード
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id={passwordInputId}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
className="w-full pl-10 pr-4 py-2 rounded-lg border-2 border-border bg-background text-foreground focus:outline-none focus:border-primary"
/>
</div>
</div>
</Card>

{/* Sign Up Link */}
<Card className="p-4 border-2 text-center">
<p className="text-sm text-muted-foreground">
アカウントをお持ちでない方は
<Link href="/signup" className="text-primary hover:underline ml-1">
新規登録
</Link>
</p>
</Card>

{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}

<Button type="submit" disabled={isLoading} size="lg" className="w-full rounded-full">
{isLoading ? (
<>
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
ログイン中...
</>
) : (
"ログイン"
)}
</Button>
</form>

<div className="text-center">
<Link href="/reset-password" className="text-sm text-primary hover:underline">
パスワードを忘れた方
</Link>
</div>
</div>
</main>
</div>
</AuthCard>

<AuthCard withPadding={false} className="p-4 text-center">
<p className="text-sm text-muted-foreground">
アカウントをお持ちでない方は
<Link href="/signup" className="text-primary hover:underline ml-1">
新規登録
</Link>
</p>
</AuthCard>
</AuthShell>
);
}
Loading
Loading