Skip to content

Purchase Order UI — Direct Send Design

This document builds up and discusses the UI design changes needed to let users send a Purchase Order directly from the system — part of PDEV-970 — Enable Direct email sending for Purchase Orders (under the PDEV-968 umbrella, alongside its Email Order sibling PDEV-969). The backend send capability already exists — see the Email module API reference (POST /v1/shop-access/email/job), which accepts attachments. The work here is the front-end surface that will call it for the Purchase Order flow.

The Email Order (direct-send design) sends the order as the email body. A Purchase Order is different: it is a formal document the supplier expects as a PDF, and the system already generates that PDF. So the model here is a cover email with the PO PDF attached — not the PO rendered inline. We start by recording the current PO sidebar, then show the proposed order sheet with a “Send via email” primary, and finally the cover-email composer that primary opens.

The two composers (this one and the Email Order sibling) share ~90% of their chrome. A companion audit — Direct-Send Composer — Shared-Component Audit — enumerates the overlap and proposes a shared foundation that PDEV-1019 can execute against.

The Purchase Order surface is the OrderSidebar slide-over in arda-frontend-app. It opens from the right when a user starts a purchase order for items whose supplier order method is PURCHASE_ORDER. It is a full order sheet — not an email — assembled from several sections:

  • Header — a “Purchase order” title with a close (X) control.
  • Order header form (OrderHeaderForm) — supplier (“To”), PO number, created-on / deliver-by dates, deliver-to address, and vendor details.
  • Order sheet (OrderSheet) — the editable line-items table.
  • Tax / fee / discount and Order summary — adjustments and the computed totals.
  • Terms (OrderTermsSection) — payment terms, shipping terms, shipping via, a note to the vendor, and terms & conditions.
  • FooterCancel, Preview, Download, and Start order.

The only outbound actions today are Download and Preview, which render the order to a PO PDF (generatePurchaseOrderPDF); Start order accepts the items without producing a document. In every case the order’s items are accepted and moved to In Progress. To actually get the PO to the supplier, the user downloads the PDF and emails it themselves from their own mail client. Closing that gap — a real “Send” that emails the PO PDF via the backend job endpoint — is the purpose of this project.

A working prototype of the redesigned PO sidebar. Think of it as two views in one panel: the order sheet (the surface above, redressed with a new primary action), and the cover-email composer the primary opens. Both run live below; try the interactions directly.

Structurally the same as today — order header form, line items, taxes / fees / discounts, grand total — with one change in the footer: the primary action is a split button labelled “Send via email”. Its caret menu carries Download (the current PDF export) and Start order (accept without emailing), which today live as top-level footer buttons. Cancel stays on the left.

Clicking Send via email advances the panel to the composer; Cancel in the composer footer returns to the order sheet.

The mock renders the sheet as a grey-block skeleton (no dummy data) so reviewers focus on the layout and the new primary rather than fictional line items.

  • PO PDF as the payload; the email is a cover note. The generated PurchaseOrder-{number}.pdf sits directly below the body as a chip with a paperclip glyph inside the chip — it rides along automatically on a direct send. An Edit ghost button next to the chip returns to the order-sheet page without losing edits to the body.
  • Gmail-style addresses, From first. From, To, and Cc are recipient-chip fields (in that order — reply-to reads at the top). From defaults to your logged-in email and Cc is pre-seeded with the same address (self-CC). To opens with the supplier’s order address. Type and press Enter (or comma) to add a chip, double-click a chip to edit, and click × — or Backspace in the empty input — to remove it.
  • Subject. Defaults to Purchase Order {number} from {tenant} and is editable inline.
  • Body — a single prefilled rich-text canvas. The body is one Tiptap-powered editor with StarterKit (paragraphs, bold/italic, lists) — no preview toggle. It arrives prefilled with the greeting, cover line, and sign-off; you can rewrite any of it inline.
  • Powered by Arda — toggle switch. A switch below the attachment controls whether the “Powered by Arda” signature is included in the sent message (default on).
  • Revert & undo. Revert all in the footer (shown once any field has been edited) resets every field, the body, and the badge toggle to their defaults; ⌘Z / Ctrl+Z undoes the most recent address change (Tiptap owns its own undo stack for the body).
  • Send — split primary. Send is a split button: the main action sends the HTML body with the PO PDF attached automatically (fires on ⌘↵ / Ctrl+↵), and the caret menu offers Open as mailto. A successful Send closes the panel and accepts the order (in this mock, the panel switches to a confirmation with a Reset to start over).
  • Copy — plain text. Copy writes the cover note as plain text and accepts the order. A tooltip reminds you: “Don’t forget to attach the PO” — the clipboard can’t carry the PDF.

A toast also confirms each outcome (e.g. “Purchase order sent”), but the panel closing is the primary confirmation.

This section captures the variant for when direct send is not available — for example, a tenant whose EmailConfiguration has no authorized send address, so the composer falls back to a copy-only flow. The address chips and the Send split are hidden. The body, attachment row, and Powered-by-Arda toggle are unchanged, and the footer offers Open in email as the default action with Copy secondary.

The key PO-specific wrinkle: a mailto: draft and the clipboard cannot carry attachments. So in restricted mode the attachment row carries a Download PO PDF button and a short hint — the user downloads the PO PDF, opens a draft (or copies the cover note) and attaches the PDF to their own message before sending.

The cover email is assembled from data: the body is prefilled from the PO draft plus tenant/user context, the PO PDF is the generated document, and the send envelope (To, Cc, subject, from) is resolved at send time. The table lists each data-driven element, where it comes from in the system, the placeholder the live mock above uses (in mocks/_sample-po.ts), and whether the mock lets you edit it inline (click into the body to edit; “Revert all” in the footer restores every field). Static chrome — the “Powered by Arda” badge (toggleable via the switch below the attachment) — is omitted from the table.

ElementWhere the data comes fromMock value (this page)Editable in mock
Cover-note bodyPrefilled from the PO draft supplier name (OrderDraft.supplierName), PO number and deliver-by date (OrderDraft.poNumber / deliverBy), and the authenticated user’s sign-off (userContext.name / userContext.email) — a single Tiptap document combining greeting, cover line, and sign-off.bodyDefault — greeting to “Stark Industries”, references PO-001234 / Jul 14, 2026, sign-off from “developer@arda.cardsYes — one rich-text canvas (Tiptap + StarterKit)
Attachment — PO PDFThe document produced by generatePurchaseOrderPDF from the draft, sent as EmailJobInput.attachments[]attachmentName (“PurchaseOrder-PO-001234.pdf”, 38 KB)No — generated from the order sheet
Recipient (To)Supplier order/contact email (Business Affiliate). Required for direct send.toAddresses (“orders@…”, “tony@…”)Yes — Gmail-style To chips (add / edit / remove)
Recipient (Cc)Carbon-copy recipients. Seeded with the logged-in user’s email (self-CC) so a copy returns to the sender; editable/removable.ccAddresses (seeded with “developer@arda.cards”)Yes — Gmail-style Cc chips (add / edit / remove)
SubjectTemplate of PO number + tenant company name (composed client-side, editable)subjectDefault (“Purchase Order PO-001234 from Acme Operations”)Yes
From address (reply-to)The editable “From” is the message reply-to, defaulting to the logged-in user’s email. The actual envelope sender is bound to the tenant’s EmailConfiguration (shown read-only as “sent via …”).From chip = senderEmail (“developer@arda.cards”); envelope sender = fromAddress (“noreply@…”)Yes — single From chip (reply-to); envelope sender is read-only