Skip to content

Commit

Permalink
feat: add company document signer state machine
Browse files Browse the repository at this point in the history
  • Loading branch information
serikjensen committed Feb 25, 2025
1 parent b2dac30 commit 8d0e1c2
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 18 deletions.
29 changes: 29 additions & 0 deletions src/components/Company/DocumentSigner/DocumentSigner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createMachine } from 'robot3'
import { Flow } from '@/components/Flow/Flow'
import type { BaseComponentInterface } from '@/components/Base'

import {
documentSignerMachine,
AssignSignatory,
type DocumentSignerContextInterface,
} from './documentSignerStateMachine'

export interface DocumentSignerProps extends BaseComponentInterface {
companyId: string
signatoryId?: string
}

export const DocumentSigner = ({ companyId, signatoryId, onEvent }: DocumentSignerProps) => {
const manageEmployees = createMachine(
'index',
documentSignerMachine,
(initialContext: DocumentSignerContextInterface) => ({
...initialContext,
component: AssignSignatory,
isSelfSignatory: false,
companyId,
signatoryId,
}),
)
return <Flow machine={manageEmployees} onEvent={onEvent} />
}
150 changes: 150 additions & 0 deletions src/components/Company/DocumentSigner/documentSignerStateMachine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { transition, reduce, state } from 'robot3'
import { companyEvents } from '@/shared/constants'

import * as Company from '@/components/Company'
import { useFlowParams, type UseFlowParamsProps } from '@/components/Flow/hooks/useFlowParams'
import { Schemas } from '@/types/schema'
import { FlowContextInterface } from '@/components/Flow'
import { type MachineEventType } from '@/types/Helpers'

type EventPayloads = {
[companyEvents.COMPANY_VIEW_FORM_TO_SIGN]: Schemas['Form']
[companyEvents.COMPANY_SIGNATORY_INVITED]: Schemas['Signatory']
[companyEvents.COMPANY_SIGNATORY_CREATED]: Schemas['Signatory']
[companyEvents.COMPANY_SIGNATORY_UPDATED]: Schemas['Signatory']
}

export interface DocumentSignerContextInterface extends FlowContextInterface {
companyId: string
signatoryId?: string
form?: Schemas['Form']
}

function useDocumentSignerFlowParams(props: UseFlowParamsProps<DocumentSignerContextInterface>) {
return useFlowParams(props)
}

export function AssignSignatory() {
const { companyId, signatoryId, onEvent } = useDocumentSignerFlowParams({
component: 'AssignSignatory',
requiredParams: ['companyId'],
})
return (
<Company.AssignSignatory companyId={companyId} signatoryId={signatoryId} onEvent={onEvent} />
)
}

export function DocumentList() {
const { companyId, signatoryId, onEvent } = useDocumentSignerFlowParams({
component: 'DocumentList',
requiredParams: ['companyId'],
})

return <Company.DocumentList companyId={companyId} signatoryId={signatoryId} onEvent={onEvent} />
}

export function SignatureForm() {
const { companyId, form, onEvent } = useDocumentSignerFlowParams({
component: 'SignatureForm',
requiredParams: ['companyId', 'form'],
})

return <Company.SignatureForm companyId={companyId} form={form} onEvent={onEvent} />
}

const assignSignatoryState = state(
transition(
companyEvents.COMPANY_SIGNATORY_INVITED,
'documentList',
reduce(
(
ctx: DocumentSignerContextInterface,
ev: MachineEventType<EventPayloads, typeof companyEvents.COMPANY_SIGNATORY_INVITED>,
): DocumentSignerContextInterface => ({
...ctx,
component: DocumentList,
}),
),
),
transition(
companyEvents.COMPANY_SIGNATORY_CREATED,
'documentList',
reduce(
(
ctx: DocumentSignerContextInterface,
ev: MachineEventType<EventPayloads, typeof companyEvents.COMPANY_SIGNATORY_CREATED>,
): DocumentSignerContextInterface => ({
...ctx,
signatoryId: ev.payload.uuid,
component: DocumentList,
}),
),
),
transition(
companyEvents.COMPANY_SIGNATORY_UPDATED,
'documentList',
reduce(
(
ctx: DocumentSignerContextInterface,
ev: MachineEventType<EventPayloads, typeof companyEvents.COMPANY_SIGNATORY_UPDATED>,
): DocumentSignerContextInterface => ({
...ctx,
signatoryId: ev.payload.uuid,
component: DocumentList,
}),
),
),
)

