@flamingo-stack/openframe-frontend-core 0.0.668-snapshot.20260925231935 → 0.0.668
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/help-center-pages/index.cjs +19 -127
- package/dist/components/help-center-pages/index.cjs.map +1 -1
- package/dist/components/help-center-pages/index.d.ts +1 -1
- package/dist/components/help-center-pages/index.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.js +20 -128
- package/dist/components/help-center-pages/index.js.map +1 -1
- package/dist/components/help-center-pages/trust-center-page.d.ts +0 -6
- package/dist/components/help-center-pages/trust-center-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/trust-center-sections.d.ts +2 -17
- package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/help-center-pages/__tests__/trust-center-page.test.tsx +1 -28
- package/src/components/help-center-pages/index.ts +1 -1
- package/src/components/help-center-pages/trust-center-page.tsx +16 -59
- package/src/components/help-center-pages/trust-center-sections.tsx +8 -159
|
@@ -12,17 +12,7 @@
|
|
|
12
12
|
* server answers as one flat, grouped, counted list.
|
|
13
13
|
*/
|
|
14
14
|
import { type ReactNode } from 'react';
|
|
15
|
-
import { type TrustCenterAiPractice, type TrustCenterControlsPage, type TrustCenterDocument, type TrustCenterFramework, type
|
|
16
|
-
/**
|
|
17
|
-
* The page body's layout, ONE copy for the loaded page and its skeleton, so the
|
|
18
|
-
* skeleton cannot drift from what replaces it: the status row, then the sections
|
|
19
|
-
* beside the section rail.
|
|
20
|
-
*/
|
|
21
|
-
export declare const TRUST_STATUS_ROW_CLASS = "flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between";
|
|
22
|
-
export declare const TRUST_BODY_GRID_CLASS = "grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]";
|
|
23
|
-
export declare const TRUST_SECTIONS_COLUMN_CLASS = "flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]";
|
|
24
|
-
/** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */
|
|
25
|
-
export declare const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>>;
|
|
15
|
+
import { type TrustCenterAiPractice, type TrustCenterControlsPage, type TrustCenterDocument, type TrustCenterFramework, type TrustCenterSubprocessor } from '../../types/trust-center';
|
|
26
16
|
/** One anchored page section: an `h2` + optional lead line + content. */
|
|
27
17
|
export declare function TrustSection({ id, title, lead, children, }: {
|
|
28
18
|
id: string;
|
|
@@ -94,11 +84,6 @@ export declare function SubprocessorsSection({ subprocessors }: {
|
|
|
94
84
|
export declare function ContactSection({ onContact }: {
|
|
95
85
|
onContact: () => void;
|
|
96
86
|
}): import("react").JSX.Element;
|
|
97
|
-
/**
|
|
98
|
-
* The page BODY while the trust center loads: built on the loaded body's own
|
|
99
|
-
* layout classes and the real section headings and panels, so nothing moves when
|
|
100
|
-
* the content replaces it. The status row, then Compliance, Controls and
|
|
101
|
-
* Documents (what fills the first screens), beside the section rail.
|
|
102
|
-
*/
|
|
87
|
+
/** Mirrors the page: a card row, then the controls grid. */
|
|
103
88
|
export declare function TrustCenterSkeleton(): import("react").JSX.Element;
|
|
104
89
|
//# sourceMappingURL=trust-center-sections.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trust-center-sections.d.ts","sourceRoot":"","sources":["../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhE,OAAO,
|
|
1
|
+
{"version":3,"file":"trust-center-sections.d.ts","sourceRoot":"","sources":["../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhE,OAAO,EAML,KAAK,qBAAqB,EAE1B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAE7B,MAAM,0BAA0B,CAAC;AA4ClC,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAC3B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAkBA;AAMD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,qBAAqB,EAAE,CAAA;CAAE,+BAkB9E;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,oBAAoB,EAAE,CAAA;CAAE,+BA0BvF;AAkED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,+BAkFtG;AAMD,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,WAAW,EACX,SAAS,GACV,EAAE;IACD,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,kGAAkG;IAClG,WAAW,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;IAC9D,oEAAoE;IACpE,SAAS,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACnD,+BA0CA;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAgCA;AA8BD,wBAAgB,oBAAoB,CAAC,EAAE,aAAa,EAAE,EAAE;IAAE,aAAa,EAAE,uBAAuB,EAAE,CAAA;CAAE,+BA4BnG;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,IAAI,CAAA;CAAE,+BA8BtE;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,gCAOlC"}
|
package/package.json
CHANGED
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
type TrustCenterPublic,
|
|
15
15
|
} from '../../../types/trust-center';
|
|
16
16
|
import { TRUST_CENTER_FIXTURE_WINDOW_MS, makeTrustCenterData } from '../__fixtures__/trust-center';
|
|
17
|
-
import { TrustCenterPage
|
|
18
|
-
import { TRUST_SECTION_LEADS } from '../trust-center-sections';
|
|
17
|
+
import { TrustCenterPage } from '../trust-center-page';
|
|
19
18
|
|
|
20
19
|
// The real ContactForm needs the endpoints + chat runtimes; the page only
|
|
21
20
|
// decides WHAT it is handed, so a stub that echoes its props is the honest
|
|
@@ -115,32 +114,6 @@ describe('TrustCenterPage', () => {
|
|
|
115
114
|
expect(String(fetchMock.mock.calls[0][0])).toBe('/content/api/trust-center');
|
|
116
115
|
});
|
|
117
116
|
|
|
118
|
-
it('while it loads, the skeleton shows the real section headings and leads the content will fill', async () => {
|
|
119
|
-
let answer: (response: Response) => void = () => {};
|
|
120
|
-
fetchMock.mockReturnValueOnce(new Promise<Response>(resolve => (answer = resolve)));
|
|
121
|
-
render(<TrustCenterPage endpoint="/content/api/trust-center" />);
|
|
122
|
-
const loading = await screen.findByLabelText('Loading the trust center');
|
|
123
|
-
for (const [label, lead] of [
|
|
124
|
-
['Compliance', TRUST_SECTION_LEADS.compliance],
|
|
125
|
-
['Controls', TRUST_SECTION_LEADS.controls],
|
|
126
|
-
['Documents', TRUST_SECTION_LEADS.documents],
|
|
127
|
-
] as const) {
|
|
128
|
-
expect(within(loading).getByRole('heading', { level: 2, name: label, hidden: true })).toBeInTheDocument();
|
|
129
|
-
expect(within(loading).getByText(lead as string)).toBeInTheDocument();
|
|
130
|
-
}
|
|
131
|
-
act(() => answer(jsonResponse(makeData())));
|
|
132
|
-
expect(await screen.findByText('SOC 2 Type II')).toBeInTheDocument();
|
|
133
|
-
expect(screen.queryByLabelText('Loading the trust center')).toBeNull();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('TrustCenterPageSkeleton wears the page chrome it is given: no title and subtitle means no header at all', () => {
|
|
137
|
-
const { rerender } = render(<TrustCenterPageSkeleton shell={false} title="" subtitle="" />);
|
|
138
|
-
expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
|
|
139
|
-
expect(screen.getByLabelText('Loading the trust center')).toBeInTheDocument();
|
|
140
|
-
rerender(<TrustCenterPageSkeleton shell={false} />);
|
|
141
|
-
expect(screen.getByRole('heading', { level: 1, name: TRUST_CENTER_TITLE })).toBeInTheDocument();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
117
|
it('is ONE page: every section is an anchored h2, in reading order — AI & data use after Subprocessors, Contact before FAQ', () => {
|
|
145
118
|
render(<TrustCenterPage initialData={makeData()} />);
|
|
146
119
|
const headings = screen.getAllByRole('heading', { level: 2 }).map(h => h.textContent);
|
|
@@ -21,7 +21,7 @@ export { RoadmapPage, type RoadmapPageProps } from './roadmap-page';
|
|
|
21
21
|
export { ProductReleasesListPage, type ProductReleasesListPageProps } from './product-releases-list-page';
|
|
22
22
|
export { DeliveryPage, type DeliveryPageProps } from './delivery-page';
|
|
23
23
|
export { OnboardingGuidesCatalogPage, type OnboardingGuidesCatalogPageProps } from './onboarding-guides-catalog-page';
|
|
24
|
-
export { TrustCenterPage,
|
|
24
|
+
export { TrustCenterPage, type TrustCenterPageProps } from './trust-center-page';
|
|
25
25
|
|
|
26
26
|
// Existing full-pages re-exported for a single Help Center import site.
|
|
27
27
|
export { FaqDocumentPage, type FaqDocumentPageProps } from '../faq';
|
|
@@ -61,10 +61,6 @@ import {
|
|
|
61
61
|
DocumentRequestModal,
|
|
62
62
|
DocumentsSection,
|
|
63
63
|
SubprocessorsSection,
|
|
64
|
-
TRUST_BODY_GRID_CLASS,
|
|
65
|
-
TRUST_SECTIONS_COLUMN_CLASS,
|
|
66
|
-
TRUST_SECTION_LEADS,
|
|
67
|
-
TRUST_STATUS_ROW_CLASS,
|
|
68
64
|
TrustCenterSkeleton,
|
|
69
65
|
TrustSection,
|
|
70
66
|
} from './trust-center-sections';
|
|
@@ -150,6 +146,7 @@ export function TrustCenterPage({
|
|
|
150
146
|
title = TRUST_CENTER_TITLE,
|
|
151
147
|
subtitle = TRUST_CENTER_TAGLINE,
|
|
152
148
|
}: TrustCenterPageProps) {
|
|
149
|
+
const router = useRouter();
|
|
153
150
|
const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {
|
|
154
151
|
initialData,
|
|
155
152
|
revalidateOnVisibleAfterMs: TRUST_CENTER_CACHE_SECONDS * 1000,
|
|
@@ -194,6 +191,11 @@ export function TrustCenterPage({
|
|
|
194
191
|
const openRequest = useCallback((documentTitle: string | null) => setRequest({ open: true, documentTitle }), []);
|
|
195
192
|
const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []);
|
|
196
193
|
|
|
194
|
+
const backCfg =
|
|
195
|
+
backButton === false
|
|
196
|
+
? undefined
|
|
197
|
+
: { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
|
|
198
|
+
|
|
197
199
|
const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false;
|
|
198
200
|
const actions = hasGatedDocuments
|
|
199
201
|
? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }]
|
|
@@ -203,19 +205,19 @@ export function TrustCenterPage({
|
|
|
203
205
|
// sections show comes from `visibleSections` — this map only says what each renders.
|
|
204
206
|
const views: Record<TrustCenterSectionId, TrustSectionView> = {
|
|
205
207
|
ai: {
|
|
206
|
-
lead:
|
|
208
|
+
lead: 'How customer data is handled by the AI in our products.',
|
|
207
209
|
render: d => <AiSection practices={d.aiPractices} />,
|
|
208
210
|
},
|
|
209
211
|
compliance: {
|
|
210
|
-
lead:
|
|
212
|
+
lead: 'Frameworks we are audited against, and what is next.',
|
|
211
213
|
render: d => <ComplianceSection frameworks={d.frameworks} />,
|
|
212
214
|
},
|
|
213
215
|
controls: {
|
|
214
|
-
lead:
|
|
216
|
+
lead: 'Security controls currently passing in continuous monitoring (via Vanta).',
|
|
215
217
|
render: d => <ControlsSection endpoint={endpoint} seed={controlsSeed(d)} />,
|
|
216
218
|
},
|
|
217
219
|
documents: {
|
|
218
|
-
lead:
|
|
220
|
+
lead: 'Public documents open directly. Gated documents are shared under NDA after one short request.',
|
|
219
221
|
render: d => (
|
|
220
222
|
<DocumentsSection
|
|
221
223
|
documents={d.documents}
|
|
@@ -225,7 +227,7 @@ export function TrustCenterPage({
|
|
|
225
227
|
),
|
|
226
228
|
},
|
|
227
229
|
subprocessors: {
|
|
228
|
-
lead:
|
|
230
|
+
lead: 'Third parties that process customer data on our behalf.',
|
|
229
231
|
render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,
|
|
230
232
|
},
|
|
231
233
|
faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },
|
|
@@ -241,7 +243,7 @@ export function TrustCenterPage({
|
|
|
241
243
|
const status = monitoringStatus(data, hydrated, nowMs);
|
|
242
244
|
body = (
|
|
243
245
|
<>
|
|
244
|
-
<div className=
|
|
246
|
+
<div className="flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between">
|
|
245
247
|
<StatusIndicator status={status.status} label={status.label} />
|
|
246
248
|
{data.connected ? (
|
|
247
249
|
<DataAttribution
|
|
@@ -261,8 +263,8 @@ export function TrustCenterPage({
|
|
|
261
263
|
) : null}
|
|
262
264
|
</div>
|
|
263
265
|
|
|
264
|
-
<div className=
|
|
265
|
-
<div className=
|
|
266
|
+
<div className="grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]">
|
|
267
|
+
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]">
|
|
266
268
|
{sections.map(section => {
|
|
267
269
|
const view = views[section.id];
|
|
268
270
|
return (
|
|
@@ -294,34 +296,6 @@ export function TrustCenterPage({
|
|
|
294
296
|
);
|
|
295
297
|
}
|
|
296
298
|
|
|
297
|
-
return (
|
|
298
|
-
<TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton} actions={actions}>
|
|
299
|
-
{body}
|
|
300
|
-
</TrustCenterChrome>
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* The page's chrome, ONE copy for the page and its skeleton: the standalone
|
|
306
|
-
* `PageShell` or the host's padded box (`shell`), and the frozen `PageLayout`
|
|
307
|
-
* header (title, subtitle, back button, actions).
|
|
308
|
-
*/
|
|
309
|
-
function TrustCenterChrome({
|
|
310
|
-
shell,
|
|
311
|
-
title,
|
|
312
|
-
subtitle,
|
|
313
|
-
backButton,
|
|
314
|
-
actions,
|
|
315
|
-
children,
|
|
316
|
-
}: Required<Pick<TrustCenterPageProps, 'shell' | 'title' | 'subtitle' | 'backButton'>> & {
|
|
317
|
-
actions?: Array<{ label: string; variant: 'accent'; onClick: () => void }>;
|
|
318
|
-
children: ReactNode;
|
|
319
|
-
}) {
|
|
320
|
-
const router = useRouter();
|
|
321
|
-
const backCfg =
|
|
322
|
-
backButton === false
|
|
323
|
-
? undefined
|
|
324
|
-
: { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
|
|
325
299
|
const inner = (
|
|
326
300
|
<PageLayout
|
|
327
301
|
title={title}
|
|
@@ -332,26 +306,9 @@ function TrustCenterChrome({
|
|
|
332
306
|
actions={actions}
|
|
333
307
|
actionsVariant="primary-buttons"
|
|
334
308
|
>
|
|
335
|
-
{
|
|
309
|
+
{body}
|
|
336
310
|
</PageLayout>
|
|
337
311
|
);
|
|
338
|
-
return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
|
|
339
|
-
}
|
|
340
312
|
|
|
341
|
-
|
|
342
|
-
* The whole page while a host loads the data itself (the hub's admin preview):
|
|
343
|
-
* the SAME chrome and the same body skeleton `TrustCenterPage` shows while it
|
|
344
|
-
* self-fetches. Pass the chrome props the page will get.
|
|
345
|
-
*/
|
|
346
|
-
export function TrustCenterPageSkeleton({
|
|
347
|
-
shell = true,
|
|
348
|
-
backButton = false,
|
|
349
|
-
title = TRUST_CENTER_TITLE,
|
|
350
|
-
subtitle = TRUST_CENTER_TAGLINE,
|
|
351
|
-
}: Pick<TrustCenterPageProps, 'shell' | 'backButton' | 'title' | 'subtitle'>) {
|
|
352
|
-
return (
|
|
353
|
-
<TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton}>
|
|
354
|
-
<TrustCenterSkeleton />
|
|
355
|
-
</TrustCenterChrome>
|
|
356
|
-
);
|
|
313
|
+
return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
|
|
357
314
|
}
|
|
@@ -19,7 +19,6 @@ import { useDebounce } from '../../hooks/ui/use-debounce';
|
|
|
19
19
|
import { useSelfFetch } from '../../hooks/use-self-fetch';
|
|
20
20
|
import {
|
|
21
21
|
TRUST_CENTER_SEARCH_DEBOUNCE_MS,
|
|
22
|
-
TRUST_CENTER_SECTIONS,
|
|
23
22
|
normalizeTrustControlQuery,
|
|
24
23
|
trustCenterControlsUrl,
|
|
25
24
|
trustFrameworkBadge,
|
|
@@ -29,7 +28,6 @@ import {
|
|
|
29
28
|
type TrustCenterControlsPage,
|
|
30
29
|
type TrustCenterDocument,
|
|
31
30
|
type TrustCenterFramework,
|
|
32
|
-
type TrustCenterSectionId,
|
|
33
31
|
type TrustCenterSubprocessor,
|
|
34
32
|
type TrustFrameworkMonitoringEntry,
|
|
35
33
|
} from '../../types/trust-center';
|
|
@@ -45,7 +43,8 @@ import { ShieldCheckIcon } from '../icons-v2-generated/security/shield-check-ico
|
|
|
45
43
|
import { CheckIcon } from '../icons-v2-generated/signs-and-symbols/check-icon';
|
|
46
44
|
import { SECTION_HEADING_CLASS } from '../layout/page-heading';
|
|
47
45
|
import { ListEmptyState } from '../list-empty-state';
|
|
48
|
-
import {
|
|
46
|
+
import { CardSkeletonGrid } from '../loading/card-skeleton';
|
|
47
|
+
import { TextSkeleton } from '../loading/unified-skeleton';
|
|
49
48
|
import { Button } from '../ui/button/button';
|
|
50
49
|
import { ComplianceLogo } from '../ui/compliance-badge';
|
|
51
50
|
import { EntityImage } from '../ui/entity-image';
|
|
@@ -63,27 +62,8 @@ const FRAMEWORK_ICONS: Record<TrustFrameworkMonitoringEntry['icon'], typeof Shie
|
|
|
63
62
|
'file-shield': FileShieldIcon,
|
|
64
63
|
};
|
|
65
64
|
|
|
65
|
+
const STACK_CLASSES = 'flex flex-col gap-[var(--spacing-system-l)]';
|
|
66
66
|
const BODY_TEXT = 'text-h6 text-ods-text-secondary';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* The page body's layout, ONE copy for the loaded page and its skeleton, so the
|
|
70
|
-
* skeleton cannot drift from what replaces it: the status row, then the sections
|
|
71
|
-
* beside the section rail.
|
|
72
|
-
*/
|
|
73
|
-
export const TRUST_STATUS_ROW_CLASS =
|
|
74
|
-
'flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between';
|
|
75
|
-
export const TRUST_BODY_GRID_CLASS =
|
|
76
|
-
'grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]';
|
|
77
|
-
export const TRUST_SECTIONS_COLUMN_CLASS = 'flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]';
|
|
78
|
-
|
|
79
|
-
/** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */
|
|
80
|
-
export const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>> = {
|
|
81
|
-
compliance: 'Frameworks we are audited against, and what is next.',
|
|
82
|
-
controls: 'Security controls currently passing in continuous monitoring (via Vanta).',
|
|
83
|
-
documents: 'Public documents open directly. Gated documents are shared under NDA after one short request.',
|
|
84
|
-
subprocessors: 'Third parties that process customer data on our behalf.',
|
|
85
|
-
ai: 'How customer data is handled by the AI in our products.',
|
|
86
|
-
};
|
|
87
67
|
/**
|
|
88
68
|
* The right-hand action / status column: ONE width from tablet up, so every
|
|
89
69
|
* panel's rows line up; on phones it hugs its content so the title keeps the room.
|
|
@@ -560,143 +540,12 @@ export function ContactSection({ onContact }: { onContact: () => void }) {
|
|
|
560
540
|
);
|
|
561
541
|
}
|
|
562
542
|
|
|
563
|
-
|
|
564
|
-
// Skeleton
|
|
565
|
-
// ---------------------------------------------------------------------------
|
|
566
|
-
|
|
567
|
-
/** A section's heading and lead, exactly as `TrustSection` renders them (real text: it does not load). */
|
|
568
|
-
function SkeletonSection({ id, children }: { id: TrustCenterSectionId; children: ReactNode }) {
|
|
569
|
-
const label = TRUST_CENTER_SECTIONS.find(section => section.id === id)?.label ?? id;
|
|
570
|
-
const lead = TRUST_SECTION_LEADS[id];
|
|
571
|
-
return (
|
|
572
|
-
<section aria-hidden="true" className="flex flex-col gap-[var(--spacing-system-m)]">
|
|
573
|
-
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
574
|
-
<h2 className={SECTION_HEADING_CLASS}>{label}</h2>
|
|
575
|
-
{lead ? <p className={BODY_TEXT}>{lead}</p> : null}
|
|
576
|
-
</div>
|
|
577
|
-
{children}
|
|
578
|
-
</section>
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Rows of the REAL `StackedRowsPanel` (its own row heights and borders) whose
|
|
584
|
-
* cells hold bars: a mark (a 40px compliance logo or a 24px glyph), a value over
|
|
585
|
-
* a label, and the right-hand badge or button in the page's `ACTION_COLUMN`.
|
|
586
|
-
*/
|
|
587
|
-
function skeletonRows(count: number, shape: { mark: 'logo' | 'glyph'; action: 'badge' | 'button' }): PanelRow[] {
|
|
588
|
-
return Array.from({ length: count }, (_, index) => ({
|
|
589
|
-
id: `skeleton-${index}`,
|
|
590
|
-
columns: [
|
|
591
|
-
{
|
|
592
|
-
key: 'item',
|
|
593
|
-
content: (
|
|
594
|
-
<div className="flex w-full items-center gap-[var(--spacing-system-s)]">
|
|
595
|
-
<UnifiedSkeleton
|
|
596
|
-
className={shape.mark === 'logo' ? 'size-10 shrink-0 rounded-full' : 'size-6 shrink-0 rounded'}
|
|
597
|
-
/>
|
|
598
|
-
<div className="flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]">
|
|
599
|
-
<UnifiedSkeleton variant="text" className="h-5 w-1/3 md:h-6" />
|
|
600
|
-
<TextSkeleton.Body className="w-1/2" />
|
|
601
|
-
</div>
|
|
602
|
-
</div>
|
|
603
|
-
),
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
key: 'action',
|
|
607
|
-
width: ACTION_COLUMN,
|
|
608
|
-
align: 'right',
|
|
609
|
-
content:
|
|
610
|
-
shape.action === 'badge' ? (
|
|
611
|
-
<UnifiedSkeleton className="h-4 w-28 rounded" />
|
|
612
|
-
) : (
|
|
613
|
-
<UnifiedSkeleton className="h-6 w-16 rounded md:h-8" />
|
|
614
|
-
),
|
|
615
|
-
},
|
|
616
|
-
],
|
|
617
|
-
}));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/** The Controls section while loading: its tab strip, its summary and search, and the list box's own skeleton. */
|
|
621
|
-
function ControlsSectionSkeleton() {
|
|
622
|
-
return (
|
|
623
|
-
<div className="flex flex-col gap-[var(--spacing-system-m)]">
|
|
624
|
-
<div className="flex items-center gap-[var(--spacing-system-xxs)] overflow-hidden">
|
|
625
|
-
{[0, 1, 2, 3].map(index => (
|
|
626
|
-
<div key={index} className="shrink-0 p-[var(--spacing-system-m)]">
|
|
627
|
-
<UnifiedSkeleton variant="text" className="h-5 w-36 md:h-6" />
|
|
628
|
-
</div>
|
|
629
|
-
))}
|
|
630
|
-
</div>
|
|
631
|
-
<div className="flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between">
|
|
632
|
-
<TextSkeleton.Body className="w-48" />
|
|
633
|
-
<UnifiedSkeleton className="h-11 w-full rounded-[6px] md:h-12 md:w-80" />
|
|
634
|
-
</div>
|
|
635
|
-
<div className={CONTROLS_LIST_HEIGHT}>
|
|
636
|
-
<ControlsListSkeleton />
|
|
637
|
-
</div>
|
|
638
|
-
</div>
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/** The section rail while loading: `StickySectionNav`'s line and its 40px items. */
|
|
643
|
-
function SectionRailSkeleton() {
|
|
644
|
-
return (
|
|
645
|
-
<div className="sticky" style={{ top: STICKY_HEADER_OFFSET_PX }}>
|
|
646
|
-
<div className="relative">
|
|
647
|
-
<div
|
|
648
|
-
className="absolute -left-0.5 top-0 w-px bg-ods-border"
|
|
649
|
-
style={{ height: TRUST_CENTER_SECTIONS.length * 40 }}
|
|
650
|
-
/>
|
|
651
|
-
{TRUST_CENTER_SECTIONS.map(section => (
|
|
652
|
-
<div key={section.id} className="flex h-10 items-center px-3 py-2">
|
|
653
|
-
<TextSkeleton.Body className="w-24" />
|
|
654
|
-
</div>
|
|
655
|
-
))}
|
|
656
|
-
</div>
|
|
657
|
-
</div>
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* The page BODY while the trust center loads: built on the loaded body's own
|
|
663
|
-
* layout classes and the real section headings and panels, so nothing moves when
|
|
664
|
-
* the content replaces it. The status row, then Compliance, Controls and
|
|
665
|
-
* Documents (what fills the first screens), beside the section rail.
|
|
666
|
-
*/
|
|
543
|
+
/** Mirrors the page: a card row, then the controls grid. */
|
|
667
544
|
export function TrustCenterSkeleton() {
|
|
668
545
|
return (
|
|
669
|
-
|
|
670
|
-
<
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
<TextSkeleton.Body className="w-44" />
|
|
674
|
-
</div>
|
|
675
|
-
{/* `DataAttribution`'s own layout: two lines below `sm`, one row above it. */}
|
|
676
|
-
<div className="flex shrink-0 flex-col gap-[var(--spacing-system-xxs)] sm:flex-row sm:items-center sm:gap-[var(--spacing-system-sf)]">
|
|
677
|
-
<div className="flex items-center gap-[var(--spacing-system-xsf)]">
|
|
678
|
-
<UnifiedSkeleton className="size-4 rounded" />
|
|
679
|
-
<TextSkeleton.Body className="w-36" />
|
|
680
|
-
</div>
|
|
681
|
-
<TextSkeleton.Body className="w-44" />
|
|
682
|
-
</div>
|
|
683
|
-
</div>
|
|
684
|
-
<div className={TRUST_BODY_GRID_CLASS} aria-busy="true" aria-label="Loading the trust center">
|
|
685
|
-
<div className={TRUST_SECTIONS_COLUMN_CLASS}>
|
|
686
|
-
<SkeletonSection id="compliance">
|
|
687
|
-
<StackedRowsPanel rows={skeletonRows(3, { mark: 'logo', action: 'badge' })} />
|
|
688
|
-
</SkeletonSection>
|
|
689
|
-
<SkeletonSection id="controls">
|
|
690
|
-
<ControlsSectionSkeleton />
|
|
691
|
-
</SkeletonSection>
|
|
692
|
-
<SkeletonSection id="documents">
|
|
693
|
-
<StackedRowsPanel rows={skeletonRows(2, { mark: 'glyph', action: 'button' })} />
|
|
694
|
-
</SkeletonSection>
|
|
695
|
-
</div>
|
|
696
|
-
<aside className="hidden lg:block" aria-hidden="true">
|
|
697
|
-
<SectionRailSkeleton />
|
|
698
|
-
</aside>
|
|
699
|
-
</div>
|
|
700
|
-
</>
|
|
546
|
+
<div className={STACK_CLASSES}>
|
|
547
|
+
<CardSkeletonGrid count={2} variant="category" />
|
|
548
|
+
<CardSkeletonGrid count={4} variant="category" />
|
|
549
|
+
</div>
|
|
701
550
|
);
|
|
702
551
|
}
|