mirror of
https://github.com/vee1e/workorder-desk.git
synced 2026-09-01 09:50:13 +00:00
fix(a11y): improve contrast and add page titles and labels
This commit is contained in:
parent
bf76061847
commit
0a4574551a
12 changed files with 46 additions and 9 deletions
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Work Order Desk — track field work from dispatch to done." />
|
||||
<meta name="theme-color" content="#121317" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Work Order Desk</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
|
|
|||
|
|
@ -5,11 +5,22 @@ export function Card({ className, children }: { className?: string; children: Re
|
|||
return <div className={cn('rounded-lg border border-line bg-ink-900 shadow-[0_1px_0_0_rgba(255,255,255,0.03)_inset]', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function CardHeader({ title, description, children }: { title: string; description?: string; children?: ReactNode }) {
|
||||
export function CardHeader({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
as = 'h2',
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
as?: 'h1' | 'h2' | 'h3';
|
||||
}) {
|
||||
const Heading = as;
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-4 border-b border-line px-5 py-4">
|
||||
<div>
|
||||
<h2 className="font-display text-xl font-semibold uppercase tracking-wide text-ice">{title}</h2>
|
||||
<Heading className="font-display text-xl font-semibold uppercase tracking-wide text-ice">{title}</Heading>
|
||||
{description && <p className="mt-0.5 text-sm text-steel-300">{description}</p>}
|
||||
</div>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function FilterTabs({
|
|||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div role="group" className="inline-flex flex-wrap gap-1 rounded-lg border border-line bg-ink-900 p-1">
|
||||
<div role="group" aria-label="Filter by status" className="inline-flex flex-wrap gap-1 rounded-lg border border-line bg-ink-900 p-1">
|
||||
{options.map((o) => (
|
||||
<button
|
||||
key={o.value}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { usePageTitle } from '../../hooks/usePageTitle';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { forgotPasswordSchema } from '@workorders/shared';
|
||||
|
|
@ -10,6 +11,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
|
|||
import { ErrorBanner } from '../../components/primitives/Feedback';
|
||||
|
||||
export function ForgotPasswordPage() {
|
||||
usePageTitle('Reset your password');
|
||||
const [email, setEmail] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
|
@ -37,7 +39,7 @@ export function ForgotPasswordPage() {
|
|||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader title="Reset your password" description="We will email you a reset link if the account exists." />
|
||||
<CardHeader as="h1" title="Reset your password" description="We will email you a reset link if the account exists." />
|
||||
<CardBody>
|
||||
{submitted ? (
|
||||
<p className="text-sm text-steel-300">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { usePageTitle } from '../../hooks/usePageTitle';
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { registerSchema } from '@workorders/shared';
|
||||
|
|
@ -9,6 +10,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
|
|||
import { ErrorBanner } from '../../components/primitives/Feedback';
|
||||
|
||||
export function RegisterPage() {
|
||||
usePageTitle('Create account');
|
||||
const register = useRegister();
|
||||
const navigate = useNavigate();
|
||||
const [values, setValues] = useState({ name: '', email: '', password: '' });
|
||||
|
|
@ -37,7 +39,7 @@ export function RegisterPage() {
|
|||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader title="Create account" description="Join the work order desk." />
|
||||
<CardHeader as="h1" title="Create account" description="Join the work order desk." />
|
||||
<CardBody>
|
||||
{formError && <ErrorBanner className="mb-4" message={formError} />}
|
||||
<form onSubmit={onSubmit} className="space-y-4" noValidate>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { usePageTitle } from '../../hooks/usePageTitle';
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { resetPasswordSchema } from '@workorders/shared';
|
||||
|
|
@ -11,6 +12,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
|
|||
import { ErrorBanner } from '../../components/primitives/Feedback';
|
||||
|
||||
export function ResetPasswordPage() {
|
||||
usePageTitle('Choose a new password');
|
||||
const [params] = useSearchParams();
|
||||
const token = params.get('token') ?? '';
|
||||
const [password, setPassword] = useState('');
|
||||
|
|
@ -42,7 +44,7 @@ export function ResetPasswordPage() {
|
|||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader title="Choose a new password" />
|
||||
<CardHeader as="h1" title="Choose a new password" />
|
||||
<CardBody>
|
||||
{!token && <ErrorBanner message="This reset link is missing its token. Request a new one." />}
|
||||
{token && (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import { Button } from '../../components/primitives/Spinner';
|
|||
import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
|
||||
import { Field, Input } from '../../components/primitives/Input';
|
||||
|
||||
import { usePageTitle } from '../../hooks/usePageTitle';
|
||||
|
||||
export function ProfilePage() {
|
||||
usePageTitle('Profile');
|
||||
const { data: user } = useMe();
|
||||
const [name, setName] = useState(user?.name ?? '');
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import { PageHeader } from '../../components/primitives/Feedback';
|
|||
import { Card, CardBody } from '../../components/primitives/Card';
|
||||
import { messageFromError } from '../../lib/errors';
|
||||
|
||||
import { usePageTitle } from '../../hooks/usePageTitle';
|
||||
|
||||
export function WorkOrderCreatePage() {
|
||||
usePageTitle('New work order');
|
||||
const create = useCreateWorkOrder();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
|
|||
7
frontend/src/hooks/usePageTitle.ts
Normal file
7
frontend/src/hooks/usePageTitle.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
export function usePageTitle(title: string): void {
|
||||
useEffect(() => {
|
||||
document.title = title ? `${title} · Work Order Desk` : 'Work Order Desk';
|
||||
}, [title]);
|
||||
}
|
||||
|
|
@ -8,7 +8,10 @@ const boardTickets = [
|
|||
{ title: 'Seal window draft — Unit 3A', no: '№ 4B81D', priority: 'low' as const, status: 'done' as const },
|
||||
];
|
||||
|
||||
import { usePageTitle } from '../hooks/usePageTitle';
|
||||
|
||||
export function LandingPage() {
|
||||
usePageTitle('');
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-ink-950 text-ice">
|
||||
<header className="border-b border-line bg-ink-900/70">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '../components/primitives/Spinner';
|
||||
|
||||
import { usePageTitle } from '../hooks/usePageTitle';
|
||||
|
||||
export function NotFoundPage() {
|
||||
usePageTitle('Page not found');
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-ink-950 px-4 text-center">
|
||||
<p className="font-display text-8xl font-bold uppercase leading-none text-steel-600">404</p>
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ export default {
|
|||
},
|
||||
signal: {
|
||||
400: '#FF6B3D',
|
||||
500: '#FF5C33',
|
||||
600: '#E94F28',
|
||||
500: '#E04820',
|
||||
600: '#C73D18',
|
||||
},
|
||||
steel: {
|
||||
300: '#A9B2BF',
|
||||
400: '#8A94A3',
|
||||
500: '#6A7484',
|
||||
500: '#7C8794',
|
||||
600: '#4A5360',
|
||||
},
|
||||
work: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue