Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .claude/agents/dashboard-block-documenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ You write the partner-facing documentation entry for a newly migrated Employee
Dashboard block in the embedded-react-sdk. The feature name and the paths to
the new block, card, and edit-form files are provided in the user's message.

## Workspace ownership

You write documentation files. You do not control the git workspace. **Do not
run `git` commands. Do not stage. Do not commit. Do not push. Do not amend.**
The parent agent owns staging and commits; it will pick up your edits as part
of whatever commit grouping it chooses.

You may run formatters (`prettier`, etc.) against the files you edited if it
keeps your output consistent with the rest of the repo. You may not run lint,
tests, or any other workspace-mutating command beyond your edits and
optional formatting.

## Step 1 — Read the reference docs before writing anything

Read these files in full. Your output must match their structure, section order,
Expand Down Expand Up @@ -107,6 +119,7 @@ surfaces.

Return:

- Confirmation that `employee-management.md` was updated
- Confirmation that `employee-management.md` was updated (and unstaged — see
"Workspace ownership" above)
- The heading path of the new section (e.g. `### EmployeeManagement.Compensation`)
- Any events or props you could not document with reasons
14 changes: 13 additions & 1 deletion .claude/agents/sdk-hook-documenter.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ You write partner-facing documentation for a new SDK form hook in the
embedded-react-sdk. The hook name and file path are provided in the user's
message.

## Workspace ownership

You write documentation files. You do not control the git workspace. **Do not
run `git` commands. Do not stage. Do not commit. Do not push. Do not amend.**
The parent agent owns staging and commits; it will pick up your edits as part
of whatever commit grouping it chooses.

You may run formatters (`prettier`, etc.) against the files you edited if it
keeps your output consistent with the rest of the repo. You may not run lint,
tests, or any other workspace-mutating command beyond your edits and
optional formatting.

## Step 1 — Read the reference docs before writing anything

Read these files in full. Your output must match their structure, section order,
Expand Down Expand Up @@ -73,6 +85,6 @@ Voice and style rules (from CLAUDE.md `docs/` section):

Return:

- The path to the created doc file
- The path to the created doc file (unstaged — see "Workspace ownership" above)
- Confirmation that `docs/hooks/hooks.md` was updated
- Any fields or behaviors you skipped with reasons
12 changes: 9 additions & 3 deletions .claude/skills/migrate-dashboard-card-to-block/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,13 +572,19 @@ The block does **not** read from `Employee.Dashboard`. Whatever the card surface

### When the feature already has multiple namespaces

Some features have `Employee.<Feature>.json` (the onboarding/form namespace) plus `Employee.<Feature>.Management.json` (the management namespace). Pattern from [`HomeAddress.tsx`](../../../src/components/Employee/HomeAddress/management/HomeAddress.tsx):
Some features have `Employee.<Feature>.json` (the onboarding/form namespace) plus `Employee.<Feature>.Management.json` (the management namespace). The default is to load **only** the management namespace from the block, the card, and the edit screen:

```tsx
useI18n(['Employee.HomeAddress.Management', 'Employee.HomeAddress'])
useI18n('Employee.HomeAddress.Management')
```

The management block can load both: its own `Management` namespace for management-specific strings, plus the feature's base namespace for strings shared with the edit/form path. The block's _new_ strings always go into `.Management.json`; reuse from the base namespace only for strings that genuinely live in both contexts (e.g. validation messages on a form field shared with onboarding). When in doubt, copy the string into `.Management.json` — duplication is cheaper than coupling the block to onboarding's namespace.
Don't reach for the base namespace just because a shared form **hook** is rendered in both places. Form hooks like `useHomeAddressForm` emit field _components_ (e.g. `Street1`, `City`, `Zip`); they don't render any text themselves. Each call site passes its own `label` and `validationMessages` props from whatever namespace is convenient. The onboarding consumer reads from `Employee.HomeAddress`, the management consumer reads from `Employee.HomeAddress.Management`, and the two never need to share a translation namespace just because they share field components.

