2026-06-15 21:35:20 +07:00
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
|
|
|
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
2026-06-02 02:42:07 +07:00
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
|
import { AuthForm } from "@/components/auth-form";
|
2026-06-15 21:35:20 +07:00
|
|
|
import { apiClient, setAuthToken } from "@/lib/api-client";
|
2026-06-02 02:42:07 +07:00
|
|
|
import { useAuthStore } from "@/store/auth-store";
|
2026-05-22 16:51:01 +00:00
|
|
|
|
|
|
|
|
export function LoginPage() {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const setUser = useAuthStore((state) => state.setUser);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
2026-06-15 21:35:20 +07:00
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
const oauthTokenConsumed = useRef(false);
|
|
|
|
|
|
|
|
|
|
// Handle OAuth callback: the API redirects to /login?token=<session_token>
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const token = searchParams.get("token");
|
|
|
|
|
if (!token || oauthTokenConsumed.current) return;
|
|
|
|
|
oauthTokenConsumed.current = true;
|
|
|
|
|
|
|
|
|
|
// Store token for future API calls and fetch user
|
|
|
|
|
setAuthToken(token);
|
|
|
|
|
|
|
|
|
|
apiClient
|
|
|
|
|
.getMe()
|
|
|
|
|
.then((data) => {
|
|
|
|
|
setUser(data.user);
|
|
|
|
|
queryClient.setQueryData(["auth", "me"], data);
|
|
|
|
|
navigate("/dashboard", { replace: true });
|
|
|
|
|
})
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
setAuthToken(null);
|
|
|
|
|
setError(err instanceof Error ? err.message : "Google login gagal");
|
|
|
|
|
});
|
|
|
|
|
}, [searchParams, setUser, queryClient, navigate]);
|
|
|
|
|
|
|
|
|
|
// Show OAuth error from query param
|
|
|
|
|
const oauthError = searchParams.get("error");
|
2026-06-02 02:42:07 +07:00
|
|
|
const meQuery = useQuery({
|
|
|
|
|
queryKey: ["auth", "me"],
|
|
|
|
|
queryFn: () => apiClient.getMe(),
|
|
|
|
|
});
|
2026-05-22 16:51:01 +00:00
|
|
|
|
|
|
|
|
const mutation = useMutation({
|
|
|
|
|
mutationFn: apiClient.login,
|
|
|
|
|
onSuccess: (response) => {
|
|
|
|
|
setUser(response.user);
|
2026-06-02 02:42:07 +07:00
|
|
|
queryClient.setQueryData(["auth", "me"], response);
|
|
|
|
|
navigate("/dashboard");
|
2026-05-22 16:51:01 +00:00
|
|
|
},
|
2026-06-02 02:42:07 +07:00
|
|
|
onError: (err) =>
|
|
|
|
|
setError(err instanceof Error ? err.message : "Login gagal"),
|
2026-05-22 16:51:01 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<main className="flex min-h-screen items-center justify-center px-6 py-12">
|
2026-06-06 02:15:43 +07:00
|
|
|
<div className="w-full max-w-sm md:max-w-md space-y-4">
|
2026-05-22 16:51:01 +00:00
|
|
|
<AuthForm
|
|
|
|
|
mode="login"
|
|
|
|
|
isSubmitting={mutation.isPending}
|
2026-06-15 21:35:20 +07:00
|
|
|
error={oauthError || error}
|
2026-06-02 02:42:07 +07:00
|
|
|
googleOAuthEnabled={Boolean(
|
|
|
|
|
meQuery.data?.features.googleOAuthEnabled,
|
|
|
|
|
)}
|
2026-05-22 16:55:47 +00:00
|
|
|
onSubmit={async ({ email, password }) => {
|
|
|
|
|
setError(null);
|
|
|
|
|
return mutation.mutateAsync({ email, password });
|
|
|
|
|
}}
|
|
|
|
|
onFieldChange={() => setError(null)}
|
2026-05-22 16:51:01 +00:00
|
|
|
/>
|
|
|
|
|
<p className="text-center text-sm text-muted-foreground">
|
2026-06-02 02:42:07 +07:00
|
|
|
Belum punya akun?{" "}
|
|
|
|
|
<Link className="text-primary" to="/register">
|
|
|
|
|
Daftar
|
|
|
|
|
</Link>
|
2026-05-22 16:51:01 +00:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
);
|
|
|
|
|
}
|