264 lines
8.5 KiB
TypeScript
264 lines
8.5 KiB
TypeScript
import { useState } from 'react';
|
|
import type { FormEvent } from 'react';
|
|
import PocketBase from 'pocketbase';
|
|
import { Input } from "./ui/input";
|
|
import { Button } from "./ui/button";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
|
|
import { Eye, EyeOff, Loader2 } from "lucide-react";
|
|
import { Separator } from "./ui/separator";
|
|
import { Label } from "./ui/label";
|
|
|
|
interface AuthStatus {
|
|
message: string;
|
|
isError: boolean;
|
|
}
|
|
|
|
interface UserRecord {
|
|
id: string;
|
|
email: string;
|
|
name?: string;
|
|
avatar?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface AuthResponse {
|
|
token: string;
|
|
record: UserRecord;
|
|
}
|
|
|
|
const LoginPage = () => {
|
|
const [email, setEmail] = useState('suvodip@siliconpin.com');
|
|
const [password, setPassword] = useState('Simple2pass');
|
|
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
const [status, setStatus] = useState<AuthStatus>({ message: '', isError: false });
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const pb = new PocketBase("https://tst-pb.s38.siliconpin.com");
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
setIsLoading(true);
|
|
setStatus({ message: '', isError: false });
|
|
|
|
try {
|
|
const authData = await pb.collection("users").authWithPassword(email, password);
|
|
|
|
const authResponse: AuthResponse = {
|
|
token: authData.token,
|
|
record: {
|
|
query: 'new',
|
|
id: authData.record.id,
|
|
email: authData.record.email,
|
|
name: authData.record.name || '',
|
|
avatar: authData.record.avatar || ''
|
|
}
|
|
};
|
|
|
|
await syncSessionWithBackend(authResponse);
|
|
// window.location.href = '/profile';
|
|
} catch (error) {
|
|
console.error("Login failed:", error);
|
|
setStatus({
|
|
message: "Login failed. Please check your credentials.",
|
|
isError: true
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const loginWithOAuth2 = async (provider: 'google' | 'facebook' | 'github') => {
|
|
try {
|
|
setIsLoading(true);
|
|
setStatus({ message: '', isError: false });
|
|
|
|
const authData = await pb.collection('users').authWithOAuth2({ provider });
|
|
|
|
if (!authData?.record) {
|
|
throw new Error("No user record found");
|
|
}
|
|
|
|
const authResponse: AuthResponse = {
|
|
token: authData.token,
|
|
record: {
|
|
query: 'new',
|
|
id: authData.record.id,
|
|
email: authData.record.email || '',
|
|
name: authData.record.name || '',
|
|
avatar: authData.record.avatar || ''
|
|
}
|
|
};
|
|
|
|
await syncSessionWithBackend(authResponse);
|
|
// window.location.href = '/profile';
|
|
} catch (error) {
|
|
console.error(`${provider} Login failed:`, error);
|
|
setStatus({
|
|
message: `${provider} login failed. Please try again.`,
|
|
isError: true
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const syncSessionWithBackend = async (authData: AuthResponse) => {
|
|
try {
|
|
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', {
|
|
method: 'POST',
|
|
credentials: 'include', // Crucial for cookies
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
accessToken: authData.token,
|
|
email: authData.record.email,
|
|
name: authData.record.name,
|
|
avatar: authData.record.avatar
|
|
? pb.files.getUrl(authData.record, authData.record.avatar)
|
|
: '',
|
|
isAuthenticated: true,
|
|
id: authData.record.id
|
|
})
|
|
});
|
|
|
|
if (!response.ok) throw new Error('Failed to sync session');
|
|
|
|
const data = await response.json();
|
|
console.log('Session synced:', data);
|
|
} catch (error) {
|
|
console.error('Error syncing session:', error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center p-4">
|
|
<Card className="w-full max-w-md shadow-lg rounded-xl overflow-hidden">
|
|
<CardHeader className="text-center space-y-1">
|
|
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
|
<CardDescription className="">
|
|
Sign in to access your account
|
|
</CardDescription>
|
|
</CardHeader>
|
|
|
|
<CardContent className="space-y-6">
|
|
{status.message && (
|
|
<div className={`p-3 rounded-md text-sm ${status.isError ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>
|
|
{status.message}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email" className="">
|
|
Email Address
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
placeholder="you@example.com"
|
|
required
|
|
className="focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between items-center">
|
|
<Label htmlFor="password" className="">
|
|
Password
|
|
</Label>
|
|
<button
|
|
type="button"
|
|
onClick={() => setPasswordVisible(!passwordVisible)}
|
|
className="text-sm text-[#6d9e37]"
|
|
>
|
|
|
|
</button>
|
|
</div>
|
|
<div className="relative">
|
|
<Input
|
|
id="password"
|
|
type={passwordVisible ? "text" : "password"}
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
required
|
|
className="focus:ring-2 focus:ring-blue-500 pr-10"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setPasswordVisible(!passwordVisible)}
|
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
>
|
|
{passwordVisible ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={isLoading}
|
|
className="w-full"
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Signing in...
|
|
</>
|
|
) : (
|
|
'Sign In'
|
|
)}
|
|
</Button>
|
|
</form>
|
|
|
|
<div className="relative">
|
|
<div className="absolute inset-0 flex items-center">
|
|
<Separator className="w-full" />
|
|
</div>
|
|
<div className="relative flex justify-center text-xs uppercase">
|
|
<span className="bg-background px-2 text-muted-foreground">
|
|
Or continue with
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => loginWithOAuth2('google')}
|
|
disabled={isLoading}
|
|
className="flex items-center justify-center gap-2"
|
|
>
|
|
<img src="/assets/google.svg" alt="Google" className="h-6 w-6" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => loginWithOAuth2('facebook')}
|
|
disabled={isLoading}
|
|
className="flex items-center justify-center gap-2"
|
|
>
|
|
<img src="/assets/facebook.svg" alt="Facebook" className="h-6 w-6" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => loginWithOAuth2('github')}
|
|
disabled={isLoading}
|
|
className="flex items-center justify-center gap-2"
|
|
>
|
|
<img src="/assets/github.svg" alt="GitHub" className="h-6 w-6" />
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
|
|
<div className="px-6 pb-6 text-center text-sm text-gray-500">
|
|
Don't have an account?{' '}
|
|
<a href="/sign-up" className="font-medium text-[#6d9e37] hover:underline">
|
|
Sign up
|
|
</a>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoginPage; |