From 17eb90cd7c638ffb7c594dd71c5876f8984593a8 Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Sat, 4 Jan 2025 11:43:17 +0100 Subject: [PATCH 01/27] Signing panel scaffolding --- src/app-components/Panel/Panel.tsx | 2 +- .../expressions/expression-functions.ts | 1 + src/features/instance/ProcessContext.tsx | 11 +- src/language/texts/en.ts | 5 +- src/language/texts/nb.ts | 5 +- src/language/texts/nn.ts | 5 +- .../ActionButton/ActionButtonComponent.tsx | 4 +- .../CustomButton/CustomButtonComponent.tsx | 22 +-- .../SigneeList/SigneeListComponent.test.tsx | 4 + src/layout/SigneeList/SigneeListComponent.tsx | 7 +- src/layout/SigneeList/SigneeStateTag.test.tsx | 24 ++- src/layout/SigneeList/api.ts | 2 + .../SigningDocumentListComponent.tsx | 7 +- .../AwaitingCurrentUserSignaturePanel.tsx | 60 ++++++ .../SigningStatusPanel/SigningPanel.tsx | 47 +++++ .../SigningStatusPanelComponent.tsx | 175 ++++++++++++++++++ src/layout/SigningStatusPanel/config.ts | 18 ++ src/layout/SigningStatusPanel/index.tsx | 14 ++ src/types/shared.ts | 6 +- 19 files changed, 381 insertions(+), 38 deletions(-) create mode 100644 src/layout/SigningStatusPanel/AwaitingCurrentUserSignaturePanel.tsx create mode 100644 src/layout/SigningStatusPanel/SigningPanel.tsx create mode 100644 src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx create mode 100644 src/layout/SigningStatusPanel/config.ts create mode 100644 src/layout/SigningStatusPanel/index.tsx diff --git a/src/app-components/Panel/Panel.tsx b/src/app-components/Panel/Panel.tsx index f37838fda6..a3af820028 100644 --- a/src/app-components/Panel/Panel.tsx +++ b/src/app-components/Panel/Panel.tsx @@ -16,7 +16,7 @@ import { useIsMobile } from 'src/hooks/useDeviceWidths'; export type PanelVariant = (typeof PANEL_VARIANT)[keyof typeof PANEL_VARIANT]; -type PanelProps = PropsWithChildren<{ +export type PanelProps = PropsWithChildren<{ variant: PanelVariant; showIcon?: boolean; forceMobileLayout?: boolean; diff --git a/src/features/expressions/expression-functions.ts b/src/features/expressions/expression-functions.ts index 0c345a221e..25f7e3457c 100644 --- a/src/features/expressions/expression-functions.ts +++ b/src/features/expressions/expression-functions.ts @@ -244,6 +244,7 @@ export const ExprFunctions = { confirm: true, sign: true, reject: true, + complete: true, }; if (key === null || authContextKeys[key] !== true) { diff --git a/src/features/instance/ProcessContext.tsx b/src/features/instance/ProcessContext.tsx index 4431a994ae..840c5f46f9 100644 --- a/src/features/instance/ProcessContext.tsx +++ b/src/features/instance/ProcessContext.tsx @@ -14,7 +14,7 @@ import { fetchProcessState } from 'src/queries/queries'; import { isProcessTaskType, ProcessTaskType } from 'src/types'; import { behavesLikeDataTask } from 'src/utils/formLayout'; import type { QueryDefinition } from 'src/core/queries/usePrefetchQuery'; -import type { IProcess } from 'src/types/shared'; +import type { IActionType, IProcess } from 'src/types/shared'; import type { HttpClientError } from 'src/utils/network/sharedNetworking'; // Also used for prefetching @see appPrefetcher.ts @@ -71,6 +71,15 @@ export const useHasProcessProvider = () => useContext(ProcessContext) !== undefi export const useLaxProcessData = () => useContext(ProcessContext)?.data; export const useReFetchProcessData = () => useContext(ProcessContext)?.refetch; +export const useIsAuthorised = () => { + const processData = useLaxProcessData(); + + return (action: IActionType): boolean => { + const userAction = processData?.currentTask?.userActions?.find((a) => a.id === action); + return !!userAction?.authorized; + }; +}; + /** * This returns the task type of the current process task, as we got it from the backend */ diff --git a/src/language/texts/en.ts b/src/language/texts/en.ts index fc0ca0536d..5837bdf662 100644 --- a/src/language/texts/en.ts +++ b/src/language/texts/en.ts @@ -344,9 +344,11 @@ export function en() { validation_invalid_response_from_server: 'An error occurred. Please try again later.', unknown_error: 'An unknown error occurred. Please try again later.', }, + signing: { + wrong_task_error: 'The {0} component is only available in a signing task.', + }, signee_list: { parse_error: 'Error loading signee list.', - wrong_task_error: 'The SigneeList component is only available in a signing task.', unknown_api_error: 'An error occurred when fetching signees.', api_error_display: 'An error occurred when fetching signees. See devtool logs for more information.', signee_status_signed: 'Signed', @@ -359,7 +361,6 @@ export function en() { }, signing_document_list: { parse_error: 'Error loading signee document list.', - wrong_task_error: 'The SigningDocumentList component is only available in a signing task.', unknown_api_error: 'An error occurred when fetching documents.', api_error_display: 'An error occurred when fetching documents. See devtool logs for more information.', header_filename: 'Name', diff --git a/src/language/texts/nb.ts b/src/language/texts/nb.ts index 0a44b994d7..face5069a3 100644 --- a/src/language/texts/nb.ts +++ b/src/language/texts/nb.ts @@ -345,9 +345,11 @@ export function nb(): FixedLanguageList { validation_invalid_response_from_server: 'Det oppstod en feil. Vennligst prøv igjen senere.', unknown_error: 'Ukjent feil. Vennligst prøv igjen senere.', }, + signing: { + wrong_task_error: '{0}-komponenten er kun tilgjengelig i et signeringssteg.', + }, signee_list: { parse_error: 'Feil ved lasting av signatarliste.', - wrong_task_error: 'SigneeList-komponenten er kun tilgjengelig i et signeringssteg.', unknown_api_error: 'En feil oppstod under henting av signatarer.', api_error_display: 'En feil oppstod under henting av signatarer. Se devtool-loggene for mer informasjon.', signee_status_signed: 'Signert', @@ -360,7 +362,6 @@ export function nb(): FixedLanguageList { }, signing_document_list: { parse_error: 'Feil ved lasting av dokumenter.', - wrong_task_error: 'SigningDocumentList-komponenten er kun tilgjengelig i et signeringssteg.', unknown_api_error: 'En feil oppstod under henting av doumenter.', api_error_display: 'En feil oppstod under henting av dokumenter. Se devtool-loggene for mer informasjon.', header_filename: 'Navn', diff --git a/src/language/texts/nn.ts b/src/language/texts/nn.ts index 7627a94e87..e2c4f4b6f1 100644 --- a/src/language/texts/nn.ts +++ b/src/language/texts/nn.ts @@ -345,9 +345,11 @@ export function nn(): FixedLanguageList { validation_invalid_response_from_server: 'Det oppstod ein feil. Ver venleg, prøv igjen seinare.', unknown_error: 'Det oppstod ein feil. Ver venleg, prøv igjen seinare.', }, + signing: { + wrong_task_error: '{0}-komponenten er berre tilgjengeleg i eit signeringssteg.', + }, signee_list: { parse_error: 'Feil ved lasting av signatarliste.', - wrong_task_error: 'SigneeList-komponenten er berre tilgjengeleg i eit signeringssteg.', unknown_api_error: 'Ein feil oppstod under henting av signatarar.', api_error_display: 'Ein feil oppstod under henting av signatarar. Sjå devtool-loggane for meir informasjon.', signee_status_signed: 'Signert', @@ -360,7 +362,6 @@ export function nn(): FixedLanguageList { }, signing_document_list: { parse_error: 'Feil ved lasting av dokumenter.', - wrong_task_error: 'SigningDocumentList-komponenten er berre tilgjengeleg i eit signeringssteg.', unknown_api_error: 'Ein feil oppstod under henting av dokumenter.', api_error_display: 'Ein feil oppstod under henting av dokumenter. Sjå devtool-loggane for meir informasjon.', header_filename: 'Namn', diff --git a/src/layout/ActionButton/ActionButtonComponent.tsx b/src/layout/ActionButton/ActionButtonComponent.tsx index d7908ae8fe..d455bb5d94 100644 --- a/src/layout/ActionButton/ActionButtonComponent.tsx +++ b/src/layout/ActionButton/ActionButtonComponent.tsx @@ -3,10 +3,10 @@ import React from 'react'; import type { PropsFromGenericComponent } from '..'; import { Button, type ButtonColor, type ButtonVariant } from 'src/app-components/Button/Button'; +import { useIsAuthorised } from 'src/features/instance/ProcessContext'; import { useProcessNavigation } from 'src/features/instance/ProcessNavigationContext'; import { Lang } from 'src/features/language/Lang'; import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper'; -import { useActionAuthorization } from 'src/layout/CustomButton/CustomButtonComponent'; import { useNodeItem } from 'src/utils/layout/useNodeItem'; import type { ActionButtonStyle } from 'src/layout/ActionButton/config.generated'; @@ -19,7 +19,7 @@ export type IActionButton = PropsFromGenericComponent<'ActionButton'>; export function ActionButtonComponent({ node }: IActionButton) { const { busyWithId, busy, next } = useProcessNavigation() || {}; - const { isAuthorized } = useActionAuthorization(); + const isAuthorized = useIsAuthorised(); const { action, buttonStyle, id, textResourceBindings } = useNodeItem(node); const disabled = !isAuthorized(action); diff --git a/src/layout/CustomButton/CustomButtonComponent.tsx b/src/layout/CustomButton/CustomButtonComponent.tsx index 472c1c502a..ea2d56e13e 100644 --- a/src/layout/CustomButton/CustomButtonComponent.tsx +++ b/src/layout/CustomButton/CustomButtonComponent.tsx @@ -7,7 +7,7 @@ import { Button } from 'src/app-components/Button/Button'; import { useAppMutations } from 'src/core/contexts/AppQueriesProvider'; import { useResetScrollPosition } from 'src/core/ui/useResetScrollPosition'; import { FD } from 'src/features/formData/FormDataWrite'; -import { useLaxProcessData } from 'src/features/instance/ProcessContext'; +import { useIsAuthorised } from 'src/features/instance/ProcessContext'; import { Lang } from 'src/features/language/Lang'; import { useIsSubformPage, useNavigationParam } from 'src/features/routing/AppRoutingContext'; import { useOnPageNavigationValidation } from 'src/features/validation/callbacks/onPageNavigationValidation'; @@ -22,7 +22,7 @@ import type { BackendValidationIssueGroups } from 'src/features/validation'; import type { PropsFromGenericComponent } from 'src/layout'; import type * as CBTypes from 'src/layout/CustomButton/config.generated'; import type { ClientActionHandlers } from 'src/layout/CustomButton/typeHelpers'; -import type { IInstance, IUserAction } from 'src/types/shared'; +import type { IActionType, IInstance } from 'src/types/shared'; type Props = PropsFromGenericComponent<'CustomButton'>; @@ -195,20 +195,6 @@ function useHandleServerActionMutation(lockTools: FormDataLockTools): UsePerform return { handleServerAction, isPending }; } -export function useActionAuthorization() { - const currentTask = useLaxProcessData()?.currentTask; - const userActions = currentTask?.userActions; - const actionPermissions = currentTask?.actions; - - const isAuthorized = useCallback( - (action: IUserAction['id']) => - (!!actionPermissions?.[action] || userActions?.find((a) => a.id === action)?.authorized) ?? false, - [actionPermissions, userActions], - ); - - return { isAuthorized }; -} - export const buttonStyles: { [style in CBTypes.ButtonStyle]: { color: ButtonColor; variant: ButtonVariant } } = { primary: { variant: 'primary', color: 'success' }, secondary: { variant: 'secondary', color: 'first' }, @@ -234,7 +220,7 @@ export const CustomButtonComponent = ({ node }: Props) => { const { textResourceBindings, actions, id, buttonColor, buttonSize, buttonStyle } = useNodeItem(node); const lockTools = FD.useLocking(id); - const { isAuthorized } = useActionAuthorization(); + const isAuthorized = useIsAuthorised(); const { handleClientActions } = useHandleClientActions(); const { handleServerAction, isPending } = useHandleServerActionMutation(lockTools); const onPageNavigationValidation = useOnPageNavigationValidation(); @@ -247,7 +233,7 @@ export const CustomButtonComponent = ({ node }: Props) => { const isPermittedToPerformActions = actions .filter((action) => action.type === 'ServerAction') - .reduce((acc, action) => acc && isAuthorized(action.id), true); + .reduce((acc, action) => acc && isAuthorized(action.id as IActionType), true); const disabled = !isPermittedToPerformActions || isPending; const isSubformCloseButton = actions.filter((action) => action.id === 'closeSubform').length > 0; diff --git a/src/layout/SigneeList/SigneeListComponent.test.tsx b/src/layout/SigneeList/SigneeListComponent.test.tsx index f4c3c3355e..1d1915fb03 100644 --- a/src/layout/SigneeList/SigneeListComponent.test.tsx +++ b/src/layout/SigneeList/SigneeListComponent.test.tsx @@ -20,6 +20,7 @@ const mockSigneeStates: Awaited> = [ hasSigned: true, delegationSuccessful: true, notificationSuccessful: true, + partyId: 123, }, { name: 'name2', @@ -27,6 +28,7 @@ const mockSigneeStates: Awaited> = [ hasSigned: false, delegationSuccessful: false, notificationSuccessful: false, + partyId: 123, }, { name: 'name3', @@ -34,6 +36,7 @@ const mockSigneeStates: Awaited> = [ hasSigned: false, delegationSuccessful: true, notificationSuccessful: false, + partyId: 123, }, { name: 'name4', @@ -41,6 +44,7 @@ const mockSigneeStates: Awaited> = [ hasSigned: false, delegationSuccessful: true, notificationSuccessful: true, + partyId: 123, }, ]; diff --git a/src/layout/SigneeList/SigneeListComponent.tsx b/src/layout/SigneeList/SigneeListComponent.tsx index 89303edc53..1cf0b97905 100644 --- a/src/layout/SigneeList/SigneeListComponent.tsx +++ b/src/layout/SigneeList/SigneeListComponent.tsx @@ -30,7 +30,12 @@ export function SigneeListComponent({ node }: PropsFromGenericComponent<'SigneeL }); if (taskType !== ProcessTaskType.Signing) { - return ; + return ( + + ); } if (error) { diff --git a/src/layout/SigneeList/SigneeStateTag.test.tsx b/src/layout/SigneeList/SigneeStateTag.test.tsx index 788bffb2a9..618b514fed 100644 --- a/src/layout/SigneeList/SigneeStateTag.test.tsx +++ b/src/layout/SigneeList/SigneeStateTag.test.tsx @@ -10,25 +10,41 @@ jest.mock('src/features/language/Lang', () => ({ Lang: ({ id }: { id: string }) describe('SigneeStateTag', () => { it('should display a tag with name "signed" when status is "signed"', () => { - render(); + render( + , + ); screen.getByText(SIGNEE_STATUS.signed); }); it('should display a tag with name "delegationFailed" when status is "delegationFailed"', () => { - render(); + render( + , + ); screen.getByText(SIGNEE_STATUS.delegationFailed); }); it('should display a tag with name "notificationFailed" when status is "notificationFailed"', () => { - render(); + render( + , + ); screen.getByText(SIGNEE_STATUS.notificationFailed); }); it('should display a tag with name "waiting" when status is "waiting"', () => { - render(); + render( + , + ); screen.getByText(SIGNEE_STATUS.waiting); }); diff --git a/src/layout/SigneeList/api.ts b/src/layout/SigneeList/api.ts index abbf952c7d..53fc2a2290 100644 --- a/src/layout/SigneeList/api.ts +++ b/src/layout/SigneeList/api.ts @@ -11,6 +11,7 @@ const signeeStateSchema = z hasSigned: z.boolean(), delegationSuccessful: z.boolean(), notificationSuccessful: z.boolean(), + partyId: z.number(), }) .refine(({ name, organisation }) => name || organisation, 'Either name or organisation must be present.'); @@ -20,6 +21,7 @@ export const signeeListQuery = (partyId: string, instanceGuid: string) => queryOptions({ queryKey: ['signeeList', partyId, instanceGuid], queryFn: () => fetchSigneeList(partyId, instanceGuid), + staleTime: 1000 * 30, // 30 secondsx }); export async function fetchSigneeList(partyId: string, instanceGuid: string): Promise { diff --git a/src/layout/SigningDocumentList/SigningDocumentListComponent.tsx b/src/layout/SigningDocumentList/SigningDocumentListComponent.tsx index d1493774cb..b51c84d2fc 100644 --- a/src/layout/SigningDocumentList/SigningDocumentListComponent.tsx +++ b/src/layout/SigningDocumentList/SigningDocumentListComponent.tsx @@ -34,7 +34,12 @@ export function SigningDocumentListComponent({ node }: PropsFromGenericComponent }); if (taskType !== ProcessTaskType.Signing) { - return ; + return ( + + ); } if (error) { diff --git a/src/layout/SigningStatusPanel/AwaitingCurrentUserSignaturePanel.tsx b/src/layout/SigningStatusPanel/AwaitingCurrentUserSignaturePanel.tsx new file mode 100644 index 0000000000..804484c6eb --- /dev/null +++ b/src/layout/SigningStatusPanel/AwaitingCurrentUserSignaturePanel.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; + +import { Checkbox } from '@digdir/designsystemet-react'; + +import { Button } from 'src/app-components/Button/Button'; +import { useIsAuthorised } from 'src/features/instance/ProcessContext'; +import { SigningPanel } from 'src/layout/SigningStatusPanel/SigningPanel'; + +export function AwaitingCurrentUserSignaturePanel() { + const isAuthorised = useIsAuthorised(); + const canWrite = isAuthorised('write'); + const canSign = isAuthorised('sign'); + + const [confirmReadDocuments, setConfirmReadDocuments] = useState(false); + + if (!canSign) { + return
Something went wrong. Current user should sign, but does not have rights...
; + } + + function handleSign() { + // TODO: implement + } + + function handleReject() { + // TODO: implement + } + + return ( + + Avbryt signering + + ) : undefined + } + actionButton={ + + } + > + setConfirmReadDocuments(!confirmReadDocuments)} + > + Jeg bekrefter at opplysningene og dokumentene er riktige. {/* TODO: get this text from config? API? */} + + + ); +} diff --git a/src/layout/SigningStatusPanel/SigningPanel.tsx b/src/layout/SigningStatusPanel/SigningPanel.tsx new file mode 100644 index 0000000000..db05944653 --- /dev/null +++ b/src/layout/SigningStatusPanel/SigningPanel.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { PropsWithChildren, ReactElement } from 'react'; + +import { Heading } from '@digdir/designsystemet-react'; + +import { Panel } from 'src/app-components/Panel/Panel'; +import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; +import type { Button } from 'src/app-components/Button/Button'; +import type { PanelProps } from 'src/app-components/Panel/Panel'; + +type SigningPanelProps = { + heading: string; + description?: string; + variant?: PanelProps['variant']; + actionButton: ReactElement; + secondaryButton?: ReactElement; +}; + +export function SigningPanel({ + heading, + description, + variant = 'info', + secondaryButton, + actionButton, + children, +}: PropsWithChildren) { + return ( + + +
+ + {heading} + + {description &&

{description}

} + {children} +
+ {secondaryButton} + {actionButton} +
+
+
+
+ ); +} diff --git a/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx b/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx new file mode 100644 index 0000000000..fead49ed58 --- /dev/null +++ b/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; + +import { Spinner } from '@digdir/designsystemet-react'; +import { useQuery } from '@tanstack/react-query'; + +import { Button } from 'src/app-components/Button/Button'; +import { Panel } from 'src/app-components/Panel/Panel'; +import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; +import { useIsAuthorised, useTaskTypeFromBackend } from 'src/features/instance/ProcessContext'; +import { Lang } from 'src/features/language/Lang'; +import { signeeListQuery } from 'src/layout/SigneeList/api'; +import { AwaitingCurrentUserSignaturePanel } from 'src/layout/SigningStatusPanel/AwaitingCurrentUserSignaturePanel'; +import { SigningPanel } from 'src/layout/SigningStatusPanel/SigningPanel'; +import { ProcessTaskType } from 'src/types'; +import type { PropsFromGenericComponent } from 'src/layout'; +import type { SigneeState } from 'src/layout/SigneeList/api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function SigningStatusPanelComponent({ node }: PropsFromGenericComponent<'SigningStatusPanel'>) { + const taskType = useTaskTypeFromBackend(); + const { partyId, instanceGuid } = useParams(); + const { data: signeeList, isLoading } = useQuery(signeeListQuery(partyId!, instanceGuid!)); + const currentUserStatus = getCurrentUserStatus(signeeList, partyId); + const canWrite = useIsAuthorised()('write'); + + if (taskType !== ProcessTaskType.Signing) { + return ( + + ); + } + + if (isLoading) { + return ( + + +
+ +
+
+
+ ); + } + + const allHaveSigned = signeeList?.every((signee) => signee.hasSigned) ?? false; + + if (currentUserStatus === 'waiting') { + return ; + } + + if (canWrite) { + return ( + + ); + } + + return ( + + ); +} + +function SigningPanelWriteAccess({ + allHaveSigned, + currentUserStatus, +}: { + allHaveSigned: boolean; + currentUserStatus: Exclude; +}) { + function handleRejectSigning() { + // TODO: implement + } + + function handleSubmit() { + // TODO: implement + } + const description = allHaveSigned + ? 'Alle har signert! Velg send inn skjemaet for å fullføre.' + : [ + currentUserStatus === 'signed' ? 'Takk for at du har signert!' : null, + 'Når alle har signert kan du sende inn skjemaet.', + ] + .filter((it) => it != null) + .join(' '); + + return ( + + Avbryt signering + + } + actionButton={ + + } + /> + ); +} + +function SigningPanelNoWriteAccess({ + allHaveSigned, + currentUserStatus, +}: { + allHaveSigned: boolean; + currentUserStatus: Exclude; +}) { + function handleGoToInbox() { + // TODO: implement + } + + const hasSigned = currentUserStatus === 'signed'; + return ( + + Gå til innboksen + + } + /> + ); +} + +type CurrentUserStatus = 'waiting' | 'signed' | 'notSigning'; +function getCurrentUserStatus(signeeList: SigneeState[] | undefined, partyId: string | undefined): CurrentUserStatus { + const currentUserSignee = signeeList?.find((signee) => signee.partyId.toString() === partyId); + if (!currentUserSignee) { + return 'notSigning'; + } + if (currentUserSignee.hasSigned) { + return 'signed'; + } + return 'waiting'; +} diff --git a/src/layout/SigningStatusPanel/config.ts b/src/layout/SigningStatusPanel/config.ts new file mode 100644 index 0000000000..365ae9ef54 --- /dev/null +++ b/src/layout/SigningStatusPanel/config.ts @@ -0,0 +1,18 @@ +import { CG } from 'src/codegen/CG'; +import { CompCategory } from 'src/layout/common'; + +export const Config = new CG.component({ + category: CompCategory.Presentation, + capabilities: { + renderInTable: false, + renderInButtonGroup: false, + renderInAccordion: false, + renderInAccordionGroup: false, + renderInCards: false, + renderInCardsMedia: false, + renderInTabs: false, + }, + functionality: { + customExpressions: false, + }, +}); diff --git a/src/layout/SigningStatusPanel/index.tsx b/src/layout/SigningStatusPanel/index.tsx new file mode 100644 index 0000000000..ab72fae24d --- /dev/null +++ b/src/layout/SigningStatusPanel/index.tsx @@ -0,0 +1,14 @@ +import React, { forwardRef } from 'react'; +import type { JSX } from 'react'; + +import { SigningStatusPanelDef } from 'src/layout/SigningStatusPanel/config.def.generated'; +import { SigningStatusPanelComponent } from 'src/layout/SigningStatusPanel/SigningStatusPanelComponent'; +import type { PropsFromGenericComponent } from 'src/layout'; + +export class SigningStatusPanel extends SigningStatusPanelDef { + render = forwardRef>( + function SigningStatusPanelComponentRender(props, _): JSX.Element | null { + return ; + }, + ); +} diff --git a/src/types/shared.ts b/src/types/shared.ts index 76e48df0cd..9c38e8c074 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -223,10 +223,8 @@ export interface ISelfLinks { platform: string; } -type ProcessActionIds = 'read' | 'write' | 'complete'; - export interface IUserAction { - id: ProcessActionIds | string; + id: IActionType | string; authorized: boolean; type: 'ProcessAction' | 'ServerAction'; } @@ -288,7 +286,7 @@ export interface IInstanceDataSources { instanceOwnerPartyType: InstanceOwnerPartyType; } -export type IActionType = 'instantiate' | 'confirm' | 'sign' | 'reject'; +export type IActionType = 'instantiate' | 'confirm' | 'sign' | 'reject' | 'read' | 'write' | 'complete'; export type IAuthContext = { read: boolean; From 7abe0f5d4d0333ee00f2d028abe6723a1ba91aa0 Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Sun, 5 Jan 2025 12:40:09 +0100 Subject: [PATCH 02/27] fixes tests --- src/layout/SigneeList/SigneeListComponent.test.tsx | 2 +- src/layout/SigneeList/{apit.test.ts => api.test.ts} | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) rename src/layout/SigneeList/{apit.test.ts => api.test.ts} (94%) diff --git a/src/layout/SigneeList/SigneeListComponent.test.tsx b/src/layout/SigneeList/SigneeListComponent.test.tsx index 1d1915fb03..0ccec98e60 100644 --- a/src/layout/SigneeList/SigneeListComponent.test.tsx +++ b/src/layout/SigneeList/SigneeListComponent.test.tsx @@ -132,7 +132,7 @@ describe('SigneeListComponent', () => { />, ); - screen.getByText('signee_list.wrong_task_error'); + screen.getByText('signing.wrong_task_error'); }); it('should render error message when API call fails', () => { diff --git a/src/layout/SigneeList/apit.test.ts b/src/layout/SigneeList/api.test.ts similarity index 94% rename from src/layout/SigneeList/apit.test.ts rename to src/layout/SigneeList/api.test.ts index eb575d2382..74522d9186 100644 --- a/src/layout/SigneeList/apit.test.ts +++ b/src/layout/SigneeList/api.test.ts @@ -21,6 +21,7 @@ describe('fetchSigneeList', () => { hasSigned: true, delegationSuccessful: true, notificationSuccessful: false, + partyId: 123, }, { name: 'Jane Doe', @@ -28,6 +29,7 @@ describe('fetchSigneeList', () => { hasSigned: false, delegationSuccessful: false, notificationSuccessful: false, + partyId: 123, }, ], }); @@ -41,6 +43,7 @@ describe('fetchSigneeList', () => { hasSigned: true, delegationSuccessful: true, notificationSuccessful: false, + partyId: 123, }, { name: 'Jane Doe', @@ -48,6 +51,7 @@ describe('fetchSigneeList', () => { hasSigned: false, delegationSuccessful: false, notificationSuccessful: false, + partyId: 123, }, ]); }); @@ -99,6 +103,7 @@ describe('fetchSigneeList', () => { hasSigned: true, delegationSuccessful: true, notificationSuccessful: false, + partyId: 123, }, { name: 'Mary Jane', @@ -106,6 +111,7 @@ describe('fetchSigneeList', () => { hasSigned: false, delegationSuccessful: false, notificationSuccessful: false, + partyId: 123, }, ], }); @@ -118,6 +124,7 @@ describe('fetchSigneeList', () => { hasSigned: false, delegationSuccessful: false, notificationSuccessful: false, + partyId: 123, }, { name: 'Sylvester Stallone', @@ -125,6 +132,7 @@ describe('fetchSigneeList', () => { hasSigned: true, delegationSuccessful: true, notificationSuccessful: false, + partyId: 123, }, ]); }); From 031b47d436f3c7dca949f44627c8828fd4e2d95b Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Sun, 5 Jan 2025 13:36:47 +0100 Subject: [PATCH 03/27] updates eslint rules for test files --- eslint.config.mjs | 1 + src/layout/Datepicker/DatepickerComponent.test.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index a0f7fb2194..70652f6612 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -174,6 +174,7 @@ export default tseslint.config( rules: { 'testing-library/await-async-queries': ['warn'], 'jsx-a11y/label-has-associated-control': ['off'], + '@typescript-eslint/consistent-type-imports': ['off'], }, }, { diff --git a/src/layout/Datepicker/DatepickerComponent.test.tsx b/src/layout/Datepicker/DatepickerComponent.test.tsx index 8cd5b6ca83..e2f5a66865 100644 --- a/src/layout/Datepicker/DatepickerComponent.test.tsx +++ b/src/layout/Datepicker/DatepickerComponent.test.tsx @@ -13,7 +13,6 @@ import type { RenderGenericComponentTestProps } from 'src/test/renderWithProvide // Mock dateformat jest.mock('src/app-components/Datepicker/utils/dateHelpers', () => ({ __esModules: true, - // eslint-disable-next-line @typescript-eslint/consistent-type-imports ...jest.requireActual( 'src/app-components/Datepicker/utils/dateHelpers', ), From 2234c76d2d292b62e699c37a23ee9d0b56ca4a1d Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Sun, 5 Jan 2025 13:38:23 +0100 Subject: [PATCH 04/27] fixes function name casing --- src/components/altinnAppHeader.tsx | 4 ++-- src/components/presentation/NavBar.tsx | 4 ++-- src/components/presentation/Presentation.test.tsx | 6 +++--- src/utils/urls/urlHelper.test.ts | 10 +++++----- src/utils/urls/urlHelper.ts | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/altinnAppHeader.tsx b/src/components/altinnAppHeader.tsx index 525e098988..2613def645 100644 --- a/src/components/altinnAppHeader.tsx +++ b/src/components/altinnAppHeader.tsx @@ -10,7 +10,7 @@ import { LandmarkShortcuts } from 'src/components/LandmarkShortcuts'; import { AltinnLogo, LogoColor } from 'src/components/logo/AltinnLogo'; import { Lang } from 'src/features/language/Lang'; import { renderParty } from 'src/utils/party'; -import { returnUrlToAllSchemas, returnUrlToMessagebox, returnUrlToProfile } from 'src/utils/urls/urlHelper'; +import { returnUrlToAllSchemas, returnUrlToMessageBox, returnUrlToProfile } from 'src/utils/urls/urlHelper'; import type { IProfile } from 'src/types/shared'; export interface IHeaderProps { @@ -49,7 +49,7 @@ export const AltinnAppHeader = ({ profile }: IHeaderProps) => {
  • diff --git a/src/components/presentation/NavBar.tsx b/src/components/presentation/NavBar.tsx index 9d1ee3b84d..2700af1dd2 100644 --- a/src/components/presentation/NavBar.tsx +++ b/src/components/presentation/NavBar.tsx @@ -15,7 +15,7 @@ import { useNavigatePage, usePreviousPageKey } from 'src/hooks/useNavigatePage'; import { PresentationType, ProcessTaskType } from 'src/types'; import { httpGet } from 'src/utils/network/networking'; import { getRedirectUrl } from 'src/utils/urls/appUrlHelper'; -import { returnUrlToMessagebox } from 'src/utils/urls/urlHelper'; +import { returnUrlToMessageBox } from 'src/utils/urls/urlHelper'; export interface INavBarProps { type: PresentationType | ProcessTaskType; @@ -43,7 +43,7 @@ export const NavBar = ({ type }: INavBarProps) => { const handleModalCloseButton = async () => { const queryParameterReturnUrl = new URLSearchParams(window.location.search).get('returnUrl'); - const messageBoxUrl = returnUrlToMessagebox(window.location.origin, party?.partyId); + const messageBoxUrl = returnUrlToMessageBox(window.location.origin, party?.partyId); if (queryParameterReturnUrl) { const returnUrl = diff --git a/src/components/presentation/Presentation.test.tsx b/src/components/presentation/Presentation.test.tsx index 5cddf830b0..a0264525e4 100644 --- a/src/components/presentation/Presentation.test.tsx +++ b/src/components/presentation/Presentation.test.tsx @@ -11,7 +11,7 @@ import { renderWithInstanceAndLayout } from 'src/test/renderWithProviders'; import { AltinnPalette } from 'src/theme/altinnAppTheme'; import { ProcessTaskType } from 'src/types'; import { HttpStatusCodes } from 'src/utils/network/networking'; -import { returnUrlToMessagebox } from 'src/utils/urls/urlHelper'; +import { returnUrlToMessageBox } from 'src/utils/urls/urlHelper'; import type { IPresentationProvidedProps } from 'src/components/presentation/Presentation'; jest.mock('axios'); @@ -59,7 +59,7 @@ describe('Presentation', () => { const returnUrl = 'https://altinn.cloud.no'; const mockedLocation = { ...realLocation, search: `?returnUrl=${returnUrl}`, assign: assignMock, origin }; jest.spyOn(window, 'location', 'get').mockReturnValue(mockedLocation); - const messageBoxUrl = returnUrlToMessagebox(origin, getPartyMock().partyId); + const messageBoxUrl = returnUrlToMessageBox(origin, getPartyMock().partyId); mockedAxios.get.mockRejectedValue({ data: 'Error', @@ -83,7 +83,7 @@ describe('Presentation', () => { it('should change window.location.href to default messagebox url if query parameter returnUrl is not found', async () => { const origin = 'https://local.altinn.cloud'; const partyId = getPartyMock().partyId; - const messageBoxUrl = returnUrlToMessagebox(origin, partyId); + const messageBoxUrl = returnUrlToMessageBox(origin, partyId); const mockedLocation = { ...realLocation, assign: assignMock, origin, search: '' }; jest.spyOn(window, 'location', 'get').mockReturnValue(mockedLocation); diff --git a/src/utils/urls/urlHelper.test.ts b/src/utils/urls/urlHelper.test.ts index b79366b038..cc4b202eb3 100644 --- a/src/utils/urls/urlHelper.test.ts +++ b/src/utils/urls/urlHelper.test.ts @@ -5,29 +5,29 @@ import { makeUrlRelativeIfSameDomain, returnBaseUrlToAltinn, returnUrlToAllSchemas, - returnUrlToMessagebox, + returnUrlToMessageBox, returnUrlToProfile, } from 'src/utils/urls/urlHelper'; describe('Shared urlHelper.ts', () => { test('returnUrlToMessagebox() returning production messagebox', () => { const origin = 'https://tdd.apps.altinn.no/tdd/myappname'; - expect(returnUrlToMessagebox(origin)).toContain('altinn.no'); + expect(returnUrlToMessageBox(origin)).toContain('altinn.no'); }); test('returnUrlToMessagebox() returning at21 messagebox', () => { const origin = 'https://tdd.apps.at21.altinn.cloud/tdd/myappname'; - expect(returnUrlToMessagebox(origin)).toContain('at21.altinn.cloud'); + expect(returnUrlToMessageBox(origin)).toContain('at21.altinn.cloud'); }); test('returnUrlToMessagebox() returning tt02 messagebox', () => { const origin = 'https://tdd.apps.tt02.altinn.no/tdd/myappname'; - expect(returnUrlToMessagebox(origin)).toContain('tt02.altinn.no'); + expect(returnUrlToMessageBox(origin)).toContain('tt02.altinn.no'); }); test('returnUrlToMessagebox() returning null when unknown origin', () => { const origin = 'https://www.vg.no'; - expect(returnUrlToMessagebox(origin)).toBe(null); + expect(returnUrlToMessageBox(origin)).toBe(null); }); test('returnBaseUrlToAltinn() returning correct environemnts', () => { diff --git a/src/utils/urls/urlHelper.ts b/src/utils/urls/urlHelper.ts index 1e16f8a5a4..93b617e7cf 100644 --- a/src/utils/urls/urlHelper.ts +++ b/src/utils/urls/urlHelper.ts @@ -12,7 +12,7 @@ const prodRegex = new RegExp(baseHostnameAltinnProd); const testRegex = new RegExp(baseHostnameAltinnTest); const localRegex = new RegExp(baseHostnameAltinnLocal); -export const returnUrlToMessagebox = (url: string, partyId?: number | undefined): string | null => { +export const returnUrlToMessageBox = (url: string, partyId?: number | undefined): string | null => { const baseUrl = returnBaseUrlToAltinn(url); if (!baseUrl) { return null; From 340194a9c9487ffb70f2a06378b9f3ccb97cf82d Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Sun, 5 Jan 2025 13:41:10 +0100 Subject: [PATCH 05/27] makes panel full width by default and moves full width and conditional wrappers to app-components --- .../ConditionalWrapper.test.tsx | 2 +- .../ConditionalWrapper.tsx | 0 .../FullWidthWrapper.module.css | 0 .../FullWidthWrapper}/FullWidthWrapper.tsx | 2 +- src/app-components/Panel/Panel.tsx | 70 +++++++++++------ src/components/form/RadioButton.tsx | 2 +- src/components/message/ErrorReport.tsx | 76 +++++++++---------- src/features/pdf/PdfView2.tsx | 2 +- src/layout/Checkboxes/WrappedCheckbox.tsx | 2 +- src/layout/Dropdown/DropdownComponent.tsx | 2 +- .../FileUploadTable/FileTableButtons.tsx | 2 +- src/layout/Grid/GridComponent.tsx | 4 +- src/layout/Group/GroupComponent.tsx | 9 +-- src/layout/IFrame/IFrameComponent.tsx | 1 + .../MultipleSelectComponent.tsx | 2 +- src/layout/Panel/PanelComponent.tsx | 31 +++----- .../Container/RepeatingGroupContainer.tsx | 4 +- .../Pagination/RepeatingGroupPagination.tsx | 2 +- .../Table/RepeatingGroupTableRow.tsx | 2 +- .../SigningStatusPanel/SigningPanel.tsx | 37 +++++---- .../SigningStatusPanelComponent.tsx | 22 +++--- 21 files changed, 138 insertions(+), 136 deletions(-) rename src/{components => app-components/ConditionalWrapper}/ConditionalWrapper.test.tsx (93%) rename src/{components => app-components/ConditionalWrapper}/ConditionalWrapper.tsx (100%) rename src/{components/form => app-components/FullWidthWrapper}/FullWidthWrapper.module.css (100%) rename src/{components/form => app-components/FullWidthWrapper}/FullWidthWrapper.tsx (88%) diff --git a/src/components/ConditionalWrapper.test.tsx b/src/app-components/ConditionalWrapper/ConditionalWrapper.test.tsx similarity index 93% rename from src/components/ConditionalWrapper.test.tsx rename to src/app-components/ConditionalWrapper/ConditionalWrapper.test.tsx index 97948b3950..a3306062d1 100644 --- a/src/components/ConditionalWrapper.test.tsx +++ b/src/app-components/ConditionalWrapper/ConditionalWrapper.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { jest } from '@jest/globals'; import { render as rtlRender, screen } from '@testing-library/react'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; describe('ConditionalWrapper', () => { it('should pass children to wrapper callback when condition is true', () => { diff --git a/src/components/ConditionalWrapper.tsx b/src/app-components/ConditionalWrapper/ConditionalWrapper.tsx similarity index 100% rename from src/components/ConditionalWrapper.tsx rename to src/app-components/ConditionalWrapper/ConditionalWrapper.tsx diff --git a/src/components/form/FullWidthWrapper.module.css b/src/app-components/FullWidthWrapper/FullWidthWrapper.module.css similarity index 100% rename from src/components/form/FullWidthWrapper.module.css rename to src/app-components/FullWidthWrapper/FullWidthWrapper.module.css diff --git a/src/components/form/FullWidthWrapper.tsx b/src/app-components/FullWidthWrapper/FullWidthWrapper.tsx similarity index 88% rename from src/components/form/FullWidthWrapper.tsx rename to src/app-components/FullWidthWrapper/FullWidthWrapper.tsx index 239ad80d1e..0296ca6b13 100644 --- a/src/components/form/FullWidthWrapper.tsx +++ b/src/app-components/FullWidthWrapper/FullWidthWrapper.tsx @@ -3,7 +3,7 @@ import type { HTMLProps } from 'react'; import cn from 'classnames'; -import classes from 'src/components/form/FullWidthWrapper.module.css'; +import classes from 'src/app-components/FullWidthWrapper/FullWidthWrapper.module.css'; export interface IFullWidthWrapperProps extends HTMLProps { children?: React.ReactNode; diff --git a/src/app-components/Panel/Panel.tsx b/src/app-components/Panel/Panel.tsx index a3af820028..e3fe9a658a 100644 --- a/src/app-components/Panel/Panel.tsx +++ b/src/app-components/Panel/Panel.tsx @@ -10,6 +10,8 @@ import { } from '@navikt/aksel-icons'; import cn from 'classnames'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; +import { FullWidthWrapper } from 'src/app-components/FullWidthWrapper/FullWidthWrapper'; import { PANEL_VARIANT } from 'src/app-components/Panel/constants'; import classes from 'src/app-components/Panel/Panel.module.css'; import { useIsMobile } from 'src/hooks/useDeviceWidths'; @@ -22,6 +24,9 @@ export type PanelProps = PropsWithChildren<{ forceMobileLayout?: boolean; title?: JSX.Element; style?: React.CSSProperties; + fullWidth?: boolean; + isOnBottom?: boolean; + isOnTop?: boolean; }>; type PanelIconProps = { @@ -68,6 +73,9 @@ export const Panel: React.FC = ({ variant, showIcon = false, forceMobileLayout = false, + fullWidth = true, + isOnBottom, + isOnTop, title, style, children, @@ -76,34 +84,46 @@ export const Panel: React.FC = ({ const isMobileLayout = forceMobileLayout || isMobile; return ( -
    ( + + {child} + + )} > -
    - {showIcon && ( -
    - -
    - )} -
    - {title && ( - - {title} - +
    +
    + {showIcon && ( +
    + +
    )} -
    {children}
    +
    + {title && ( + + {title} + + )} +
    {children}
    +
    -
    + ); }; diff --git a/src/components/form/RadioButton.tsx b/src/components/form/RadioButton.tsx index d436884b70..29e569a324 100644 --- a/src/components/form/RadioButton.tsx +++ b/src/components/form/RadioButton.tsx @@ -4,7 +4,7 @@ import { HelpText, Radio } from '@digdir/designsystemet-react'; import cn from 'classnames'; import type { RadioProps } from '@digdir/designsystemet-react'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import classes from 'src/components/form/RadioButton.module.css'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; diff --git a/src/components/message/ErrorReport.tsx b/src/components/message/ErrorReport.tsx index 4d3f55f0ab..f2f6fc01a4 100644 --- a/src/components/message/ErrorReport.tsx +++ b/src/components/message/ErrorReport.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { Flex } from 'src/app-components/Flex/Flex'; import { PANEL_VARIANT } from 'src/app-components/Panel/constants'; import { Panel } from 'src/app-components/Panel/Panel'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import classes from 'src/components/message/ErrorReport.module.css'; import { useNavigateToNode } from 'src/features/form/layout/NavigateToNode'; import { Lang } from 'src/features/language/Lang'; @@ -33,50 +32,49 @@ export const ErrorReport = ({ renderIds, formErrors, taskErrors }: IErrorReportP return (
    - - } - variant={PANEL_VARIANT.Error} + } + variant={PANEL_VARIANT.Error} + isOnBottom + > + - -
      - {taskErrors.map((error) => ( -
    • - -
    • - ))} - {formErrors.map((error) => ( - + {taskErrors.map((error) => ( +
    • + - ))} -
    -
    - {renderIds.map((id) => ( - - ))} +
  • + ))} + {formErrors.map((error) => ( + + ))} + - - + {renderIds.map((id) => ( + + ))} + + ); }; diff --git a/src/features/pdf/PdfView2.tsx b/src/features/pdf/PdfView2.tsx index 106623f860..66c5fb906f 100644 --- a/src/features/pdf/PdfView2.tsx +++ b/src/features/pdf/PdfView2.tsx @@ -3,8 +3,8 @@ import type { PropsWithChildren } from 'react'; import { Heading } from '@digdir/designsystemet-react'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Flex } from 'src/app-components/Flex/Flex'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; import { OrganisationLogo } from 'src/components/presentation/OrganisationLogo/OrganisationLogo'; import { DummyPresentation } from 'src/components/presentation/Presentation'; import { ReadyForPrint } from 'src/components/ReadyForPrint'; diff --git a/src/layout/Checkboxes/WrappedCheckbox.tsx b/src/layout/Checkboxes/WrappedCheckbox.tsx index 6afeb8a134..80b27bacae 100644 --- a/src/layout/Checkboxes/WrappedCheckbox.tsx +++ b/src/layout/Checkboxes/WrappedCheckbox.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Checkbox, HelpText } from '@digdir/designsystemet-react'; import cn from 'classnames'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; import { Lang } from 'src/features/language/Lang'; diff --git a/src/layout/Dropdown/DropdownComponent.tsx b/src/layout/Dropdown/DropdownComponent.tsx index 11ea58e6d4..038cea534a 100644 --- a/src/layout/Dropdown/DropdownComponent.tsx +++ b/src/layout/Dropdown/DropdownComponent.tsx @@ -2,9 +2,9 @@ import React, { useCallback } from 'react'; import { Combobox } from '@digdir/designsystemet-react'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Label } from 'src/app-components/Label/Label'; import { AltinnSpinner } from 'src/components/AltinnSpinner'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; import { FD } from 'src/features/formData/FormDataWrite'; diff --git a/src/layout/FileUpload/FileUploadTable/FileTableButtons.tsx b/src/layout/FileUpload/FileUploadTable/FileTableButtons.tsx index 117b0c400c..01f757500c 100644 --- a/src/layout/FileUpload/FileUploadTable/FileTableButtons.tsx +++ b/src/layout/FileUpload/FileUploadTable/FileTableButtons.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { PencilIcon, TrashIcon } from '@navikt/aksel-icons'; import { Button } from 'src/app-components/Button/Button'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; import { isAttachmentUploaded } from 'src/features/attachments'; diff --git a/src/layout/Grid/GridComponent.tsx b/src/layout/Grid/GridComponent.tsx index 27b1c47ca5..2e8a832647 100644 --- a/src/layout/Grid/GridComponent.tsx +++ b/src/layout/Grid/GridComponent.tsx @@ -4,10 +4,10 @@ import type { PropsWithChildren } from 'react'; import { Table } from '@digdir/designsystemet-react'; import cn from 'classnames'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; +import { FullWidthWrapper } from 'src/app-components/FullWidthWrapper/FullWidthWrapper'; import { Fieldset } from 'src/app-components/Label/Fieldset'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; import { Caption } from 'src/components/form/caption/Caption'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import { HelpTextContainer } from 'src/components/form/HelpTextContainer'; import { LabelContent } from 'src/components/label/LabelContent'; import { Lang } from 'src/features/language/Lang'; diff --git a/src/layout/Group/GroupComponent.tsx b/src/layout/Group/GroupComponent.tsx index 15e62ad696..525cf21e27 100644 --- a/src/layout/Group/GroupComponent.tsx +++ b/src/layout/Group/GroupComponent.tsx @@ -4,10 +4,9 @@ import type { JSX } from 'react'; import { Heading } from '@digdir/designsystemet-react'; import cn from 'classnames'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Fieldset } from 'src/app-components/Label/Fieldset'; import { Panel } from 'src/app-components/Panel/Panel'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import { Lang } from 'src/features/language/Lang'; import classes from 'src/layout/Group/GroupComponent.module.css'; import { BaseLayoutNode } from 'src/utils/layout/LayoutNode'; @@ -65,11 +64,7 @@ export function GroupComponent({
    ( - - {child} - - )} + wrapper={(child) => {child}} >
    variant={PANEL_VARIANT.Error} showIcon={true} title={} + fullWidth={false} >

    diff --git a/src/layout/MultipleSelect/MultipleSelectComponent.tsx b/src/layout/MultipleSelect/MultipleSelectComponent.tsx index d9f80642ba..240b0702dc 100644 --- a/src/layout/MultipleSelect/MultipleSelectComponent.tsx +++ b/src/layout/MultipleSelect/MultipleSelectComponent.tsx @@ -2,9 +2,9 @@ import React, { useCallback } from 'react'; import { Combobox } from '@digdir/designsystemet-react'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Label } from 'src/app-components/Label/Label'; import { AltinnSpinner } from 'src/components/AltinnSpinner'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; import { getDescriptionId } from 'src/components/label/Label'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; diff --git a/src/layout/Panel/PanelComponent.tsx b/src/layout/Panel/PanelComponent.tsx index 6a5bf6979d..485ff66d31 100644 --- a/src/layout/Panel/PanelComponent.tsx +++ b/src/layout/Panel/PanelComponent.tsx @@ -2,8 +2,6 @@ import React from 'react'; import { PANEL_VARIANT } from 'src/app-components/Panel/constants'; import { Panel } from 'src/app-components/Panel/Panel'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import { Lang } from 'src/features/language/Lang'; import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper'; import { LayoutPage } from 'src/utils/layout/LayoutPage'; @@ -32,26 +30,17 @@ export const PanelComponent = ({ node }: IPanelProps) => { return ( - ( - - {child} - - )} + : undefined} + showIcon={showIcon ?? true} + variant={variant ?? PANEL_VARIANT.Info} + forceMobileLayout={!fullWidth} + isOnBottom={isOnBottom} + isOnTop={isOnTop} + fullWidth={fullWidth} > - : undefined} - showIcon={showIcon ?? true} - variant={variant ?? PANEL_VARIANT.Info} - forceMobileLayout={!fullWidth} - > - - - + + ); }; diff --git a/src/layout/RepeatingGroup/Container/RepeatingGroupContainer.tsx b/src/layout/RepeatingGroup/Container/RepeatingGroupContainer.tsx index 214db5e286..371c0eb692 100644 --- a/src/layout/RepeatingGroup/Container/RepeatingGroupContainer.tsx +++ b/src/layout/RepeatingGroup/Container/RepeatingGroupContainer.tsx @@ -4,10 +4,10 @@ import type { JSX } from 'react'; import { Add as AddIcon } from '@navikt/ds-icons'; import { Button } from 'src/app-components/Button/Button'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Flex } from 'src/app-components/Flex/Flex'; +import { FullWidthWrapper } from 'src/app-components/FullWidthWrapper/FullWidthWrapper'; import { Fieldset } from 'src/app-components/Label/Fieldset'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import { useLanguage } from 'src/features/language/useLanguage'; import { AllComponentValidations } from 'src/features/validation/ComponentValidations'; import { RepeatingGroupsEditContainer } from 'src/layout/RepeatingGroup/EditContainer/RepeatingGroupsEditContainer'; diff --git a/src/layout/RepeatingGroup/Pagination/RepeatingGroupPagination.tsx b/src/layout/RepeatingGroup/Pagination/RepeatingGroupPagination.tsx index 63e78b0985..af9d09e2f4 100644 --- a/src/layout/RepeatingGroup/Pagination/RepeatingGroupPagination.tsx +++ b/src/layout/RepeatingGroup/Pagination/RepeatingGroupPagination.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useMemo } from 'react'; import { Pagination, Table, usePagination } from '@digdir/designsystemet-react'; import { ChevronLeftIcon, ChevronRightIcon } from '@navikt/aksel-icons'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { useResetScrollPosition } from 'src/core/ui/useResetScrollPosition'; import { useLanguage } from 'src/features/language/useLanguage'; import { useIsMini, useIsMobile, useIsMobileOrTablet } from 'src/hooks/useDeviceWidths'; diff --git a/src/layout/RepeatingGroup/Table/RepeatingGroupTableRow.tsx b/src/layout/RepeatingGroup/Table/RepeatingGroupTableRow.tsx index dc3fdf5633..4e9a9e32da 100644 --- a/src/layout/RepeatingGroup/Table/RepeatingGroupTableRow.tsx +++ b/src/layout/RepeatingGroup/Table/RepeatingGroupTableRow.tsx @@ -6,8 +6,8 @@ import { Delete as DeleteIcon, Edit as EditIcon, ErrorColored as ErrorIcon } fro import cn from 'classnames'; import { Button } from 'src/app-components/Button/Button'; +import { ConditionalWrapper } from 'src/app-components/ConditionalWrapper/ConditionalWrapper'; import { Flex } from 'src/app-components/Flex/Flex'; -import { ConditionalWrapper } from 'src/components/ConditionalWrapper'; import { DeleteWarningPopover } from 'src/features/alertOnChange/DeleteWarningPopover'; import { useAlertOnChange } from 'src/features/alertOnChange/useAlertOnChange'; import { useDisplayDataProps } from 'src/features/displayData/useDisplayData'; diff --git a/src/layout/SigningStatusPanel/SigningPanel.tsx b/src/layout/SigningStatusPanel/SigningPanel.tsx index db05944653..46dfc96105 100644 --- a/src/layout/SigningStatusPanel/SigningPanel.tsx +++ b/src/layout/SigningStatusPanel/SigningPanel.tsx @@ -4,7 +4,6 @@ import type { PropsWithChildren, ReactElement } from 'react'; import { Heading } from '@digdir/designsystemet-react'; import { Panel } from 'src/app-components/Panel/Panel'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import type { Button } from 'src/app-components/Button/Button'; import type { PanelProps } from 'src/app-components/Panel/Panel'; @@ -25,23 +24,23 @@ export function SigningPanel({ children, }: PropsWithChildren) { return ( - - -

    - - {heading} - - {description &&

    {description}

    } - {children} -
    - {secondaryButton} - {actionButton} -
    -
    - - + + + {heading} + + {description &&

    {description}

    } + {children} +
    + {secondaryButton} + {actionButton} +
    +
    ); } diff --git a/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx b/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx index fead49ed58..251f4ca8a5 100644 --- a/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx +++ b/src/layout/SigningStatusPanel/SigningStatusPanelComponent.tsx @@ -6,7 +6,6 @@ import { useQuery } from '@tanstack/react-query'; import { Button } from 'src/app-components/Button/Button'; import { Panel } from 'src/app-components/Panel/Panel'; -import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper'; import { useIsAuthorised, useTaskTypeFromBackend } from 'src/features/instance/ProcessContext'; import { Lang } from 'src/features/language/Lang'; import { signeeListQuery } from 'src/layout/SigneeList/api'; @@ -35,16 +34,17 @@ export function SigningStatusPanelComponent({ node }: PropsFromGenericComponent< if (isLoading) { return ( - - -
    - -
    -
    -
    + +
    + +
    +
    ); } From f6275e74a59107f815859111119a69af7c5bb13d Mon Sep 17 00:00:00 2001 From: Camilla Marie Dalan Date: Thu, 9 Jan 2025 10:15:10 +0100 Subject: [PATCH 06/27] implements functions and cleans up code --- src/app-components/Button/Button.tsx | 24 ++-- .../AwaitingCurrentUserSignaturePanel.tsx | 61 ++++++---- .../SigningStatusPanel/GoToInboxPanel.tsx | 41 +++++++ .../SigningStatusPanel/SigningPanel.tsx | 32 +++-- .../SigningStatusPanelComponent.tsx | 110 ++---------------- src/layout/SigningStatusPanel/SubmitPanel.tsx | 70 +++++++++++ src/queries/queries.ts | 12 +- 7 files changed, 203 insertions(+), 147 deletions(-) create mode 100644 src/layout/SigningStatusPanel/GoToInboxPanel.tsx create mode 100644 src/layout/SigningStatusPanel/SubmitPanel.tsx diff --git a/src/app-components/Button/Button.tsx b/src/app-components/Button/Button.tsx index 8136814d1d..05e4fa6aba 100644 --- a/src/app-components/Button/Button.tsx +++ b/src/app-components/Button/Button.tsx @@ -34,6 +34,7 @@ export type ButtonProps = { | 'aria-labelledby' | 'aria-describedby' | 'onKeyUp' + | 'asChild' >; export const Button = forwardRef>(function Button( @@ -54,6 +55,7 @@ export const Button = forwardRef - {isLoading && ( -