[PRD] Revamp Add Single Customer in Right-Side Drawer
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Zhelia Alifa |
| PRD Version | 1.2 |
| Status | DRAFT |
| PRD Type | ADJUSTMENT (Frontend) |
| Epic | TBD |
| Squad | CDP Squad |
| RFC Link | TBD |
| Repo | qontak-customer-fe (Nuxt 4 / Vue 3 / Mekari Pixel 3) |
| Platform | Website only |
| Figma | Add customer — right-side drawer |
| Parent | PRD CDP Q4 2026 |
| Labels | epic:qontak-cdp | module:customers | feature:add-customer-drawer |
| Last Updated | 2026-07-25 |
0. Adjustment Context (Conditional Block)
Parent / current behavior: adding a single customer today is a full-page flow at route
/customers/add (pages/customers/add.vue → features/customers/create/views/Index.vue). This
PRD adjusts only the surface that hosts the create form — from a full page to a right-side
drawer. It does not change what the form collects or how it saves.
Adjustment scope: move the existing create-customer form into a Mekari Pixel 3 right-side drawer opened from the customer list, and change the save/cancel flow to close-and-refresh in place instead of navigating away. Website only.
Parent scope preserved: all current create behavior is retained — the same dynamic,
layout-driven fields (GET /v1/contacts/field_properties?layout_id=default), the same validation,
the same POST /v1/contacts save, the same permission gate (customers_customers_add), and the
same Mixpanel events. No new fields; no CRM-module association added.
Reason for the adjustment (grounded): the full-page layout is a 3-column grid
gridTemplateColumns: '1fr 2fr 1fr' (features/customers/create/views/Index.vue:44-49) where the
form occupies only the first 1fr column and the middle "Customer Activity" (2fr) and right
"Customer Association" (1fr) columns are rendered as empty placeholders
(Index.vue:25-29). So ~⅔ of the page is intentional blank space — scaffolding for
activity/association panels that were never built, because create is a create-only action
(association only exists later, on the detail page). A focused drawer removes the blank space and
keeps the user in context on the customer list.
User-facing UI changes: Yes — the create-customer surface changes from a full page to a right-side drawer (this is the point of the adjustment). No other screen changes.
1. One-liner + Problem
One-liner: Move the add single customer create form from a full page into a right-side drawer on the CDP customer list — website only.
Problem: Creating a single customer opens a full page (/customers/add) whose layout was
built for activity + association panels that don't exist at create time — so two of its three
columns render empty (Index.vue:25-29, :44-49) and ~⅔ of the screen is blank. Because create is
a create-only action (a user cannot and should not associate the customer with other CRM modules
— deal / ticket / task / company — during creation; association only exists later on the detail
page), the full page is oversized for the job, adds an unnecessary navigation away from the list, and
reads poorly. Users just need a focused field-entry form.
2. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — CRM/CDP agent or admin | Adds customers to CDP one at a time | Add a customer quickly without losing the list context | Full-page create navigates away, is oversized, and shows large blank space | Fills the sparse full page, then navigates back to the list |
| Secondary — Team lead / ops | Reviews the customer list, adds ad-hoc customers | Stay on the list while adding | Context switch to a full page for a simple form | — |
3. Non-Goals
- Not adding CRM-module association during create — deal / ticket / task / company association
stays a detail-page capability (
features/customers/detail/.../Associated*.vue); create remains create-only. - Not changing the fields or the field source — fields still come from the backend layout
(
GET /v1/contacts/field_properties?layout_id=default); no fields added/removed by this PRD. - Not changing the save contract — still
POST /v1/contacts; no backend change. - Not the bulk / multiple-customers flow —
/customers/bulk-uploadis unchanged. - Not mobile — website only.
- Not a redesign of the customer list page — only the add-customer entry point behavior changes.
4. Constraints
| Constraint | Value |
|---|---|
| Platform | Website only (qontak-customer-fe, Nuxt 4 / Vue 3). No mobile. |
| Design system | Mekari Pixel 3 MpDrawer family (MpDrawerOverlay/Content/Header/Body/Footer/CloseButton), placement="right" — reuse the established pattern (ExportCustomerDrawer.vue, DrawerSegment.vue). |
| No backend change | Same POST /v1/contacts + GET /v1/contacts/field_properties?layout_id=default contract, unchanged. |
| Permission | Entry gated by customers_customers_add (unchanged, Breadcrumb.vue:139-146). |
| Feature flag | cdp_add_customer_drawer_enabled | default: OFF — flips the "Single customer" entry from the full-page route to the drawer, for safe rollback. |
| Layout / scroll | The form can be long (many layout fields + cascading address). The drawer body scrolls with a sticky footer (Cancel / Save) and a sticky header ("Add customer" + close ✕), per Figma. |
| No new fields | The drawer renders exactly the fields the current form renders (dynamic layout). |
| Performance (no regression) | Create success rate ≥ current full-page baseline and create p95 latency ≤ baseline (same POST /v1/contacts); drawer open/render adds no perceptible delay vs the page. These are the §11 gate values. |
Scope Changes
Engineering surfaces this PRD touches (controlled vocab: Backend · Frontend · Mobile · Infra · Data · Design · Docs · None).
- Frontend —
qontak-customer-fe: host the existing create-customerForm.vueinside a Pixel 3 right-sideMpDraweropened from the customer list; change the list entry point (Breadcrumb.vue:68-70) to open the drawer instead of navigating to/customers/add; change the form's save/cancel from a hardrouter.push('/customers/all-customers')(Form.vue:86,:621-622) to emitclose/savedand refresh the list in place; mount + toggle the drawer from the list page (mirroringListPage.vueisExportDrawerOpen). - Design — new right-side "Add customer" drawer layout (Figma).
5. Feature Changes
5.1 Change block — Add-customer surface: full page → right-side drawer
| Field | Value |
|---|---|
| Change ID | ACD-CH-01 |
| Change Type | UX surface change (re-host existing form) |
| Page / URL | Customer list /customers/all-customers (entry) · current full page /customers/add (superseded as the primary surface) |
| Page intent | Create a single customer without leaving the list |
Before → After (element-level):
| Element | Before (full page) | After (drawer) |
|---|---|---|
| Surface | Full page at /customers/add (pages/customers/add.vue → Index.vue) | Right-side Pixel 3 MpDrawer (placement="right") mounted on the list page |
| Layout | 3-column grid 1fr 2fr 1fr (Index.vue:44-49); cols 2 & 3 empty placeholders (Index.vue:25-29) → ~⅔ blank | Single-column form in the drawer body; no blank columns |
| Header | Breadcrumb "Customers / Add Customer" (Index.vue) | Drawer header "Add customer" + close ✕ + "Select view: Default view" selector (Figma) |
| Form | Form.vue in column 1 | Same Form.vue, rendered in MpDrawerBody |
| Footer | Save / Cancel inline in the form column (Form.vue) | Sticky Cancel / Save in MpDrawerFooter (Figma) |
| Entry point | "Add customer" popover → "Single customer" = NuxtLink to="/customers/add" (Breadcrumb.vue:68-70) | Same popover item → opens the drawer (sets isAddCustomerDrawerOpen = true); no route navigation |
| On Save success | router.push('/customers/all-customers') + toast (Form.vue:621-627) | Close the drawer, show the success toast, and refresh the list in place (no navigation) |
| On Cancel / close | router.push('/customers/all-customers') (Form.vue:86) | Close the drawer, stay on the list (unsaved input discarded; see OQ-3) |
Fields (unchanged, from the default layout): Full name* (0/60), Username (@-source prefix),
Phone number (multi, max 3), Email, Source, Owner*, Owner's team (flag cdp_team_permission_enabled),
Assignee (+ "Assign to me"), Date of birth, Sex (None/Male/Female), Status, Job title, and cascading
Province → City → District → Subdistrict → Zip code, plus any custom layout fields — all rendered by
the existing dynamic dispatcher (Form.vue:66-72; source GET /v1/contacts/field_properties?layout_id=default,
Form.vue:404-408). (The Figma frame shows the Default-view field set.)
Figma: Add customer — right-side drawer
7. API & Webhook Behavior
No API change. Create still calls POST /v1/contacts (Form.vue:611-616) and fields still load
from GET /v1/contacts/field_properties?layout_id=default (Form.vue:404-408) — identical request
and response. This PRD only changes the FE surface that hosts the form.
8. System Flow + User Stories + ACs
8.1 System Flow
Flow type: User Journey (create-customer via drawer).
flowchart TD
A[Customer list /customers/all-customers] -->|Add customer → Single customer<br/>Breadcrumb.vue:69| B{cdp_add_customer_drawer_enabled?}
D[Deep-link /customers/add<br/>host router RemoteContactRouter.vue:50-51] -->|flag ON → redirect| B
B -->|OFF| P[Full page /customers/add<br/>legacy, safe rollback]
B -->|ON| C[Open right-side MpDrawer<br/>fire 'Open Add Customer Drawer']
C --> E[Load layout<br/>GET field_properties?layout_id=default]
E -->|load fails| E1[Inline error / retry in drawer]
E --> F[Render Form.vue + fill fields]
F --> G{Save or Cancel?}
G -->|Cancel / ✕| H[Close drawer, stay on list<br/>discard input]
G -->|Save| I[POST /v1/contacts]
I -->|success| J[Close drawer + toast +<br/>refresh list in place +<br/>fire 'Add Single Customer']
I -->|error| K[Keep drawer open,<br/>preserve values, error toast]
- On the customer list (
/customers/all-customers), the user (withcustomers_customers_add) opens the "Add customer" popover and clicks Single customer. - With the flag ON, this opens the right-side drawer (no navigation) instead of routing to
/customers/add; the Mixpanel "Click Add Single Customer" event still fires. - The drawer loads the field layout (
field_properties?layout_id=default) and rendersForm.vue. - The user fills the form; validation is identical to today.
- Save →
POST /v1/contacts→ on success: close the drawer, show the success toast, refresh the list in place, fire "Add Single Customer". Cancel / ✕ → close the drawer, stay on the list.
8.2 User Stories
| User Story | Importance | Mockup | Technical Notes | Acceptance Criteria |
|---|---|---|---|---|
| [ACD-S01] — Open the Add Customer drawer from the list As an agent/admin, I want the "Single customer" action to open a right-side drawer, so that I can add a customer without leaving the list. | Must Have | Figma — Add customer drawer | Grounded: entry point today is NuxtLink to="/customers/add" in the "Add customer" popover (Breadcrumb.vue:68-70), gated by customers_customers_add (:139-146). Change it to toggle an isAddCustomerDrawerOpen ref (mirror ListPage.vue isExportDrawerOpen, :246, :123-130); mount a new AddCustomerDrawer using Pixel 3 MpDrawer placement="right" (pattern: ExportCustomerDrawer.vue:2-8).Before → After: Before — navigates to full page /customers/add. After — opens the drawer over the list. | • AC-1: Given a user with customers_customers_add on the customer list, when they click "Add customer" → "Single customer", then a right-side drawer titled "Add customer" opens over the list (no route navigation).• AC-2: Given the drawer is open, when the user clicks the ✕ or the overlay, then the drawer closes and the list is unchanged. • AC-3: Given a user without customers_customers_add, then the entry point is not shown (unchanged gate).— Error — • ERR-1: Given the field layout fails to load, then the drawer shows an inline error/retry state (form not left blank silently). |
| [ACD-S02] — Create a customer in the drawer (form + save parity) As an agent/admin, I want the drawer form to create a customer exactly as the full page did, so that nothing about creating is lost. | Must Have | Figma | Grounded: reuse Form.vue's field/validation/save logic in MpDrawerBody — same dynamic fields (Form.vue:66-72), yup validation (:164-291), POST /v1/contacts (:611-616). Not fully unchanged (D-7): the Save/Cancel buttons currently live inside Form.vue (Form.vue:83-91), but the drawer needs them in a sticky MpDrawerFooter — so extract them, defineExpose submitForm + loadingSubmit for the footer's Save, and have Cancel emit close. Also point the scroll-to-error (document.querySelector('.q-form--error').scrollIntoView, Form.vue:643) at the drawer body's scroll container. Drop the Index.vue chrome (breadcrumb + empty grid). | • AC-1: Given the drawer is open, then it renders the same fields as the current create form for the default layout (Full name*, Username, Phone, Email, Source, Owner*, Owner's team, Assignee, DOB, Sex, Status, Job title, Province→City→District→Subdistrict→Zip, + custom fields). • AC-2: Given valid input, when the user clicks the Save button in the sticky drawer footer, then it triggers the form's exposed submitForm and POST /v1/contacts is called with the same payload shape as today.• AC-3: Given invalid input (e.g. empty required Full name / Owner), then the same validation errors show inline, Save is blocked, and the drawer scrolls to the first error within the drawer body (not the window). • AC-4: Given the cascading address fields, then City is disabled until Province is chosen, District until City, etc. (unchanged dependency behavior). • AC-5: Given a long form, then Cancel/Save stay pinned in the sticky footer while the body scrolls, and the Save button reflects the form's loadingSubmit state. |
| [ACD-S03] — Save closes the drawer and refreshes the list in place As an agent/admin, I want a successful save to return me to the updated list without a full-page reload, so that I keep my context. | Must Have | Figma | Grounded: today success does router.push('/customers/all-customers') + toast (Form.vue:621-627). In the drawer, replace the hard navigation with an emitted saved event; the list page closes the drawer, shows the toast, and refreshes the list data in place. Mixpanel [Qontak One] [Customer] Add Single Customer still fires (Form.vue:593-595). | • AC-1: Given a successful create, when the drawer is used, then the drawer closes, the success toast "Customer successfully created" shows, and the newly created customer appears in the refreshed list — without a full-page navigation. • AC-2: Given the save, then the "Add Single Customer" Mixpanel event fires exactly once (parity with today). — Error — • ERR-1: Given POST /v1/contacts fails, then the drawer stays open, the entered values are preserved, and the same error toast shows. |
| [ACD-S04] — Cancel / close discards and stays on the list As an agent/admin, I want Cancel or ✕ to close the drawer and keep me on the list, so that abandoning is frictionless. | Must Have | Figma | Grounded: today Cancel does router.push('/customers/all-customers') (Form.vue:86). In the drawer, Cancel/✕ emits close; the list page hides the drawer with no navigation. Unsaved-changes confirmation = OQ-3. | • AC-1: Given the drawer is open with entered values, when the user clicks Cancel or ✕, then the drawer closes, no customer is created, and the user remains on the list (no navigation). • AC-2: Given the drawer was closed, when the user reopens it, then it opens with a fresh, empty form (no stale state). |
| [ACD-S05] — Layout/view selection in the drawer As an admin, I want the drawer to respect the customer field layout (view), so that custom fields still appear. | Should Have | Figma — "Select view: Default view" | Grounded: fields come from field_properties?layout_id=default (Form.vue:404-408); the Figma header shows a "Select view: Default view" control. Scope of switching views in the drawer = OQ-2 (default: render the Default view; a switcher is a follow-up if not already supported). | • AC-1: Given a company with custom layout fields, when the drawer opens on the Default view, then those custom fields render and post under custom_fields[] (unchanged).• AC-2: Given the "Select view" control (if in scope, OQ-2), when a different view is chosen, then the drawer re-renders that layout's fields. |
[ACD-S06] — Handle the /customers/add deep-link (second entry point) so the full page actually retiresAs a CDP engineer, I want every path to add-customer to reach the drawer (or redirect to it), so that we don't ship two divergent create experiences and the blank-space page is truly retired. | Must Have | N/A — routing | Grounded — second entry point: the list breadcrumb (Breadcrumb.vue:69) is not the only way into create. The micro-frontend host router matches any /customers/add path and renders the full-page component (common/components/RemoteContactRouter.vue:50-51: else if (/^\/customers\/add/.test(pathName)) { asyncPageComponent = Add }, registered at :29). So an external shell / deep-link bypasses the list and lands on the full page. With the flag ON, /customers/add must redirect to the list + auto-open the drawer (default, OQ-1) — otherwise drawer-from-list and full-page-from-deep-link coexist and the page never retires. | • AC-1: Given the flag is ON, when a user (or an external shell) navigates directly to /customers/add — including via the host router (RemoteContactRouter.vue:50-51) — then they land on the customer list with the Add Customer drawer auto-opened, never the standalone full page.• AC-2: Given the flag is ON, then there is no path that still renders the full-page create surface (the empty-column page is fully retired for that scope). • AC-3: Given the flag is OFF, then /customers/add and the host route render the full page exactly as today (safe rollback).— Error — • ERR-1: Given the host cannot open the drawer (e.g. list context unavailable), then it falls back to the full page rather than a broken screen, and the fallback is logged. |
Dependencies: ACD-S02/S03/S04 reuse the existing Form.vue; ACD-S06 depends on the micro-frontend host routing decision (OQ-1).
9. Rollout
| Aspect | Detail |
|---|---|
| Feature flag | cdp_add_customer_drawer_enabled | default: OFF. ON = the "Single customer" entry opens the drawer; OFF = the current full-page route. |
| Rollout sequence | Internal / QA on staging → enable for a small % of companies → GA. Entry-point swap + drawer are entirely behind the flag. |
| Backward compatibility | Yes. With the flag OFF, /customers/add and the full page are untouched. The POST /v1/contacts + field_properties contract is unchanged, so no data/behavior migration. |
| Rollback | Flip the flag OFF → instantly reverts to the full-page flow; no redeploy. |
| Host compatibility (GA gate) | The /customers/add deep-link handled by the micro-frontend host router (RemoteContactRouter.vue:50-51) must redirect to the list + auto-open the drawer under the flag (ACD-S06). GA is blocked until this is done — otherwise the full page is still reachable and never retires. |
10. Observability
Events (Mixpanel — reuse existing, add surface context)
| Event | Trigger | Properties |
|---|---|---|
[Qontak One] [Customer] Click Add Single Customer | User clicks "Single customer" (now opens the drawer) | existing props + surface: drawer | page |
[Qontak One] [Customer] Open Add Customer Drawer (new) | The Add Customer drawer is opened (from the list or a /customers/add deep-link redirect) | entry: list | deeplink, company_sso_id |
[Qontak One] [Customer] Add Single Customer | Successful create (drawer save) | existing props + surface: drawer | page |
[Qontak One] [Customer] Close Add Customer Drawer (new) | Drawer closed without saving (Cancel/✕/overlay) | had_input: bool, company_sso_id |
(The first two already exist — Breadcrumb.vue:157-159, Form.vue:593-595; adding a surface
property lets us compare drawer vs the legacy page during rollout.)
Monitoring
| Signal | Threshold | Routing |
|---|---|---|
| Create success rate (drawer vs page) | drawer ≥ page baseline | CDP FE — investigate a drop before widening rollout |
POST /v1/contacts error rate from the drawer | no increase vs baseline | CDP FE |
| Drawer open → save conversion | tracked for the funnel | CDP PM dashboard |
Dashboard owner: CDP squad. Cadence: review daily during staged rollout, then weekly post-GA.
11. Success Metrics
| Category | Metric | Definition | Baseline | Target |
|---|---|---|---|---|
| Quality ⭐ | ⭐ Create-customer success rate (no regression) | Successful POST /v1/contacts / create attempts, drawer vs current page | Current full-page success rate | ≥ baseline (no regression) |
| Efficiency | Time-to-create (open → save) | Median seconds from opening the create surface to a successful save | Current full-page median | ≤ baseline (faster / same) |
| Adoption | Drawer usage share | % of single-customer creates via the drawer once GA | 0% (new) | ≥ 95% of single-customer creates post-GA |
| Efficiency | Context retention | % of creates that return to the list without a full-page navigation | 0% today (always navigates) | 100% via the drawer |
⭐ Primary KPI = create success rate maintained (no regression) while moving 100% of single-customer creates off the full page — this is an ADJUSTMENT, so "don't break create" is the gate.
12. Launch Plan & Stage Gates
| Stage | Audience | Duration | Success gate |
|---|---|---|---|
| 1 — Internal / QA | Staging + internal companies (flag ON) | until sign-off | Full form parity vs the page (fields, validation, save); Figma match; /customers/add host case handled |
| 2 — Canary % | Small % of companies (flag ON) | 1 week | Create success rate ≥ baseline; no POST /v1/contacts error spike from the drawer |
| 3 — GA | All website companies | — | Sustained ≥ baseline success rate; ≥ 95% of single-customer creates via the drawer |
Each gate references §11 (success rate) + §10 (error/conversion). Failing a gate → hold; regression → flip the flag OFF (§9).
13. Dependencies
| Dependency | Owner | Deliverable | Blocking? |
|---|---|---|---|
| Design — Add customer drawer | Design (CDP) | Final Figma for the right-side drawer (link) | YES |
Pixel 3 MpDrawer | Mekari Pixel | Right-placement drawer components (already used in-repo) | NO (available) |
Micro-frontend host routing — the /customers/add deep-link (second entry point) | CDP FE / host owner | With the flag ON, redirect the host-matched /customers/add path (RemoteContactRouter.vue:50-51, registered :29) to the list + auto-open the drawer, so no path still renders the full page (ACD-S06) | YES (blocking for GA) — without it the full page never retires and two create experiences coexist |
Form.vue refactor (D-7) | CDP FE | Extract Save/Cancel from Form.vue (Form.vue:83-91) into the sticky MpDrawerFooter; defineExpose submitForm + loadingSubmit; Cancel emits close; save emits saved instead of router.push (Form.vue:86, :621-622); scroll-to-error targets the drawer body (Form.vue:643) | YES |
14. Key Decisions + Alternatives Rejected
14a — Decisions Made
| ID | Date | Decision | Rationale |
|---|---|---|---|
| D-1 | 2026-07-25 | Reuse the existing Form.vue's field/validation/save logic inside a Pixel 3 MpDrawer; don't rebuild the form — but with a targeted refactor (D-7), not "unchanged". | The dynamic fields + yup validation + POST /v1/contacts are reused as-is; the page chrome, the navigation, and the Save/Cancel button placement are what change (see D-7). |
| D-2 | 2026-07-25 | No backend change — keep POST /v1/contacts + field_properties. | The adjustment is UI-surface only; the create contract is correct as-is. |
| D-3 | 2026-07-25 | Save/Cancel emit saved/close and refresh the list in place, instead of router.push. | Keeping the user on the list is the whole point of the drawer. |
| D-4 | 2026-07-25 | Gate the swap behind cdp_add_customer_drawer_enabled (OFF). | Safe, instant rollback to the full page if create regresses. |
| D-5 | 2026-07-25 | Create stays create-only — no CRM-module association in the drawer. | Association is a detail-page capability; adding it here is out of scope and defeats the "focused" intent. |
| D-6 | 2026-07-25 | Retire the full page for real: the /customers/add deep-link (host router) redirects to the drawer under the flag. | The list breadcrumb isn't the only entry point (RemoteContactRouter.vue:50-51); without redirecting the deep-link, two create experiences coexist and the blank-space page never retires. |
| D-7 | 2026-07-25 | Lift the Save/Cancel buttons out of Form.vue into the sticky MpDrawerFooter; expose submitForm + loadingSubmit from Form.vue (defineExpose) and have Cancel emit close. | The Figma footer is sticky, but today the buttons live inside Form.vue (Form.vue:83-91; Cancel $router.push :86, Save submitForm :91). A sticky drawer footer can't wrap the form's own buttons — the submit trigger must be decoupled so the footer owns Cancel/Save. This is the one real Form.vue change (beyond navigation). |
14b — Alternatives Rejected
| Alternative | Date | Why rejected |
|---|---|---|
| Keep the full page but delete the two empty columns | 2026-07-25 | Removes blank space but still navigates the user away from the list and keeps a heavyweight page for a light task. |
| Use a centered modal dialog instead of a drawer | 2026-07-25 | The form is long (many layout fields + cascading address); a right-side drawer scrolls better and matches the Pixel pattern already used for export/segment. |
| Rebuild the create form as a new drawer component | 2026-07-25 | Unnecessary — Form.vue already works standalone; rebuilding risks validation/field-parity regressions. |
15. Open Questions
| # | Type | Question | Mitigation / Default | Owner | Deadline |
|---|---|---|---|---|---|
| OQ-1 | Decision (blocking for GA — ACD-S06) | With the flag ON, how is the /customers/add deep-link handled by the host router (RemoteContactRouter.vue:50-51) — redirect to list + auto-open drawer, or render the drawer standalone at that path? | Default: redirect /customers/add → list with the drawer auto-opened; keep the route resolvable so the host doesn't 404. Must be resolved before GA (else the full page never retires). | CDP FE + host owner | 2026-10-09 (RFC) |
| OQ-2 | Scope | Does the "Select view: Default view" control in the drawer let the user switch layouts, or is it display-only (Default view)? | Default v1: render the Default view; a view switcher is a follow-up unless already supported by field_properties?layout_id=. | PM + CDP FE | 2026-10-09 |
| OQ-3 | Decision | On Cancel/✕ with unsaved input — discard silently or show an "unsaved changes" confirm? | Default: show a lightweight confirm if the form is dirty; otherwise close immediately. | PM + CDP FE | 2026-10-16 |
| OQ-4 | Design | Drawer width + long-form scroll behavior (sticky header/footer) on smaller desktop widths. | Follow Figma; sticky header (title + ✕) and footer (Cancel/Save), body scrolls. | Design + FE | 2026-10-16 |
| OQ-5 | Risk | Overlay stacking inside the drawer — the form's dropdowns (Source/Owner/Status/address selects), the @-username dropdown, and the date picker open floating layers; inside an MpDrawer these can clip or render behind the drawer if not portalled with the right z-index (cf. use-portal / zIndex: modal+n patterns at properties/.../ListTable.vue:55, UserSelectionDrawer.vue:7). | Verify each field overlay renders above the drawer (use Pixel use-portal / correct z-index); QA every dropdown + date picker inside the drawer before GA. | CDP FE + QA | 2026-10-16 |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.2 | 2026-07-25 | Zhelia Alifa (drafted w/ AI, grounded) | §14 (D-1/D-7), §8.2 (ACD-S02), §13, §15 (OQ-5) | UPDATED | Closed a second grounded gap: Form.vue is not reusable "unchanged". The Figma drawer has a sticky footer for Cancel/Save, but those buttons live inside Form.vue (Form.vue:83-91; Cancel $router.push :86, Save submitForm :91). So the footer must own the buttons: extract them into MpDrawerFooter, defineExpose submitForm + loadingSubmit, Cancel emits close, and point scroll-to-error (Form.vue:643) at the drawer body. Corrected D-1 ("not unchanged"), added D-7, rewrote ACD-S02 tech notes + added AC-5 (sticky footer / loading state) and tightened AC-2/AC-3; made the §13 Form.vue dependency the button-extraction refactor; added OQ-5 (overlay/dropdown/date-picker z-index inside the drawer — verify portalling). No change to fields or the create contract. |
| 1.1 | 2026-07-25 | Zhelia Alifa (drafted w/ AI, grounded) | §8.2 (ACD-S06), §9, §13, §15, §10, §14, §4, §8.1 | UPDATED | Closed the grounded gap + score-prd polish. Gap: the list breadcrumb (Breadcrumb.vue:69) is not the only entry point — the micro-frontend host router matches /customers/add and renders the full page (RemoteContactRouter.vue:50-51), a second (deep-link) entry point. Promoted ACD-S06 Should → Must (redirect the deep-link to the list + auto-open the drawer so the full page truly retires; +ERR fallback), made host-route handling a blocking §13 dependency + §9 GA gate, sharpened OQ-1. Polish: added a 4th Mixpanel event (Open Add Customer Drawer) to §10; dated all §14 decisions + rejected alternatives; added a system-flow mermaid to §8.1; added a no-regression Performance constraint to §4. No change to the create contract or fields. |
| 1.0 | 2026-07-25 | Zhelia Alifa (drafted w/ AI, grounded) | All | CREATED | First PRD to revamp add single customer into a right-side drawer (ADJUSTMENT, website only). Grounded against qontak-customer-fe: current full page is /customers/add (pages/customers/add.vue → Index.vue) with a 3-column grid 1fr 2fr 1fr (Index.vue:44-49) whose "Customer Activity"/"Customer Association" columns are empty placeholders (Index.vue:25-29) → ~⅔ blank; create is create-only (association only on the detail page, Associated*.vue). Solution: host the existing dynamic Form.vue (fields from field_properties?layout_id=default, save POST /v1/contacts) inside a Pixel 3 right-side MpDrawer (pattern: ExportCustomerDrawer.vue/DrawerSegment.vue), opened from Breadcrumb.vue:68-70, with save/cancel emitting saved/close + refresh-in-place instead of router.push (Form.vue:86, :621-622), behind flag cdp_add_customer_drawer_enabled. Stories ACD-S01..S06; §5 Feature Changes before/after; rollout/observability/metrics/launch; decisions + OQs (route handling, view switch, unsaved-changes, drawer width). |