Skip to content

Commit

Permalink
feat(frontend): in-person browser isolation (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
gregory-j-baker authored Mar 4, 2025
1 parent d2b34c1 commit edcc560
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 252 deletions.
33 changes: 21 additions & 12 deletions frontend/app/hooks/use-tab-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,21 @@ function subscribe(sessionStorageKey: string, callback: () => void): () => void
}

type Options = {
/**
* The query parameter key used for storing the tab id in the URL.
* Defaults to `'tid'`.
*/
idSearchParamKey?: string;
/**
* Whether to automatically update the URL with the tab id as a query parameter.
* Defaults to `true`.
*/
navigate?: false;
/**
* The query parameter key used for storing the tab id in the URL.
* Defaults to `'tid'`.
* Whether to reload the document after navigating.
* Defaults to `false`.
*/
searchParamKey?: string;
reloadDocument?: boolean;
/**
* The session storage key used for persisting the tab id.
* Defaults to `'tab-id'`.
Expand All @@ -74,34 +79,38 @@ type Options = {
*/
export function useTabId(options?: Options): string | undefined {
const {
navigate = true, //
searchParamKey = SEARCH_PARAM_KEY,
idSearchParamKey = SEARCH_PARAM_KEY,
navigate = true,
reloadDocument = false,
sessionStorageKey = SESSION_STORAGE_KEY,
} = options ?? {};

const { search } = useLocation();
const navigateFn = useNavigate();

const searchParamId = new URLSearchParams(search).get(searchParamKey);
const idSearchParam = new URLSearchParams(search).get(idSearchParamKey);

// fetch the current tab id from session storage
// this will often be undefined on first access
const id = useSyncExternalStore(
(callback) => subscribe(sessionStorageKey, callback),
() => getSnapshot(sessionStorageKey),
() => searchParamId ?? undefined,
() => idSearchParam ?? undefined,
);

useEffect(() => {
if (navigate) {
if (id !== undefined && id !== searchParamId) {
// if the tab id in session storage doesn't match the URL, update the URL
if (id !== undefined && id !== idSearchParam) {
// if the tab id in session storage doesn't match the URL, update the URL and optionally reload
const urlSearchParams = new URLSearchParams(search);
urlSearchParams.set(searchParamKey, id);
void navigateFn({ search: urlSearchParams.toString() }, { replace: true });
urlSearchParams.set(idSearchParamKey, id);

void Promise.resolve(navigateFn({ search: urlSearchParams.toString() }, { replace: true })).then(() => {
if (reloadDocument) window.location.reload();
});
}
}
}, [id, navigate, search]);
}, [id, navigate, navigateFn, search]);

return id;
}
179 changes: 93 additions & 86 deletions frontend/app/routes/protected/person-case/@types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,101 @@ import 'express-session';

import type { ServerEnvironment } from '~/.server/environment';

export type BirthDetailsData =
| { country: ServerEnvironment['PP_CANADA_COUNTRY_CODE']; province: string; city: string; fromMultipleBirth: boolean }
| { country: string; province?: string; city?: string; fromMultipleBirth: boolean };

export type ContactInformationData = {
preferredLanguage: string;
primaryPhoneNumber: string;
secondaryPhoneNumber?: string;
emailAddress?: string;
country: string;
address: string;
postalCode: string;
city: string;
province: string;
};

export type CurrentNameData =
| { preferredSameAsDocumentName: true }
| {
preferredSameAsDocumentName: false;
firstName: string;
middleName?: string;
lastName: string;
supportingDocuments:
| { required: false } //
| { required: true; documentTypes: string[] };
};

export type ParentDetailsData = (
| { unavailable: true }
| {
unavailable: false;
givenName: string;
lastName: string;
birthLocation:
| { country: 'CAN'; province: string; city: string } //
| { country: string; province?: string; city?: string };
}
)[];

export type PersonalInfoData = {
firstNamePreviouslyUsed?: string[];
lastNameAtBirth: string;
lastNamePreviouslyUsed?: string[];
gender: string;
};

export type PreviousSinData = {
hasPreviousSin: string;
socialInsuranceNumber?: string;
};

export type PrimaryDocumentData = {
citizenshipDate: string;
clientNumber: string;
currentStatusInCanada: string;
dateOfBirth: string;
documentType: string;
gender: string;
givenName: string;
lastName: string;
registrationNumber: string;
};

export type PrivacyStatementData = {
agreedToTerms: true;
};

export type RequestDetailsData = {
type: string;
scenario: string;
};

export type SecondaryDocumentData = {
documentType: string;
// document: File; TODO :: enable me!
expiryDate: string;
};

export type InPersonSinApplication = Partial<{
currentNameInfo: CurrentNameData;
privacyStatement: PrivacyStatementData;
requestDetails: RequestDetailsData;
primaryDocuments: PrimaryDocumentData;
personalInformation: PersonalInfoData;
secondaryDocument: SecondaryDocumentData;
previousSin: PreviousSinData;
contactInformation: ContactInformationData;
birthDetails: BirthDetailsData;
parentDetails: ParentDetailsData;
}>;

declare module 'express-session' {
interface SessionData {
inPersonSINCase: Partial<{
currentNameInfo:
| { preferredSameAsDocumentName: true }
| {
preferredSameAsDocumentName: false;
firstName: string;
middleName?: string;
lastName: string;
supportingDocuments:
| { required: false } //
| { required: true; documentTypes: string[] };
};
privacyStatement: {
agreedToTerms: true;
};
requestDetails: {
type: string;
scenario: string;
};
primaryDocuments: {
citizenshipDate: string;
clientNumber: string;
currentStatusInCanada: string;
dateOfBirth: string;
documentType: string;
gender: string;
givenName: string;
lastName: string;
registrationNumber: string;
/*
TODO: Enable file upload
document: File;
*/
};
personalInformation: {
firstNamePreviouslyUsed?: string[];
lastNameAtBirth: string;
lastNamePreviouslyUsed?: string[];
gender: string;
};
secondaryDocument: {
documentType: string;
/*
TODO: Enable file upload
document: File;
*/
expiryDate: string;
};
previousSin: {
hasPreviousSin: string;
socialInsuranceNumber?: string;
};
contactInformation: {
preferredLanguage: string;
primaryPhoneNumber: string;
secondaryPhoneNumber?: string;
emailAddress?: string;
country: string;
address: string;
postalCode: string;
city: string;
province: string;
};
birthDetails:
| { country: ServerEnvironment['PP_CANADA_COUNTRY_CODE']; province: string; city: string; fromMultipleBirth: boolean }
| { country: string; province?: string; city?: string; fromMultipleBirth: boolean };
parentDetails: (
| { unavailable: true }
| {
unavailable: false;
givenName: string;
lastName: string;
birthLocation:
| {
country: 'CAN';
province: string;
city: string;
}
| {
country: string;
province?: string;
city?: string;
};
}
)[];
}>;
inPersonSinApplications: Record<string, InPersonSinApplication | undefined>;
}
}

Expand Down
Loading

0 comments on commit edcc560

Please sign in to comment.