diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-25 08:22:51 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-25 08:22:51 +0700 |
| commit | 3f5c6e86c57f3dbcc76a12294cbad94e8b130443 (patch) | |
| tree | b9602314767a387666e460f870fb26f28c254325 | |
| parent | 3190b87c73b35b50681c91e9bc080f9ce5bc2b47 (diff) | |
<Miqdad> Fix login
| -rw-r--r-- | app/login/page.tsx | 220 |
1 files changed, 164 insertions, 56 deletions
diff --git a/app/login/page.tsx b/app/login/page.tsx index 9e0a5fe..3ea2190 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,84 +1,192 @@ "use client"; - -import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { + Box, + Button, + FormControl, + FormLabel, + TextField, + Typography, +} from "@mui/material"; +import Header from "../lib/camera/component/hedear"; import odooApi from "../lib/api/odooApi"; +import { getAuth, setAuth } from "../lib/api/auth"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; + +// Ambil tipe parameter untuk setAuth agar sesuai tepat dengan definisinya +type AuthProps = Parameters<typeof setAuth>[0]; type LoginStatus = { code?: number; description?: string }; -type LoginResult = { token?: string; email?: string; [k: string]: unknown }; +type LoginResult = { + is_auth?: boolean; + reason?: "NOT_FOUND" | "NOT_ACTIVE" | string; + user?: unknown; // akan dicast ke AuthProps jika lolos +}; type LoginResponse = { status?: LoginStatus; result?: LoginResult }; -export default function LoginPage() { +const Login = () => { const router = useRouter(); + + // state untuk validasi MUI helperText + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const [emailError, setEmailError] = useState(false); + const [emailErrorMessage, setEmailErrorMessage] = useState(""); + const [passwordError, setPasswordError] = useState(false); + const [passwordErrorMessage, setPasswordErrorMessage] = useState(""); const [loading, setLoading] = useState(false); - const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => { - e.preventDefault(); - setLoading(true); + useEffect(() => { + const token = getAuth(); + if (token) router.push("/"); + }, [router]); - const fd = new FormData(e.currentTarget); + const validateInputs = (e: string, p: string) => { + let ok = true; + + if (!e || !/\S+@\S+\.\S+/.test(e)) { + setEmailError(true); + setEmailErrorMessage("Please enter a valid email address."); + ok = false; + } else { + setEmailError(false); + setEmailErrorMessage(""); + } + + if (!p || p.length < 6) { + setPasswordError(true); + setPasswordErrorMessage("Password must be at least 6 characters long."); + ok = false; + } else { + setPasswordError(false); + setPasswordErrorMessage(""); + } + + return ok; + }; - // Narrowing ke string, bukan File/null + const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + + // Ambil dari FormData agar sesuai dengan form yang disubmit + const fd = new FormData(event.currentTarget); const rawEmail = fd.get("email"); const rawPassword = fd.get("password"); - const email = typeof rawEmail === "string" ? rawEmail.trim() : ""; - const password = typeof rawPassword === "string" ? rawPassword : ""; + const emailStr = typeof rawEmail === "string" ? rawEmail.trim() : ""; + const passwordStr = typeof rawPassword === "string" ? rawPassword : ""; - if (!email || !password) { - alert("Email dan password wajib diisi."); - setLoading(false); - return; - } + if (!validateInputs(emailStr, passwordStr)) return; try { + setLoading(true); + const res = (await odooApi("POST", "/api/v1/user/login", { - email, - password, + email: emailStr, + password: passwordStr, })) as unknown as LoginResponse; - if (res?.status?.code === 200) { - // Jika kamu punya util setAuth(res.result), panggil di sini. + const auth = res?.result; + + if (res?.status?.code === 200 && auth?.is_auth) { + // Cast auth.user ke AuthProps → cocok dengan setAuth + if (auth.user && typeof auth.user === "object") { + setAuth(auth.user as AuthProps); + } router.push("/"); - } else { - alert(res?.status?.description || "Login gagal. Periksa email/password."); + return; } - } catch (err) { - console.error(err); - alert("Terjadi kesalahan saat login."); + + // Tangani alasan gagal umum + switch (auth?.reason) { + case "NOT_FOUND": + alert("Email tidak ditemukan"); + break; + case "NOT_ACTIVE": + alert("Akun anda belum aktif"); + break; + default: + alert(res?.status?.description || "Login gagal. Periksa email/password."); + } + } catch (error) { + console.error(error); + alert("Gagal login, silahkan coba lagi"); } finally { setLoading(false); } }; return ( - <main className="min-h-screen flex items-center justify-center"> - <form onSubmit={onSubmit} className="w-full max-w-sm p-6 space-y-3 border rounded"> - <h1 className="text-xl font-semibold">Login</h1> - - <input - name="email" - type="email" - placeholder="Email" - className="w-full border rounded px-3 py-2" - required - /> - - <input - name="password" - type="password" - placeholder="Password" - className="w-full border rounded px-3 py-2" - required - /> - - <button - type="submit" - disabled={loading} - className="w-full bg-red-600 text-white rounded py-2" - > - {loading ? "Loading..." : "Login"} - </button> - </form> - </main> + <div className="bg-[#fafeff] h-screen overflow-auto"> + <Header /> + <div className="py-4 px-4 sm:px-96 pt-20"> + <div className="bg-white py-6 px-4 sm:px-96 shadow-md rounded-sm"> + <Typography + component="h1" + variant="h4" + sx={{ width: "100%", fontSize: "clamp(2rem, 10vw, 2.15rem)", mb: 4 }} + > + Sign in + </Typography> + + <Box + component="form" + onSubmit={handleSubmit} + noValidate + sx={{ display: "flex", flexDirection: "column", width: "100%", gap: 2 }} + > + <FormControl> + <FormLabel htmlFor="email">Email</FormLabel> + <TextField + error={emailError} + helperText={emailErrorMessage} + id="email" + type="email" + name="email" + placeholder="your@email.com" + autoComplete="email" + autoFocus + required + fullWidth + variant="outlined" + color={emailError ? "error" : "primary"} + size="small" + inputProps={{ "aria-label": "email" }} + value={email} + onChange={(e) => setEmail(e.target.value)} + /> + </FormControl> + + <FormControl> + <Box sx={{ display: "flex", justifyContent: "space-between" }}> + <FormLabel htmlFor="password">Password</FormLabel> + </Box> + <TextField + error={passwordError} + helperText={passwordErrorMessage} + name="password" + placeholder="••••••" + type="password" + id="password" + autoComplete="current-password" + required + fullWidth + variant="outlined" + color={passwordError ? "error" : "primary"} + size="small" + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + </FormControl> + + <Button type="submit" fullWidth variant="contained" disabled={loading}> + {loading ? "Loading..." : "Sign in"} + </Button> + </Box> + </div> + </div> + </div> ); -}
\ No newline at end of file +}; + +export default Login; |
