gauth-0002-library-programs-broadcasts-notes · frontend/src
Implementation spec for Program Library v2 (current).dc.html (design) and Program Library v2 - AntD code.dc.html (antd composition).
Audience: a developer or Claude Code working in RandomCoffee/randomcoffee, branch master, under frontend/src/.
Today the template library is a flat grid of use cases at frontend/src/pages/account/admin/campaigns/templates.tsx (UseCaseLibrary), reachable from the sidebar as Campaigns → Use Cases Library.
v2 turns it into a single unified library that serves two program types:
Both types live in one library, browsable along two independent axes:
| Axis | Values | Source today |
|---|---|---|
| Program type | All types (default) · Coffee Connect · Mentoring | does not exist yet — new field |
| Category | Onboarding, Career, Cross-team, Remote, Social, Leadership, D&I | useCase.categories (already returned) |
Everything else stays: creating from scratch, previewing before use, creating the campaign from a template.
These already work and v2 does not change their contract:
dispatch({ type: 'company/getUseCases', language }) and the company.usecases selectorcampaigns/createFromTemplate and the redirect to /account/${slug}/admin/campaigns/details/${id}getBilling() (isOnFree(), canCreateCampaign()), getPermissions().hasCreateProgramsPermission()UpgradeButton, PermissionAction, Tags, EmptyPage from @/componentsusecases:preview:click, usecases:modal:next:click, campaign:created:from-template, usecases:cancel:click, campaigns:add:own:campaign
ConfigProvider theme in layouts/config.tsx — v2 introduces no new design tokens**pages/account/admin/campaigns/templates.tsx**
PrgramFilters (note: existing typo in the name) currently renders a Search + a multi-select of tags. v2 replaces it with a left filter rail: a Menu mode="inline" for program type and a second Menu mode="inline" for category. The search moves into the library header, horizontally centered.
UseCaseLibrary's filter predicate gains a program-type condition alongside the existing search + selected (categories) conditions.
TemplateItem's Card gains a cover (image) and actions, and shows program type as a colored Tag. The Button type="primary" block>Preview in the card footer is replaced by a Use template → action; preview becomes the whole-card click.
Alert that hosts CreateCampaignButton in its action slot is kept, restyled asthe "Start from scratch" row pinned above the grid.
**pages/account/admin/campaigns/template-modal.tsx**
Row gutter={[32,32]}, two Col md={12}, TagsBlock / AboutBlock / SetupBlock / OptionsBlock on the left, Segmented optin/match + email preview on the right, footer [Alert, Back to programs, UpgradeButton]).
Mentoring add-on, the modal body is replaced by the add-on pitch and the footer's primary action becomes Unlock Mentoring instead of Use this program.
**layouts/sidebar.tsx** — no change. v2 lives entirely under the existing templates key.
pages/account/admin/campaigns/library/
index.tsx # page shell: header (search) + filter rail + results
filters.tsx # ProgramTypeMenu + CategoryMenu
template-card.tsx # card with cover, type Tag, category Tag, actions
start-choice.tsx # "from scratch" vs "use a template" entry step
locked-card.tsx # greyscale cover + "Add-on" Badge.Ribbon + Unlock action
unlock-modal.tsx # Mentoring add-on upsell
All eight screens are on the canvas in reading order. Component names below are the actual antd components used — see Program Library v2 - AntD code.dc.html for the exact composition.
Existing page. Entry point is the Create program primary Button in the page header. Wrap in <PageLayout title={gettext('Programs')} actions={[...]}> per layouts/page.tsx (Title level={3}).
New step shown when the user clicks Create program.
| Element | antd |
|---|---|
| Progress | Steps size="small" — Start / Setup / Launch |
| Two options | Row + Col span={12}, each a Card hoverable with cover and Card.Meta |
| Template count | Badge.Ribbon text="12 templates" on the recommended card |
| Popular templates | Row + 3 × Col span={8} with small Cards |
Left card → history.push('/account/${slug}/admin/campaigns/new') (existing route). Right card → the library (③).
The core screen.
| Region | antd |
|---|---|
| Header | Input.Search allowClear centered, Button type="text" back/close |
| Type filter | Menu mode="inline" — keys all (default) / coffee / mentoring |
| Category filter | Menu mode="inline" — key cat-all default |
| Results bar | Typography.Text strong + count, Select sort (Most used / Recently added / A→Z) |
| From scratch | Alert type="info" with action={<Button size="small">Create</Button>} |
| Grid | Row gutter + Col span={8} (xs=24 md=8 in the app), Card size="small" hoverable with cover + actions |
| Type / category | Tag color="orange" (Coffee Connect) · Tag color="purple" (Mentoring) · plain Tag for category |
Default state matters: "All types" is selected on arrival and shows both program types interleaved. Do not default to a single type.
Same shell, type menu on coffee, results title and count follow the filter. Confirms the filter is a filter, not a mode switch.
Grid narrows to Col span={12}; a 340px panel opens on the right.
| Element | antd |
|---|---|
| Selected card | 2px colorPrimary border + 0 0 0 3px rgba(68,81,234,.10) halo + check pastille on the cover |
| Rating | Rate disabled allowHalf + uses count |
| Settings | Descriptions column={1} size="small" bordered — Matching method / Intros frequency / Groups size, the real fields from SetupBlock (template.algo, template.frequency, template.users_per_group) |
| What's included | List size="small" split={false} |
| Footer | Button type="primary" block Use this template · Button block Preview program |
Note: the product's own preview is a 1200px Modal (template-modal.tsx). This side panel is a new, lighter affordance for scanning; the full modal stays available behind "Preview program". Decide with design whether both survive or the panel replaces the modal.
Same library, degraded for entitlement.
| Treatment | antd |
|---|---|
| Type menu | Mentoring item disabled: true with a LockOutlined and the sub-label "Add-on required" |
| Banner | Alert type="info" — "The Mentoring add-on isn't included in your plan" + action Unlock Mentoring |
| Locked cards | Badge.Ribbon text="Add-on" color="#6D28D9", cover filter: grayscale(1) + dark overlay, action Unlock with a lock icon |
Rules
every plan.
Modal width={460} centered with a LockOutlined title, a short pitch, a List of the five Mentoring capabilities, and footer [Maybe later, Unlock Mentoring].
The five capabilities are fixed copy, identical everywhere they appear:
Sells what ships with a template: Row of four Card size="small", each with a Tag color="green" Included in extra.
| Asset | Preview built with |
|---|---|
| Kickoff Broadcast | mini email mock |
| Expectations Survey | Radio.Group |
| Session structure | Steps size="small" — Intro / Goals / Review / Wrap-up |
| Matching rules | List size="small" |
Right rail: Descriptions at-a-glance + Alert "Fully customizable after creating the campaign" (the existing product string from template-modal.tsx's footer).
This is the part the design cannot fake. The use-case payload behind company/getUseCases needs:
| Field | Type | Why | |
|---|---|---|---|
program_type | `'coffee_connect' \ | 'mentoring'` | drives the type filter and the type Tag; backfill every existing use case to coffee_connect |
cover_image | url | card covers; needs a fallback per category while the library is being illustrated | |
category | string | the cards show one primary category. categories (plural) already exists and stays the filter source — decide whether category is a new field or categories[0] | |
uses_count | int | the "2.3k uses" affordance and the "Most used" sort | |
rating | float, optional | Rate in the detail panel; hide the row if absent |
Sort options need backend or client support for most_used (uses_count desc) and recent.
Add-on access is not isOnFree(). It needs its own check, e.g. getBilling().hasAddon('mentoring'). Gate three things with it:
Menu (disabled)Unlock Mentoring vs Use this program)Creating a Mentoring campaign must also be refused server-side — the client gate is UX, not security.
program_type on use cases + backfill; expose uses_count.library/index.tsx shell with the two filter Menus, reusing the existing getUseCases data and client-side filtering. Ship it behind the existing templates route.
template-card.tsx with cover + type/category tags, replacing TemplateItem.start-choice.tsx (②) in front of campaigns/new.cover_image and ratings once the content team has assets.Steps 1–3 are shippable on their own and already deliver the unified library.
The antd file runs in a browser canvas, so three things there are scaffolding:
dc-props={{...}} passes dataSource / renderItem / style. In the app write dataSource={...} and style={...} normally.
contain:paint and transitionName="" / maskTransitionName="" to keep the inline Modal (getContainer={false}) inside its frame. In the app use a normal portal Modal with default motion.
import { ... } from 'antd'.The bespoke 56px page headers on ③–⑧ exist to frame the canvas. In the app these screens wrap in <PageLayout title={...} actions={[...]}>.