export const documentSignerMachine = {
index: assignSignatoryState,
documentList: state(
transition(
companyEvents.COMPANY_VIEW_FORM_TO_SIGN,
'signatureForm',
reduce(
(
ctx: DocumentSignerContextInterface,
ev: MachineEventType<EventPayloads, typeof companyEvents.COMPANY_VIEW_FORM_TO_SIGN>,
): DocumentSignerContextInterface => ({
...ctx,
form: ev.payload,
component: SignatureForm,
}),
),
),
transition(
companyEvents.COMPANY_FORM_EDIT_SIGNATORY,
'assignSignatory',
reduce(
(ctx: DocumentSignerContextInterface): DocumentSignerContextInterface => ({
...ctx,
component: AssignSignatory,
}),
),
),
),
assignSignatory: assignSignatoryState,
signatureForm: state(
transition(
companyEvents.COMPANY_SIGN_FORM_DONE,
'documentList',
reduce(
(ctx: DocumentSignerContextInterface): DocumentSignerContextInterface => ({
...ctx,
component: DocumentList,
}),
),
),
transition(
companyEvents.COMPANY_SIGN_FORM_BACK,
'documentList',
reduce(
(ctx: DocumentSignerContextInterface): DocumentSignerContextInterface => ({
...ctx,
component: DocumentList,
}),
),
),
),
}
1 change: 1 addition & 0 deletions src/components/Company/DocumentSigner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DocumentSigner } from './DocumentSigner'
1 change: 1 addition & 0 deletions src/components/Company/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export { Industry } from './Industry'
export { AssignSignatory } from './AssignSignatory'
export { DocumentList } from './DocumentSigner/DocumentList'
export { SignatureForm } from './DocumentSigner/SignatureForm'
export { DocumentSigner } from './DocumentSigner'
export { OnboardingSummary, OnboardingSummaryContextual } from './OnboardingSummary'
10 changes: 3 additions & 7 deletions src/components/Flow/StateMachines/employeeOnboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@/shared/constants'
import type { EmployeeOnboardingContextInterface } from '@/components/Flow/EmployeeOnboardingFlow'
import { SDKI18next } from '@/contexts'
import { type MachineEventType } from '@/types/Helpers'

type EventPayloads = {
[componentEvents.EMPLOYEE_UPDATE]: {
Expand All @@ -28,11 +29,6 @@ type EventPayloads = {
}
}

type MachineEventType<T extends keyof EventPayloads = keyof EventPayloads> = {
type: T
payload: EventPayloads[T]
}

const cancelTransition = (target: string, component?: React.ComponentType) =>
transition(
componentEvents.CANCEL,
Expand Down Expand Up @@ -75,7 +71,7 @@ export const employeeOnboardingMachine = {
reduce(
(
ctx: EmployeeOnboardingContextInterface,
ev: MachineEventType<typeof componentEvents.EMPLOYEE_UPDATE>,
ev: MachineEventType<EventPayloads, typeof componentEvents.EMPLOYEE_UPDATE>,
): EmployeeOnboardingContextInterface => {
return {
...ctx,
Expand All @@ -95,7 +91,7 @@ export const employeeOnboardingMachine = {
reduce(
(
ctx: EmployeeOnboardingContextInterface,
ev: MachineEventType<typeof componentEvents.EMPLOYEE_PROFILE_DONE>,
ev: MachineEventType<EventPayloads, typeof componentEvents.EMPLOYEE_PROFILE_DONE>,
): EmployeeOnboardingContextInterface => ({
...ctx,
component: CompensationContextual,
Expand Down
28 changes: 17 additions & 11 deletions src/components/Flow/hooks/useFlowParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ export interface UseFlowParamsProps<TFlowContext> {
requiredParams?: Array<keyof TFlowContext>
}

function hasRequiredParams<TParams extends object, TRequiredParams extends keyof TParams>(
params: TParams,
requiredParams: TRequiredParams[],
): params is TParams & Required<Pick<TParams, TRequiredParams>> {
return requiredParams.every(param => param in params && typeof params[param] !== 'undefined')
}

export function useFlowParams<TFlowContext extends FlowContextInterface>({
component,
requiredParams = [],
}: UseFlowParamsProps<TFlowContext>) {
const params = useFlow<TFlowContext>()
const { t } = useTranslation('common')

requiredParams.forEach(param => {
if (!(param in params)) {
throw new Error(
t('errors.missingParamsOrContext', {
param,
component,
provider: 'FlowProvider',
}),
)
}
})
if (!hasRequiredParams(params, requiredParams)) {
const missingParam = requiredParams.find(param => typeof params[param] === 'undefined')
throw new Error(
t('errors.missingParamsOrContext', {
param: missingParam,
component,
provider: 'FlowProvider',
}),
)
}

return params
}
8 changes: 8 additions & 0 deletions src/types/Helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ export type DeepPartial<T> = {
export type RequireAtLeastOne<T> = {
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>
}[keyof T]

export type MachineEventType<
TEventPayloads,
TEventType extends keyof TEventPayloads = keyof TEventPayloads,
> = {
type: TEventType
payload: TEventPayloads[TEventType]
}

0 comments on commit 8d0e1c2

Please sign in to comment.