Skip to content

feat(onboarding) - add new step to select country #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
115 changes: 65 additions & 50 deletions example/src/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import {
EmploymentCreationResponse,
EmploymentResponse,
ContractDetailsFormPayload,
SelectCountrySuccess,
SelectCountryFormPayload,
} from '@remoteoss/remote-flows';
import './App.css';
import { useState } from 'react';

const STEPS = [
'Select Country',
'Basic Information',
'Contract Details',
'Benefits',
Expand All @@ -27,34 +30,33 @@ type MultiStepFormProps = {
function Review({
meta,
}: {
meta: Record<string, { label: string; prettyValue: string | boolean }>;
meta: Record<string, { label?: string; prettyValue?: string | boolean }>;
}) {
return (
<div className="onboarding-values">
{Object.values(meta)
.filter(Boolean)
.map((value) => {
if (!value.prettyValue) {
return Object.values(value)
.filter(Boolean)
.map((v) => {
const val = v as unknown as {
label: string;
prettyValue: string;
};
return (
<pre>
{val.label}: {value.prettyValue === true ? 'Yes' : 'No'}
</pre>
);
});
}
return (
<pre>
{value.label}: {value.prettyValue === true ? 'Yes' : 'No'}
</pre>
);
})}
{Object.entries(meta).map(([key, value]) => {
const label = value?.label;
const prettyValue = value?.prettyValue;

// Skip if there's no label or prettyValue is undefined or empty string
if (!label || prettyValue === undefined || prettyValue === '') {
return null;
}

// Handle boolean prettyValue
const displayValue =
typeof prettyValue === 'boolean'
? prettyValue
? 'Yes'
: 'No'
: prettyValue;

return (
<pre key={key}>
{label}: {displayValue}
</pre>
);
})}
</div>
);
}
Expand All @@ -67,10 +69,32 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => {
SubmitButton,
BackButton,
OnboardingInvite,
SelectCountryStep,
} = components;
const [apiError, setApiError] = useState<string | null>();

switch (onboardingBag.stepState.currentStep.name) {
case 'select_country':
return (
<>
<SelectCountryStep
onSubmit={(payload: SelectCountryFormPayload) =>
console.log('payload', payload)
}
onSuccess={(response: SelectCountrySuccess) =>
console.log('response', response)
}
onError={(error: Error) => setApiError(error.message)}
/>
<div className="buttons-container">
<SubmitButton
className="submit-button"
disabled={onboardingBag.isSubmitting}
>
Continue
</SubmitButton>
</div>
</>
);
case 'basic_information':
return (
<>
Expand All @@ -85,6 +109,12 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => {
/>
{apiError && <p className="error">{apiError}</p>}
<div className="buttons-container">
<BackButton
className="back-button"
onClick={() => setApiError(null)}
>
Previous Step
</BackButton>
<SubmitButton
className="submit-button"
disabled={onboardingBag.isSubmitting}
Expand Down Expand Up @@ -155,6 +185,14 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => {
case 'review':
return (
<div className="onboarding-review">
<h2 className="title">Select country</h2>
<Review meta={onboardingBag.meta.fields.select_country} />
<button
className="back-button"
onClick={() => onboardingBag.goTo('select_country')}
>
Edit Country
</button>
<h2 className="title">Basic Information</h2>
<Review meta={onboardingBag.meta.fields.basic_information} />
<button
Expand Down Expand Up @@ -247,19 +285,13 @@ const fetchToken = () => {
};

type OnboardingFormData = {
countryCode: string;
type: 'employee' | 'contractor';
employmentId: string;
};

const OnboardingWithProps = ({
countryCode,
type,
employmentId,
}: OnboardingFormData) => (
const OnboardingWithProps = ({ type, employmentId }: OnboardingFormData) => (
<RemoteFlows auth={fetchToken}>
<OnboardingFlow
countryCode={countryCode}
type={type}
render={OnBoardingRender}
employmentId={employmentId}
Expand All @@ -269,7 +301,6 @@ const OnboardingWithProps = ({

export const OnboardingForm = () => {
const [formData, setFormData] = useState<OnboardingFormData>({
countryCode: 'PRT',
type: 'employee',
employmentId: '',
});
Expand All @@ -286,22 +317,6 @@ export const OnboardingForm = () => {

return (
<form onSubmit={handleSubmit} className="onboarding-form-container">
<div className="onboarding-form-group">
<label htmlFor="countryCode" className="onboarding-form-label">
Country Code:
</label>
<input
id="countryCode"
type="text"
value={formData.countryCode}
onChange={(e) =>
setFormData((prev) => ({ ...prev, countryCode: e.target.value }))
}
required
placeholder="e.g. PRT"
className="onboarding-form-input"
/>
</div>
<div className="onboarding-form-group">
<label htmlFor="type" className="onboarding-form-label">
Type:
Expand Down
5 changes: 3 additions & 2 deletions src/flows/Onboarding/OnboardingFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { OnboardingFlowParams } from '@/src/flows/Onboarding/types';
import { OnboardingInvite } from '@/src/flows/Onboarding/OnboardingInvite';
import { ContractDetailsStep } from '@/src/flows/Onboarding/ContractDetailsStep';
import { BenefitsStep } from '@/src/flows/Onboarding/BenefitsStep';
import { SelectCountryStep } from '@/src/flows/Onboarding/SelectCountryStep';

export type OnboardingRenderProps = {
/**
Expand All @@ -33,6 +34,7 @@ export type OnboardingRenderProps = {
OnboardingInvite: typeof OnboardingInvite;
ContractDetailsStep: typeof ContractDetailsStep;
BenefitsStep: typeof BenefitsStep;
SelectCountryStep: typeof SelectCountryStep;
};
};

Expand All @@ -45,15 +47,13 @@ type OnboardingFlowProps = OnboardingFlowParams & {

export const OnboardingFlow = ({
employmentId,
countryCode,
type = 'employee',
render,
options,
}: OnboardingFlowProps) => {
const formId = useId();
const onboardingBag = useOnboarding({
employmentId,
countryCode,
type,
options,
});
Expand All @@ -74,6 +74,7 @@ export const OnboardingFlow = ({
SubmitButton: OnboardingSubmit,
BackButton: OnboardingBack,
OnboardingInvite: OnboardingInvite,
SelectCountryStep: SelectCountryStep,
},
})}
</OnboardingContext.Provider>
Expand Down
55 changes: 55 additions & 0 deletions src/flows/Onboarding/SelectCountryStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { useOnboardingContext } from './context';
import { OnboardingForm } from '@/src/flows/Onboarding/OnboardingForm';
import {
SelectCountryFormPayload,
SelectCountrySuccess,
} from '@/src/flows/Onboarding/types';
import { $TSFixMe } from '@remoteoss/json-schema-form';

type SelectCountryStepProps = {
/*
* The function is called when the form is submitted. It receives the form values as an argument.
*/
onSubmit?: (payload: SelectCountryFormPayload) => void | Promise<void>;
/*
* The function is called when the form submission is successful.
*/
onSuccess?: (data: SelectCountrySuccess) => void | Promise<void>;
/*
* The function is called when an error occurs during form submission.
*/
onError?: (error: Error) => void;
};

export function SelectCountryStep({
onSubmit,
onSuccess,
onError,
}: SelectCountryStepProps) {
const { onboardingBag } = useOnboardingContext();
const handleSubmit = async (payload: $TSFixMe) => {
try {
await onSubmit?.({ countryCode: payload.country });
const response = await onboardingBag.onSubmit(payload);
if (response?.data) {
await onSuccess?.(response?.data as SelectCountrySuccess);
onboardingBag?.next();
return;
}
if (response?.error) {
onError?.(response?.error);
}
} catch (error: unknown) {
onError?.(error as Error);
}
};

const initialValues =
onboardingBag.stepState.values?.select_country ||
onboardingBag.initialValues.select_country;

return (
<OnboardingForm defaultValues={initialValues} onSubmit={handleSubmit} />
);
}
Loading
Loading