From 156eb98cfa3c7c971a1b80d4d23eda0fe4e60137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Janou=C5=A1ek?= Date: Thu, 24 Sep 2026 21:07:12 +0200 Subject: [PATCH 1/3] fix: expand account-change metadata by default Show technical metadata immediately during account-change review. Reset that default for each request while retaining manual collapse controls. --- src/pages/app/admin/RequestDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/app/admin/RequestDetailPage.tsx b/src/pages/app/admin/RequestDetailPage.tsx index d2ac54ed..2a074141 100644 --- a/src/pages/app/admin/RequestDetailPage.tsx +++ b/src/pages/app/admin/RequestDetailPage.tsx @@ -451,7 +451,7 @@ export function RequestDetailPage() { -
+
Date: Thu, 24 Sep 2026 21:15:52 +0200 Subject: [PATCH 2/3] Organize registration review and expand technical metadata Group applicant context, requested setup, and address into readable cards. Open technical metadata for both registration and account-change reviews while retaining manual collapse and reset on the next request. Update the existing browser checks for the expanded default and responsive request links without changing approval or API behavior. --- .../admin/requests_operations_smoke.spec.ts | 20 ++-- src/i18n/locales/cs/requests.ts | 2 + src/i18n/locales/en/requests.ts | 2 + src/pages/app/admin/RequestDetailPage.tsx | 100 +++++++++++------- 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/e2e/specs/admin/requests_operations_smoke.spec.ts b/e2e/specs/admin/requests_operations_smoke.spec.ts index ab686462..8d9d93ab 100644 --- a/e2e/specs/admin/requests_operations_smoke.spec.ts +++ b/e2e/specs/admin/requests_operations_smoke.spec.ts @@ -112,16 +112,18 @@ test('@workflow-matrix @smoke admin requests: one list row opens the canonical d }); await page.goto('/admin/requests'); - await expect(page.getByTestId('admin.requests.table')).toBeVisible(); - const desktopRow = page.getByTestId('admin.requests.row.registration.123'); - await expect(desktopRow).not.toHaveAttribute('tabindex'); - await expect(desktopRow.getByRole('link', { name: '#123' })).toBeVisible(); + const mobile = page.viewportSize()!.width < 768; + const requestLink = mobile + ? page.getByTestId('admin.requests.mobile.row.registration.123').getByRole('link') + : page.getByTestId('admin.requests.row.registration.123').getByRole('link', { name: '#123' }); + await expect(requestLink).toBeVisible(); + if (!mobile) await expect(page.getByTestId('admin.requests.row.registration.123')).not.toHaveAttribute('tabindex'); await expect.poll(() => states.includes('awaiting')).toBeTruthy(); expect(new URL(page.url()).searchParams.get('state')).toBeNull(); await expect(page.getByTestId('admin.requests.quick.awaiting')).toHaveAttribute('aria-pressed', 'true'); - await desktopRow.getByRole('link', { name: '#123' }).press('Enter'); + await requestLink.press('Enter'); await expect(page).toHaveURL(/\/admin\/requests\/registration\/123\?returnTo=/); const returnTo = new URL(page.url()).searchParams.get('returnTo'); expect(new URL(returnTo ?? '/', 'https://example.test').pathname).toBe('/admin/requests'); @@ -234,6 +236,8 @@ for (const language of ['en', 'cs'] as const) { await page.goto('/admin/requests/registration/124'); + await expect(page.getByTestId('admin.requests.detail.metadata').locator('details')).toHaveAttribute('open', ''); + await expect(page.getByTestId('admin.requests.detail.registration.preferences')).toBeVisible(); const review = page.getByTestId('admin.requests.detail.review'); const summary = review.getByTestId('admin.requests.detail.risk.summary'); await expect(summary).toBeInViewport(); @@ -335,10 +339,12 @@ test('@workflow-matrix @pr-smoke @pr-smoke-mobile @smoke admin requests: success await expect.poll(osm.requestCount).toBe(1); const metadataChevron = page.getByTestId('admin.requests.detail.metadata.chevron'); await expect(metadataChevron).toBeVisible(); - await expect(metadataChevron).toHaveCSS('rotate', 'none'); - await page.getByTestId('admin.requests.detail.metadata.toggle').click(); await expect(page.getByTestId('admin.requests.detail.metadata').locator('details')).toHaveAttribute('open', ''); await expect(metadataChevron).toHaveCSS('rotate', '90deg'); + await page.getByTestId('admin.requests.detail.metadata.toggle').click(); + await expect(page.getByTestId('admin.requests.detail.metadata').locator('details')).not.toHaveAttribute('open'); + await page.getByTestId('admin.requests.detail.metadata.toggle').click(); + await expect(metadataChevron).toHaveCSS('rotate', '90deg'); await page.getByTestId('admin.requests.resolve.action.approve').click(); await expect(page.getByTestId('admin.requests.resolve.modal')).toContainText(/approve request|schválit žádost/i); await expect(page.getByTestId('admin.requests.resolve.action_select')).toHaveCount(0); diff --git a/src/i18n/locales/cs/requests.ts b/src/i18n/locales/cs/requests.ts index abf9f07f..041b0779 100644 --- a/src/i18n/locales/cs/requests.ts +++ b/src/i18n/locales/cs/requests.ts @@ -86,6 +86,8 @@ export const csRequests = { 'requests.detail.card.request': 'Žádost', 'requests.detail.admin': 'Administrátor', 'requests.detail.org_id': 'IČ / identifikátor organizace', + 'requests.detail.registration.context': 'Doplňující údaje', + 'requests.detail.registration.preferences': 'Požadované nastavení', 'requests.detail.registration.title': 'Údaje žadatele', 'requests.detail.registration.subtitle': 'Úplné údaje odeslané v registrační žádosti.', 'requests.detail.change.title': 'Požadovaná změna', diff --git a/src/i18n/locales/en/requests.ts b/src/i18n/locales/en/requests.ts index f8048e42..e694b4f3 100644 --- a/src/i18n/locales/en/requests.ts +++ b/src/i18n/locales/en/requests.ts @@ -86,6 +86,8 @@ export const enRequests = { 'requests.detail.card.request': 'Request', 'requests.detail.admin': 'Admin', 'requests.detail.org_id': 'Organisation ID', + 'requests.detail.registration.context': 'Additional details', + 'requests.detail.registration.preferences': 'Requested setup', 'requests.detail.registration.title': 'Applicant details', 'requests.detail.registration.subtitle': 'Complete data submitted with the registration request.', 'requests.detail.change.title': 'Requested change', diff --git a/src/pages/app/admin/RequestDetailPage.tsx b/src/pages/app/admin/RequestDetailPage.tsx index 2a074141..259f0d54 100644 --- a/src/pages/app/admin/RequestDetailPage.tsx +++ b/src/pages/app/admin/RequestDetailPage.tsx @@ -83,30 +83,51 @@ function RegistrationDetails(props: { request: RegistrationRequest }) { const request = props.request; return ( -
- - - - - - -
-
{t('requests.field.address')}
-
- -
+
+ + + +
+ + + + + + +
+
+

{t('requests.detail.registration.context')}

+
+ + +
+
+
+
+
+ + + +
+ + + + + +
+
+
+ + + + + +
- - - - - - - -
+ ); } @@ -429,16 +450,13 @@ export function RequestDetailPage() { {reqType === 'registration' ? : null} -
- - - - {reqType === 'registration' ? ( - - ) : ( +
+ {reqType === 'registration' ? ( + + ) : ( + + + - )} - - + + + )} -
+
{t('requests.detail.api_ip')}
-
{stringValue(request.api_ip_addr)}
- {request.api_ip_ptr ?
{request.api_ip_ptr}
: null} +
{stringValue(request.api_ip_addr)}
+ {request.api_ip_ptr ?
{request.api_ip_ptr}
: null}
{t('requests.detail.client_ip')}
-
{stringValue(request.client_ip_addr)}
- {request.client_ip_ptr ?
{request.client_ip_ptr}
: null} +
{stringValue(request.client_ip_addr)}
+ {request.client_ip_ptr ?
{request.client_ip_ptr}
: null}
{hasOperationalLinks ? ( From cda7dc22786c86188848726d7bcc8beb5cc9dc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Janou=C5=A1ek?= Date: Fri, 25 Sep 2026 00:04:13 +0200 Subject: [PATCH 3/3] Restore registration checks below applicant details Return to the original single applicant card and place the risk summary and IP/email checks below it, as requested. Keep metadata expanded for registration and account-change reviews. --- .../admin/requests_operations_smoke.spec.ts | 26 ++--- src/i18n/locales/cs/requests.ts | 2 - src/i18n/locales/en/requests.ts | 2 - src/pages/app/admin/RequestDetailPage.tsx | 104 ++++++++---------- src/pages/app/admin/RequestFraudChecks.tsx | 63 +++++------ 5 files changed, 84 insertions(+), 113 deletions(-) diff --git a/e2e/specs/admin/requests_operations_smoke.spec.ts b/e2e/specs/admin/requests_operations_smoke.spec.ts index 8d9d93ab..77be8702 100644 --- a/e2e/specs/admin/requests_operations_smoke.spec.ts +++ b/e2e/specs/admin/requests_operations_smoke.spec.ts @@ -237,27 +237,27 @@ for (const language of ['en', 'cs'] as const) { await page.goto('/admin/requests/registration/124'); await expect(page.getByTestId('admin.requests.detail.metadata').locator('details')).toHaveAttribute('open', ''); - await expect(page.getByTestId('admin.requests.detail.registration.preferences')).toBeVisible(); const review = page.getByTestId('admin.requests.detail.review'); - const summary = review.getByTestId('admin.requests.detail.risk.summary'); - await expect(summary).toBeInViewport(); + const summary = page.getByTestId('admin.requests.detail.risk.summary'); + await expect(summary).toBeVisible(); const summaryBox = await summary.boundingBox(); - const decisionBox = await review.getByTestId('admin.requests.detail.decision').boundingBox(); - expect(summaryBox!.y + summaryBox!.height).toBeLessThan(decisionBox!.y); - await expect(review.getByTestId('admin.requests.detail.risk.ip')).toBeVisible(); - await expect(review.getByTestId('admin.requests.detail.risk.mail')).toBeVisible(); - if (page.viewportSize()!.width >= 1024) { - const detailsBox = await page.getByTestId('admin.requests.detail.registration.fields').boundingBox(); - const reviewBox = await review.boundingBox(); - expect(reviewBox!.x).toBeGreaterThan(detailsBox!.x + detailsBox!.width); + const detailsBox = await page.getByTestId('admin.requests.detail.registration.fields').boundingBox(); + expect(summaryBox!.y).toBeGreaterThan(detailsBox!.y + detailsBox!.height); + await expect(review.getByTestId('admin.requests.detail.risk.ip')).toHaveCount(0); + await expect(review.getByTestId('admin.requests.detail.risk.mail')).toHaveCount(0); + for (const kind of ['ip', 'mail']) { + const check = page.getByTestId(`admin.requests.detail.risk.${kind}`); + await expect(check).toBeVisible(); + const box = await check.boundingBox(); + expect(box!.y).toBeGreaterThan(summaryBox!.y + summaryBox!.height); } for (const kind of ['ip', 'mail']) { - await review.getByTestId(`admin.requests.detail.risk.${kind}.details`).locator('summary').click(); + await page.getByTestId(`admin.requests.detail.risk.${kind}.details`).locator('summary').click(); } expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(page.viewportSize()!.width); await page.evaluate(() => window.scrollTo(0, 0)); - await page.screenshot({ path: testInfo.outputPath('request-risk-sidebar.png'), fullPage: true }); + await page.screenshot({ path: testInfo.outputPath('request-risk-below-details.png'), fullPage: true }); const copyAddress = page.getByTestId('admin.requests.detail.registration.address.map.copy'); await expect(copyAddress).toBeVisible(); const addressBox = await page.getByTestId('admin.requests.detail.registration.address.map') diff --git a/src/i18n/locales/cs/requests.ts b/src/i18n/locales/cs/requests.ts index 041b0779..abf9f07f 100644 --- a/src/i18n/locales/cs/requests.ts +++ b/src/i18n/locales/cs/requests.ts @@ -86,8 +86,6 @@ export const csRequests = { 'requests.detail.card.request': 'Žádost', 'requests.detail.admin': 'Administrátor', 'requests.detail.org_id': 'IČ / identifikátor organizace', - 'requests.detail.registration.context': 'Doplňující údaje', - 'requests.detail.registration.preferences': 'Požadované nastavení', 'requests.detail.registration.title': 'Údaje žadatele', 'requests.detail.registration.subtitle': 'Úplné údaje odeslané v registrační žádosti.', 'requests.detail.change.title': 'Požadovaná změna', diff --git a/src/i18n/locales/en/requests.ts b/src/i18n/locales/en/requests.ts index e694b4f3..f8048e42 100644 --- a/src/i18n/locales/en/requests.ts +++ b/src/i18n/locales/en/requests.ts @@ -86,8 +86,6 @@ export const enRequests = { 'requests.detail.card.request': 'Request', 'requests.detail.admin': 'Admin', 'requests.detail.org_id': 'Organisation ID', - 'requests.detail.registration.context': 'Additional details', - 'requests.detail.registration.preferences': 'Requested setup', 'requests.detail.registration.title': 'Applicant details', 'requests.detail.registration.subtitle': 'Complete data submitted with the registration request.', 'requests.detail.change.title': 'Requested change', diff --git a/src/pages/app/admin/RequestDetailPage.tsx b/src/pages/app/admin/RequestDetailPage.tsx index 259f0d54..4aa47d3e 100644 --- a/src/pages/app/admin/RequestDetailPage.tsx +++ b/src/pages/app/admin/RequestDetailPage.tsx @@ -37,7 +37,7 @@ import { requestResourceLabel, safeRequestsReturnTo, } from './RequestDetailModel'; -import { RequestFraudChecks, RequestFraudSummary } from './RequestFraudChecks'; +import { RequestFraudChecks } from './RequestFraudChecks'; import { RequestOperationalLinks, RequestReviewActions, @@ -83,51 +83,30 @@ function RegistrationDetails(props: { request: RegistrationRequest }) { const request = props.request; return ( -
- - - -
- - - - - - -
-
-

{t('requests.detail.registration.context')}

-
- - -
-
-
-
-
- - - -
- - - - - -
-
-
- - - - - - +
+ + + + + + +
+
{t('requests.field.address')}
+
+ +
-
+ + + + + + + + ); } @@ -405,7 +384,6 @@ export function RequestDetailPage() {
-
- {reqType === 'registration' ? ( - - ) : ( - - - +
+ + + + {reqType === 'registration' ? ( + + ) : ( - - - )} + )} + + + + {reqType === 'registration' ? : null}
@@ -503,13 +485,13 @@ export function RequestDetailPage() {
{t('requests.detail.api_ip')}
-
{stringValue(request.api_ip_addr)}
- {request.api_ip_ptr ?
{request.api_ip_ptr}
: null} +
{stringValue(request.api_ip_addr)}
+ {request.api_ip_ptr ?
{request.api_ip_ptr}
: null}
{t('requests.detail.client_ip')}
-
{stringValue(request.client_ip_addr)}
- {request.client_ip_ptr ?
{request.client_ip_ptr}
: null} +
{stringValue(request.client_ip_addr)}
+ {request.client_ip_ptr ?
{request.client_ip_ptr}
: null}
{hasOperationalLinks ? ( diff --git a/src/pages/app/admin/RequestFraudChecks.tsx b/src/pages/app/admin/RequestFraudChecks.tsx index 72a15cd4..b512fd78 100644 --- a/src/pages/app/admin/RequestFraudChecks.tsx +++ b/src/pages/app/admin/RequestFraudChecks.tsx @@ -4,7 +4,7 @@ import { Clock3, ShieldAlert, ShieldCheck } from 'lucide-react'; import { useI18n } from '../../../app/i18n'; import type { RegistrationRequest } from '../../../lib/api/requests'; import { Badge } from '../../../components/ui/Badge'; -import { Card, CardBody } from '../../../components/ui/Card'; +import { Card, CardBody, CardHeader } from '../../../components/ui/Card'; import { clsx } from '../../../components/ui/clsx'; import { fraudCheckStatus, type FraudCheckStatus } from './RequestDetailModel'; @@ -137,24 +137,26 @@ function CheckCard(props: { ? 'border-warn-border' : 'border-ok-border')} > -
-

{props.title}

-
- - {t('requests.detail.risk.score', { score: props.score ?? '—' })} - - - {t(`requests.detail.risk.status.${props.status}`)} - -
-
+ + + {t('requests.detail.risk.score', { score: props.score ?? '—' })} + + + {t(`requests.detail.risk.status.${props.status}`)} + + + )} + /> {props.status === 'pending' ? (
{t('requests.detail.risk.pending_help')}
@@ -167,9 +169,9 @@ function CheckCard(props: {
{props.summarySignals.map((signal) => ( -
+
{t(signal.labelKey)}
-
{signalValue(props.request[signal.key], t('common.yes'), t('common.no'))}
+
{signalValue(props.request[signal.key], t('common.yes'), t('common.no'))}
))}
@@ -191,8 +193,10 @@ function CheckCard(props: { ); } -export function RequestFraudSummary(props: { request: RegistrationRequest }) { +export function RequestFraudChecks(props: { request: RegistrationRequest }) { const { t } = useI18n(); + const ipStatus = fraudCheckStatus(props.request.ip_checked, props.request.ip_success); + const mailStatus = fraudCheckStatus(props.request.mail_checked, props.request.mail_success); const summary = requestFraudRiskSummary(props.request); const SummaryIcon = summary.state === 'clear' ? ShieldCheck @@ -208,7 +212,7 @@ export function RequestFraudSummary(props: { request: RegistrationRequest }) {
) : null}
-
- ); -} - -export function RequestFraudChecks(props: { request: RegistrationRequest }) { - const { t } = useI18n(); - const ipStatus = fraudCheckStatus(props.request.ip_checked, props.request.ip_success); - const mailStatus = fraudCheckStatus(props.request.mail_checked, props.request.mail_success); - - return ( -
-
+