Skip to content

Commit f519988

Browse files
Fix: Copilot suggested fixes
- ConsentApprovalDialog now shows “Loading consent details...” while approval details load, instead of briefly showing the empty mandatory state. - Added the new consentRegistry.modals.approval.loading i18n key. - validityTime is normalized to 0 when omitted, so the table consistently shows “Not applicable”. - README now matches the repo
1 parent a9d4eed commit f519988

5 files changed

Lines changed: 161 additions & 137 deletions

File tree

portal/frontend/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pnpm preview
6969

7070
Tests are written with [Vitest](https://vitest.dev/) and [React Testing Library](https://testing-library.com/react).
7171

72-
- **Test files**: Located in `src/__tests__/` with `.test.tsx` extension
72+
- **Test files**: Located in `src/__tests__/` with `.test.ts`/`.test.tsx` extensions
7373
- **Setup**: Global setup in `vitest.setup.ts` imports jest-dom matchers
7474
- **Run tests**: `pnpm test` or `pnpm test:watch` for watch mode
7575
- **Coverage**: `pnpm test:coverage` generates HTML coverage report in `coverage/`
@@ -86,8 +86,7 @@ src/
8686
├── utils/ # Utility functions and helpers
8787
├── __tests__/ # Test files
8888
├── App.tsx # Root component
89-
├── main.tsx # Entry point
90-
└── index.css # Global styles
89+
└── main.tsx # Entry point
9190
```
9291

9392
## AI Instructions

portal/frontend/src/__tests__/ConsentRegistryModals.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ afterEach(() => {
4242
})
4343

4444
describe('consent registry dialogs', () => {
45+
it('shows loading text instead of empty states while approval details load', () => {
46+
renderWithProviders(
47+
<ConsentApprovalDialog
48+
open
49+
consentId="consent-123"
50+
loading
51+
purposes={[]}
52+
onClose={vi.fn()}
53+
onConfirm={vi.fn()}
54+
/>,
55+
)
56+
57+
expect(screen.getByText('Loading consent details...')).toBeInTheDocument()
58+
expect(
59+
screen.queryByText('No mandatory requirements for this consent.'),
60+
).not.toBeInTheDocument()
61+
})
62+
4563
it('submits selected optional permissions from approval dialog', () => {
4664
const onConfirm = vi.fn()
4765

portal/frontend/src/features/consent-registry/components/ConsentApprovalDialog.tsx

Lines changed: 139 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -133,144 +133,150 @@ function ConsentApprovalDialog({
133133
</Stack>
134134
</DialogTitle>
135135

136-
<DialogContent sx={{ px: 3, mt: 1.5, pb: 3 }}>
137-
<Stack spacing={3} sx={{ mt: 0.5 }}>
138-
<Box>
139-
<Typography
140-
variant="caption"
141-
sx={{
142-
display: 'block',
143-
mb: 1.5,
144-
fontWeight: 700,
145-
color: 'primary.main',
146-
letterSpacing: 0.6,
147-
textTransform: 'uppercase',
148-
}}
149-
>
150-
{t('consentRegistry.modals.approval.mandatory', 'Mandatory Elements (Required)')}
151-
</Typography>
152-
<Stack spacing={1.25}>
153-
{mandatoryElements.map((element) => (
154-
<Stack
155-
key={toElementKey(element.purposeName, element.elementName)}
156-
direction="row"
157-
alignItems="center"
158-
justifyContent="space-between"
159-
sx={{
160-
p: 1.5,
161-
borderRadius: 1,
162-
border: 1,
163-
borderColor: 'divider',
164-
bgcolor: 'background.paper',
165-
gap: 1.5,
166-
}}
167-
>
168-
<Box>
169-
<Typography variant="body2" fontWeight={600}>
170-
{element.elementName}
171-
</Typography>
172-
<Typography variant="caption" color="text.secondary">
173-
{element.purposeName}
174-
</Typography>
175-
</Box>
176-
<Chip
177-
size="small"
178-
color="error"
179-
variant="outlined"
180-
label={t('consentRegistry.modals.approval.required', 'Required')}
181-
/>
182-
</Stack>
183-
))}
184-
185-
{mandatoryElements.length === 0 ? (
186-
<Typography variant="body2" color="text.secondary">
187-
{t(
188-
'consentRegistry.modals.approval.noMandatory',
189-
'No mandatory requirements for this consent.',
190-
)}
191-
</Typography>
192-
) : null}
193-
</Stack>
194-
</Box>
136+
<DialogContent sx={{ px: 3, mt: 3, pb: 3 }}>
137+
{loading ? (
138+
<Typography variant="body2" color="text.secondary">
139+
{t('consentRegistry.modals.approval.loading', 'Loading consent details...')}
140+
</Typography>
141+
) : (
142+
<Stack spacing={3} sx={{ mt: 0.5 }}>
143+
<Box>
144+
<Typography
145+
variant="caption"
146+
sx={{
147+
display: 'block',
148+
mb: 1.5,
149+
fontWeight: 700,
150+
color: 'primary.main',
151+
letterSpacing: 0.6,
152+
textTransform: 'uppercase',
153+
}}
154+
>
155+
{t('consentRegistry.modals.approval.mandatory', 'Mandatory Elements (Required)')}
156+
</Typography>
157+
<Stack spacing={1.25}>
158+
{mandatoryElements.map((element) => (
159+
<Stack
160+
key={toElementKey(element.purposeName, element.elementName)}
161+
direction="row"
162+
alignItems="center"
163+
justifyContent="space-between"
164+
sx={{
165+
p: 1.5,
166+
borderRadius: 1,
167+
border: 1,
168+
borderColor: 'divider',
169+
bgcolor: 'background.paper',
170+
gap: 1.5,
171+
}}
172+
>
173+
<Box>
174+
<Typography variant="body2" fontWeight={600}>
175+
{element.elementName}
176+
</Typography>
177+
<Typography variant="caption" color="text.secondary">
178+
{element.purposeName}
179+
</Typography>
180+
</Box>
181+
<Chip
182+
size="small"
183+
color="error"
184+
variant="outlined"
185+
label={t('consentRegistry.modals.approval.required', 'Required')}
186+
/>
187+
</Stack>
188+
))}
195189

196-
{optionalElements.length > 0 ? (
197-
<>
198-
<Divider />
190+
{mandatoryElements.length === 0 ? (
191+
<Typography variant="body2" color="text.secondary">
192+
{t(
193+
'consentRegistry.modals.approval.noMandatory',
194+
'No mandatory requirements for this consent.',
195+
)}
196+
</Typography>
197+
) : null}
198+
</Stack>
199+
</Box>
199200

200-
<Box>
201-
<Typography
202-
variant="caption"
203-
sx={{
204-
display: 'block',
205-
mb: 1.5,
206-
fontWeight: 700,
207-
color: 'primary.main',
208-
letterSpacing: 0.6,
209-
textTransform: 'uppercase',
210-
}}
211-
>
212-
{t('consentRegistry.modals.approval.optional', 'Optional Elements')}
213-
</Typography>
214-
<Stack spacing={1.25}>
215-
{optionalElements.map((element) => {
216-
const key = toElementKey(element.purposeName, element.elementName)
217-
const checked = selectedOptionalKeys.includes(key)
201+
{optionalElements.length > 0 ? (
202+
<>
203+
<Divider />
218204

219-
return (
220-
<Stack
221-
key={key}
222-
direction="row"
223-
alignItems="center"
224-
justifyContent="space-between"
225-
sx={{
226-
p: 1.5,
227-
borderRadius: 1,
228-
border: 1,
229-
borderColor: checked ? 'primary.main' : 'divider',
230-
bgcolor: 'background.paper',
231-
gap: 1.5,
232-
}}
233-
>
234-
<Box>
235-
<Typography variant="body2" fontWeight={600}>
236-
{element.elementName}
237-
</Typography>
238-
<Typography variant="caption" color="text.secondary">
239-
{element.purposeName}
240-
</Typography>
241-
</Box>
242-
<Switch
243-
checked={checked}
244-
onChange={() => {
245-
setSelectedOptionalKeys((previousKeys) => {
246-
if (previousKeys.includes(key)) {
247-
return previousKeys.filter((existingKey) => existingKey !== key)
248-
}
205+
<Box>
206+
<Typography
207+
variant="caption"
208+
sx={{
209+
display: 'block',
210+
mb: 1.5,
211+
fontWeight: 700,
212+
color: 'primary.main',
213+
letterSpacing: 0.6,
214+
textTransform: 'uppercase',
215+
}}
216+
>
217+
{t('consentRegistry.modals.approval.optional', 'Optional Elements')}
218+
</Typography>
219+
<Stack spacing={1.25}>
220+
{optionalElements.map((element) => {
221+
const key = toElementKey(element.purposeName, element.elementName)
222+
const checked = selectedOptionalKeys.includes(key)
249223

250-
return [...previousKeys, key]
251-
})
224+
return (
225+
<Stack
226+
key={key}
227+
direction="row"
228+
alignItems="center"
229+
justifyContent="space-between"
230+
sx={{
231+
p: 1.5,
232+
borderRadius: 1,
233+
border: 1,
234+
borderColor: checked ? 'primary.main' : 'divider',
235+
bgcolor: 'background.paper',
236+
gap: 1.5,
252237
}}
253-
slotProps={{
254-
input: {
255-
'aria-label': t(
256-
'consentRegistry.modals.approval.toggleWithDetails',
257-
'Toggle permission for {{elementName}} in {{purposeName}}',
258-
{
259-
elementName: element.elementName,
260-
purposeName: element.purposeName,
261-
},
262-
),
263-
},
264-
}}
265-
/>
266-
</Stack>
267-
)
268-
})}
269-
</Stack>
270-
</Box>
271-
</>
272-
) : null}
273-
</Stack>
238+
>
239+
<Box>
240+
<Typography variant="body2" fontWeight={600}>
241+
{element.elementName}
242+
</Typography>
243+
<Typography variant="caption" color="text.secondary">
244+
{element.purposeName}
245+
</Typography>
246+
</Box>
247+
<Switch
248+
checked={checked}
249+
onChange={() => {
250+
setSelectedOptionalKeys((previousKeys) => {
251+
if (previousKeys.includes(key)) {
252+
return previousKeys.filter((existingKey) => existingKey !== key)
253+
}
254+
255+
return [...previousKeys, key]
256+
})
257+
}}
258+
slotProps={{
259+
input: {
260+
'aria-label': t(
261+
'consentRegistry.modals.approval.toggleWithDetails',
262+
'Toggle permission for {{elementName}} in {{purposeName}}',
263+
{
264+
elementName: element.elementName,
265+
purposeName: element.purposeName,
266+
},
267+
),
268+
},
269+
}}
270+
/>
271+
</Stack>
272+
)
273+
})}
274+
</Stack>
275+
</Box>
276+
</>
277+
) : null}
278+
</Stack>
279+
)}
274280
</DialogContent>
275281

276282
<DialogActions

portal/frontend/src/features/consent-registry/hooks/useConsentQueries.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ function toConsentRow(consent: ConsentDetailAPI): ConsentRecord {
9191
status: normalizedStatus,
9292
purposes: consent.purposes.map((purpose) => purpose.name),
9393
updatedAt: new Date(toEpochMilliseconds(consent.updatedTime) ?? 0).toISOString(),
94-
expirationTime: consent.validityTime,
94+
expirationTime: consent.validityTime ?? 0,
9595
canRevoke: isConsentRevokableStatus(normalizedStatus),
9696
canApprove: isConsentApprovableStatus(normalizedStatus),
9797
}

portal/frontend/src/i18n/resources/en/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const commonEn = {
120120
required: 'Required',
121121
toggle: 'Toggle permission',
122122
toggleWithDetails: 'Toggle permission for {{elementName}} in {{purposeName}}',
123+
loading: 'Loading consent details...',
123124
noMandatory: 'No mandatory requirements for this consent.',
124125
confirm: 'Approve & Continue',
125126
},

0 commit comments

Comments
 (0)