Files
iddaai-fe/src/components/auth/login-modal.tsx
T
2026-04-24 00:27:14 +03:00

348 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Box, Heading, Input, Text, VStack, HStack } from "@chakra-ui/react";
import { Button } from "@/components/ui/buttons/button";
import { Field } from "@/components/ui/forms/field";
import { InputGroup } from "@/components/ui/forms/input-group";
import { PasswordInput } from "@/components/ui/forms/password-input";
import {
DialogBody,
DialogCloseTrigger,
DialogContent,
DialogHeader,
DialogRoot,
DialogTitle,
} from "@/components/ui/overlays/dialog";
import { useTranslations } from "next-intl";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { signIn } from "next-auth/react";
import { toaster } from "@/components/ui/feedback/toaster";
import { useState, useEffect } from "react";
import { MdMail } from "react-icons/md";
import { BiUser } from "react-icons/bi";
import { authService } from "@/lib/api/auth/service";
/* ────────────────────────── Schemas ────────────────────────── */
const loginSchema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(6).required(),
});
const registerSchema = yup.object({
name: yup.string().required(),
email: yup.string().email().required(),
password: yup.string().min(8).required(),
});
type LoginForm = yup.InferType<typeof loginSchema>;
type RegisterForm = yup.InferType<typeof registerSchema>;
/* ────────────────────────── Props ────────────────────────── */
interface LoginModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
initialMode?: "login" | "register";
}
/* ────────────────────────── Component ────────────────────────── */
export function LoginModal({ open, onOpenChange, initialMode = "login" }: LoginModalProps) {
const t = useTranslations();
const [mode, setMode] = useState<"login" | "register">(initialMode);
const [loading, setLoading] = useState(false);
// Update mode when modal opens
useEffect(() => {
if (open) {
setMode(initialMode);
}
}, [open, initialMode]);
/* ── Login form ── */
const loginForm = useForm<LoginForm>({
resolver: yupResolver(loginSchema),
mode: "onChange",
});
/* ── Register form ── */
const registerForm = useForm<RegisterForm>({
resolver: yupResolver(registerSchema),
mode: "onChange",
});
/* ── Switch mode ── */
const switchMode = (newMode: "login" | "register") => {
setMode(newMode);
loginForm.reset();
registerForm.reset();
};
/* ── Handle login ── */
const onLogin = async (formData: LoginForm) => {
try {
setLoading(true);
const res = await signIn("credentials", {
redirect: false,
email: formData.email,
password: formData.password,
});
if (res?.error) {
throw new Error(res.error);
}
onOpenChange(false);
toaster.success({
title: t("auth.login-success") || "Giriş başarılı!",
type: "success",
});
} catch (error) {
toaster.error({
title: (error as Error).message || "Giriş yapılamadı!",
type: "error",
});
} finally {
setLoading(false);
}
};
/* ── Handle register ── */
const onRegister = async (formData: RegisterForm) => {
try {
setLoading(true);
await authService.register({
email: formData.email,
password: formData.password,
firstName: formData.name,
lastName: "",
});
// Auto-login after successful registration
const res = await signIn("credentials", {
redirect: false,
email: formData.email,
password: formData.password,
});
if (res?.error) {
throw new Error(res.error);
}
onOpenChange(false);
toaster.success({
title: t("auth.register-success") || "Kayıt başarılı!",
type: "success",
});
} catch (error) {
toaster.error({
title: (error as Error).message || "Kayıt yapılamadı!",
type: "error",
});
} finally {
setLoading(false);
}
};
return (
<DialogRoot
open={open}
onOpenChange={(e) => {
onOpenChange(e.open);
if (!e.open) {
// Reset to login when closing
setMode("login");
loginForm.reset();
registerForm.reset();
}
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Heading size="lg" color="primary.500">
{mode === "login" ? t("auth.sign-in") : t("auth.sign-up")}
</Heading>
</DialogTitle>
<DialogCloseTrigger />
</DialogHeader>
<DialogBody>
{/* ────── Tab Switcher ────── */}
<HStack
gap={0}
mb={5}
borderWidth="1px"
borderColor={{ base: "gray.200", _dark: "gray.700" }}
borderRadius="xl"
overflow="hidden"
>
<Button
flex={1}
size="sm"
variant={mode === "login" ? "solid" : "ghost"}
colorPalette={mode === "login" ? "primary" : "gray"}
borderRadius="0"
onClick={() => switchMode("login")}
>
{t("auth.sign-in")}
</Button>
<Button
flex={1}
size="sm"
variant={mode === "register" ? "solid" : "ghost"}
colorPalette={mode === "register" ? "primary" : "gray"}
borderRadius="0"
onClick={() => switchMode("register")}
>
{t("auth.sign-up")}
</Button>
</HStack>
{/* ────── LOGIN FORM ────── */}
{mode === "login" && (
<Box as="form" onSubmit={loginForm.handleSubmit(onLogin)}>
<VStack gap={4}>
<Field
label={t("email")}
errorText={loginForm.formState.errors.email?.message}
invalid={!!loginForm.formState.errors.email}
>
<InputGroup w="full" startElement={<MdMail size="1rem" />}>
<Input
borderRadius="md"
fontSize="sm"
type="text"
placeholder={t("email")}
{...loginForm.register("email")}
/>
</InputGroup>
</Field>
<Field
label={t("password")}
errorText={loginForm.formState.errors.password?.message}
invalid={!!loginForm.formState.errors.password}
>
<PasswordInput
rootProps={{ w: "full" }}
borderRadius="md"
fontSize="sm"
placeholder={t("password")}
{...loginForm.register("password")}
/>
</Field>
<Button
loading={loading}
type="submit"
bg="primary.400"
w="100%"
color="white"
_hover={{ bg: "primary.500" }}
>
{t("auth.sign-in")}
</Button>
<Text fontSize="sm" color="fg.muted">
{t("auth.dont-have-account")}{" "}
<Text
as="span"
color="primary.500"
fontWeight="bold"
cursor="pointer"
_hover={{ textDecoration: "underline" }}
onClick={() => switchMode("register")}
>
{t("auth.sign-up")}
</Text>
</Text>
</VStack>
</Box>
)}
{/* ────── REGISTER FORM ────── */}
{mode === "register" && (
<Box as="form" onSubmit={registerForm.handleSubmit(onRegister)}>
<VStack gap={4}>
<Field
label={t("name")}
errorText={registerForm.formState.errors.name?.message}
invalid={!!registerForm.formState.errors.name}
>
<InputGroup w="full" startElement={<BiUser size="1rem" />}>
<Input
borderRadius="md"
fontSize="sm"
type="text"
placeholder={t("name")}
{...registerForm.register("name")}
/>
</InputGroup>
</Field>
<Field
label={t("email")}
errorText={registerForm.formState.errors.email?.message}
invalid={!!registerForm.formState.errors.email}
>
<InputGroup w="full" startElement={<MdMail size="1rem" />}>
<Input
borderRadius="md"
fontSize="sm"
type="text"
placeholder={t("email")}
{...registerForm.register("email")}
/>
</InputGroup>
</Field>
<Field
label={t("password")}
errorText={registerForm.formState.errors.password?.message}
invalid={!!registerForm.formState.errors.password}
>
<PasswordInput
rootProps={{ w: "full" }}
borderRadius="md"
fontSize="sm"
placeholder={t("password")}
{...registerForm.register("password")}
/>
</Field>
<Button
loading={loading}
type="submit"
bg="primary.400"
w="100%"
color="white"
_hover={{ bg: "primary.500" }}
>
{t("auth.sign-up")}
</Button>
<Text fontSize="sm" color="fg.muted">
{t("auth.already-have-an-account")}{" "}
<Text
as="span"
color="primary.500"
fontWeight="bold"
cursor="pointer"
_hover={{ textDecoration: "underline" }}
onClick={() => switchMode("login")}
>
{t("auth.sign-in")}
</Text>
</Text>
</VStack>
</Box>
)}
</DialogBody>
</DialogContent>
</DialogRoot>
);
}