Skip to content

Commit

Permalink
Add form and porting update
Browse files Browse the repository at this point in the history
  • Loading branch information
timomeh committed Feb 23, 2024
1 parent b5bf002 commit 904dbfa
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 65 deletions.
34 changes: 25 additions & 9 deletions lib/PortingEmbed/PortingEmbed.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from 'preact/hooks'

import { Porting } from '../types'
import { Porting, UpdatePortingBody } from '../types'
import { PortingForm } from './PortingForm'

export type CustomizableEmbedProps = {
// TODO: add styling options
Expand All @@ -9,15 +8,32 @@ export type CustomizableEmbedProps = {
}
}

export type ValidationChangeEvent = {
isValid: boolean
}

type CoreEmbedProps = {
token: string
initialPorting: Porting
porting: Porting
onValidationChange?: (event: ValidationChangeEvent) => unknown
onPortingUpdate?: (updatedFields: UpdatePortingBody) => unknown
}

type PortingEmbedProps = CoreEmbedProps & CustomizableEmbedProps

export function PortingEmbed({ token: _, initialPorting }: PortingEmbedProps) {
const [porting, _setPorting] = useState(initialPorting)

return <div className="__gigsPortingEmbed">Hello {porting.id}!</div>
export function PortingEmbed({
porting,
onPortingUpdate,
onValidationChange,
}: PortingEmbedProps) {
return (
<div className="__gigsPortingEmbed">
<PortingForm
porting={porting}
onValidationChange={onValidationChange}
onSubmit={async (updatedFields) => {
await onPortingUpdate?.(updatedFields)
}}
/>
</div>
)
}
95 changes: 95 additions & 0 deletions lib/PortingEmbed/PortingForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { required, useForm } from '@modular-forms/preact'
import { useSignalEffect } from '@preact/signals'

import { Porting } from '../types'

type Props = {
porting: Porting
onValidationChange?: (event: { isValid: boolean }) => unknown
onSubmit: (data: PortingForm) => unknown
}

type PortingForm = {
accountPin: string
accountNumber: string
birthday: string
firstName: string
lastName: string
}

export function PortingForm({ porting, onValidationChange, onSubmit }: Props) {
const [portingForm, { Form, Field }] = useForm<PortingForm>({
initialValues: {
accountNumber: porting.accountNumber || '',
accountPin: porting.accountPinExists ? '[***]' : '',
birthday: porting.birthday || '',
firstName: porting.firstName || '',
lastName: porting.lastName || '',
},
validateOn: 'blur',
})

useSignalEffect(() => {
const isValid = !portingForm.invalid.value
onValidationChange?.({ isValid })
})

return (
<Form id="gigsPortingEmbedForm" role="form" onSubmit={onSubmit}>
<Field name="accountNumber" validate={[required('Please enter')]}>
{(field, props) => (
<div>
<label for="accountNumber">Account Number</label>
<input
id="accountNumber"
type="text"
value={field.value}
{...props}
/>
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>

<Field name="accountPin" validate={[required('Please enter')]}>
{(field, props) => (
<div>
<label for="accountPin">Account PIN</label>
<input id="accountPin" type="text" value={field.value} {...props} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>

<Field name="birthday" validate={[required('Please enter')]}>
{(field, props) => (
<div>
<label for="birthday">Birthday</label>
<input id="birthday" type="text" value={field.value} {...props} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>

<Field name="firstName" validate={[required('Please enter')]}>
{(field, props) => (
<div>
<label for="firstName">First Name</label>
<input id="firstName" type="text" value={field.value} {...props} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>

<Field name="lastName" validate={[required('Please enter')]}>
{(field, props) => (
<div>
<label for="lastName">Last Name</label>
<input id="lastName" type="text" value={field.value} {...props} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
</Form>
)
}
39 changes: 34 additions & 5 deletions lib/PortingEmbed/__stories__/PortingEmbed.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import { Meta } from '@storybook/preact'

import { portingFactory } from '@/testing/factories/porting'

import { PortingEmbed } from '../PortingEmbed'

export default {
title: 'Porting Embed/Base',
const meta: Meta<typeof PortingEmbed> = {
title: 'Porting/Embed',
component: PortingEmbed,
tags: ['autodocs'],
argTypes: {
token: { control: 'text' },
initialPorting: { control: 'object' },
onPortingUpdate: { action: 'onPortingUpdate' },
onValidationChange: { action: 'onValidationChange' },
},
decorators: [
(Story) => (
<div>
{Story()}
<button form="gigsPortingEmbedForm" type="submit">
Submit!
</button>
</div>
),
],
}

export default meta

export const EmptyPorting = {
args: {
porting: portingFactory.build(),
},
}

export const Primary = {
export const PrefilledPorting = {
args: {
token: 'abc:123',
initialPorting: portingFactory.build(),
porting: portingFactory
.params({
accountNumber: '1234',
accountPinExists: true,
birthday: '01.01.1990',
firstName: 'Jane',
lastName: 'Doe',
})
.build(),
},
}
8 changes: 4 additions & 4 deletions lib/PortingEmbed/__tests__/PortingEmbed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { portingFactory } from '@/testing/factories/porting'

import { PortingEmbed } from '../PortingEmbed'

it('gets the porting', () => {
it('renders a form', () => {
const porting = portingFactory.params({ id: 'prt_123' }).build()
render(<PortingEmbed initialPorting={porting} token="abc:123" />)
const greeting = screen.getByText(/Hello prt_123/i)
expect(greeting).toBeInTheDocument()
render(<PortingEmbed porting={porting} />)
const form = screen.getByRole('form')
expect(form).toBeInTheDocument()
})
82 changes: 78 additions & 4 deletions lib/PortingEmbed/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { render } from '@testing-library/preact'
import { render, screen, waitFor } from '@testing-library/preact'
import userEvent from '@testing-library/user-event'

import { db } from '@/testing/db'
import { connectSessionFactory } from '@/testing/factories/connectSession'
import { portingFactory } from '@/testing/factories/porting'
import { subscriptionFactory } from '@/testing/factories/subscription'
Expand All @@ -18,7 +20,14 @@ async function createFixtures() {
}

beforeEach(() => {
render(<div id="mount" />)
render(
<>
<div id="mount" />
<button type="submit" form="gigsPortingEmbedForm">
Submit
</button>
</>,
)
})

describe('mounting', () => {
Expand Down Expand Up @@ -142,10 +151,10 @@ describe('initialization', () => {
expect(init).resolves.toBeDefined()
})

it('initializes with declined', async () => {
it('throws with declined', async () => {
const csn = await createWithStatus('declined')
const init = PortingEmbed(csn, { project })
expect(init).resolves.toBeDefined()
expect(init).rejects.toThrow(/UNSUPPORTED/)
})

it('throws with draft', async () => {
Expand Down Expand Up @@ -179,3 +188,68 @@ describe('initialization', () => {
})
})
})

describe('updating a porting', () => {
it('sends the updated data', async () => {
const user = userEvent.setup()
const updatedFn = vitest.fn()

const csn = await createFixtures()
const embed = await PortingEmbed(csn, { project })
embed.mount('#mount')
embed.on('portingUpdated', updatedFn)

await user.type(screen.getByLabelText('Account Number'), '11880')
await user.type(screen.getByLabelText('Account PIN'), '1337')
await user.type(screen.getByLabelText('Birthday'), '01.01.1990')
await user.type(screen.getByLabelText('First Name'), 'Jane')
await user.type(screen.getByLabelText('Last Name'), 'Doe')
await user.click(screen.getByRole('button', { name: 'Submit' }))

await waitFor(() => expect(updatedFn).toHaveBeenCalled())

const sub = db.subscriptions.find(
(s) => s.id === csn.intent.completePorting.subscription,
)
const prt = db.portings.find((p) => p.id === sub!.porting!.id)

expect(prt).toMatchObject({
accountPinExists: true,
accountNumber: '11880',
birthday: '01.01.1990',
firstName: 'Jane',
lastName: 'Doe',
})
})
})

describe('validationChange event', () => {
it('fires initially always valid', async () => {
const event = vitest.fn()

const csn = await createFixtures()
const embed = await PortingEmbed(csn, { project })
embed.mount('#mount')
embed.on('validationChange', event)

await waitFor(() => expect(event).toHaveBeenCalledWith({ isValid: true }))
})

it('fires with invalid field', async () => {
const event = vitest.fn()
const user = userEvent.setup()

const csn = await createFixtures()
const embed = await PortingEmbed(csn, { project })
embed.mount('#mount')
embed.on('validationChange', event)

await user.type(screen.getByLabelText('Account Number'), '123')
await user.clear(screen.getByLabelText('Account Number'))
await user.click(screen.getByLabelText('Account PIN'))

await waitFor(() =>
expect(event).toHaveBeenLastCalledWith({ isValid: false }),
)
})
})
Loading

0 comments on commit 904dbfa

Please sign in to comment.