---
id: w1/m11.5/t003
title: Add Custom Domain dialog (FQDN input → addCustomDomain mutation)
worker: worker1
status: done
estimate: 30m
depends_on: [w1/m11.5/t002]
---

## Objective

Wire the "Add Custom Domain" button to a dialog containing a hostname input that
calls `addCustomDomain(id, name)` and appends the new domain to the table on
success — matching Render's add-domain UX.

## Context

- Render: clicking "Add Custom Domain" in Settings opens an in-page form (a modal
  or inline expansion) with an FQDN text field and a submit button.
- Use shadcn `Dialog` + `Input` + `Button` pattern (consistent with the env-vars
  add-variable flow in `src/features/services/`).
- The mutation returns a `CustomDomain` object; on success, refetch the
  `customDomains` query (Apollo's `refetchQueries` or cache update).
- Validation: empty input should disable the submit button; the service layer
  already rejects an empty hostname with 400, but validate client-side too.
- Show a `toast` on error (e.g. "Failed to add domain"); success closes the dialog.

## Steps

1. Add an "Add Custom Domain" button to `CustomDomainsSection` (or the empty state)
   that opens a shadcn `Dialog`.
2. Inside the dialog: a `Label` + `Input` for the FQDN, a "Add Domain" submit
   `Button` (disabled while loading or input is empty), and a Cancel button.
3. On submit, call `useAddCustomDomainMutation` from t001 with
   `{ variables: { id: serviceId, name: trimmedInput } }` and
   `refetchQueries: ["CustomDomains"]`.
4. On error, show a toast; on success, close the dialog and clear the input.
5. Add locale keys (`services.addCustomDomain`, `services.domainPlaceholder`,
   `services.addDomainButton`, …).

## Files

- `dashboard/src/features/services/components/custom-domains-section.tsx` (extend)
- `dashboard/src/features/services/locales/{en,zh}.ts` (locale keys)

## Acceptance criteria

- [ ] "Add Custom Domain" button opens a dialog with an FQDN text input.
- [ ] Submitting a valid FQDN calls the mutation and the new domain appears in
      the table.
- [ ] Submit is disabled while the input is empty.
- [ ] An error from the API surfaces as a toast; dialog stays open for retry.
- [ ] Dialog closes and input is cleared on success.

## Out of scope

- Delete (t004). DNS instructions / CNAME copy-to-clipboard (future).
