Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
98d3c7a
pre-onboarding requirements
gabrielseco May 13, 2026
f49ff25
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 14, 2026
b7282c8
log things out
gabrielseco May 14, 2026
cb106d4
types generated
gabrielseco May 14, 2026
35d0b1d
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 14, 2026
8f903d4
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 15, 2026
8555005
generate provisional types
gabrielseco May 15, 2026
07fbf3a
integrate
gabrielseco May 15, 2026
e1245c2
fix api/eor
gabrielseco May 15, 2026
3db05c8
fix local script
gabrielseco May 15, 2026
29ba259
fix openapi local
gabrielseco May 15, 2026
6161652
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 18, 2026
32cd7b3
gen types
gabrielseco May 18, 2026
f026a82
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 18, 2026
7c5a9ae
fix create
gabrielseco May 18, 2026
d8603f8
fix create documnet
gabrielseco May 18, 2026
510e643
now sign work
gabrielseco May 19, 2026
1e2e4e0
render
gabrielseco May 19, 2026
3ec6c39
remove mocks
gabrielseco May 19, 2026
99060f2
add tests && mocks
gabrielseco May 19, 2026
fa522ce
add fix for local testing
gabrielseco May 19, 2026
25f6257
remove headers
gabrielseco May 19, 2026
76db117
use mutationToPromise
gabrielseco May 19, 2026
6dfd52e
fix tests
gabrielseco May 19, 2026
d126456
disable onboarding button when the requirements aren't signed
gabrielseco May 19, 2026
f4b66b0
fix tests
gabrielseco May 19, 2026
3cfe202
add tests
gabrielseco May 19, 2026
35ec892
Merge branch 'main' into pre-onboarding-requirements
gabrielseco May 21, 2026
78d16be
onboarding requirements demo
gabrielseco May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions example/src/ReviewOnboardingStep.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import {
CreditRiskStatus,
OnboardingRenderProps,
Expand All @@ -7,7 +8,26 @@ import {
NormalizedFieldError,
MetaValues,
NestedMeta,
PreOnboardingRequirementsBag,
} from '@remoteoss/remote-flows';
import {
FullScreenDialog,
FullScreenDialogContent,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
Button,
Checkbox,
Label,
Input,
Card,
CardHeader,
CardTitle,
CardContent,
CardDescription,
} from '@remoteoss/remote-flows/internals';
import { AlertError } from './AlertError';
import { OnboardingAlertStatuses } from './OnboardingAlertStatuses';

Expand Down Expand Up @@ -206,6 +226,269 @@ export const MyOnboardingInviteButton = ({
return null;
};

const SignatureDialog = ({
requirementName,
isOpen,
onClose,
onSign,
isSigning,
}: {
requirementName: string;
isOpen: boolean;
onClose: () => void;
onSign: (signature: string) => Promise<void>;
isSigning: boolean;
}) => {
const [signature, setSignature] = useState('');

const handleSign = async () => {
if (!signature.trim()) return;
await onSign(signature);
setSignature('');
};

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && signature.trim() && !isSigning) {
e.preventDefault();
handleSign();
}
};

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Sign {requirementName}</DialogTitle>
</DialogHeader>

<div className='py-4'>
<p className='text-sm text-muted-foreground mb-4'>
By typing your full name below, you agree to the terms and
conditions outlined in this document.
</p>

<div className='flex flex-col gap-2'>
<Label htmlFor='signature'>Your full name (signature)</Label>
<Input
id='signature'
type='text'
placeholder='Type your full name'
value={signature}
onChange={(e) => setSignature(e.target.value)}
onKeyDown={handleKeyDown}
disabled={isSigning}
autoComplete='name'
autoFocus
/>
</div>
</div>

<DialogFooter>
<Button variant='outline' onClick={onClose} disabled={isSigning}>
Cancel
</Button>
<Button
onClick={handleSign}
disabled={!signature.trim() || isSigning}
>
{isSigning ? 'Signing...' : 'Sign Document'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

const DocumentPreviewModal = ({
isOpen,
onClose,
documentPreview,
onSignDocument,
isSigning,
requirementName,
}: {
isOpen: boolean;
onClose: () => void;
documentPreview: PreOnboardingRequirementsBag['documentPreview'];
onSignDocument: PreOnboardingRequirementsBag['onSignDocument'];
isSigning: PreOnboardingRequirementsBag['isSigning'];
requirementName: string;
}) => {
const [isPdfLoading, setIsPdfLoading] = useState(true);
const [showSignatureDialog, setShowSignatureDialog] = useState(false);

const handleSignClick = () => {
setShowSignatureDialog(true);
};

const handleSign = async (signature: string) => {
await onSignDocument(signature);
setShowSignatureDialog(false);
onClose();
};

const basePdfUrl = documentPreview?.pre_onboarding_document?.content;
const pdfUrl = basePdfUrl ? `${basePdfUrl}#view=FitV&toolbar=0` : undefined;

return (
<>
<FullScreenDialog open={isOpen} onOpenChange={onClose}>
<FullScreenDialogContent>
{/* Header with Sign button */}
<div className='flex items-center justify-between px-6 py-4 border-b bg-white'>
<div className='flex items-center gap-4'>
<Button
variant='ghost'
size='icon'
onClick={onClose}
disabled={isSigning}
>
<svg
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<path d='M19 12H5M12 19l-7-7 7-7' />
</svg>
</Button>
<h2 className='text-lg font-semibold'>{requirementName}</h2>
</div>
<Button onClick={handleSignClick} disabled={isSigning}>
Sign Document
</Button>
</div>

{/* Full screen PDF viewer */}
<div className='flex-1 relative bg-gray-50 overflow-hidden'>
{isPdfLoading && (
<div className='absolute inset-0 flex items-center justify-center bg-white z-10'>
<div className='text-center'>
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-2'></div>
<p className='text-sm text-gray-600'>Loading document...</p>
</div>
</div>
)}
{pdfUrl && (
<iframe
src={pdfUrl}
className='w-full h-full border-0'
title='Document Preview'
onLoad={() => setIsPdfLoading(false)}
/>
)}
</div>
</FullScreenDialogContent>
</FullScreenDialog>

<SignatureDialog
requirementName={requirementName}
isOpen={showSignatureDialog}
onClose={() => setShowSignatureDialog(false)}
onSign={handleSign}
isSigning={isSigning}
/>
</>
);
};

const Requirement = ({
requirement,
onCreateDocument,
onSignDocument,
documentPreview,
isCreatingDocument,
isSigning,
employeeCountry,
}: {
requirement: NonNullable<
PreOnboardingRequirementsBag['requirements']
>[number];
onCreateDocument: PreOnboardingRequirementsBag['onCreateDocument'];
onSignDocument: PreOnboardingRequirementsBag['onSignDocument'];
documentPreview: PreOnboardingRequirementsBag['documentPreview'];
isCreatingDocument: PreOnboardingRequirementsBag['isCreatingDocument'];
isSigning: PreOnboardingRequirementsBag['isSigning'];
employeeCountry?: string;
}) => {
const [constraintsAckAt, setConstraintsAckAt] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);

const needsConstraintsAck =
requirement.needs_constraints_ack && !constraintsAckAt;

const handleReviewDocument = async () => {
if (!documentPreview) {
await onCreateDocument(requirement.slug, constraintsAckAt || undefined);
}
setIsModalOpen(true);
};

return (
<div className='flex flex-col gap-4'>
{requirement.needs_constraints_ack && (
<div className='flex items-start gap-2'>
<Checkbox
id={`ack-${requirement.slug}`}
disabled={requirement.status === 'finished'}
checked={!!constraintsAckAt || requirement.status === 'finished'}
onCheckedChange={(checked) =>
checked
? setConstraintsAckAt(new Date().toISOString())
: setConstraintsAckAt(null)
}
/>
<Label
htmlFor={`ack-${requirement.slug}`}
className='text-sm cursor-pointer'
>
I acknowledge that I've understood information about hiring in{' '}
{employeeCountry ?? 'this country'}, and that these new hire details
can't be changed once they're submitted.
</Label>
</div>
)}

<Card>
<CardHeader>
<CardTitle>{requirement.name}</CardTitle>
<CardDescription>{requirement.description}</CardDescription>
</CardHeader>
<CardContent>
<Button
onClick={handleReviewDocument}
disabled={
needsConstraintsAck ||
isCreatingDocument ||
requirement.status === 'finished'
}
>
{requirement.status === 'finished'
? 'Signed'
: isCreatingDocument
? 'Loading...'
: 'Review document'}
</Button>
</CardContent>
</Card>

<DocumentPreviewModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
documentPreview={documentPreview}
onSignDocument={onSignDocument}
isSigning={isSigning}
requirementName={requirement.name}
/>
</div>
);
};

export const ReviewOnboardingStep = ({
onboardingBag,
components,
Expand All @@ -227,6 +510,7 @@ export const ReviewOnboardingStep = ({
OnboardingInvite,
BackButton,
ReviewStep: ReviewStepCreditRisk,
PreOnboardingRequirements,
} = components;

return (
Expand Down Expand Up @@ -276,6 +560,47 @@ export const ReviewOnboardingStep = ({
>
Edit Benefits
</button>

<PreOnboardingRequirements
render={({
requirements,
isLoadingRequirements,
documentPreview,
onCreateDocument,
onSignDocument,
isCreatingDocument,
isSigning,
}) => (
<>
<h2 className='title'>Pre-Onboarding Requirements</h2>

<div className='flex flex-col gap-4'>
{isLoadingRequirements ? (
<p>Loading requirements...</p>
) : (
requirements?.map((req) => (
<Requirement
key={req.slug}
requirement={req}
onCreateDocument={onCreateDocument}
onSignDocument={onSignDocument}
documentPreview={documentPreview}
isCreatingDocument={isCreatingDocument}
isSigning={isSigning}
employeeCountry={
(
onboardingBag.employment?.basic_information
?.country as { name: string }
)?.name ?? undefined
}
/>
))
)}
</div>
</>
)}
/>

<h2 className='title'>Review</h2>
<ReviewStepCreditRisk
render={({
Expand Down
4 changes: 1 addition & 3 deletions src/client/client.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
override?: Config<ClientOptions & T>,
) => Config<Required<ClientOptions> & T>;

export const client = createClient(
createConfig<ClientOptions2>({ baseUrl: 'https://gateway.remote.com/' }),
);
export const client = createClient(createConfig<ClientOptions2>());
Loading
Loading