From 80488dd5c0330a9169744d9fe2d7689437b78a93 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Sat, 28 Mar 2026 17:48:44 -0700 Subject: [PATCH] Add IDDataScreen and ManageDocumentsScreen (WV-14) (#1878) * feat(webview-app): add IDDataScreen and ManageDocumentsScreen (SELF-2422) Screen migration for WV-14. Adds 2 euclid screen wrappers with mock data for the UI mocking pass. - IDDataScreen at /id-data with ExposedIDCard, identification details, document data - ManageDocumentsScreen at /manage-documents with document list and manage dialogue - Wire Settings > Manage Documents to /manage-documents instead of /coming-soon - Add preview.html for phone-frame screen verification during development * update * rename --------- Co-authored-by: Tranquil-Flow --- packages/webview-app/src/App.tsx | 4 + .../src/components/DevRouteMenu.tsx | 4 +- .../src/screens/account/SettingsScreen.tsx | 2 +- .../src/screens/home/IDDataScreen.tsx | 83 ++++++++ .../screens/home/ManageDocumentsScreen.tsx | 77 +++++++ .../screens/home/homeSupportScreens.test.tsx | 199 ++++++++++++++++++ 6 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 packages/webview-app/src/screens/home/IDDataScreen.tsx create mode 100644 packages/webview-app/src/screens/home/ManageDocumentsScreen.tsx create mode 100644 packages/webview-app/tests/screens/home/homeSupportScreens.test.tsx diff --git a/packages/webview-app/src/App.tsx b/packages/webview-app/src/App.tsx index fd7d3445c..f231341ba 100644 --- a/packages/webview-app/src/App.tsx +++ b/packages/webview-app/src/App.tsx @@ -15,6 +15,8 @@ import { SettingsScreen } from './screens/account/SettingsScreen'; import { ComingSoonScreen } from './screens/ComingSoonScreen'; import { KeychainDebugScreen } from './screens/debug/KeychainDebugScreen'; import { HomeScreen } from './screens/home/HomeScreen'; +import { IDDataScreen } from './screens/home/IDDataScreen'; +import { ManageDocumentsScreen } from './screens/home/ManageDocumentsScreen'; import { ConfirmIdentificationScreen } from './screens/onboarding/ConfirmIdentificationScreen'; import { ConflictDetectedScreen } from './screens/onboarding/ConflictDetectedScreen'; import { CountryPickerScreen } from './screens/onboarding/CountryPickerScreen'; @@ -83,6 +85,8 @@ export const App: React.FC = () => ( } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/packages/webview-app/src/components/DevRouteMenu.tsx b/packages/webview-app/src/components/DevRouteMenu.tsx index c415fce2f..96d0f1e3a 100644 --- a/packages/webview-app/src/components/DevRouteMenu.tsx +++ b/packages/webview-app/src/components/DevRouteMenu.tsx @@ -8,6 +8,8 @@ import { useLocation, useNavigate } from 'react-router-dom'; const mockScreenLinks = [ { href: '/settings/dev-mode', label: 'Dev Mode' }, + { href: '/manage-documents', label: 'Manage Documents' }, + { href: '/id-data', label: 'ID Data' }, { href: '/proving/receipt', label: 'Proof Receipt' }, { href: '/proving/history', label: 'Proof History' }, { href: '/proving/dialogue', label: 'Simple Dialogue' }, @@ -67,7 +69,7 @@ export const DevRouteMenu: React.FC = () => { textTransform: 'uppercase', }} > - WV-13 Mock Screens + Mock Screens {mockScreenLinks.map(link => { const isActive = location.pathname === link.href; diff --git a/packages/webview-app/src/screens/account/SettingsScreen.tsx b/packages/webview-app/src/screens/account/SettingsScreen.tsx index 72c71d774..ab52f05de 100644 --- a/packages/webview-app/src/screens/account/SettingsScreen.tsx +++ b/packages/webview-app/src/screens/account/SettingsScreen.tsx @@ -53,7 +53,7 @@ export const SettingsScreen: React.FC = () => { icon: DocumentDetailsIcon, label: 'Manage Documents', description: 'Recovery phrase, passport data', - onPress: () => navigate('/coming-soon'), + onPress: () => navigate('/manage-documents'), }, { icon: LockIcon, diff --git a/packages/webview-app/src/screens/home/IDDataScreen.tsx b/packages/webview-app/src/screens/home/IDDataScreen.tsx new file mode 100644 index 000000000..b62557659 --- /dev/null +++ b/packages/webview-app/src/screens/home/IDDataScreen.tsx @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import type React from 'react'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { + IdCardIcon, + IDDataScreen as EuclidIDDataScreen, + LeftArrowIcon, + QuestionCircleStrokeIcon, +} from '@selfxyz/euclid'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; +import { WEB_SAFE_AREA } from '../../utils/insets'; + +const MOCK_ID_CARD_DETAILS = { + profileImage: '', + type: 'ID CARD', + code: 'SELF', + documentNumber: '••••••1234', + surname: 'DOE', + givenName: 'JOHN', + sex: 'M', + nationality: 'UNITED STATES', + dateOfBirth: '1990-01-15', + placeOfBirth: 'NEW YORK', + dateOfIssue: '2020-01-15', + dateOfExpiry: '2030-01-15', +}; + +const MOCK_DOCUMENT_DATA = [ + { label: 'ID Type', value: 'Passport' }, + { label: 'Document number', value: '18-299217823' }, + { label: 'Surname', value: 'Doe' }, + { label: 'Given name', value: 'John' }, + { label: 'Nationality', value: 'United States' }, + { label: 'Date of birth', value: '1990-01-15' }, +]; + +export const IDDataScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + + const onClose = useCallback(() => { + haptic.trigger('selection'); + navigate(-1); + }, [navigate, haptic]); + + const onManageID = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('id_data_manage_pressed'); + navigate('/manage-documents'); + }, [navigate, haptic, analytics]); + + return ( + + + + } + documentData={MOCK_DOCUMENT_DATA} + onClose={onClose} + onInfo={() => analytics.trackEvent('id_data_info_pressed')} + onManageID={onManageID} + closeIcon={({ size, color }) => } + infoIcon={({ size, color }) => } + /> + ); +}; diff --git a/packages/webview-app/src/screens/home/ManageDocumentsScreen.tsx b/packages/webview-app/src/screens/home/ManageDocumentsScreen.tsx new file mode 100644 index 000000000..75b5782d9 --- /dev/null +++ b/packages/webview-app/src/screens/home/ManageDocumentsScreen.tsx @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import type React from 'react'; +import { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { LeftArrowIcon, ManageDocumentsScreen as EuclidManageDocumentsScreen, PlusIcon } from '@selfxyz/euclid'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; +import { WEB_SAFE_AREA } from '../../utils/insets'; + +export const ManageDocumentsScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + const [dialogue, setDialogue] = useState<{ title: string; description: string } | undefined>(); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const onAddDocument = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('manage_docs_add_pressed'); + navigate('/onboarding/country'); + }, [navigate, haptic, analytics]); + + const onDocumentPress = useCallback(() => { + haptic.trigger('selection'); + setDialogue({ + title: 'Manage Document', + description: 'View details or remove this document from your Self ID.', + }); + }, [haptic]); + + const onViewIdDetails = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('manage_docs_view_details'); + setDialogue(undefined); + navigate('/id-data'); + }, [navigate, haptic, analytics]); + + const onRemoveId = useCallback(() => { + haptic.trigger('warning'); + analytics.trackEvent('manage_docs_remove_pressed'); + setDialogue(undefined); + }, [haptic, analytics]); + + const onDismissDialogue = useCallback(() => { + haptic.trigger('selection'); + setDialogue(undefined); + }, [haptic]); + + return ( + } + addIcon={({ size, color }) => } + documents={[ + { + id: 'mock-passport', + label: 'Passport', + description: 'Registered', + onPress: onDocumentPress, + }, + ]} + onBack={onBack} + onAddDocument={onAddDocument} + dialogue={dialogue} + onViewIdDetails={onViewIdDetails} + onRemoveId={onRemoveId} + onDismissDialogue={onDismissDialogue} + /> + ); +}; diff --git a/packages/webview-app/tests/screens/home/homeSupportScreens.test.tsx b/packages/webview-app/tests/screens/home/homeSupportScreens.test.tsx new file mode 100644 index 000000000..e0a49075c --- /dev/null +++ b/packages/webview-app/tests/screens/home/homeSupportScreens.test.tsx @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +// @vitest-environment jsdom + +import type React from 'react'; +import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { DevRouteMenu } from '../../../src/components/DevRouteMenu'; +import { SettingsScreen } from '../../../src/screens/account/SettingsScreen'; +import { HomeScreen } from '../../../src/screens/home/HomeScreen'; +import { IDDataScreen } from '../../../src/screens/home/IDDataScreen'; +import { ManageDocumentsScreen } from '../../../src/screens/home/ManageDocumentsScreen'; + +import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; + +const analytics = { trackEvent: vi.fn() }; +const haptic = { trigger: vi.fn() }; +const lifecycle = { dismiss: vi.fn() }; +const documents = { loadDocumentCatalog: vi.fn() }; + +vi.mock('../../../src/providers/SelfClientProvider', () => ({ + useSelfClient: () => ({ + analytics, + haptic, + lifecycle, + documents, + }), +})); + +vi.mock('@selfxyz/euclid', () => ({ + createSafeAreaProps: ({ top, bottom }: { top: number; bottom: number }) => ({ + insets: { top, bottom, left: 0, right: 0 }, + safeArea: { top, bottom, left: 0, right: 0 }, + }), + GearIcon: () => null, + LeftArrowIcon: () => null, + PlusIcon: () => null, + IdCardIcon: () => null, + QuestionCircleStrokeIcon: () => null, + DocumentDetailsIcon: () => null, + LockIcon: () => null, + NotificationIcon: () => null, + ChatStrokeIcon: () => null, + ShareIcon: () => null, + CodeIcon: () => null, + HomeScreen: ({ + idCard, + onAddIdPress, + topNavigationPrimaryButton, + }: { + idCard?: { title: string; subtitle: string }; + onAddIdPress: () => void; + topNavigationPrimaryButton: { onPress: () => void }; + }) => ( +
+ {idCard ?
{`${idCard.title} ${idCard.subtitle}`}
:
No document
} + + +
+ ), + SettingsViewScreen: ({ sections }: { sections: Array<{ items: Array<{ label: string; onPress: () => void }> }> }) => ( +
+ {sections.flatMap(section => + section.items.map(item => ( + + )), + )} +
+ ), + ManageDocumentsScreen: ({ + documents: docs, + onViewIdDetails, + onDismissDialogue, + dialogue, + }: { + documents: Array<{ id: string; label: string; onPress: () => void }>; + onViewIdDetails: () => void; + onDismissDialogue: () => void; + dialogue?: { title: string }; + }) => ( +
+ {docs.map(doc => ( + + ))} + {dialogue ? ( +
+
{dialogue.title}
+ + +
+ ) : null} +
+ ), + IDDataScreen: ({ onManageID, onClose }: { onManageID: () => void; onClose: () => void }) => ( +
+ + +
+ ), +})); + +const LocationDisplay: React.FC = () => { + const location = useLocation(); + return
{location.pathname}
; +}; + +const renderRoutes = (initialEntries: string[]) => + render( + + + } /> + } /> + } /> + } /> + } /> + + + + , + ); + +describe('WV-14 support screens', () => { + beforeEach(() => { + vi.clearAllMocks(); + documents.loadDocumentCatalog.mockResolvedValue({ + documents: [ + { + id: 'doc-1', + documentType: 'p', + documentCategory: 'passport', + data: '{}', + mock: true, + isRegistered: true, + }, + ], + }); + sessionStorage.clear(); + }); + + afterEach(() => { + cleanup(); + }); + + it('stitches home to settings, manage documents, and ID data', async () => { + renderRoutes(['/']); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /open settings/i })).toBeTruthy(); + }); + + fireEvent.click(screen.getByRole('button', { name: /open settings/i })); + expect(screen.getByTestId('location').textContent).toBe('/settings'); + + fireEvent.click(screen.getByRole('button', { name: /manage documents/i })); + expect(screen.getByTestId('location').textContent).toBe('/manage-documents'); + + fireEvent.click(screen.getByRole('button', { name: /passport/i })); + fireEvent.click(screen.getByRole('button', { name: /view details/i })); + expect(screen.getByTestId('location').textContent).toBe('/id-data'); + + fireEvent.click(screen.getByRole('button', { name: /manage id/i })); + expect(screen.getByTestId('location').textContent).toBe('/manage-documents'); + }); + + it('exposes manage documents and ID data in the dev route menu', async () => { + renderRoutes(['/']); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /open settings/i })).toBeTruthy(); + }); + + fireEvent.click(screen.getByRole('button', { name: /mock screens/i })); + + expect(screen.getByRole('button', { name: 'Manage Documents' })).toBeTruthy(); + expect(screen.getByRole('button', { name: 'ID Data' })).toBeTruthy(); + + fireEvent.click(screen.getByRole('button', { name: 'ID Data' })); + expect(screen.getByTestId('location').textContent).toBe('/id-data'); + }); +});