Skip to content
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

Add text customization to porting embed #9

Merged
merged 1 commit into from Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/README.md
@@ -1,4 +1,4 @@
# Docs

- [README](README.md)
- [Porting Embed](docs/porting-embed.md)
- [README](../README.md)
- [Porting Embed](./porting-embed.md)
53 changes: 52 additions & 1 deletion docs/porting-embed.md
Expand Up @@ -54,6 +54,7 @@ embed.on('completed', ({ porting }) => {
- [Show UI depending on the current step](#show-ui-depending-on-the-current-step)
- [Use the included styling](#use-the-included-styling)
- [CSS customization](#css-customization)
- [Translations and text customization](#translations-and-text-customization)
- [Continue after all fields were filled out](#continue-after-all-fields-were-filled-out)
- [Show a loading state while the form is submitted](#show-a-loading-state-while-the-form-is-submitted)
- [Reacting to validation changes](#reacting-to-validation-changes)
Expand All @@ -77,6 +78,7 @@ embed.on('completed', ({ porting }) => {
- [Values](#values)
- [Step](#step)
- [Field Names](#field-names)
- [Text](#text)

## Usage

Expand Down Expand Up @@ -156,6 +158,25 @@ See also:
- [Step](#step) for the list of steps.
- [Field Names](#field-names) for the list of field names.

### Translations and text customization

The embed ships with default text in english, but you can override any text that is rendered by the embed, if you want to support a different language or customize the text otherwise.

```js
const embed = await PortingEmbed(session, {
options: {
text: {
'field.firstName.label': 'Vorname',
'field.firstName.error.required': 'Muss ausgefüllt werden',
'field.lastName.label': 'Nachname',
'field.lastName.error.required': 'Muss ausgefüllt werden',
'field.birthday.label': 'Geburtsdatum',
// ...
}
}
})
```

### Continue after all fields were filled out

When the user entered all fields in all steps of the wizard, the embed will not render any UI anymore. You can handle this in your app by listening for the `completed` event.
Expand Down Expand Up @@ -456,4 +477,34 @@ Each field in the a step has a name. If a field is not required by a porting, it
- `state` (text)
- `country` (text)
- Form: `donorProviderApproval`
- `donorProviderApproval` (checkbox)
- `donorProviderApproval` (checkbox)

#### Text

| Key | Default |
| - | - |
| `field.accountNumber.label` | Account Number |
| `field.accountNumber.error.required` | The account number is required |
| `field.accountPin.label` | Account PIN |
| `field.accountPin.error.required` | The account pin is required |
| `field.accountPin.error.cleared` | The new account pin is empty. If you do not want to change the account pin, clear the input. |
| `field.firstName.label` | First Name |
| `field.firstName.error.required` | Your first name is required |
| `field.lastName.label` | Last Name |
| `field.lastName.error.required` | Your last name is required |
| `field.birthday.label` | Birthday |
| `field.birthday.error.required` | Your birthday is required |
| `field.line1.label` | Line 1 |
| `field.line1.error.required` | Line 1 is required |
| `field.line2.label` | Line 2 |
| `field.city.label` | City |
| `field.city.error.required` | City is required |
| `field.postalCode.label` | Postal Code |
| `field.postalCode.error.required` | Postal Code is required |
| `field.state.label` | State (ISO code) |
| `field.state.error.format` | Must be an ISO state code |
| `field.country.label` | Country (2 letter code) |
| `field.country.error.required` | Country is required |
| `field.country.error.format` | Must be an ISO country code |
| `field.donorProviderApproval.label` | I have notified my current provider of the number porting and got the approval that the number can be ported |
| `field.donorProviderApproval.error.required` | You must get the approval of your current provider |
40 changes: 39 additions & 1 deletion lib/PortingEmbed/Options.tsx
Expand Up @@ -18,6 +18,34 @@ type FormState = {

export const defaultFormId = 'gigsPortingEmbedForm'

const defaultText = {
'field.accountNumber.label': `Account Number`,
'field.accountNumber.error.required': `The account number is required`,
'field.accountPin.label': `Account PIN`,
'field.accountPin.error.required': `The account pin is required`,
'field.accountPin.error.cleared': `The new account pin is empty. If you do not want to change the account pin, clear the input.`,
'field.firstName.label': `First Name`,
'field.firstName.error.required': `Your first name is required`,
'field.lastName.label': `Last Name`,
'field.lastName.error.required': `Your last name is required`,
'field.birthday.label': `Birthday`,
'field.birthday.error.required': `Your birthday is required`,
'field.line1.label': `Line 1`,
'field.line1.error.required': `Line 1 is required`,
'field.line2.label': `Line 2`,
'field.city.label': `City`,
'field.city.error.required': `City is required`,
'field.postalCode.label': `Postal Code`,
'field.postalCode.error.required': `Postal Code is required`,
'field.state.label': `State (ISO code)`,
'field.state.error.format': `Must be an ISO state code`,
'field.country.label': `Country (2 letter code)`,
'field.country.error.required': `Country is required`,
'field.country.error.format': `Must be an ISO country code`,
'field.donorProviderApproval.label': `I have notified my current provider of the number porting and got the approval that the number can be ported`,
'field.donorProviderApproval.error.required': `You must get the approval of your current provider`,
}

export type EmbedOptions = {
formId?: string
className?: {
Expand All @@ -27,10 +55,20 @@ export type EmbedOptions = {
label?: (state: FieldState) => string
error?: (state: Omit<FieldState, 'valid'>) => string
}
text?: Partial<typeof defaultText>
}

export const OptionsContext = createContext<EmbedOptions>({})

export function useEmbedOptions() {
return useContext(OptionsContext)
const options = useContext(OptionsContext)
const mergedOptions = {
...options,
text: {
...defaultText,
...options.text,
},
}

return mergedOptions
}
34 changes: 22 additions & 12 deletions lib/PortingEmbed/StepAddressForm.tsx
Expand Up @@ -80,12 +80,14 @@ export function StepAddressForm({
>
<Field
name="line1"
validate={[required('Line 1 is required')]}
validate={[required(options.text['field.line1.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Line 1</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.line1.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -100,7 +102,9 @@ export function StepAddressForm({
<Field name="line2" transform={toTrimmed({ on: 'input' })}>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Line 2</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.line2.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -113,12 +117,14 @@ export function StepAddressForm({
</Field>
<Field
name="city"
validate={[required('City is required')]}
validate={[required(options.text['field.city.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>City</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.city.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -132,12 +138,14 @@ export function StepAddressForm({
</Field>
<Field
name="postalCode"
validate={[required('Postal Code is required')]}
validate={[required(options.text['field.postalCode.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Postal Code</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.postalCode.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -153,13 +161,15 @@ export function StepAddressForm({
name="state"
validate={pattern(
/^[A-Z]{1,3}(-[A-Z0-9]{1,3})?$/,
'Must be an ISO state code',
options.text['field.state.error.format'],
)}
transform={[toTrimmed({ on: 'input' }), toUpperCase({ on: 'input' })]}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>State (ISO code)</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.state.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -174,15 +184,15 @@ export function StepAddressForm({
<Field
name="country"
validate={[
required('Country is required'),
pattern(/^[A-Z]{2}$/, 'Must be an iso country code'),
required(options.text['field.country.error.required']),
pattern(/^[A-Z]{2}$/, options.text['field.country.error.format']),
]}
transform={[toTrimmed({ on: 'input' }), toUpperCase({ on: 'input' })]}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>
Country (2 letter code)
{options.text['field.country.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
Expand Down
16 changes: 11 additions & 5 deletions lib/PortingEmbed/StepCarrierDetailsForm.tsx
Expand Up @@ -72,7 +72,7 @@ export function StepCarrierDetailsForm({
setError(
form,
'accountPin',
'The new account pin is empty. If you do not want to change the account pin, clear the input.',
options.text['field.accountPin.error.cleared'],
)
return
}
Expand All @@ -87,12 +87,16 @@ export function StepCarrierDetailsForm({
{porting.required.includes('accountNumber') && (
<Field
name="accountNumber"
validate={[required('The account number is required')]}
validate={[
required(options.text['field.accountNumber.error.required']),
]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Account Number</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.accountNumber.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -111,12 +115,14 @@ export function StepCarrierDetailsForm({
validate={
porting.accountPinExists
? []
: [required('The account pin is required')]
: [required(options.text['field.accountPin.error.required'])]
}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Account PIN</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.accountPin.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand Down
5 changes: 2 additions & 3 deletions lib/PortingEmbed/StepDonorProviderApprovalForm.tsx
Expand Up @@ -61,7 +61,7 @@ export function StepDonorProviderApprovalForm({
name="donorProviderApproval"
type="boolean"
validate={[
required('You must get the approval of your current provider'),
required(options.text['field.donorProviderApproval.error.required']),
]}
>
{(field, props) => (
Expand All @@ -74,8 +74,7 @@ export function StepDonorProviderApprovalForm({
checked={field.value}
/>
<EmbedFieldLabel of={field}>
I have notified my current provider of the number porting and
got the approval that the number can be ported
{options.text['field.donorProviderApproval.label']}
</EmbedFieldLabel>
</div>
<EmbedFieldError of={field} />
Expand Down
18 changes: 12 additions & 6 deletions lib/PortingEmbed/StepHolderDetailsForm.tsx
Expand Up @@ -64,12 +64,14 @@ export function StepHolderDetailsForm({
{porting.required.includes('firstName') && (
<Field
name="firstName"
validate={[required('Your first name is required')]}
validate={[required(options.text['field.firstName.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>First Name</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.firstName.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -85,12 +87,14 @@ export function StepHolderDetailsForm({
{porting.required.includes('lastName') && (
<Field
name="lastName"
validate={[required('Your last name is required')]}
validate={[required(options.text['field.lastName.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Last Name</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.lastName.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand All @@ -106,12 +110,14 @@ export function StepHolderDetailsForm({
{porting.required.includes('birthday') && (
<Field
name="birthday"
validate={[required('Your birthday is required')]}
validate={[required(options.text['field.birthday.error.required'])]}
transform={toTrimmed({ on: 'input' })}
>
{(field, props) => (
<EmbedField of={field}>
<EmbedFieldLabel of={field}>Birthday</EmbedFieldLabel>
<EmbedFieldLabel of={field}>
{options.text['field.birthday.label']}
</EmbedFieldLabel>
<EmbedFieldInput
{...props}
of={field}
Expand Down
31 changes: 31 additions & 0 deletions lib/PortingEmbed/__tests__/StepAddressForm.test.tsx
Expand Up @@ -580,3 +580,34 @@ describe('form class names', () => {
expect(screen.getByRole('form')).toHaveClass('custom-class-submitting')
})
})

describe('custom labels', () => {
it('allows to specify custom labels', () => {
const porting = portingFactory.build({ required: ['address'] })
render(
<OptionsContext.Provider
value={{
text: {
'field.line1.label': 'L1',
'field.line2.label': 'L2',
'field.city.label': 'CI',
'field.postalCode.label': 'PC',
'field.state.label': 'ST',
'field.country.label': 'CO',
},
}}
>
<StepAddressForm porting={porting} onSubmit={vi.fn()} />
</OptionsContext.Provider>,
{
wrapper,
},
)
expect(screen.getByLabelText('L1')).toBeInTheDocument()
expect(screen.getByLabelText('L2')).toBeInTheDocument()
expect(screen.getByLabelText('CI')).toBeInTheDocument()
expect(screen.getByLabelText('PC')).toBeInTheDocument()
expect(screen.getByLabelText('ST')).toBeInTheDocument()
expect(screen.getByLabelText('CO')).toBeInTheDocument()
})
})