feat(uat-batch): Groups F + G + H — DocsHub/signing + admin consolidation + email
F27–F29, G30, G31, H32, H33 from the 2026-05-21 plan.
Shipped now:
F28 Past-milestones expandable history. The Past strip on the
Interest overview becomes an <Accordion> — each row collapses
to the same one-line summary as before, expands to render the
full <MilestoneSection> (steps list, sub-status, inline doc
actions). Reuses the existing MilestoneSection so no new
per-milestone rendering needs to be maintained.
F29 Watchers configurable at document creation time. The unified
create-document wizard gets a Watchers section with a
multi-select checkbox list backed by /api/v1/admin/users/picker.
Selected user ids are sent in the `watchers` array on the POST
(replacing the prior hardcoded `[]`). UI matches the
post-creation WatchersCard so reps see the same identity rows
regardless of entry point.
G30 /admin/invitations merged into /admin/users. The Users page
now wraps the existing UserList + InvitationsManager in a
Tabs control (Active users / Invitations). The standalone
/admin/invitations route returns a redirect to the merged page
for bookmark back-compat. Removed nav catalog entry +
admin-sections-browser tile; extended the Users catalog
keywords with "invitations / pending invites / onboarding"
so command-K search still lands on the right surface.
G31 /admin/ai picks up the berth-PDF-parser section + a "planned
AI surfaces" placeholder. Berth PDF parser remains
env-configured today; the page now documents it so admins
don't hunt for the controls. Closes the "where do I configure
AI?" loop.
H32 Email settings explainer panel above the SMTP cards. Spells
out why noreply + sales have separate credentials and which
workflows ship from each mailbox. Existing field titles
gained the "(noreply)" suffix so the model maps cleanly.
H33 Supplemental-info-request email rebuilt to use the shared
branded shell (logo + blurred overhead background + max-
width 600 table layout) instead of the prior plain-HTML
page. Per-port branding (logo / primary color / background /
header / footer) flows from getPortBrandingConfig. CTA
button picks up the port's primary color.
Already shipped (verified pre-shipped):
F27 DocumentsHub root view already hides the breadcrumb via
`selectedFolderId !== undefined` conditional.
Verified: tsc clean, vitest 1454/1454.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,12 @@ import type { DetailTab } from '@/components/shared/detail-layout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DatePicker } from '@/components/ui/date-picker';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import { NotesList } from '@/components/shared/notes-list';
|
||||
import { InlineEditableField } from '@/components/shared/inline-editable-field';
|
||||
import { ClientChannelEditor } from '@/components/clients/client-channel-editor';
|
||||
@@ -1022,18 +1028,41 @@ function OverviewTab({
|
||||
skip-ahead when reality calls for it (an override-confirm
|
||||
gates the actual stage move). */}
|
||||
{pastMilestones.length > 0 && (
|
||||
<div className="rounded-lg border bg-muted/20 px-4 py-2.5">
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wide">Past</span>
|
||||
{pastMilestones.map((m) => (
|
||||
<span key={m.key} className="inline-flex items-center gap-1.5">
|
||||
<CheckCircle2 className="size-3 text-emerald-600" aria-hidden />
|
||||
<span className="font-medium text-foreground">{m.title}</span>
|
||||
<span>·</span>
|
||||
<span>{m.pastSummary}</span>
|
||||
</span>
|
||||
))}
|
||||
<div className="rounded-lg border bg-muted/20">
|
||||
<div className="flex items-center gap-2 border-b px-4 py-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
<span>Past</span>
|
||||
</div>
|
||||
<Accordion type="multiple" className="px-4">
|
||||
{pastMilestones.map((m) => (
|
||||
<AccordionItem key={m.key} value={m.key} className="border-0">
|
||||
<AccordionTrigger className="py-2 text-xs font-medium hover:no-underline">
|
||||
<div className="flex flex-1 items-center gap-2 text-left text-muted-foreground">
|
||||
<CheckCircle2 className="size-3 shrink-0 text-emerald-600" aria-hidden />
|
||||
<span className="font-medium text-foreground">{m.title}</span>
|
||||
<span className="text-[10px]">·</span>
|
||||
<span className="truncate text-xs">{m.pastSummary}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{/* Reuse the same MilestoneSection layout used for the
|
||||
current milestone — the steps list, sub-status badge,
|
||||
and any inline doc actions all render the same way.
|
||||
`isActive={false}` keeps the NEXT-STEP pill off. */}
|
||||
<MilestoneSection
|
||||
title={m.title}
|
||||
icon={m.icon}
|
||||
status={m.status}
|
||||
isPending={stageMutation.isPending}
|
||||
onAdvance={advance}
|
||||
currentStage={interest.pipelineStage}
|
||||
isActive={false}
|
||||
steps={m.steps}
|
||||
footer={m.footer}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user