diff --git a/src/components/Employee/Dashboard/DashboardComponents.tsx b/src/components/Employee/Dashboard/DashboardComponents.tsx index cce0e73e8..71912ba59 100644 --- a/src/components/Employee/Dashboard/DashboardComponents.tsx +++ b/src/components/Employee/Dashboard/DashboardComponents.tsx @@ -2,6 +2,7 @@ import { Dashboard } from './Dashboard' import { HomeAddress } from '@/components/Employee/HomeAddress/management/HomeAddress' import { WorkAddress } from '@/components/Employee/WorkAddress/management/WorkAddress' import { FederalTaxes } from '@/components/Employee/FederalTaxes/management/FederalTaxes' +import { Profile } from '@/components/Employee/Profile/management/Profile' import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' import { ensureRequired } from '@/helpers/ensureRequired' @@ -28,3 +29,8 @@ export function FederalTaxesContextual() { const { employeeId, onEvent } = useFlow() return } + +export function ProfileContextual() { + const { employeeId, onEvent } = useFlow() + return +} diff --git a/src/components/Employee/Dashboard/dashboardStateMachine.ts b/src/components/Employee/Dashboard/dashboardStateMachine.ts index 14f14166f..10bd914c9 100644 --- a/src/components/Employee/Dashboard/dashboardStateMachine.ts +++ b/src/components/Employee/Dashboard/dashboardStateMachine.ts @@ -4,6 +4,7 @@ import { HomeAddressContextual, WorkAddressContextual, FederalTaxesContextual, + ProfileContextual, type DashboardContextInterface, } from './DashboardComponents' import { componentEvents } from '@/shared/constants' @@ -19,6 +20,17 @@ const returnToIndex = reduce( export const dashboardStateMachine = { index: state( + transition( + componentEvents.EMPLOYEE_UPDATE, + 'profile', + reduce( + (ctx: DashboardContextInterface): DashboardContextInterface => ({ + ...ctx, + component: ProfileContextual, + header: { type: 'minimal' }, + }), + ), + ), transition( componentEvents.EMPLOYEE_HOME_ADDRESS, 'homeAddress', @@ -59,4 +71,5 @@ export const dashboardStateMachine = { transition(componentEvents.CANCEL, 'index', returnToIndex), transition(componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE, 'index', returnToIndex), ), + profile: state(transition(componentEvents.CANCEL, 'index', returnToIndex)), } diff --git a/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx index f3300a992..6871034b3 100644 --- a/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx +++ b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx @@ -1,7 +1,7 @@ import type { PaymentMethodBankAccount } from '@gusto/embedded-api/models/components/paymentmethodbankaccount' import { FederalTaxes } from '../FederalTaxes/onboarding/FederalTaxes' import { StateTaxes } from '../StateTaxes/StateTaxes' -import type { ProfileDefaultValues } from '../Profile' +import type { ProfileDefaultValues } from '../Profile/onboarding/Profile' import type { CompensationDefaultValues } from '../Compensation' import { EmployeeList } from '../EmployeeList/onboarding/EmployeeList' import { ensureRequired } from '@/helpers/ensureRequired' diff --git a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts index 43bab8451..48ed11353 100644 --- a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts +++ b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts @@ -15,7 +15,7 @@ import { CompensationContextual } from '@/components/Employee/Compensation' import { DeductionsContextual } from '@/components/Employee/Deductions' import { EmployeeDocumentsContextual } from '@/components/Employee/EmployeeDocuments' import { PaymentMethodContextual } from '@/components/Employee/PaymentMethod' -import { ProfileContextual } from '@/components/Employee/Profile' +import { ProfileContextual } from '@/components/Employee/Profile/onboarding/Profile' import { OnboardingSummaryContextual } from '@/components/Employee/OnboardingSummary' type EventPayloads = { diff --git a/src/components/Employee/Profile/index.ts b/src/components/Employee/Profile/index.ts deleted file mode 100644 index ea36afc9c..000000000 --- a/src/components/Employee/Profile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Profile' diff --git a/src/components/Employee/Profile/management/Profile.module.scss b/src/components/Employee/Profile/management/Profile.module.scss new file mode 100644 index 000000000..5bfbb7c6f --- /dev/null +++ b/src/components/Employee/Profile/management/Profile.module.scss @@ -0,0 +1,3 @@ +.container { + width: 100%; +} diff --git a/src/components/Employee/Profile/management/Profile.tsx b/src/components/Employee/Profile/management/Profile.tsx new file mode 100644 index 000000000..a02868f79 --- /dev/null +++ b/src/components/Employee/Profile/management/Profile.tsx @@ -0,0 +1,143 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import classNames from 'classnames' +import { useEmployeeDetailsForm } from '../shared/useEmployeeDetailsForm' +import styles from './Profile.module.scss' +import { + BaseBoundaries, + BaseLayout, + type BaseComponentInterface, + type CommonComponentInterface, +} from '@/components/Base' +import { ActionsLayout } from '@/components/Common' +import { Form } from '@/components/Common/Form' +import { Grid } from '@/components/Common/Grid/Grid' +import { SDKFormProvider } from '@/partner-hook-utils/form/SDKFormProvider' +import { useI18n, useComponentDictionary } from '@/i18n' +import { componentEvents } from '@/shared/constants' +import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext' + +export interface ProfileProps extends CommonComponentInterface<'Employee.Profile'> { + employeeId: string + onEvent: BaseComponentInterface['onEvent'] +} + +export function Profile({ + FallbackComponent, + ...props +}: ProfileProps & Pick) { + return ( + + + + ) +} + +function ProfileRoot({ employeeId, className, dictionary, onEvent }: ProfileProps) { + useI18n('Employee.Profile') + useComponentDictionary('Employee.Profile', dictionary) + const { t } = useTranslation('Employee.Profile') + const Components = useComponentContext() + + const employeeDetails = useEmployeeDetailsForm({ + employeeId, + withSelfOnboardingField: false, + optionalFieldsToRequire: { + update: ['firstName', 'lastName', 'email', 'dateOfBirth', 'ssn'], + }, + }) + + const [showSuccess, setShowSuccess] = useState(false) + + if (employeeDetails.isLoading) { + return + } + + const Fields = employeeDetails.form.Fields + + const handleSubmit = async () => { + setShowSuccess(false) + const result = await employeeDetails.actions.onSubmit({ + onEmployeeUpdated: emp => { + onEvent(componentEvents.EMPLOYEE_UPDATED, emp) + }, + }) + if (!result) return + setShowSuccess(true) + } + + const handleCancel = () => { + onEvent(componentEvents.CANCEL) + } + + const alert = showSuccess ? ( + { + setShowSuccess(false) + }} + /> + ) : undefined + + return ( +
+ + +
+ {alert} + {t('title')} + + + + + + + + + + + {t('cancelCta')} + + + {t('saveCta')} + + + +
+
+
+ ) +} diff --git a/src/components/Employee/Profile/AdminProfile.module.scss b/src/components/Employee/Profile/onboarding/AdminProfile.module.scss similarity index 100% rename from src/components/Employee/Profile/AdminProfile.module.scss rename to src/components/Employee/Profile/onboarding/AdminProfile.module.scss diff --git a/src/components/Employee/Profile/AdminProfile.tsx b/src/components/Employee/Profile/onboarding/AdminProfile.tsx similarity index 96% rename from src/components/Employee/Profile/AdminProfile.tsx rename to src/components/Employee/Profile/onboarding/AdminProfile.tsx index 7e2952d32..55c63e104 100644 --- a/src/components/Employee/Profile/AdminProfile.tsx +++ b/src/components/Employee/Profile/onboarding/AdminProfile.tsx @@ -3,15 +3,15 @@ import { FormProvider, useForm, useWatch } from 'react-hook-form' import { Trans, useTranslation } from 'react-i18next' import classNames from 'classnames' import { type Employee } from '@gusto/embedded-api/models/components/employee' -import type { ProfileProps } from './Profile' +import { useEmployeeDetailsForm } from '../shared/useEmployeeDetailsForm' +import type { EmployeeDetailsOptionalFieldsToRequire } from '../shared/useEmployeeDetailsForm' +import { useCurrentHomeAddressForm } from '../shared/useHomeAddressForm' +import { useCurrentWorkAddressForm } from '../shared/useWorkAddressForm' +import type { UseEmployeeDetailsFormReady } from '../shared/useEmployeeDetailsForm' +import type { UseHomeAddressFormReady } from '../shared/useHomeAddressForm' +import type { UseWorkAddressFormReady } from '../shared/useWorkAddressForm' import styles from './AdminProfile.module.scss' -import { useEmployeeDetailsForm } from './shared/useEmployeeDetailsForm' -import type { EmployeeDetailsOptionalFieldsToRequire } from './shared/useEmployeeDetailsForm' -import { useCurrentHomeAddressForm } from './shared/useHomeAddressForm' -import { useCurrentWorkAddressForm } from './shared/useWorkAddressForm' -import type { UseEmployeeDetailsFormReady } from './shared/useEmployeeDetailsForm' -import type { UseHomeAddressFormReady } from './shared/useHomeAddressForm' -import type { UseWorkAddressFormReady } from './shared/useWorkAddressForm' +import type { ProfileProps } from './Profile' import { SDKFormProvider } from '@/partner-hook-utils/form/SDKFormProvider' import { composeSubmitHandler } from '@/partner-hook-utils/form/composeSubmitHandler' import { composeErrorHandler } from '@/partner-hook-utils/composeErrorHandler' diff --git a/src/components/Employee/Profile/EmployeeProfile.module.scss b/src/components/Employee/Profile/onboarding/EmployeeProfile.module.scss similarity index 100% rename from src/components/Employee/Profile/EmployeeProfile.module.scss rename to src/components/Employee/Profile/onboarding/EmployeeProfile.module.scss diff --git a/src/components/Employee/Profile/EmployeeProfile.tsx b/src/components/Employee/Profile/onboarding/EmployeeProfile.tsx similarity index 97% rename from src/components/Employee/Profile/EmployeeProfile.tsx rename to src/components/Employee/Profile/onboarding/EmployeeProfile.tsx index e36f3ea9d..49b1cd729 100644 --- a/src/components/Employee/Profile/EmployeeProfile.tsx +++ b/src/components/Employee/Profile/onboarding/EmployeeProfile.tsx @@ -2,11 +2,11 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import classNames from 'classnames' import { useEmployeeAddressesGetWorkAddresses } from '@gusto/embedded-api/react-query/employeeAddressesGetWorkAddresses' -import type { ProfileProps } from './Profile' +import { useEmployeeDetailsForm } from '../shared/useEmployeeDetailsForm' +import type { EmployeeDetailsOptionalFieldsToRequire } from '../shared/useEmployeeDetailsForm' +import { useCurrentHomeAddressForm } from '../shared/useHomeAddressForm' import styles from './EmployeeProfile.module.scss' -import { useEmployeeDetailsForm } from './shared/useEmployeeDetailsForm' -import type { EmployeeDetailsOptionalFieldsToRequire } from './shared/useEmployeeDetailsForm' -import { useCurrentHomeAddressForm } from './shared/useHomeAddressForm' +import type { ProfileProps } from './Profile' import { SDKFormProvider } from '@/partner-hook-utils/form/SDKFormProvider' import { composeSubmitHandler } from '@/partner-hook-utils/form/composeSubmitHandler' import { composeErrorHandler } from '@/partner-hook-utils/composeErrorHandler' diff --git a/src/components/Employee/Profile/Profile.test.tsx b/src/components/Employee/Profile/onboarding/Profile.test.tsx similarity index 100% rename from src/components/Employee/Profile/Profile.test.tsx rename to src/components/Employee/Profile/onboarding/Profile.test.tsx diff --git a/src/components/Employee/Profile/Profile.tsx b/src/components/Employee/Profile/onboarding/Profile.tsx similarity index 94% rename from src/components/Employee/Profile/Profile.tsx rename to src/components/Employee/Profile/onboarding/Profile.tsx index c3b325436..cd97e9f93 100644 --- a/src/components/Employee/Profile/Profile.tsx +++ b/src/components/Employee/Profile/onboarding/Profile.tsx @@ -1,4 +1,4 @@ -import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents' +import type { OnboardingContextInterface } from '../../OnboardingFlow/OnboardingFlowComponents' import { AdminProfile } from './AdminProfile' import { EmployeeProfile } from './EmployeeProfile' import { diff --git a/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.test.tsx b/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.test.tsx index 4911619bd..fddf485a4 100644 --- a/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.test.tsx +++ b/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.test.tsx @@ -387,7 +387,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', }), { wrapper: GustoTestProvider }, @@ -408,7 +407,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', }), { wrapper: GustoTestProvider }, @@ -433,7 +431,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', defaultValues: { selfOnboarding: true, @@ -461,7 +458,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', optionalFieldsToRequire: { create: ['email'], @@ -501,7 +497,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', optionalFieldsToRequire: { update: ['ssn'] }, }), @@ -536,7 +531,6 @@ describe('useEmployeeDetailsForm', () => { const { result } = renderHook( () => useEmployeeDetailsForm({ - companyId: 'company-1', employeeId: 'emp-1', optionalFieldsToRequire: { update: ['ssn'] }, }), diff --git a/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.tsx b/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.tsx index f9f0c4013..6bdc4019e 100644 --- a/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.tsx +++ b/src/components/Employee/Profile/shared/useEmployeeDetailsForm/useEmployeeDetailsForm.tsx @@ -46,9 +46,7 @@ export interface EmployeeDetailsSubmitCallbacks { onOnboardingStatusUpdated?: (status: unknown) => void } -export interface UseEmployeeDetailsFormProps { - companyId: string - employeeId?: string +type UseEmployeeDetailsFormSharedProps = { withSelfOnboardingField?: boolean optionalFieldsToRequire?: EmployeeDetailsOptionalFieldsToRequire defaultValues?: Partial @@ -56,6 +54,10 @@ export interface UseEmployeeDetailsFormProps { shouldFocusError?: boolean } +export type UseEmployeeDetailsFormProps = + | (UseEmployeeDetailsFormSharedProps & { companyId: string; employeeId?: never }) + | (UseEmployeeDetailsFormSharedProps & { employeeId: string; companyId?: string }) + export interface EmployeeDetailsFields { FirstName: typeof FirstNameField MiddleInitial: typeof MiddleInitialField @@ -182,6 +184,9 @@ export function useEmployeeDetailsForm({ let updatedEmployee: Employee if (isCreateMode) { + if (!companyId) { + throw new SDKInternalError('companyId is required to create an employee') + } const result = await createEmployeeMutation.mutateAsync({ request: { companyId, diff --git a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx index aba0894c6..9108c40ea 100644 --- a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx +++ b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx @@ -4,7 +4,7 @@ import type { FlowContextInterface } from '@/components/Flow/useFlow' import { useFlow } from '@/components/Flow/useFlow' import type { BaseComponentInterface } from '@/components/Base' import { Landing as LandingComponent } from '@/components/Employee/Landing' -import { Profile as ProfileComponent } from '@/components/Employee/Profile' +import { Profile as ProfileComponent } from '@/components/Employee/Profile/onboarding/Profile' import { FederalTaxes as FederalTaxesComponent } from '@/components/Employee/FederalTaxes/onboarding/FederalTaxes' import { StateTaxes as StateTaxesComponent } from '@/components/Employee/StateTaxes' import { PaymentMethod as PaymentMethodComponent } from '@/components/Employee/PaymentMethod' diff --git a/src/components/Employee/exports/employeeManagement.ts b/src/components/Employee/exports/employeeManagement.ts index d1709fa3e..2e8f8aa01 100644 --- a/src/components/Employee/exports/employeeManagement.ts +++ b/src/components/Employee/exports/employeeManagement.ts @@ -4,6 +4,7 @@ export { DashboardFlow } from '../Dashboard' export { WorkAddress } from '../WorkAddress/management/WorkAddress' export type { WorkAddressProps } from '../WorkAddress/management/WorkAddress' export { FederalTaxes, type FederalTaxesProps } from '../FederalTaxes/management/FederalTaxes' +export { Profile, type ProfileProps } from '../Profile/management/Profile' export { TerminateEmployee } from '../Terminations/TerminateEmployee/TerminateEmployee' export { TerminationSummary } from '../Terminations/TerminationSummary/TerminationSummary' export { TerminationFlow } from '../Terminations/TerminationFlow/TerminationFlow' diff --git a/src/components/Employee/exports/employeeOnboarding.ts b/src/components/Employee/exports/employeeOnboarding.ts index 9d8cd741c..68538289f 100644 --- a/src/components/Employee/exports/employeeOnboarding.ts +++ b/src/components/Employee/exports/employeeOnboarding.ts @@ -6,7 +6,7 @@ export { Landing } from '../Landing' export { DocumentSigner } from '../DocumentSigner' export { EmploymentEligibility } from '../DocumentSigner/EmploymentEligibility' -export { Profile } from '../Profile' +export { Profile } from '../Profile/onboarding/Profile' export { Compensation } from '../Compensation' export { FederalTaxes, type FederalTaxesProps } from '../FederalTaxes/onboarding/FederalTaxes' export { StateTaxes } from '../StateTaxes' diff --git a/src/components/Employee/index.ts b/src/components/Employee/index.ts index d2db480b7..8942ff368 100644 --- a/src/components/Employee/index.ts +++ b/src/components/Employee/index.ts @@ -1,7 +1,7 @@ export { EmployeeList } from './EmployeeList/onboarding/EmployeeList' export { Deductions } from './Deductions' export { OnboardingSummary } from './OnboardingSummary' -export { Profile } from './Profile' +export { Profile } from './Profile/onboarding/Profile' export { Compensation } from './Compensation' export { FederalTaxes } from './FederalTaxes' export type { FederalTaxesProps } from './FederalTaxes' diff --git a/src/i18n/en/Employee.Profile.json b/src/i18n/en/Employee.Profile.json index 5677c4dbd..b8e5f1e4b 100644 --- a/src/i18n/en/Employee.Profile.json +++ b/src/i18n/en/Employee.Profile.json @@ -15,7 +15,9 @@ "ssnMask": "***-**-****", "startDateDescription": "Your employee’s first day of work at your company.", "startDateLabel": "Start date", + "saveCta": "Save", "submitCta": "Continue", + "successAlert": "Successfully updated profile", "title": "Basics", "validations": { "email": "Valid email is required", diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts index e4a8353f7..30e0c4d5a 100644 --- a/src/types/i18next.d.ts +++ b/src/types/i18next.d.ts @@ -2046,7 +2046,9 @@ export interface EmployeeProfile{ "ssnMask":string; "startDateDescription":string; "startDateLabel":string; +"saveCta":string; "submitCta":string; +"successAlert":string; "title":string; "validations":{ "email":string;