Address PR review on data request pages

Require a verified viewer email before creating a rights request and
validate the free-text fields with the same SafeText bounds the console
uses, so this public portal mutation stays safe and bounded.

Move myRightsRequests onto the base Query, drop the now-dead count
loaders, and order the RECTIFICATION enum value before PORTABILITY so
the Postgres sort order matches RightsRequestTypes().

Harden the v2 kit primitives: SegmentedControl keeps equal-width cards
(auto-fill), preserves its selection when the active card is toggled,
and forwards an accessible name; Field associates its label and error
by id/aria instead of wrapping the control in a label. Give the type
group an accessible name, require the name field for non-complaint
types, use a timezone-stable reference year, drop the underreporting
header count, and neutralize the response-deadline copy.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-20 09:47:55 +02:00
parent 6623cbc6f2
commit 622f1ba67d
16 changed files with 156 additions and 146 deletions

View File

@@ -132,7 +132,7 @@ export function RequestsPage({ queryRef }: RequestsPageProps) {
return (
<>
<PageHeader title={t("title")} count={requests.length} actions={newRequestButton} />
<PageHeader title={t("title")} actions={newRequestButton} />
<div className={page()}>
<div className={results()}>
<ListErrorBoundary

View File

@@ -34,7 +34,7 @@ import { TextField } from "@probo/ui/src/v2/form/TextField";
import { SegmentedControl } from "@probo/ui/src/v2/SegmentedControl/SegmentedControl";
import { SegmentedControlItem } from "@probo/ui/src/v2/SegmentedControl/SegmentedControlItem";
import { Text } from "@probo/ui/src/v2/typography/Text";
import { type FormEvent, useState } from "react";
import { type FormEvent, useId, useState } from "react";
import { useTranslation } from "react-i18next";
import {
@@ -89,6 +89,7 @@ interface NewRequestFormProps {
function NewRequestForm({ onClose, connectionId, viewerEmail, viewerName }: NewRequestFormProps) {
const { t } = useTranslation("requests");
const [submit, isSubmitting] = useCreateRightsRequest();
const typeLabelId = useId();
const [type, setType] = useState<SubmittableRightsRequestType>("ACCESS");
const [name, setName] = useState(viewerName);
@@ -148,10 +149,11 @@ function NewRequestForm({ onClose, connectionId, viewerEmail, viewerName }: NewR
<DialogBody>
<div className={root()}>
<div className={label()}>
<Text size={2} weight="medium" color="neutral" highContrast>
<Text id={typeLabelId} size={2} weight="medium" color="neutral" highContrast>
{t("dialog.typeLabel")}
</Text>
<SegmentedControl
aria-labelledby={typeLabelId}
value={type}
onValueChange={value => setType(value as SubmittableRightsRequestType)}
>
@@ -173,6 +175,7 @@ function NewRequestForm({ onClose, connectionId, viewerEmail, viewerName }: NewR
<TextField
value={name}
placeholder={t("form.namePlaceholder")}
required={!config.nameOptional}
onChange={e => setName(e.target.value)}
/>
</Field>

View File

@@ -98,7 +98,8 @@ export const rightsRequestFormConfig: Record<
// Human-facing reference derived from the created year and a short suffix of the
// opaque id (display-only; not a stored sequential number).
export function formatRightsRequestReference(id: string, createdAt: string): string {
const year = new Date(createdAt).getFullYear();
// Use UTC so the reference year is stable regardless of the viewer's timezone.
const year = new Date(createdAt).getUTCFullYear();
const suffix = id.replace(/[^a-zA-Z0-9]/g, "").slice(-6).toUpperCase();
return `REQ-${year}-${suffix}`;
}

View File

@@ -35,7 +35,7 @@
"submit": "Submit Request",
"success": {
"title": "Request submitted",
"description": "We'll process your request and respond within 30 days.",
"description": "We'll process your request and respond within the timeframe required by applicable law.",
"close": "Close"
}
},

View File

@@ -35,7 +35,7 @@
"submit": "Envoyer la demande",
"success": {
"title": "Demande envoyée",
"description": "Nous traiterons votre demande et vous répondrons sous 30 jours.",
"description": "Nous traiterons votre demande et vous répondrons dans le délai prévu par la loi applicable.",
"close": "Fermer"
}
},