112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { Icon } from "@iconify/react";
|
|
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { iconStorefrontFill } from "../../_core/icons";
|
|
import { fetchUsers, login } from "../_services/auth.service";
|
|
import type { AuthUser } from "../../_core/types/auth-user";
|
|
import { PasswordForm } from "./password-form";
|
|
import { UserPicker } from "./user-picker";
|
|
|
|
export function UserLoginScreen() {
|
|
const router = useRouter();
|
|
const reduceMotion = useReducedMotion();
|
|
const [users, setUsers] = useState<AuthUser[] | null>(null);
|
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
const [selected, setSelected] = useState<AuthUser | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchUsers()
|
|
.then(setUsers)
|
|
.catch(() => setLoadError("No se pudo cargar la lista de usuarios."));
|
|
}, []);
|
|
|
|
function selectUser(user: AuthUser) {
|
|
setSelected(user);
|
|
setError(null);
|
|
}
|
|
|
|
function backToPicker() {
|
|
setSelected(null);
|
|
setError(null);
|
|
}
|
|
|
|
async function handleSubmit(password: string) {
|
|
if (!selected) return;
|
|
setSubmitting(true);
|
|
setError(null);
|
|
try {
|
|
const user = await login(selected.id, password);
|
|
sessionStorage.setItem("pdv:user", JSON.stringify(user));
|
|
router.push(user.role === "admin" ? "/admin" : "/pos");
|
|
} catch {
|
|
setError("Contraseña incorrecta. Intenta de nuevo.");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
const step = selected ? "password" : "picker";
|
|
const slide = reduceMotion ? 0 : 16;
|
|
|
|
return (
|
|
<div className="flex min-h-dvh w-full items-center justify-center overflow-hidden bg-background px-4 py-10 text-foreground">
|
|
<div className="flex w-full max-w-3xl flex-col items-center gap-10">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<div className="flex size-12 items-center justify-center rounded-2xl bg-accent-soft">
|
|
<Icon icon={iconStorefrontFill} width={24} height={24} className="text-accent-soft-foreground" aria-hidden="true" />
|
|
</div>
|
|
<AnimatePresence mode="wait" initial={false}>
|
|
<motion.h1
|
|
key={step}
|
|
initial={{ opacity: 0, y: reduceMotion ? 0 : -6 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: reduceMotion ? 0 : -6 }}
|
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
className="text-2xl font-light tracking-wide text-foreground/80"
|
|
>
|
|
{step === "password" ? "Ingresa tu contraseña" : "¿Quién va a usar el PDV?"}
|
|
</motion.h1>
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
<AnimatePresence mode="wait" initial={false}>
|
|
{step === "picker" ? (
|
|
<motion.div
|
|
key="picker"
|
|
initial={{ opacity: 0, x: -slide }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -slide }}
|
|
transition={{ duration: 0.22, ease: "easeOut" }}
|
|
className="flex w-full justify-center"
|
|
>
|
|
<UserPicker users={users} loadError={loadError} onSelect={selectUser} />
|
|
</motion.div>
|
|
) : (
|
|
<motion.div
|
|
key="password"
|
|
initial={{ opacity: 0, x: slide }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: slide }}
|
|
transition={{ duration: 0.22, ease: "easeOut" }}
|
|
className="flex w-full justify-center"
|
|
>
|
|
<PasswordForm
|
|
user={selected as AuthUser}
|
|
error={error}
|
|
submitting={submitting}
|
|
onSubmit={handleSubmit}
|
|
onBack={backToPicker}
|
|
/>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|