Welcome to the CostCalculator flow docs
After installation, import the main CSS file in your application:
@import '@remoteoss/remote-flows/styles.css';import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
} from '@remoteoss/remote-flows';
import './css/main.css';
const estimationOptions = {
title: 'Estimate for a new company',
includeBenefits: true,
includeCostBreakdowns: true,
};
export function BasicCostCalculator() {
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFlow
estimationOptions={estimationOptions}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => console.log(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => console.log({ response })}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
/>
</RemoteFlows>
);
}import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
} from '@remoteoss/remote-flows';
import './css/main.css';
const estimationOptions = {
title: 'Estimate for a new company',
includeBenefits: true,
includeCostBreakdowns: true,
};
export function BasicCostCalculatorWithDefaultValues() {
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFlow
estimationOptions={estimationOptions}
defaultValues={{
countryRegionSlug: 'a1aea868-0e0a-4cd7-9b73-9941d92e5bbe', // it's the region slug from the v1/cost-calculator/countries, different in each env
currencySlug: 'eur-acf7d6b5-654a-449f-873f-aca61a280eba', // it's a currency slug from v1/company-currencies, different in each env
salary: '50000',
}}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => console.log(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => console.log({ response })}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
/>
</RemoteFlows>
);
}import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
} from '@remoteoss/remote-flows';
import './css/main.css';
const estimationOptions = {
title: 'Estimate for a new company',
includeBenefits: true,
includeCostBreakdowns: true,
};
export function BasicCostCalculatorLabels() {
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFlow
estimationOptions={estimationOptions}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => console.log(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => console.log({ response })}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
options={{
jsfModify: {
fields: {
country: {
title: 'Select your country',
},
age: {
title: 'Enter your age',
},
},
},
}}
/>
</RemoteFlows>
);
}import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
CostCalculatorResults,
} from '@remoteoss/remote-flows';
import type { CostCalculatorEstimateResponse } from '@remoteoss/remote-flows';
import { useState } from 'react';
import './css/main.css';
export function CostCalculatoWithResults() {
const [estimations, setEstimations] =
useState<CostCalculatorEstimateResponse | null>(null);
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFlow
estimationOptions={estimationOptions}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => console.log(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => setEstimations(response)}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
/>
{estimations && (
<CostCalculatorResults employmentData={estimations.data} />
)}
</RemoteFlows>
);
}import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
useCostCalculatorEstimationPdf,
buildCostCalculatorEstimationPayload,
} from '@remoteoss/remote-flows';
import type {
CostCalculatorEstimateResponse,
CostCalculatorEstimationSubmitValues,
} from '@remoteoss/remote-flows';
import { useState } from 'react';
import './css/main.css';
const estimationOptions = {
title: 'Estimate for a new company',
includeBenefits: true,
includeCostBreakdowns: true,
};
function CostCalculatorFormDemo() {
const [estimations, setEstimations] =
useState<CostCalculatorEstimateResponse | null>(null);
const [payload, setPayload] =
useState<CostCalculatorEstimationSubmitValues | null>(null);
const exportPdfMutation = useCostCalculatorEstimationPdf();
const handleExportPdf = () => {
if (payload) {
exportPdfMutation.mutate(buildCostCalculatorEstimationPayload(payload), {
onSuccess: (response) => {
if (response?.data?.data?.content !== undefined) {
const a = document.createElement('a');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
a.href = response.data.data.content as any;
a.download = 'estimation.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
},
onError: (error) => {
console.error({ error });
},
});
}
};
return (
<>
<CostCalculatorFlow
estimationOptions={estimationOptions}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => setPayload(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => {
setEstimations(response);
}}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
/>
{estimations && <button onClick={handleExportPdf}>Export as PDF</button>}
</>
);
}
export function CostCalculatorFlowWithExportPdf() {
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFormDemo />
</RemoteFlows>
);
}import {
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
CostCalculatorResetButton,
RemoteFlows,
useCostCalculatorEstimationPdf,
buildCostCalculatorEstimationPayload,
CostCalculatorResults,
} from '@remoteoss/remote-flows';
import type {
CostCalculatorEstimateResponse,
CostCalculatorEstimationSubmitValues,
} from '@remoteoss/remote-flows';
import './css/main.css';
import { useState } from 'react';
const estimationOptions = {
title: 'Estimate for a new company',
includeBenefits: true,
includeCostBreakdowns: true,
includePremiumBenefits: true,
};
function CostCalculatorFormDemo() {
const [estimations, setEstimations] =
useState<CostCalculatorEstimateResponse | null>(null);
const [payload, setPayload] =
useState<CostCalculatorEstimationSubmitValues | null>(null);
const exportPdfMutation = useCostCalculatorEstimationPdf();
const handleExportPdf = () => {
if (payload) {
exportPdfMutation.mutate(buildCostCalculatorEstimationPayload(payload), {
onSuccess: (response) => {
if (response?.data?.data?.content !== undefined) {
const a = document.createElement('a');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
a.href = response.data.data.content as any;
a.download = 'estimation.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
},
onError: (error) => {
console.error({ error });
},
});
}
};
return (
<>
<CostCalculatorFlow
estimationOptions={estimationOptions}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSubmit={(payload) => setPayload(payload)}
onError={(error) => console.error({ error })}
onSuccess={(response) => {
setEstimations(response);
}}
/>
<CostCalculatorSubmitButton>
Get estimate
</CostCalculatorSubmitButton>
<CostCalculatorResetButton>Reset</CostCalculatorResetButton>
</div>
);
}}
/>
{estimations && (
<CostCalculatorResults employmentData={estimations.data} />
)}
{estimations && <button onClick={handleExportPdf}>Export as PDF</button>}
</>
);
}
export function CostCalculatorFlowWithPremiumBenefits() {
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}))
.catch((error) => {
console.error({ error });
throw error;
});
};
return (
<RemoteFlows auth={() => fetchToken()}>
<CostCalculatorFormDemo />
</RemoteFlows>
);
}The CostCalculatorFlow component lets you render different components like CostCalculatorForm, CostCalculatorSubmitButton, CostCalculatorResetButton
| Prop | Type | Required | Description |
|---|---|---|---|
estimationParams |
object | No | Customization for the estimation response (see table below) |
defaultValues |
object | No | Predefined form values (see table below) |
render |
(costCalculatorBag: ReturnType<typeof useCostCalculator>) |
Yes | render prop function with the params passed by the useCostCalculator hook |
options |
{jsfModify: JSFModify} |
No | JSFModify options lets you modify properties from the form, such as changing the labels |
version |
'standard' | 'marketing' |
No | Controls payload structure. 'standard' includes annual_gross_salary, 'marketing' excludes it. Default: 'standard' |
| Property | Type | Description |
|---|---|---|
title |
string |
Custom title for the estimation report |
includeBenefits |
boolean |
If true, includes benefits information in the response |
includeCostBreakdowns |
boolean |
If true, includes detailed cost breakdowns in the response |
includePremiumBenefits |
boolean |
If true, includes detailed premium benefits in the response |
includeManagementFee |
boolean |
If true, includes the management fee in the response |
enableCurrencyConversion |
boolean |
If true, enables currency conversion in the salary field |
managementFees |
Record<CurrencyCode, number> |
Override the base management fees from the SDK |
showManagementFee |
boolean |
If true, includes the management fee in the input in the form |
The management fee behavior can be configured using a combination of includeManagementFee and showManagementFee:
- No management fees (hide completely):
{
includeManagementFee: false,
showManagementFeeInput: false
}This configuration excludes management fees from both the estimation results and the UI.
- Use Remote base prices (show in estimation only):
{
includeManagementFee: true,
showManagementFeeInput: false
}This configuration includes management fees in the estimation using Remote's base prices, but hides the management fee input field in the UI.
- Full management fee control:
{
includeManagementFee: true,
showManagementFeeInput: true
}This configuration shows management fees in both the estimation results and provides a UI input field for customizing the fee.
The version prop ('standard' | 'marketing') controls three key behaviors in the cost calculator:
-
Currency Conversion:
marketing: Usesno_spread(raw exchange rate without additional fees/risk margins)standard: Usesspread(includes forex risk and service fees in exchange rate)
-
UI/Form Elements:
marketing: Hiring budget radio option is hiddenstandard: Hiring budget radio option is shown
-
Salary Field Behavior:
- Marketing Version:
- Based on last input field used:
- If last input was in country's currency →
annual_gross_salary - If last input was in employer's currency →
annual_gross_salary_in_employer_currency
- If last input was in country's currency →
- Based on last input field used:
- Standard Version:
- When hiring budget is NOT selected:
- If country currency matches selected currency →
annual_gross_salary - If currencies are different →
annual_gross_salary_in_employer_currency
- If country currency matches selected currency →
- When hiring budget IS selected:
- If country currency matches selected currency →
annual_total_cost - If currencies are different →
annual_total_cost_in_employer_currency
- If country currency matches selected currency →
- When hiring budget is NOT selected:
- Marketing Version:
Note: The spread in currency conversion accounts for foreign exchange risk (currency fluctuations), transaction costs, and service fees.
| Property | Type | Description |
|---|---|---|
countryRegionSlug |
string |
Pre-selected country/region |
currencySlug |
string |
Pre-selected currency |
salary |
string |
Pre-filled salary amount |
hiringBudget |
string |
Pre-filled hiring budget |
The options.jsfModify props accepts the same props that the modify function from the json-schema-form library
It renders the form and the fields of the cost calculator
| Prop | Type | Required | Description |
|---|---|---|---|
onSubmit |
(payload: CostCalculatorEstimateParams) => void |
No | Callback with the form payload sent to Remote API. Runs before submitting the form to Remote |
onSuccess |
(response: CostCalculatorEstimateResponse) => void |
No | Callback with the successful estimation data |
onError |
(error: Error) => void |
No | Error handling callback |
shouldResetForm |
boolean |
No | If true, the form will be reset after a successful submission. Default is false. |
resetFields |
Array<'country' or 'currency' or 'salary'> |
No | Clears specified form fields. Pass field names as an array (e.g., ['country']). When resetting nested fields like 'country', related dependent fields (e.g., region) will also be cleared automatically. |
If used both at the same time, shouldResetForm will take precedence
It renders the submit button for the form and supports all standard <button> element props. This component must be used within the render prop of the CostCalculatorFlow component to ensure proper functionality
It renders the reset button for the form and supports all standard <button> element props. This component must be used within the render prop of the CostCalculatorFlow component to ensure proper functionality
The EstimationResults component displays detailed cost breakdown for a single estimation, including monthly and annual costs, benefits, employer contributions, and onboarding information.
| Prop | Type | Required | Description |
|---|---|---|---|
estimation |
CostCalculatorEstimation |
Yes | The estimation data object containing all cost details |
title |
string |
Yes | Display title for the estimation card |
components |
EstimationResultsComponents |
No | Custom component overrides for header, footer, and sections |
onDelete |
() => void |
Yes | Callback function triggered when delete action is selected |
onExportPdf |
() => void |
Yes | Callback function triggered when export to PDF action is selected |
onEdit |
() => void |
Yes | Callback function triggered when edit action is selected |
The components prop allows you to override default sections with custom implementations:
| Property | Type | Description |
|---|---|---|
HiringSection |
React.ComponentType<{ country, countryBenefitsUrl, countryGuideUrl }> |
Custom component for hiring information |
OnboardingTimeline |
React.ComponentType<{ minimumOnboardingDays, data }> |
Custom component for onboarding timeline |
Header |
React.ComponentType<{ title, region?, country, onDelete, onExportPdf }> |
Custom component for results header |
Footer |
React.ComponentType |
Custom component for results footer |
import {
RemoteFlows,
CostCalculatorFlow,
CostCalculatorForm,
CostCalculatorSubmitButton,
EstimationResults,
} from '@remoteoss/remote-flows';
import type { CostCalculatorEstimateResponse } from '@remoteoss/remote-flows';
import { useState } from 'react';
export function CostCalculatorWithEstimationResults() {
const [estimation, setEstimation] =
useState<CostCalculatorEstimateResponse | null>(null);
const fetchToken = () => {
return fetch('/api/token')
.then((res) => res.json())
.then((data) => ({
accessToken: data.access_token,
expiresIn: data.expires_in,
}));
};
const handleDelete = () => {
setEstimation(null);
};
const handleExportPdf = () => {
// Implement PDF export logic
console.log('Exporting PDF...');
};
const handleEdit = () => {
// Implement edit logic
console.log('Editing estimation...');
};
return (
<RemoteFlows auth={fetchToken}>
<CostCalculatorFlow
estimationOptions={{
title: 'Employee Cost Estimate',
includeBenefits: true,
includeCostBreakdowns: true,
}}
render={(props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<CostCalculatorForm
onSuccess={(response) => setEstimation(response)}
onError={(error) => console.error(error)}
/>
<CostCalculatorSubmitButton>Calculate</CostCalculatorSubmitButton>
</div>
);
}}
/>
{estimation && (
<EstimationResults
estimation={estimation.data}
title='Cost Estimate'
onDelete={handleDelete}
onExportPdf={handleExportPdf}
onEdit={handleEdit}
/>
)}
</RemoteFlows>
);
}The EstimationResults component displays:
- Header: Country flag, title, region (if applicable), and annual gross salary
- Actions Menu: Edit, Export, and Delete options
- Monthly Cost Breakdown:
- Gross monthly salary
- Mandatory employer costs (with detailed breakdown)
- Benefits (with detailed breakdown)
- Monthly management fee (if included)
- Annual Cost Breakdown:
- Gross annual salary
- Mandatory employer costs (with detailed breakdown)
- Benefits (with detailed breakdown)
- Extra statutory payments
- Annual management fee (if included)
- Onboarding Timeline: Step-by-step onboarding process with estimated days
- Hiring Section: Links to country-specific hiring guides and benefits
When the employer currency differs from the employee's regional currency, the component displays both currencies side by side:
- Employee currency: Cost in the country's local currency
- Employer currency: Cost converted to the employer's selected currency
You can override specific sections with custom components:
const CustomFooter = () => (
<div>
<p>Contact us for more details</p>
</div>
);
<EstimationResults
estimation={estimation.data}
title='Cost Estimate'
components={{
Footer: CustomFooter,
}}
onDelete={handleDelete}
onExportPdf={handleExportPdf}
onEdit={handleEdit}
/>;The component uses CSS classes prefixed with RemoteFlows__EstimationResults__* for custom styling. Key classes include:
RemoteFlows__EstimationResults__Card- Main containerRemoteFlows__EstimationResults__Header- Header sectionRemoteFlows__EstimationRow__Grid- Cost breakdown rowsRemoteFlows__BreakdownList- Nested cost details
A component that displays a comparative summary of costs across multiple estimations.
- Minimum Estimations: The component requires at least 2 estimations to render. It will return
nullif fewer estimations are provided. - Currency: All estimations must use the same currency (taken from the first estimation).
| Prop | Type | Description |
|---|---|---|
estimations |
CostCalculatorEstimation[] |
Array of employments to compare. Must contain at least 2 entries for the component to render. |
The useCostCalculator hook provides access to the underlying functionality of the cost calculator, allowing for custom implementations.
| Property | Type | Description |
|---|---|---|
stepState |
{ current: number; total: number; isLastStep: boolean } |
Information about the current step in multi-step forms |
fields |
Field[] |
Array of form field definitions with metadata (json-schema-form format) |
validationSchema |
yup.Schema |
Yup validation schema for the form |
handleValidation |
Function |
Function to handle custom field validation |
isSubmitting |
boolean |
Whether the form is currently submitting |
isLoading |
boolean |
Whether any required data is still loading |
onSubmit |
(values: CostCalculatorEstimationSubmitValues) => Promise<Result<CostCalculatorEstimateResponse, EstimationError>> |
Function to submit the form data to the Remote API |
resetForm |
Function |
Function that clears country and region selection state |
| Parameter | Type | Required | Description |
|---|---|---|---|
defaultRegion |
string |
No | Pre-selected region slug |
estimationOptions |
CostCalculatorEstimationOptions |
Yes | Options for the cost estimation (same as estimationParams in the CostCalculator component) |
The estimationOptions object has the following properties:
| Property | Type | Description |
|---|---|---|
title |
string |
Custom title for the estimation report |
includeBenefits |
boolean |
If true, includes benefits information in the response |
includeCostBreakdowns |
boolean |
If true, includes detailed cost breakdowns in the response |
includePremiumBenefits |
boolean |
If true, includes detailed premium benefits in the response |
includeManagementFee |
boolean |
If true, includes the management fee in the response |
enableCurrencyConversion |
boolean |
If true, enables currency conversion in the salary field |
managementFees |
Record<CurrencyCode, number> |
Override the base management fees from the SDK |
showManagementFee |
boolean |
If true, includes the management fee in the input in the form |