feat(uat-batch-10): copy polish, TTL trim, and a11y discrete fixes
- Supplemental-info link TTL trimmed from 30 → 14 days (single constant in supplemental-forms.service). - LinkedBerthsList toggle renamed "Mark in EOI bundle" → "Include in EOI"; tooltip aria-label updated to match. - Icon-only row-action triggers on the interest / client / berth list tables gain aria-label (Row actions for <name>) so SR users hear the row context. - Table / Board view toggle on interest list gains aria-label + aria-pressed on each variant; wrapper gets role="group". - Upcoming-milestones disclosure on interest-tabs gains aria-expanded + aria-controls; recommender Hide/Add filters button matches. - BrandedAuthShell logo alt no longer defaults to "Sign in" — uses the configured `appName` when known, empty string otherwise so screen readers don't announce "Sign in" on password-reset / set-password pages. tsc clean. 1419/1419 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,7 @@ function ActionsCell({ row }: { row: { original: BerthRow } }) {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
aria-label={`Row actions for berth ${berth.mooringNumber ?? ''}`.trim()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
|
||||
@@ -302,6 +302,7 @@ export function getClientColumns({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
aria-label={`Row actions for ${row.original.fullName ?? 'client'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
|
||||
@@ -503,6 +503,8 @@ export function BerthRecommenderPanel({
|
||||
variant="outline"
|
||||
onClick={() => setFiltersOpen((v) => !v)}
|
||||
disabled={!hasDimensions}
|
||||
aria-expanded={filtersOpen}
|
||||
aria-controls="recommender-filters-body"
|
||||
>
|
||||
<Filter className="mr-1.5 size-3.5" aria-hidden />
|
||||
{filtersOpen ? 'Hide filters' : 'Add filters'}
|
||||
@@ -542,7 +544,9 @@ export function BerthRecommenderPanel({
|
||||
</div>
|
||||
</div>
|
||||
{!collapsed && filtersOpen && hasDimensions ? (
|
||||
<AmenityFilterForm filters={amenityFilters} onChange={setAmenityFilters} />
|
||||
<div id="recommender-filters-body">
|
||||
<AmenityFilterForm filters={amenityFilters} onChange={setAmenityFilters} />
|
||||
</div>
|
||||
) : null}
|
||||
{!collapsed && hasDimensions && areaChips.length > 1 ? (
|
||||
<div className="flex flex-wrap items-center gap-1.5 pt-1">
|
||||
|
||||
@@ -297,6 +297,7 @@ export function getInterestColumns({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
aria-label={`Row actions for ${row.original.clientName ?? 'interest'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
|
||||
@@ -183,11 +183,17 @@ export function InterestList() {
|
||||
{/* Kanban view is desktop-only — mobile drops the toggle and
|
||||
falls back to the list/cards view (the board's column
|
||||
horizontal-scroll model is unusable at phone widths). */}
|
||||
<div className="hidden sm:flex items-center border rounded-md overflow-hidden">
|
||||
<div
|
||||
className="hidden sm:flex items-center border rounded-md overflow-hidden"
|
||||
role="group"
|
||||
aria-label="View mode"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={viewMode === 'table' ? 'default' : 'ghost'}
|
||||
className="rounded-none"
|
||||
aria-label="Table view"
|
||||
aria-pressed={viewMode === 'table'}
|
||||
onClick={() => setViewMode('table')}
|
||||
>
|
||||
<LayoutList className="h-4 w-4" aria-hidden />
|
||||
@@ -196,6 +202,8 @@ export function InterestList() {
|
||||
size="sm"
|
||||
variant={viewMode === 'board' ? 'default' : 'ghost'}
|
||||
className="rounded-none"
|
||||
aria-label="Board view"
|
||||
aria-pressed={viewMode === 'board'}
|
||||
onClick={() => setViewMode('board')}
|
||||
>
|
||||
<Kanban className="h-4 w-4" aria-hidden />
|
||||
|
||||
@@ -555,6 +555,8 @@ function FutureMilestones({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
aria-expanded={expanded}
|
||||
aria-controls="future-milestones-body"
|
||||
className="flex w-full items-center justify-between gap-2 px-4 py-2.5 text-sm text-muted-foreground hover:text-foreground hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
<span>
|
||||
@@ -565,6 +567,7 @@ function FutureMilestones({
|
||||
</button>
|
||||
{expanded && (
|
||||
<div
|
||||
id="future-milestones-body"
|
||||
className={cn(
|
||||
'grid grid-cols-1 gap-4 p-4 pt-0',
|
||||
milestones.length === 1 ? '' : 'lg:grid-cols-2',
|
||||
|
||||
@@ -374,14 +374,14 @@ function LinkedBerthRowItem({
|
||||
htmlFor={`bundle-${row.berthId}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
Mark in EOI bundle
|
||||
Include in EOI
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground hover:bg-muted/60 hover:text-foreground"
|
||||
aria-label="What does Mark in EOI bundle do?"
|
||||
aria-label="What does Include in EOI do?"
|
||||
>
|
||||
<HelpCircle className="h-3.5 w-3.5" aria-hidden />
|
||||
</button>
|
||||
|
||||
@@ -29,7 +29,11 @@ export function BrandedAuthShell({ children, branding }: BrandedAuthShellProps)
|
||||
const ctx = useAuthBranding();
|
||||
const logoUrl = branding?.logoUrl ?? ctx?.logoUrl ?? null;
|
||||
const backgroundUrl = branding?.backgroundUrl ?? ctx?.backgroundUrl ?? null;
|
||||
const altText = branding?.appName ?? ctx?.appName ?? 'Sign in';
|
||||
// When no port name is known, treat the logo as decorative — "Sign in"
|
||||
// as alt text was being read on every auth page even when the page
|
||||
// itself isn't a sign-in surface (e.g. password reset, set-password).
|
||||
const appName = branding?.appName ?? ctx?.appName ?? null;
|
||||
const altText = appName ?? '';
|
||||
// fixed inset-0 anchors the auth surface to the viewport directly —
|
||||
// iOS Safari ignores overflow-hidden on inner divs for body-level
|
||||
// scrolling, so a regular `h-dvh overflow-hidden` wrapper doesn't
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '@/lib/db/schema';
|
||||
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
|
||||
|
||||
const TOKEN_TTL_DAYS = 30;
|
||||
const TOKEN_TTL_DAYS = 14;
|
||||
const TOKEN_BYTES = 32; // 256-bit → ~43 base64url chars; brute-force infeasible.
|
||||
|
||||
function generateToken(): string {
|
||||
|
||||
Reference in New Issue
Block a user