diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 97e595a9..109324ba 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,17 +1,14 @@ +import { withThemeByDataAttribute } from '@storybook/addon-themes'; import type { Preview } from '@storybook/react'; -import React, { lazy, Suspense, useEffect, useState } from 'react'; import { type AbstractIntlMessages } from 'next-intl'; -import { - SUPPORTED_LOCALE_OBJECTS, - type Locale, -} from '~/lib/localisation/config'; +import { Lexend, Roboto_Mono } from 'next/font/google'; +import { lazy, Suspense, useEffect, useState } from 'react'; import { getLangDir } from 'rtl-detect'; -import { withThemeByDataAttribute } from '@storybook/addon-themes'; -import '~/styles/global.css'; -import '~/styles/themes/default.css'; import Providers from '~/app/_components/Providers'; -import { Lexend, Roboto_Mono } from 'next/font/google'; import { cn } from '~/lib/utils'; +import { type Locale, SUPPORTED_LOCALE_OBJECTS } from '~/schemas/protocol/i18n'; +import '~/styles/global.css'; +import '~/styles/themes/default.css'; const LazyInterviewTheme = lazy(() => import('./InterviewTheme')); @@ -127,8 +124,6 @@ const preview: Preview = { (context.parameters.forceTheme as string) ?? (context.globals.visualTheme as string); - console.log('theme', theme); - return (
& { iconName: NodeIcon; diff --git a/components/interview/NodeList.tsx b/components/interview/NodeList.tsx index 43a6e123..a2338288 100644 --- a/components/interview/NodeList.tsx +++ b/components/interview/NodeList.tsx @@ -1,21 +1,17 @@ import Node from '~/components/Node'; - -export type Node = { - id: string; - label: string; -}; +import { type TNode } from '~/schemas/network/network'; export default function NodeList({ items, nodeSize = 'lg', }: { - items: Node[]; + items: TNode[]; nodeSize?: 'sm' | 'lg'; }) { return (
{items.map((node, index) => ( - + ))}
); diff --git a/components/interview/Prompts/Prompts.tsx b/components/interview/Prompts/Prompts.tsx index b131181c..12ae399c 100644 --- a/components/interview/Prompts/Prompts.tsx +++ b/components/interview/Prompts/Prompts.tsx @@ -1,32 +1,26 @@ 'use client'; -import Prompt from './Prompt'; -import Pips from './Pips'; -import type { TPrompts } from '~/schemas/protocol/shared/prompt'; import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import type { Prompt as PromptType } from '~/schemas/protocol/prompt'; +import Pips from './Pips'; +import Prompt from './Prompt'; export default function Prompts({ prompts, - currentPromptId, ...props }: { - prompts: TPrompts; - currentPromptId: string; + prompts: PromptType[]; } & React.HTMLProps) { - const currentIndex = prompts.findIndex(({ id }) => id === currentPromptId); + const [currentPromptIndex] = useState(0); const t = useTranslations(`Protocol.Prompts`); + const { id } = prompts[currentPromptIndex]!; + return (
- -
- {prompts.map( - ({ id }) => - prompts[currentIndex]?.id === id && ( - - ), - )} -
+ +
); } diff --git a/components/interview/interfaces/name-generator/NameGenerator.tsx b/components/interview/interfaces/name-generator/NameGenerator.tsx index c688dccf..1075facd 100644 --- a/components/interview/interfaces/name-generator/NameGenerator.tsx +++ b/components/interview/interfaces/name-generator/NameGenerator.tsx @@ -10,189 +10,118 @@ import { cn } from '~/lib/utils'; import { interfaceWrapperClasses } from '../../ui/SimpleShell'; import { withOnboardingWizard } from '~/components/onboard-wizard/withOnboardingWizard'; import { type InterviewStage } from '../../ui/InterviewShell'; -import { type TNodeType } from '~/schemas/protocol/codebook/entities'; - -const demoPrompts = [ - { - id: '1', - text: { - en: 'Within the past 6 months, who have you felt close to, or discussed important personal matters with?', - fr: 'French prompt', - }, - }, - { - id: '2', - text: 'Who do you feel most comfortable with?', - }, -]; - -const demoPanels = [ - { - id: '1', - title: 'People you have already mentioned', - nodes: [ - { - id: '1', - label: 'John', - }, - { - id: '2', - label: 'Jeff', - }, - ], - }, - { - id: '2', - title: 'Classmates you have mentioned', - nodes: [ - { - id: '3', - label: 'Tim', - }, - { - id: '4', - label: 'Blake', - }, - ], - }, -]; +import { useTranslations } from 'next-intl'; +import devProtocol from '~/lib/db/sample-data/dev-protocol'; +import { type NameGeneratorInterface } from '~/schemas/protocol/interfaces/name-generator'; const demoNodes = [ { id: '5', - label: 'Matt', + type: 'Person', + attributes: { label: 'Matt' }, }, { id: '6', - label: 'Taylor', + type: 'Person', + attributes: { label: 'Taylor' }, }, { id: '7', - label: 'Maggie', + type: 'Person', + attributes: { label: 'Maggie' }, }, { id: '8', - label: 'Emma', + type: 'Person', + attributes: { label: 'Emma' }, }, ]; -const demoNodeType = { - color: 'node-1', - icon: 'user-round', -} as TNodeType; - -function NameGenerator(_props: InterviewStage) { +export default function NameGenerator(_props: InterviewStage) { + const config = devProtocol.stages[0] as NameGeneratorInterface; + const stageNodeType = devProtocol.codebook.nodes!.person!; return ( -
- -
- -
- + <> +
+ +
+ {config.panels && } +
+ +
+
- -
+ ); } -export default withOnboardingWizard(NameGenerator, { - id: 'name-generator', - name: { - en: 'Name Generator Help', - }, - priority: 'Task', - description: { - en: [ - { - type: 'paragraph', - children: [ - { - text: 'Help with the current task, including how to add new people, how to delete people, and how to edit people.', - }, - ], - }, - ], - }, - steps: [ +// For tomorrow: should the steps be keyed by locale, or should title and content etc be localised records? + +export const defaultWizard = { + en: [ { - title: { - en: 'Welcome to the Name Generator', - }, - content: { - en: [ - { - 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.', - }, - ], - }, - { - type: 'image', - props: { - src: 'https://documentation.networkcanvas.com/assets/img/interface-documentation/name-generators/ng-quick.png', - alt: 'Name Generator Interface', + title: t('Steps.Welcome.Title'), + content: [ + { + type: 'paragraph', + children: [ + { + text: t('Steps.Welcome.Text'), }, - children: [], + ], + }, + { + type: 'image', + props: { + src: 'https://documentation.networkcanvas.com/assets/img/interface-documentation/name-generators/ng-quick.png', + alt: 'Name Generator Interface', }, - ], - }, + children: [], + }, + ], }, { targetElementId: 'data-wizard-prompts', - title: { - en: 'Prompts', - }, - content: { - en: [ - { - type: 'paragraph', - children: [ - { - text: 'These are the prompts. They help you think about the people you want to nominate.', - }, - ], - }, - ], - }, + title: t('Steps.Prompts.Title'), + content: [ + { + type: 'paragraph', + children: [ + { + text: t('Steps.Prompts.Text'), + }, + ], + }, + ], }, { targetElementId: 'data-wizard-task-step-2', - title: { - en: 'Side Panels', - }, - content: { - en: [ - { - 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.', - }, - ], - }, - ], - }, + title: t('Steps.SidePanels.Title'), + content: [ + { + type: 'paragraph', + children: [ + { + text: t('Steps.SidePanels.Text'), + }, + ], + }, + ], }, { targetElementId: 'data-wizard-task-step-3', - title: { - en: 'Adding a person', - }, - content: { - en: [ - { - type: 'paragraph', - children: [ - { - text: 'Click this button to add a new person', - }, - ], - }, - ], - }, + title: t('Steps.AddPerson.Title'), + content: [ + { + type: 'paragraph', + children: [ + { + text: t('Steps.AddPerson.Text'), + }, + ], + }, + ], }, ], -}); +}; diff --git a/components/interview/interfaces/name-generator/NodePanels.tsx b/components/interview/interfaces/name-generator/NodePanels.tsx index 5fba65be..3d09b885 100644 --- a/components/interview/interfaces/name-generator/NodePanels.tsx +++ b/components/interview/interfaces/name-generator/NodePanels.tsx @@ -1,27 +1,27 @@ 'use client'; -import type { Node } from '~/components/interview/NodeList'; import * as Accordion from '@radix-ui/react-accordion'; import { useState } from 'react'; import NodeList from '~/components/interview/NodeList'; import NodePanel from './NodePanel'; - -// TODO: Remove once connected to state -type Panel = { - id: string; - title: string; - nodes: Node[]; -}; +import { type Panel } from '~/schemas/protocol/interfaces/name-generator'; +import { useLocale } from 'next-intl'; +import { type Locale } from '~/schemas/protocol/i18n'; +import { type TNode } from '~/schemas/network/network'; type NodePanels = { panels: Panel[]; } & React.ComponentProps<'div'>; export default function NodePanels({ panels, ...rest }: NodePanels) { + const locale = useLocale() as Locale; const [values, setValues] = useState( panels.map((panel) => panel.id), ); + // TODO: fetch nodes based on panel.source + const [nodes] = useState([]); + return ( - + ))}
diff --git a/components/interview/interfaces/name-generator/QuickNodeForm.tsx b/components/interview/interfaces/name-generator/QuickNodeForm.tsx index 50ba1fd0..16074286 100644 --- a/components/interview/interfaces/name-generator/QuickNodeForm.tsx +++ b/components/interview/interfaces/name-generator/QuickNodeForm.tsx @@ -3,9 +3,9 @@ import { useState } from 'react'; import ActionButton from '~/components/interview/ActionButton'; import Node from '~/components/Node'; -import type { TNodeType } from '~/schemas/protocol/codebook/entities'; +import type { NodeType } from '~/schemas/protocol/entities'; -export default function QuickNodeForm({ nodeType }: { nodeType: TNodeType }) { +export default function QuickNodeForm({ nodeType }: { nodeType: NodeType }) { const [showForm, setShowForm] = useState(false); return ( diff --git a/components/interview/ui/HelpButton.tsx b/components/interview/ui/HelpButton.tsx index ca5f34f6..2b8e0244 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,20 +16,16 @@ 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'); - 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) => ( <>
@@ -40,17 +35,15 @@ export default function HelpButton({ id }: { id?: string }) { .map((wizard) => ( resolve(wizard.id)} > - {renderLocalisedValue( - getLocalisedValue(wizard.description, locale), - )} + {renderLocalisedValue(wizard.description)} ))} { // eslint-disable-next-line no-console console.log('TODO: Implement contact organiser feature'); @@ -62,7 +55,9 @@ export default function HelpButton({ id }: { id?: string }) { secondaryAction={ env.NODE_ENV === 'development' && ( <> - +