@@ -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',
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
Reference in New Issue
Block a user