aboutsummaryrefslogtreecommitdiff
path: root/web/src/pages/LoginPage.tsx
diff options
context:
space:
mode:
authorzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
committerzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
commitef55d9bb24430610b60c1b77821307cd72c08c55 (patch)
treee6935a810b34370b0a4c821e55f6397aac50c832 /web/src/pages/LoginPage.tsx
downloadhelpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.tar.gz
helpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.zip
feat: upload of the projectHEADmaster
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'web/src/pages/LoginPage.tsx')
-rw-r--r--web/src/pages/LoginPage.tsx124
1 files changed, 124 insertions, 0 deletions
diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..73f355b
--- /dev/null
+++ b/web/src/pages/LoginPage.tsx
@@ -0,0 +1,124 @@
+/**
+ * LoginPage.tsx
+ * Página de autenticação — ponto de entrada da aplicação.
+ */
+
+import { useState, type FormEvent } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { useMutation } from '@tanstack/react-query';
+import { api } from '@/lib/api';
+import { useAuth } from '@/context/AuthContext';
+import { useToast } from '@/context/ToastContext';
+import styles from './LoginPage.module.css';
+
+export default function LoginPage() {
+ const navigate = useNavigate();
+ const { login } = useAuth();
+ const { addToast } = useToast();
+
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [showPass, setShowPass] = useState(false);
+
+ const { mutate, isPending, isError, error } = useMutation({
+ mutationFn: () => api.auth.login({ email, password }),
+ onSuccess: ({ token, user }) => {
+ login(token, user);
+ addToast('Login realizado com sucesso!', 'success');
+ navigate('/dashboard', { replace: true });
+ },
+ onError: (err: any) => {
+ addToast(err.message || 'Erro ao realizar login', 'error');
+ }
+ });
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+ mutate();
+ }
+
+ const errorMsg = isError && error instanceof Error ? error.message : null;
+
+ return (
+ <div className={styles.page}>
+ <div className={styles.card}>
+
+ {/* Logo */}
+ <div className={styles.logo}>
+ <svg width="40" height="40" viewBox="0 0 32 32" fill="none" aria-hidden="true">
+ <rect width="32" height="32" rx="8" fill="var(--color-accent)" fillOpacity="0.2" />
+ <path d="M8 11h16M8 16h10M8 21h12" stroke="var(--color-accent)" strokeWidth="2.2" strokeLinecap="round" />
+ </svg>
+ <div>
+ <h1 className={styles.appName}>Helpdesk TI</h1>
+ <p className={styles.appSub}>Central de Chamados</p>
+ </div>
+ </div>
+
+ <h2 className={styles.heading}>Entrar na sua conta</h2>
+
+ <form className={styles.form} onSubmit={handleSubmit} noValidate>
+ <div className={styles.field}>
+ <label htmlFor="login-email" className={styles.label}>E-mail</label>
+ <input
+ id="login-email"
+ type="email"
+ className="input"
+ placeholder="seu@email.com"
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ required
+ autoComplete="email"
+ disabled={isPending}
+ />
+ </div>
+
+ <div className={styles.field}>
+ <label htmlFor="login-password" className={styles.label}>Senha</label>
+ <div className={styles.passwordWrapper}>
+ <input
+ id="login-password"
+ type={showPass ? 'text' : 'password'}
+ className="input"
+ placeholder="••••••••"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ required
+ autoComplete="current-password"
+ disabled={isPending}
+ />
+ <button
+ type="button"
+ className={styles.showPass}
+ onClick={() => setShowPass((v) => !v)}
+ aria-label={showPass ? 'Ocultar senha' : 'Mostrar senha'}
+ >
+ {showPass ? '🙈' : '👁️'}
+ </button>
+ </div>
+ </div>
+
+ {errorMsg && (
+ <div className={styles.error} role="alert">{errorMsg}</div>
+ )}
+
+ <button
+ type="submit"
+ className={`btn btn--primary ${styles.submitBtn}`}
+ disabled={isPending || !email || !password}
+ aria-busy={isPending}
+ >
+ {isPending ? (
+ <><span className="spinner" style={{ width: 16, height: 16, borderWidth: 2 }} /> Entrando...</>
+ ) : 'Entrar'}
+ </button>
+ </form>
+
+ <p className={styles.registerLink}>
+ Não tem conta?{' '}
+ <Link to="/register">Solicite acesso ao administrador</Link>
+ </p>
+ </div>
+ </div>
+ );
+}