Files
iddaai-fe/src/components/auth/login-modal.tsx
T
fahricansecer 5c8619b282
Deploy Iddaai Frontend / build-and-deploy (push) Failing after 34s
gg
2026-05-10 22:59:27 +03:00

352 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 as="span" 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>
);
}