From 0d1f80ea57bac09e235798d899500366896c366f Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Mon, 30 Sep 2024 14:32:58 -0700 Subject: [PATCH 01/13] refactor: define wizard messages with other translations --- .../name-generator/NameGenerator.tsx | 80 ++++++-------- components/interview/ui/HelpButton.tsx | 10 +- components/interview/ui/Navigation.tsx | 64 +++++------ components/onboard-wizard/WizardStep.tsx | 13 +-- .../onboard-wizard/withOnboardingWizard.tsx | 4 +- lib/db/sample-data/dev-protocol.ts | 14 +++ lib/localisation/messages/en.json | 46 ++++++++ lib/onboarding-wizard/store.ts | 10 +- lib/routes/nextjs-routes.d.ts | 103 ++++++++---------- schemas/protocol/protocol.ts | 15 +++ 10 files changed, 195 insertions(+), 164 deletions(-) diff --git a/components/interview/interfaces/name-generator/NameGenerator.tsx b/components/interview/interfaces/name-generator/NameGenerator.tsx index 0878c1db..f83b1ea8 100644 --- a/components/interview/interfaces/name-generator/NameGenerator.tsx +++ b/components/interview/interfaces/name-generator/NameGenerator.tsx @@ -10,6 +10,7 @@ import { cn } from '~/lib/utils'; import { interfaceWrapperClasses } from '../../ui/SimpleShell'; import { withOnboardingWizard } from '~/components/onboard-wizard/withOnboardingWizard'; import { type InterviewStage } from '../../ui/InterviewShell'; +import { useTranslations } from 'next-intl'; const demoPrompts = [ { @@ -90,36 +91,33 @@ function NameGenerator(_props: InterviewStage) { ); } -export default withOnboardingWizard(NameGenerator, { - id: 'name-generator', - name: { - en: 'Name Generator Help', - }, - priority: 'Task', - description: { - en: [ +export default withOnboardingWizard(NameGenerator, () => { + const t = useTranslations('Interview.Wizards.NameGenerator'); + + return { + id: 'name-generator', + name: t('Name'), + priority: 'Task', + description: [ { type: 'paragraph', children: [ { - text: 'Help with the current task, including how to add new people, how to delete people, and how to edit people.', + text: t('Description'), }, ], }, ], - }, - steps: [ - { - title: { - en: 'Welcome to the Name Generator', - }, - content: { - en: [ + steps: [ + { + title: t('Steps.Welcome.Title'), + + content: [ { type: 'paragraph', children: [ { - text: 'This is the name generator interface. This interface allows you to nominate people. First, read the prompt and think about the people who meet the criteria.', + text: t('Steps.Welcome.Text'), }, ], }, @@ -133,60 +131,48 @@ export default withOnboardingWizard(NameGenerator, { }, ], }, - }, - { - targetElementId: 'data-wizard-prompts', - title: { - en: 'Prompts', - }, - content: { - en: [ + { + targetElementId: 'data-wizard-prompts', + title: t('Steps.Prompts.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'These are the prompts. They help you think about the people you want to nominate.', + text: t('Steps.Prompts.Text'), }, ], }, ], }, - }, - { - targetElementId: 'data-wizard-task-step-2', - title: { - en: 'Side Panels', - }, - content: { - en: [ + { + targetElementId: 'data-wizard-task-step-2', + title: t('Steps.SidePanels.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'These are side panels. They show the people you have already mentioned. You can drag and drop a person into the main area to nominate them.', + text: t('Steps.SidePanels.Text'), }, ], }, ], }, - }, - { - targetElementId: 'data-wizard-task-step-3', - title: { - en: 'Adding a person', - }, - content: { - en: [ + { + targetElementId: 'data-wizard-task-step-3', + title: t('Steps.AddPerson.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'Click this button to add a new person', + text: t('Steps.AddPerson.Text'), }, ], }, ], }, - }, - ], + ], + }; }); diff --git a/components/interview/ui/HelpButton.tsx b/components/interview/ui/HelpButton.tsx index 9ead0988..f36ce349 100644 --- a/components/interview/ui/HelpButton.tsx +++ b/components/interview/ui/HelpButton.tsx @@ -1,10 +1,9 @@ import { HelpCircle } from 'lucide-react'; import { NavButtonWithTooltip } from './NavigationButton'; -import { useLocale, useTranslations } from 'next-intl'; +import { useTranslations } from 'next-intl'; import { useWizardController } from '~/components/onboard-wizard/useWizardController'; import { env } from '~/env'; import { WIZARD_LOCAL_STORAGE_KEY } from '~/lib/onboarding-wizard/Provider'; -import { getLocalisedValue } from '~/lib/localisation/utils'; import { renderLocalisedValue } from '~/components/RenderRichText'; import { useDialog } from '~/lib/dialogs/DialogProvider'; import { Button } from '~/components/ui/Button'; @@ -17,7 +16,6 @@ import Form from '~/components/ui/form/Form'; export default function HelpButton({ id }: { id?: string }) { const { openDialog } = useDialog(); const { wizards, setActiveWizard } = useWizardController(); - const locale = useLocale(); const t = useTranslations('Interview.Navigation'); const t2 = useTranslations('Components.ContextualHelp'); @@ -39,12 +37,10 @@ export default function HelpButton({ id }: { id?: string }) { .map((wizard) => ( resolve(wizard.id)} > - {renderLocalisedValue( - getLocalisedValue(wizard.description, locale), - )} + {renderLocalisedValue(wizard.description)} ))} { + const t = useTranslations('Interview.Wizards.General'); + + return { + id: 'general-interview-information', + name: t('Name'), + priority: 'Navigation', + description: [ { type: 'paragraph', children: [ { - text: 'Help with the general interview process, including how to navigate the interview, and general tips to help you get started.', + text: t('Description'), }, ], }, ], - }, - steps: [ - { - title: { - en: 'Welcome to the Interview!', - }, - content: { - en: [ + steps: [ + { + title: t('Steps.Welcome.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'Before you begin, here are some general tips to help you navigate the interview process.', + text: t('Steps.Welcome.Text'), }, ], }, ], }, - }, - { - targetElementId: 'navigation-bar', - title: { - en: 'Navigation Bar', - }, - content: { - en: [ + { + targetElementId: 'navigation-bar', + title: t('Steps.NavigationBar.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'The navigation bar helps you move through the interview process, and get help if you need it.', + text: t('Steps.NavigationBar.Text'), }, ], }, @@ -161,24 +153,20 @@ export default withOnboardingWizard(Navigation, { }, ], }, - }, - { - targetElementId: 'interview-movement', - title: { - en: 'Navigating the Interview', - }, - content: { - en: [ + { + targetElementId: 'interview-movement', + title: t('Steps.InterviewMovement.Title'), + content: [ { type: 'paragraph', children: [ { - text: 'Use the back and forward buttons to move through the interview, and track your progress using the progress bar.', + text: t('Steps.InterviewMovement.Text'), }, ], }, ], }, - }, - ], + ], + }; }); diff --git a/components/onboard-wizard/WizardStep.tsx b/components/onboard-wizard/WizardStep.tsx index b243a33c..0758c5ce 100644 --- a/components/onboard-wizard/WizardStep.tsx +++ b/components/onboard-wizard/WizardStep.tsx @@ -2,20 +2,15 @@ import Popover from '~/components/ui/Popover'; import { Button } from '../ui/Button'; import { useWizardController } from './useWizardController'; import RenderRichText from '../RenderRichText'; -import { useLocale, useTranslations } from 'next-intl'; +import { useTranslations } from 'next-intl'; import { useElementPosition } from '~/lib/onboarding-wizard/utils'; import { type Step } from '~/lib/onboarding-wizard/store'; -import { getLocalisedValue } from '~/lib/localisation/utils'; import Form from '../ui/form/Form'; import { generatePublicId } from '~/lib/generatePublicId'; import { ControlledDialog } from '~/lib/dialogs/ControlledDialog'; export default function WizardStep({ step }: { step: Step }) { const { title, content, targetElementId } = step; - const locale = useLocale(); - - const localisedStepContent = getLocalisedValue(content, locale); - const localisedStepTitle = getLocalisedValue(title, locale); const { closeWizard, @@ -31,7 +26,7 @@ export default function WizardStep({ step }: { step: Step }) { const renderContent = () => ( <> - + {renderContent()} diff --git a/components/onboard-wizard/withOnboardingWizard.tsx b/components/onboard-wizard/withOnboardingWizard.tsx index 191af5bb..c756f169 100644 --- a/components/onboard-wizard/withOnboardingWizard.tsx +++ b/components/onboard-wizard/withOnboardingWizard.tsx @@ -1,12 +1,12 @@ import type { Wizard } from '~/lib/onboarding-wizard/store'; import OnboardWizard from './OnboardWizard'; -// A HOC that wraps a stage with an onboarding wizard export function withOnboardingWizard

( WrappedComponent: React.ComponentType

, - wizard: Wizard, + getWizard: () => Wizard, // function to create a wizard ) { const WithOnboardingWizard = (props: P) => { + const wizard = getWizard(); return ( <> diff --git a/lib/db/sample-data/dev-protocol.ts b/lib/db/sample-data/dev-protocol.ts index f5f53cfb..5c418917 100644 --- a/lib/db/sample-data/dev-protocol.ts +++ b/lib/db/sample-data/dev-protocol.ts @@ -66,6 +66,20 @@ export const devProtocol: Protocol = { }, }, }, + Interview: { + Wizards: { + General: { + Name: "Informations générales sur l'entretien", + Description: "Description de l'étape", + Steps: { + Welcome: { + Title: 'Bienvenue', + Text: "Bienvenue dans l'entretien", + }, + }, + }, + }, + }, }, }, codebook: { diff --git a/lib/localisation/messages/en.json b/lib/localisation/messages/en.json index e452ac4b..12ad3523 100644 --- a/lib/localisation/messages/en.json +++ b/lib/localisation/messages/en.json @@ -95,6 +95,52 @@ "LanguageSwitcher": "Change interview language", "Help": "Help menu", "Progress": "You have completed {percent}% of the interview" + }, + "Wizards": { + "General": { + "Name": "General Interview Information", + "Description": "Help with the general interview process, including how to navigate the interview, and general tips to help you get started.", + "Steps": { + "Welcome": { + "Title": "Welcome to the interview", + "Text": "Before you begin, here are some general tips to help you navigate the interview process." + }, + "NavigationBar": { + "Title": "Navigation Bar", + "Text": "The navigation bar helps you move through the interview process, and get help if you need it." + }, + "InterviewMovement": { + "Title": "Navigating the Interview", + "Text": "This is the third step of the interview. Here you will be introduced to the interview process and given an overview of what to expect." + }, + "Summary": { + "Title": "Summary", + "Text": "Use the back and forward buttons to move through the interview, and track your progress using the progress bar." + } + } + }, + "NameGenerator": { + "Name": "Name Generator Help", + "Description": "Help with the current task, including how to add new people, how to delete people, and how to edit people.", + "Steps": { + "Welcome": { + "Title": "Welcome to the Name Generator", + "Text": "This is the name generator interface. This interface allows you to nominate people. First, read the prompt and think about the people who meet the criteria." + }, + "Prompts": { + "Title": "Prompts", + "Text": "These are the prompts. They help you think about the people you want to nominate." + }, + "SidePanels": { + "Title": "Side Panels", + "Text": "These are side panels. They show the people you have already mentioned. You can drag and drop a person into the main area to nominate them." + }, + "AddPerson": { + "Title": "Adding a person", + "Text": "Click this button to add a new person" + } + } + } } }, "Generic": { diff --git a/lib/onboarding-wizard/store.ts b/lib/onboarding-wizard/store.ts index f3d0945f..47f43c13 100644 --- a/lib/onboarding-wizard/store.ts +++ b/lib/onboarding-wizard/store.ts @@ -1,5 +1,5 @@ import { createStore } from 'zustand'; -import type { LocalisedString, LocalisedRecord } from '~/schemas/shared'; +import type { JSONRichText } from '~/schemas/shared'; import { type LocalStorageState } from '~/lib/createLocalStorageStore'; const Priorities = { @@ -14,14 +14,14 @@ type Priority = keyof typeof Priorities; export type Step = { targetElementId?: string; - title: LocalisedString; - content: LocalisedRecord; + title: string; + content: JSONRichText; }; export type Wizard = { id: string; - name: LocalisedString; - description: LocalisedRecord; + name: string; + description: JSONRichText; steps: Step[]; priority: Priority; }; diff --git a/lib/routes/nextjs-routes.d.ts b/lib/routes/nextjs-routes.d.ts index 3e7b8ca7..b4081e28 100644 --- a/lib/routes/nextjs-routes.d.ts +++ b/lib/routes/nextjs-routes.d.ts @@ -89,35 +89,26 @@ declare module "nextjs-routes" { declare module "next/link" { import type { Route, RouteLiteral } from "nextjs-routes";; import type { LinkProps as NextLinkProps } from "next/dist/client/link"; - import type { - AnchorHTMLAttributes, - DetailedReactHTMLElement, - MouseEventHandler, - PropsWithChildren, - } from "react"; - export * from "next/dist/client/link"; + import type React from "react"; type StaticRoute = Exclude["pathname"]; - export interface LinkProps - extends Omit, - AnchorHTMLAttributes { + export type LinkProps = Omit & { href: StaticRoute | RouteLiteral; locale?: false; } - type LinkReactElement = DetailedReactHTMLElement< - { - onMouseEnter?: MouseEventHandler | undefined; - onClick: MouseEventHandler; - href?: string | undefined; - ref?: any; - }, - HTMLElement - >; - - declare function Link(props: PropsWithChildren): LinkReactElement; - + /** + * A React component that extends the HTML `` element to provide [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching) + * and client-side navigation between routes. + * + * It is the primary way to navigate between routes in Next.js. + * + * Read more: [Next.js docs: ``](https://nextjs.org/docs/app/api-reference/components/link) + */ + declare const Link: React.ForwardRefExoticComponent, keyof LinkProps> & LinkProps & { + children?: React.ReactNode; + } & React.RefAttributes>; export default Link; } @@ -187,41 +178,41 @@ declare module "next/navigation" { export * from "next/dist/client/components/navigation"; import type { RoutedQuery, RouteLiteral } from "nextjs-routes"; -/** - * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook - * that lets you read the current URL's pathname. - * - * @example - * ```ts - * "use client" - * import { usePathname } from 'next/navigation' - * - * export default function Page() { - * const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar - * // ... - * } - * ``` - * - * Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname) - */ + /** + * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook + * that lets you read the current URL's pathname. + * + * @example + * ```ts + * "use client" + * import { usePathname } from 'next/navigation' + * + * export default function Page() { + * const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar + * // ... + * } + * ``` + * + * Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname) + */ export function usePathname(): RouteLiteral; -/** - * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook - * that lets you read a route's dynamic params filled in by the current URL. - * - * @example - * ```ts - * "use client" - * import { useParams } from 'next/navigation' - * - * export default function Page() { - * // on /dashboard/[team] where pathname is /dashboard/nextjs - * const { team } = useParams() // team === "nextjs" - * } - * ``` - * - * Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params) - */ + /** + * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook + * that lets you read a route's dynamic params filled in by the current URL. + * + * @example + * ```ts + * "use client" + * import { useParams } from 'next/navigation' + * + * export default function Page() { + * // on /dashboard/[team] where pathname is /dashboard/nextjs + * const { team } = useParams() // team === "nextjs" + * } + * ``` + * + * Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params) + */ export function useParams(): RoutedQuery; } diff --git a/schemas/protocol/protocol.ts b/schemas/protocol/protocol.ts index 81a2f560..4458c364 100644 --- a/schemas/protocol/protocol.ts +++ b/schemas/protocol/protocol.ts @@ -24,6 +24,21 @@ export const ProtocolMessagesSchema = z.object({ .optional(), Prompts: z.record(z.string(), z.string()).optional(), }), + Interview: z.object({ + Wizards: z.object({ + General: z.object({ + Name: z.string(), + Description: z.string(), + Steps: z.record( + z.string(), + z.object({ + Title: z.string(), + Text: z.string(), + }), + ), + }), + }), + }), }); const LocalisedStringsSchema = z.record( From 451c53ca73b462709a7fffd6a68134d663c41c5e Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Mon, 30 Sep 2024 15:00:07 -0700 Subject: [PATCH 02/13] deepmerge messages so that entire wizards dont have to be redefined --- lib/localisation/locale.ts | 6 ++---- package.json | 1 + pnpm-lock.yaml | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/localisation/locale.ts b/lib/localisation/locale.ts index 440412db..980f6cb7 100644 --- a/lib/localisation/locale.ts +++ b/lib/localisation/locale.ts @@ -14,6 +14,7 @@ import Negotiator from 'negotiator'; import { match } from '@formatjs/intl-localematcher'; import { getCurrentPath, getInterviewId } from '../serverUtils'; import type { ProtocolMessages } from '~/schemas/protocol/protocol'; +import deepmerge from 'deepmerge'; async function getProtocolLocales(interviewId: string): Promise { try { @@ -123,10 +124,7 @@ export async function getLocaleMessages( default: IntlMessages; }; - return { - ...mainMessages.default, - ...protocolMessages, - }; + return deepmerge(mainMessages.default, protocolMessages); } export async function setUserLocale(locale: Locale) { diff --git a/package.json b/package.json index 3156f8ff..2ffef088 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@vercel/analytics": "^1.3.1", "@vercel/kv": "^2.0.0", "clsx": "^2.1.1", + "deepmerge": "^4.3.1", "eslint-config-prettier": "^9.1.0", "framer-motion": "12.0.0-alpha.1", "knip": "^5.30.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 395db4e1..52323c49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,6 +85,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.0) @@ -9371,7 +9374,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0) eslint-plugin-react: 7.36.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -9406,7 +9409,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -9424,7 +9427,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 From cf48c82c170ace331e4f691d34219ba84027fecf Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Tue, 1 Oct 2024 10:23:25 -0700 Subject: [PATCH 03/13] allow stage-specific overrides for wizard content --- .../name-generator/NameGenerator.tsx | 13 ++- .../onboard-wizard/withOnboardingWizard.tsx | 5 +- lib/db/sample-data/dev-protocol.ts | 22 ++++ lib/routes/nextjs-routes.d.ts | 103 ++++++++++-------- schemas/protocol/protocol.ts | 43 +++++--- 5 files changed, 121 insertions(+), 65 deletions(-) diff --git a/components/interview/interfaces/name-generator/NameGenerator.tsx b/components/interview/interfaces/name-generator/NameGenerator.tsx index f83b1ea8..beebf982 100644 --- a/components/interview/interfaces/name-generator/NameGenerator.tsx +++ b/components/interview/interfaces/name-generator/NameGenerator.tsx @@ -91,8 +91,17 @@ function NameGenerator(_props: InterviewStage) { ); } -export default withOnboardingWizard(NameGenerator, () => { - const t = useTranslations('Interview.Wizards.NameGenerator'); +export default withOnboardingWizard(NameGenerator, (_props) => { + // TODO: get the stage id from the props + const stageId = '1'; + + let t = useTranslations(`Protocol.Stages.${stageId}.Wizard`); + + // hacky way to check if the translation exists. This tells us if there are stage-level translations for the wizard + if (t('Name').includes(`Protocol.Stages.${stageId}.Wizard.Name`)) { + // use the default stage type wizard steps. will either be user-supplied or our defaults + t = useTranslations('Interview.Wizards.NameGenerator'); + } return { id: 'name-generator', diff --git a/components/onboard-wizard/withOnboardingWizard.tsx b/components/onboard-wizard/withOnboardingWizard.tsx index c756f169..0897273d 100644 --- a/components/onboard-wizard/withOnboardingWizard.tsx +++ b/components/onboard-wizard/withOnboardingWizard.tsx @@ -1,12 +1,13 @@ import type { Wizard } from '~/lib/onboarding-wizard/store'; import OnboardWizard from './OnboardWizard'; +// A HOC that wraps a stage with an onboarding wizard export function withOnboardingWizard

( WrappedComponent: React.ComponentType

, - getWizard: () => Wizard, // function to create a wizard + getWizard: (props: P) => Wizard, // function to create a wizard ) { const WithOnboardingWizard = (props: P) => { - const wizard = getWizard(); + const wizard = getWizard(props); return ( <> diff --git a/lib/db/sample-data/dev-protocol.ts b/lib/db/sample-data/dev-protocol.ts index 5c418917..5c390be6 100644 --- a/lib/db/sample-data/dev-protocol.ts +++ b/lib/db/sample-data/dev-protocol.ts @@ -51,6 +51,28 @@ export const devProtocol: Protocol = { Stages: { '1': { Label: 'Générateur de noms', + Wizard: { + Name: 'French wizard stage specific override', + Description: 'Description!', + Steps: { + Welcome: { + Title: 'Title - Welcome override', + Text: 'Stage specific override', + }, + Prompts: { + Title: 'Title - Prompts override', + Text: 'Stage specific override', + }, + SidePanels: { + Title: 'Title - Side Panels override', + Text: 'Stage specific override', + }, + AddPerson: { + Title: 'Title - Add Person override', + Text: 'Stage specific override', + }, + }, + }, }, }, Prompts: { diff --git a/lib/routes/nextjs-routes.d.ts b/lib/routes/nextjs-routes.d.ts index b4081e28..3e7b8ca7 100644 --- a/lib/routes/nextjs-routes.d.ts +++ b/lib/routes/nextjs-routes.d.ts @@ -89,26 +89,35 @@ declare module "nextjs-routes" { declare module "next/link" { import type { Route, RouteLiteral } from "nextjs-routes";; import type { LinkProps as NextLinkProps } from "next/dist/client/link"; - import type React from "react"; + import type { + AnchorHTMLAttributes, + DetailedReactHTMLElement, + MouseEventHandler, + PropsWithChildren, + } from "react"; + export * from "next/dist/client/link"; type StaticRoute = Exclude["pathname"]; - export type LinkProps = Omit & { + export interface LinkProps + extends Omit, + AnchorHTMLAttributes { href: StaticRoute | RouteLiteral; locale?: false; } - /** - * A React component that extends the HTML `` element to provide [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching) - * and client-side navigation between routes. - * - * It is the primary way to navigate between routes in Next.js. - * - * Read more: [Next.js docs: ``](https://nextjs.org/docs/app/api-reference/components/link) - */ - declare const Link: React.ForwardRefExoticComponent, keyof LinkProps> & LinkProps & { - children?: React.ReactNode; - } & React.RefAttributes>; + type LinkReactElement = DetailedReactHTMLElement< + { + onMouseEnter?: MouseEventHandler | undefined; + onClick: MouseEventHandler; + href?: string | undefined; + ref?: any; + }, + HTMLElement + >; + + declare function Link(props: PropsWithChildren): LinkReactElement; + export default Link; } @@ -178,41 +187,41 @@ declare module "next/navigation" { export * from "next/dist/client/components/navigation"; import type { RoutedQuery, RouteLiteral } from "nextjs-routes"; - /** - * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook - * that lets you read the current URL's pathname. - * - * @example - * ```ts - * "use client" - * import { usePathname } from 'next/navigation' - * - * export default function Page() { - * const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar - * // ... - * } - * ``` - * - * Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname) - */ +/** + * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook + * that lets you read the current URL's pathname. + * + * @example + * ```ts + * "use client" + * import { usePathname } from 'next/navigation' + * + * export default function Page() { + * const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar + * // ... + * } + * ``` + * + * Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname) + */ export function usePathname(): RouteLiteral; - /** - * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook - * that lets you read a route's dynamic params filled in by the current URL. - * - * @example - * ```ts - * "use client" - * import { useParams } from 'next/navigation' - * - * export default function Page() { - * // on /dashboard/[team] where pathname is /dashboard/nextjs - * const { team } = useParams() // team === "nextjs" - * } - * ``` - * - * Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params) - */ +/** + * A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook + * that lets you read a route's dynamic params filled in by the current URL. + * + * @example + * ```ts + * "use client" + * import { useParams } from 'next/navigation' + * + * export default function Page() { + * // on /dashboard/[team] where pathname is /dashboard/nextjs + * const { team } = useParams() // team === "nextjs" + * } + * ``` + * + * Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params) + */ export function useParams(): RoutedQuery; } diff --git a/schemas/protocol/protocol.ts b/schemas/protocol/protocol.ts index 4458c364..1979c604 100644 --- a/schemas/protocol/protocol.ts +++ b/schemas/protocol/protocol.ts @@ -12,6 +12,19 @@ export const ProtocolMessagesSchema = z.object({ z.string(), z.object({ Label: z.string(), + Wizard: z + .object({ + Name: z.string(), + Description: z.string(), + Steps: z.record( + z.string(), + z.object({ + Title: z.string(), + Text: z.string(), + }), + ), + }) + .optional(), // Stage-specific overrides are optional }), ), Panels: z @@ -24,21 +37,23 @@ export const ProtocolMessagesSchema = z.object({ .optional(), Prompts: z.record(z.string(), z.string()).optional(), }), - Interview: z.object({ - Wizards: z.object({ - General: z.object({ - Name: z.string(), - Description: z.string(), - Steps: z.record( - z.string(), - z.object({ - Title: z.string(), - Text: z.string(), - }), - ), + Interview: z + .object({ + Wizards: z.object({ + General: z.object({ + Name: z.string(), + Description: z.string(), + Steps: z.record( + z.string(), + z.object({ + Title: z.string(), + Text: z.string(), + }), + ), + }), }), - }), - }), + }) + .optional(), }); const LocalisedStringsSchema = z.record( From eedf540bffc0cef56b76ff3243d5cbfca71e64bd Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Tue, 1 Oct 2024 12:15:52 -0700 Subject: [PATCH 04/13] specify step keys given that these are constant for nav --- schemas/protocol/protocol.ts | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/schemas/protocol/protocol.ts b/schemas/protocol/protocol.ts index 1979c604..fe9ff80d 100644 --- a/schemas/protocol/protocol.ts +++ b/schemas/protocol/protocol.ts @@ -41,15 +41,28 @@ export const ProtocolMessagesSchema = z.object({ .object({ Wizards: z.object({ General: z.object({ - Name: z.string(), - Description: z.string(), - Steps: z.record( - z.string(), - z.object({ - Title: z.string(), - Text: z.string(), - }), - ), + Name: z.string().optional(), + Description: z.string().optional(), + Steps: z.object({ + Welcome: z + .object({ + Title: z.string(), + Text: z.string(), + }) + .optional(), + Overview: z + .object({ + Title: z.string(), + Text: z.string(), + }) + .optional(), + Summary: z + .object({ + Title: z.string(), + Text: z.string(), + }) + .optional(), + }), }), }), }) From 194ae7b83971e38d88dd3c39746f3171e71503db Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Fri, 4 Oct 2024 16:05:11 +0200 Subject: [PATCH 05/13] reorganize help button messages --- .vscode/settings.json | 7 +- components/interview/ui/HelpButton.tsx | 21 +++-- lib/db/sample-data/dev-protocol.ts | 3 +- lib/localisation/messages/ar.json | 80 +------------------ lib/localisation/messages/en.json | 38 +++++---- lib/localisation/messages/es.json | 102 +------------------------ 6 files changed, 44 insertions(+), 207 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4225dc59..561cb1c0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,10 @@ ".vscode/tailwind.json" ], "editor.defaultFormatter": "esbenp.prettier-vscode", - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "i18n-ally.localesPaths": [ + "lib/localisation/messages", + "app/api/interview/[interviewId]/languages", + "app/api/interview/[interviewId]/messages" + ] } \ No newline at end of file diff --git a/components/interview/ui/HelpButton.tsx b/components/interview/ui/HelpButton.tsx index 7634218d..2b8e0244 100644 --- a/components/interview/ui/HelpButton.tsx +++ b/components/interview/ui/HelpButton.tsx @@ -17,18 +17,15 @@ export default function HelpButton({ id }: { id?: string }) { const { openDialog } = useDialog(); const { wizards, setActiveWizard } = useWizardController(); - const t = useTranslations('Interview.Navigation'); - const t2 = useTranslations('Components.ContextualHelp'); - const t3 = useTranslations('Components.ContextualHelp.ContactCard'); + const t = useTranslations('Interview.Navigation.HelpButton'); + const genericT = useTranslations('Generic'); const handleOpenDialog = async () => { - // Return type should be string | null - const result = await openDialog({ type: 'custom', id: 'help-dialog', - title: t2('Title'), - description: t2('Description'), + title: t('Dialog.Title'), + description: t('Dialog.Description'), renderContent: (resolve) => ( <>

@@ -45,8 +42,8 @@ export default function HelpButton({ id }: { id?: string }) { ))} { // eslint-disable-next-line no-console console.log('TODO: Implement contact organiser feature'); @@ -58,7 +55,9 @@ export default function HelpButton({ id }: { id?: string }) { secondaryAction={ env.NODE_ENV === 'development' && ( <> - +