Concrete heuristic — only dual-load the base namespace if **a runtime-shared piece of UI** (a shared presentational component that itself calls `useTranslation('Employee.<Feature>')`) is rendered inside the management path. If the management-side consumer of those strings is only ever rendered in the management path (e.g. a `<Feature>View` that exists solely under `management/`), copy the strings it needs into `Employee.<Feature>.Management.json` and read everything from one namespace. Don't reach across.

Concretely: the field labels, validation messages, and courtesy-withholding copy that `HomeAddress/management/HomeAddressView.tsx` renders are duplicated into `Employee.HomeAddress.Management.json` rather than dual-loading `Employee.HomeAddress`, because `HomeAddressView` is exclusive to the management path. Onboarding's `EmployeeProfile`/`AdminProfile` keep reading the same keys from `Employee.HomeAddress`.

Duplication is the cost; it buys a fully self-contained block. A partner who only overrides `Employee.HomeAddress.Management` via `useComponentDictionary` gets a coherent result without having to discover that they also need to override `Employee.HomeAddress`. When the same copy needs to change in both contexts (rare for field labels; almost never for validation messages), the change is two edits instead of one — that's a worthwhile trade.

### Strings to move out of `Employee.Dashboard` during migration

Expand Down
8 changes: 0 additions & 8 deletions docs/reference/endpoint-inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,10 +833,6 @@
"method": "GET",
"path": "/v1/employees/:employeeId/forms"
},
{
"method": "GET",
"path": "/v1/employees/:employeeId/home_addresses"
},
{
"method": "GET",
"path": "/v1/employees/:employeeId/jobs"
Expand Down Expand Up @@ -1982,10 +1978,6 @@
"method": "GET",
"path": "/v1/employees/:employeeId/forms"
},
{
"method": "GET",
"path": "/v1/employees/:employeeId/home_addresses"
},
{
"method": "GET",
"path": "/v1/employees/:employeeId/jobs"
Expand Down
2 changes: 0 additions & 2 deletions docs/reference/endpoint-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json'
| **Employee.DashboardFlow** | DELETE | `/v1/compensations/:compensationId` |
| | GET | `/v1/employees/:employeeId` |
| | GET | `/v1/employees/:employeeId/forms` |
| | GET | `/v1/employees/:employeeId/home_addresses` |
| | GET | `/v1/employees/:employeeId/jobs` |
| | GET | `/v1/employees/:employeeId/pay_stubs` |
| | GET | `/v1/employees/:employeeId/work_addresses` |
Expand Down Expand Up @@ -374,7 +373,6 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json'
| **EmployeeManagement.DashboardFlow** | DELETE | `/v1/compensations/:compensationId` |
| | GET | `/v1/employees/:employeeId` |
| | GET | `/v1/employees/:employeeId/forms` |
| | GET | `/v1/employees/:employeeId/home_addresses` |
| | GET | `/v1/employees/:employeeId/jobs` |
| | GET | `/v1/employees/:employeeId/pay_stubs` |
| | GET | `/v1/employees/:employeeId/work_addresses` |
Expand Down
113 changes: 113 additions & 0 deletions docs/workflows-overview/employee-management/employee-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Employee management components can be used to compose your own workflow, or can
- [EmployeeManagement.DashboardFlow](#employeemanagementdashboardflow)
- [EmployeeManagement.Profile](#employeemanagementprofile)
- [Composing from EmployeeManagement.ProfileCard and EmployeeManagement.ProfileEditForm directly](#composing-from-employeemanagementprofilecard-and-employeemanagementprofileeditform-directly)
- [EmployeeManagement.HomeAddress](#employeemanagementhomeaddress)
- [Composing from EmployeeManagement.HomeAddressCard and EmployeeManagement.HomeAddressEditForm directly](#composing-from-employeemanagementhomeaddresscard-and-employeemanagementhomeaddresseditform-directly)

### EmployeeManagement.DashboardFlow

Expand Down Expand Up @@ -207,3 +209,114 @@ function MyBasicDetailsPanel({ employeeId }) {
| ------------------------------------------ | --------------------------------------------------- | ------------------------- |
| EMPLOYEE_PROFILE_MANAGEMENT_UPDATED | Fired after the edit form is successfully submitted | Updated `Employee` entity |
| EMPLOYEE_PROFILE_MANAGEMENT_EDIT_CANCELLED | Fired when the user clicks Cancel on the edit form | None |

### EmployeeManagement.HomeAddress

A self-contained block for viewing and managing an employee's home address — the same "Home address" experience the dashboard surfaces, but as a drop-in component that doesn't require the surrounding dashboard chrome. Renders a read-only card showing the employee's current home address. Clicking the card's "Manage" CTA swaps the card view for an inline manage screen that surfaces the current address, the address history, and forms for editing the current address, adding a new one, or deleting a non-active one. Clicking Back returns to the card view; creates, updates, and deletes happen in place on the manage screen and do not navigate away.

```jsx
import { EmployeeManagement } from '@gusto/embedded-react-sdk'

function MyComponent() {
return (
<EmployeeManagement.HomeAddress
employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e"
onEvent={() => {}}
/>
)
}
```

#### Props

| Name | Type | Description |
| ------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| employeeId Required | string | The associated employee identifier. |
| onEvent Required | function | See events table for available events. |
| dictionary | object | Optional translations for component text. Keys are namespaced under `Employee.HomeAddress.Management` — see the source JSON for the set. |
| FallbackComponent | React.ComponentType | Optional custom error fallback component used by the internal `BaseBoundaries` wrapper. |

#### Events

| Event type | Description | Data |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------- |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_REQUESTED | Fired when the user clicks the "Manage" CTA on the card; the block swaps to the manage screen | { employeeId: string } |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_CANCELLED | Fired when the user clicks Back on the manage screen; the block returns to the card view | None |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_CREATED | Fired after a new home address is successfully created from the manage screen | Created `EmployeeAddress` entity |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_UPDATED | Fired after an existing home address is successfully updated from the manage screen | Updated `EmployeeAddress` entity |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_DELETED | Fired after a non-active home address is successfully deleted from the manage screen | Deleted `EmployeeAddress` entity |

#### Composing from EmployeeManagement.HomeAddressCard and EmployeeManagement.HomeAddressEditForm directly

`EmployeeManagement.HomeAddress` above is the recommended entry point for the home-address experience — it bundles the card, the manage screen, and the swap between them as a single drop-in. The card and edit form are also exported individually for cases where that orchestration is the wrong fit — for example, when the manage screen needs to render in a modal or drawer, when the card needs to appear read-only with no manage affordance, or when the swap is driven by a router. Using them directly means owning the swap and any cross-component state yourself.

`EmployeeManagement.HomeAddressCard` renders the read-only home-address card and emits a single event when its "Manage" CTA is clicked. `EmployeeManagement.HomeAddressEditForm` renders the corresponding manage screen and emits events on create, update, delete, and cancel. Each piece's `onEvent` receives the event type as its first argument and any associated payload as its second — branch on the event type to drive the swap (and any of your own behavior, e.g. surfacing a success message after a save). The per-piece events tables below list every event each piece emits.

```jsx
import { useState } from 'react'
import { componentEvents, EmployeeManagement } from '@gusto/embedded-react-sdk'

function MyHomeAddressPanel({ employeeId }) {
const [isEditing, setIsEditing] = useState(false)

if (isEditing) {
return (
<EmployeeManagement.HomeAddressEditForm
employeeId={employeeId}
onEvent={eventType => {
if (eventType === componentEvents.EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_CANCELLED) {
setIsEditing(false)
}
}}
/>
)
}

return (
<EmployeeManagement.HomeAddressCard
employeeId={employeeId}
onEvent={eventType => {
if (eventType === componentEvents.EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_REQUESTED) {
setIsEditing(true)
}
}}
/>
)
}
```

##### EmployeeManagement.HomeAddressCard

**Props**

| Name | Type | Description |
| ------------------- | -------- | -------------------------------------- |
| employeeId Required | string | The associated employee identifier. |
| onEvent Required | function | See events table for available events. |

**Events**

| Event type | Description | Data |
| ----------------------------------------------- | ------------------------------------------------------- | ---------------------- |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_REQUESTED | Fired when the user clicks the "Manage" CTA on the card | { employeeId: string } |

##### EmployeeManagement.HomeAddressEditForm

**Props**

| Name | Type | Description |
| ------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| employeeId Required | string | The associated employee identifier. |
| onEvent Required | function | See events table for available events. |
| className | string | Optional class applied to the form's root section element. |
| dictionary | object | Optional translations for component text. Keys are namespaced under `Employee.HomeAddress.Management` — see the source JSON for the set. |
| FallbackComponent | React.ComponentType | Optional custom error fallback component used by the internal `BaseBoundaries` wrapper. |

**Events**

| Event type | Description | Data |
| ----------------------------------------------- | ------------------------------------------------------------- | -------------------------------- |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_CREATED | Fired after a new home address is successfully created | Created `EmployeeAddress` entity |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_UPDATED | Fired after an existing home address is successfully updated | Updated `EmployeeAddress` entity |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_DELETED | Fired after a non-active home address is successfully deleted | Deleted `EmployeeAddress` entity |
| EMPLOYEE_HOME_ADDRESS_MANAGEMENT_EDIT_CANCELLED | Fired when the user clicks Back on the manage screen | None |
2 changes: 2 additions & 0 deletions sdk-app/src/generated-registry-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const ENTITY_REQUIREMENTS: Record<string, string[]> = {
'EmployeeManagement.EmployeeListFlow': ['companyId'],
'EmployeeManagement.FederalTaxes': ['employeeId'],
'EmployeeManagement.HomeAddress': ['employeeId'],
'EmployeeManagement.HomeAddressCard': ['employeeId'],
'EmployeeManagement.HomeAddressEditForm': ['employeeId'],
'EmployeeManagement.PaymentMethod': ['employeeId'],
'EmployeeManagement.Profile': ['employeeId'],
'EmployeeManagement.ProfileCard': ['employeeId'],
Expand Down
15 changes: 0 additions & 15 deletions src/components/Employee/Dashboard/BasicDetailsView.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fn } from 'storybook/test'
import type { EmployeeAddress } from '@gusto/embedded-api-v-2025-11-15/models/components/employeeaddress'
import type { EmployeeWorkAddress } from '@gusto/embedded-api-v-2025-11-15/models/components/employeeworkaddress'
import { BasicDetailsView } from './BasicDetailsView'
import { BaseComponent } from '@/components/Base'
Expand All @@ -16,21 +15,10 @@ export default {
}

const onEvent = fn().mockName('onEvent')
const onManageHomeAddress = fn().mockName('onManageHomeAddress')
const onManageWorkAddress = fn().mockName('onManageWorkAddress')

const EMPLOYEE_ID = 'employee-123'

const homeAddress: EmployeeAddress = {
uuid: 'home-address-1',
version: '1',
country: 'USA',
street1: '100 5th Ave',
city: 'New York',
state: 'NY',
zip: '10001',
}

const workAddress: EmployeeWorkAddress = {
uuid: 'work-address-1',
version: '1',
Expand All @@ -49,9 +37,7 @@ export const WithAllDetails = () => (
<BasicDetailsView
employeeId={EMPLOYEE_ID}
onEvent={onEvent}
currentHomeAddress={homeAddress}
currentWorkAddress={workAddress}
onManageHomeAddress={onManageHomeAddress}
onManageWorkAddress={onManageWorkAddress}
/>
)
Expand All @@ -60,7 +46,6 @@ export const WithoutAddresses = () => (
<BasicDetailsView
employeeId={EMPLOYEE_ID}
onEvent={onEvent}
onManageHomeAddress={onManageHomeAddress}
onManageWorkAddress={onManageWorkAddress}
/>
)
Loading
Loading