Admin Panel
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-09-29 14:57:37 +07:00
parent 04fbddba13
commit 8c6dad2a31
94 changed files with 4864 additions and 1151 deletions
+147 -95
View File
@@ -1,117 +1,169 @@
import { Form, Head } from '@inertiajs/react';
import { Form, Head, Link } from '@inertiajs/react';
import InputError from '@/components/input-error';
import PasswordInput from '@/components/password-input';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/spinner';
import { register } from '@/routes';
import { Button } from '@/components/site/button';
import Card from '@/components/site/card';
import { TextInput } from '@/components/site/form-field';
import Glow, { GridPattern } from '@/components/site/glow';
import Icon from '@/components/site/icon';
import LanguageToggle from '@/components/site/language-toggle';
import ThemeToggle from '@/components/site/theme-toggle';
import { common } from '@/content/common';
import { company } from '@/data/site';
import { useContent } from '@/hooks/use-locale';
import { home } from '@/routes';
import { store } from '@/routes/login';
import { request } from '@/routes/password';
import PasskeyVerify from '@/components/passkey-verify';
type Props = {
status?: string;
canResetPassword: boolean;
};
/**
* Admin panel sign-in. Public registration is disabled; admin accounts are
* created with `php artisan admin:create`.
*/
export default function Login({ status, canResetPassword }: Props) {
const t = useContent(common).login;
return (
<>
<Head title="Log in" />
<div className="probindo-site bg-surface-container-lowest text-on-surface relative flex min-h-svh flex-col items-center justify-center overflow-hidden p-6 font-sans antialiased">
<Head title={t.pageTitle} />
<GridPattern />
<Glow className="-top-40 -left-20 h-[500px] w-[640px]" />
<Glow className="bg-primary/15 -right-20 bottom-0 h-[420px] w-[520px]" />
<PasskeyVerify />
<div className="absolute top-5 right-5 flex items-center gap-2">
<LanguageToggle />
<ThemeToggle />
</div>
<Form
{...store.form()}
resetOnSuccess={['password']}
className="flex flex-col gap-6"
>
{({ processing, errors }) => (
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
name="email"
required
autoFocus
tabIndex={1}
autoComplete="email"
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<div className="relative flex w-full max-w-md flex-col gap-6">
<Link
href={home()}
className="flex flex-col items-center gap-3 self-center"
>
<div className="border-outline-variant/50 dark:bg-surface-container-low rounded-2xl border bg-white p-3">
<img
src={company.logo}
alt={company.name}
className="h-12 w-auto object-contain"
/>
</div>
<span className="text-on-surface-variant font-mono text-xs font-medium">
{company.name}
</span>
</Link>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
{canResetPassword && (
<TextLink
href={request()}
className="ml-auto text-sm"
tabIndex={5}
>
Forgot your password?
</TextLink>
)}
<Card padding="lg" className="shadow-xl">
<div className="mb-6 flex flex-col gap-1 text-center">
<h1 className="font-display text-on-surface text-2xl font-extrabold">
{t.title}
</h1>
<p className="text-on-surface-variant text-sm">
{t.description}
</p>
</div>
{status && (
<div
role="status"
className="border-primary/40 text-on-surface mb-4 rounded-lg border p-3 text-center text-sm"
>
{status}
</div>
)}
<Form
{...store.form()}
resetOnSuccess={['password']}
className="flex flex-col gap-5"
>
{({ processing, errors }) => (
<>
<div className="flex flex-col gap-1.5">
<label
htmlFor="email"
className="text-on-surface font-mono text-xs font-medium"
>
{t.email}
</label>
<TextInput
id="email"
type="email"
name="email"
required
autoFocus
autoComplete="email"
placeholder={t.emailPlaceholder}
/>
<InputError message={errors.email} />
</div>
<PasswordInput
id="password"
name="password"
required
tabIndex={2}
autoComplete="current-password"
placeholder="Password"
/>
<InputError message={errors.password} />
</div>
<div className="flex items-center space-x-3">
<Checkbox
id="remember"
name="remember"
tabIndex={3}
/>
<Label htmlFor="remember">Remember me</Label>
</div>
<div className="flex flex-col gap-1.5">
<div className="flex items-center justify-between">
<label
htmlFor="password"
className="text-on-surface font-mono text-xs font-medium"
>
{t.password}
</label>
{canResetPassword && (
<Link
href={request()}
className="text-primary text-xs font-semibold hover:underline"
>
{t.forgot}
</Link>
)}
</div>
<PasswordInput
id="password"
name="password"
required
autoComplete="current-password"
placeholder={t.passwordPlaceholder}
className="border-outline-variant/60 bg-surface-container-lowest text-on-surface focus-visible:border-primary h-auto rounded-xl px-4 py-2.5 text-sm"
/>
<InputError message={errors.password} />
</div>
<Button
type="submit"
className="mt-4 w-full"
tabIndex={4}
disabled={processing}
data-test="login-button"
>
{processing && <Spinner />}
Log in
</Button>
</div>
<label className="text-on-surface-variant flex cursor-pointer items-center gap-2 text-sm">
<input
type="checkbox"
name="remember"
className="h-4 w-4"
/>
{t.remember}
</label>
<div className="text-muted-foreground text-center text-sm">
Don't have an account?{' '}
<TextLink href={register()} tabIndex={5}>
Sign up
</TextLink>
</div>
</>
)}
</Form>
<Button
type="submit"
size="block"
icon={
processing
? 'progress_activity'
: 'login'
}
disabled={processing}
data-test="login-button"
>
{t.submit}
</Button>
</>
)}
</Form>
</Card>
{status && (
<div className="mb-4 text-center text-sm font-medium text-green-600">
{status}
</div>
)}
</>
<Link
href={home()}
className="text-on-surface-variant hover:text-primary flex items-center gap-1.5 self-center text-sm"
>
<Icon name="arrow_back" className="text-base" />
{t.back}
</Link>
</div>
</div>
);
}
Login.layout = {
title: 'Log in to your account',
description: 'Enter your email and password below to log in',
};
-120
View File
@@ -1,120 +0,0 @@
import { Form, Head } from '@inertiajs/react';
import InputError from '@/components/input-error';
import PasswordInput from '@/components/password-input';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/spinner';
import { login } from '@/routes';
import { store } from '@/routes/register';
type Props = {
passwordRules: string;
};
export default function Register({ passwordRules }: Props) {
return (
<>
<Head title="Register" />
<Form
{...store.form()}
resetOnSuccess={['password', 'password_confirmation']}
disableWhileProcessing
className="flex flex-col gap-6"
>
{({ processing, errors }) => (
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
type="text"
required
autoFocus
tabIndex={1}
autoComplete="name"
name="name"
placeholder="Full name"
/>
<InputError
message={errors.name}
className="mt-2"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
required
tabIndex={2}
autoComplete="email"
name="email"
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<PasswordInput
id="password"
required
tabIndex={3}
autoComplete="new-password"
name="password"
placeholder="Password"
passwordrules={passwordRules}
/>
<InputError message={errors.password} />
</div>
<div className="grid gap-2">
<Label htmlFor="password_confirmation">
Confirm password
</Label>
<PasswordInput
id="password_confirmation"
required
tabIndex={4}
autoComplete="new-password"
name="password_confirmation"
placeholder="Confirm password"
passwordrules={passwordRules}
/>
<InputError
message={errors.password_confirmation}
/>
</div>
<Button
type="submit"
className="mt-2 w-full"
tabIndex={5}
data-test="register-user-button"
>
{processing && <Spinner />}
Create account
</Button>
</div>
<div className="text-muted-foreground text-center text-sm">
Already have an account?{' '}
<TextLink href={login()} tabIndex={6}>
Log in
</TextLink>
</div>
</>
)}
</Form>
</>
);
}
Register.layout = {
title: 'Create an account',
description: 'Enter your details below to create your account',
};