mega-plant/components/auth/AuthForms.tsx
David 1ec52961ee Cash float card, Office role + Slips tab, job card PDF, manual close/delete
- Cash Floats: ledger card (opening balance, top-ups, slips), manual entries, slip photos
- Office access level; clock app Slips tab (photograph a slip, popup for supplier/cost
  code/comments/amount); only Office sees it; per-role route table in lib/clock-access
- Job cards: Machine Service PDF (with Oil block), JC numbers, manual Close job, Delete
- Hide Expenses > Vehicle Costs and Finance (Bastion) from the menu

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:09:57 +02:00

84 lines
5 KiB
TypeScript

"use client";
import { useActionState, useEffect, useState } from "react";
import { ArrowRight, UserPlus, LoaderCircle } from "lucide-react";
import { loginAction, changePasswordAction, addUserAction, type FormState } from "@/app/actions/auth";
import { STARTER_PASSWORD } from "@/lib/auth-policy";
import { PasswordField, inputClass } from "./PasswordField";
function Feedback({ state }: { state: FormState }) {
return <>
{state.error && <p role="alert" className="rounded-xl border border-[#D1495B]/20 bg-[#D1495B]/5 p-3 text-sm leading-relaxed text-[#D1495B]">{state.error}</p>}
{state.success && <p role="status" className="break-words rounded-xl border border-[#00798C]/20 bg-[#00798C]/5 p-3 text-sm leading-relaxed text-[#00798C] dark:text-[#00A6BE]">{state.success}</p>}
</>;
}
function Submit({ pending, children, adding = false }: { pending: boolean; children: React.ReactNode; adding?: boolean }) {
return <button type="submit" disabled={pending}
className="flex min-h-12 w-full items-center justify-center gap-2 rounded-xl bg-[#30638E] px-5 py-3 text-sm font-semibold text-white transition hover:bg-[#003D5B] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#30638E] disabled:cursor-wait disabled:opacity-60">
{pending ? <LoaderCircle className="h-4 w-4 animate-spin" /> : adding ? <UserPlus className="h-4 w-4" /> : null}
{pending ? "Please wait…" : children}
{!pending && !adding && <ArrowRight className="h-4 w-4" />}
</button>;
}
export function LoginForm({clock=false}:{clock?:boolean}={}) {
const [state, action, pending] = useActionState(loginAction, {});
const [email, setEmail] = useState("");
return <form action={action} className="space-y-5">
{clock&&<input type="hidden" name="clock" value="1"/>}
<div>
<label htmlFor="email" className="mb-2 block text-sm font-medium">Email address</label>
<input id="email" name="email" type="email" autoComplete="username" value={email} onChange={(event) => setEmail(event.target.value)} required maxLength={254} className={inputClass} placeholder="you@company.com" />
</div>
<PasswordField name="password" label="Password" autoComplete="current-password" />
<Feedback state={state} />
<Submit pending={pending}>Sign in</Submit>
<p className="text-center text-xs leading-relaxed text-gray-500 dark:text-gray-400">Use the account your team has created for you.</p>
</form>;
}
export function ChangePasswordForm({clock=false}:{clock?:boolean}={}) {
const [state, action, pending] = useActionState(changePasswordAction, {});
return <form action={action} className="space-y-5">
{clock&&<input type="hidden" name="clock" value="1"/>}
<PasswordField name="password" label="Your new password" autoComplete="new-password" minLength={8}
hint="At least 8 characters. No uppercase letters, numbers or special characters required." />
<PasswordField name="confirmation" label="Confirm new password" autoComplete="new-password" minLength={8} />
<Feedback state={state} />
<Submit pending={pending}>Save password & continue</Submit>
</form>;
}
export function AddUserForm() {
const [state, action, pending] = useActionState(addUserAction, {});
const [name, setName] = useState("");
const [email, setEmail] = useState("");
useEffect(() => { if (state.success) { setName(""); setEmail(""); } }, [state]);
return <form action={action} className="space-y-5">
<div>
<label htmlFor="name" className="mb-2 block text-sm font-medium">Full name</label>
<input id="name" name="name" value={name} onChange={(event) => setName(event.target.value)} autoComplete="off" required className={inputClass} placeholder="Person's name" />
</div>
<div>
<label htmlFor="email" className="mb-2 block text-sm font-medium">Email address</label>
<input id="email" name="email" type="email" value={email} onChange={(event) => setEmail(event.target.value)} autoComplete="off" required maxLength={254} className={inputClass} placeholder="person@company.com" />
</div>
<div>
<label htmlFor="access" className="mb-2 block text-sm font-medium">Access</label>
<select id="access" name="access" defaultValue="full" className={inputClass}>
<option value="full">Full — main app and clock app</option>
<option value="clock">Clock-in user — clock app only</option>
<option value="mechanic">Mechanic — Fuel and Job cards only</option>
<option value="office">Office — Slips only</option>
</select>
</div>
<div>
<label htmlFor="starter" className="mb-2 block text-sm font-medium">Starter password</label>
<input id="starter" value={STARTER_PASSWORD} readOnly className={`${inputClass} font-mono bg-gray-50`} aria-describedby="starter-hint" />
<p id="starter-hint" className="mt-2 text-xs leading-relaxed text-gray-500 dark:text-gray-400">They'll choose their own password when they first sign in.</p>
</div>
<Feedback state={state} />
<Submit pending={pending} adding>Add user</Submit>
</form>;
}