Skip to content

feat: dashboard redesign — icon sidebar, KPI hierarchy, card gallery, seed contact#1

Open
Fusion-Data-Company wants to merge 14 commits into
mainfrom
claude/epic-dewdney-8a463f
Open

feat: dashboard redesign — icon sidebar, KPI hierarchy, card gallery, seed contact#1
Fusion-Data-Company wants to merge 14 commits into
mainfrom
claude/epic-dewdney-8a463f

Conversation

@Fusion-Data-Company

@Fusion-Data-Company Fusion-Data-Company commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Icon sidebar — collapsed from 224px labeled nav to 56px icon rail; tooltips on hover; teal active accent bar
  • Dashboard — KPI cards get color accent tops + overflow fix; empty states become real CTAs (View Schedule, Import Contacts); header gains Add Contact + dev-only Load Test Data buttons
  • Card Gallery — added missing /api/cards/upload (POST, multipart, 10MB limit, image-type allowlist) and /api/cards/[id] (DELETE, tenant-scoped)
  • Dev seed/api/dev/seed inserts Alex Testfield with all 66 McKay fields, 2 children, Cowboys sports team, birthday scheduled send for tomorrow; guarded by NODE_ENV + ENABLE_DEV_ROUTES=true
  • Schema fix — added scheduledSendsRelations so with: { contact: true } in schedule routes resolves correctly
  • Shared auth util — extracted getTenantId to src/lib/auth.ts

Test Plan

  • Sign in at localhost:3000
  • Click Load test data — dashboard Coming Up shows Alex Testfield birthday
  • Navigate to Contacts — all 66 McKay fields visible in table row
  • Navigate to Card Gallery — drag/drop or Upload Cards button posts an image successfully
  • Delete the uploaded card — disappears from gallery
  • Verify icon sidebar renders correctly, tooltips appear on hover
  • Verify empty states on fresh tenant show CTA buttons (not dead text)

Security

  • Upload route validates extension allowlist (jpg/jpeg/png/gif/webp/avif) + MIME type + 10MB size cap before writing to disk
  • Seed route requires NODE_ENV !== 'production' AND ENABLE_DEV_ROUTES=true (double guard)
  • Card delete is double-fenced: query by id + tenantId first, then delete with same conditions

🤖 Generated with Claude Code

Summary by Sourcery

Redesign the dashboard and app shell while adding card gallery APIs and a dev-only seed contact for easier testing.

New Features:

  • Introduce a compact icon-only sidebar with tooltip navigation and centered user avatar.
  • Add dashboard header actions including an Add Contact button and a dev-only Load Test Data trigger.
  • Provide API endpoints to upload and delete custom card templates scoped by tenant.
  • Expose a dev-only seed route to insert a fully populated test contact with related entities and a scheduled send.

Bug Fixes:

  • Fix card gallery upload failures by adding the missing backend route and storage handling.
  • Prevent broken relational queries on scheduled sends by adding the appropriate schema relations.

Enhancements:

  • Refine dashboard KPI cards with colored accent tops, typography tweaks, and improved layout to prevent overflow.
  • Improve dashboard sections for today’s sends and upcoming occasions with consistent layout, richer empty states, and CTA buttons.
  • Extract a shared getTenantId helper for tenant resolution across API routes.
  • Define scheduled send relations in the DB schema for proper eager-loading of associated entities.

Documentation:

  • Add a detailed product spec documenting the dashboard redesign, card gallery routes, and seed contact behavior.

Chores:

  • Ignore uploaded assets under public/uploads while keeping the cards directory present in the repo.

Fusion-Data-Company and others added 8 commits May 7, 2026 20:14
Covers icon sidebar, KPI overflow fix, empty states with CTAs,
card gallery upload/delete routes, and seed contact endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t} queries work

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rdTemplate relation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented May 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rapport Ready Ready Preview, Comment May 8, 2026 2:59pm

@sourcery-ai

sourcery-ai Bot commented May 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Dashboard redesign implementing an icon-only sidebar, improved KPI and schedule layout with actionable empty states, new card gallery upload/delete APIs with validation and tenant scoping, and a dev-only seed route plus schema/auth utilities to support test data and schedule relations.

Sequence diagram for card upload via card gallery

sequenceDiagram
  actor User
  participant CardGalleryUI
  participant CardsUploadRoute as ApiCardsUpload
  participant ClerkAuth as ClerkAuth
  participant AuthUtil as AuthUtil_getTenantId
  participant Database as DB
  participant FileSystem as DiskStorage

  User->>CardGalleryUI: Select card image and occasionType
  CardGalleryUI->>CardsUploadRoute: POST /api/cards/upload (multipart formData)

  CardsUploadRoute->>ClerkAuth: auth()
  ClerkAuth-->>CardsUploadRoute: userId
  alt user not authenticated
    CardsUploadRoute-->>CardGalleryUI: 401 Unauthorized
  else user authenticated
    CardsUploadRoute->>AuthUtil: getTenantId(userId)
    AuthUtil-->>CardsUploadRoute: tenantId or null
    alt no tenantId
      CardsUploadRoute-->>CardGalleryUI: 400 No tenant
    else tenantId found
      CardsUploadRoute->>CardsUploadRoute: Validate file size, extension, MIME
      alt invalid file
        CardsUploadRoute-->>CardGalleryUI: 4xx error (size/type)
      else valid file
        CardsUploadRoute->>FileSystem: mkdir uploadDir, writeFile
        FileSystem-->>CardsUploadRoute: file persisted
        CardsUploadRoute->>Database: insert cardTemplates (tenantId, occasionType, name, imageUrl)
        Database-->>CardsUploadRoute: created cardTemplate
        CardsUploadRoute-->>CardGalleryUI: 201 Created (cardTemplate)
        CardGalleryUI-->>User: Show new card in gallery
      end
    end
  end
Loading

Sequence diagram for card delete via card gallery

sequenceDiagram
  actor User
  participant CardGalleryUI
  participant CardsDeleteRoute as ApiCardsId
  participant ClerkAuth as ClerkAuth
  participant AuthUtil as AuthUtil_getTenantId
  participant Database as DB

  User->>CardGalleryUI: Click delete on card
  CardGalleryUI->>CardsDeleteRoute: DELETE /api/cards/{id}

  CardsDeleteRoute->>ClerkAuth: auth()
  ClerkAuth-->>CardsDeleteRoute: userId
  alt user not authenticated
    CardsDeleteRoute-->>CardGalleryUI: 401 Unauthorized
  else user authenticated
    CardsDeleteRoute->>AuthUtil: getTenantId(userId)
    AuthUtil-->>CardsDeleteRoute: tenantId or null
    alt no tenantId
      CardsDeleteRoute-->>CardGalleryUI: 400 No tenant
    else tenantId found
      CardsDeleteRoute->>Database: findFirst cardTemplates where id and tenantId
      Database-->>CardsDeleteRoute: card or null
      alt card not found
        CardsDeleteRoute-->>CardGalleryUI: 404 Not found
      else card is system card
        CardsDeleteRoute-->>CardGalleryUI: 403 Cannot delete system cards
      else deletable card
        CardsDeleteRoute->>Database: delete cardTemplates where id and tenantId
        Database-->>CardsDeleteRoute: delete success
        CardsDeleteRoute-->>CardGalleryUI: 200 { success: true }
        CardGalleryUI-->>User: Remove card from gallery
      end
    end
  end
Loading

Sequence diagram for dev-only dashboard seed route

sequenceDiagram
  actor DevUser
  participant DashboardPage
  participant SeedRoute as ApiDevSeed
  participant ClerkAuth as ClerkAuth
  participant AuthUtil as AuthUtil_getTenantId
  participant Database as DB

  DevUser->>DashboardPage: Click Load test data
  DashboardPage->>SeedRoute: POST /api/dev/seed (useMutation)

  alt production environment or dev routes disabled
    SeedRoute-->>DashboardPage: 403 Not available
    DashboardPage-->>DevUser: Show error (cannot load test data)
  else dev environment allowed
    SeedRoute->>ClerkAuth: auth()
    ClerkAuth-->>SeedRoute: userId
    alt no userId
      SeedRoute-->>DashboardPage: 401 Unauthorized
    else user authenticated
      SeedRoute->>AuthUtil: getTenantId(userId)
      AuthUtil-->>SeedRoute: tenantId or null
      alt no tenantId
        SeedRoute-->>DashboardPage: 400 No tenant
      else tenantId found
        SeedRoute->>Database: delete contacts where tenantId and email alex.testfield@example.com
        Database-->>SeedRoute: delete complete
        SeedRoute->>Database: insert contacts Alex Testfield (all McKay fields)
        Database-->>SeedRoute: created contact
        SeedRoute->>Database: insert contactChildren records
        SeedRoute->>Database: insert contactSportsTeams record
        SeedRoute->>Database: insert scheduledSends birthday for tomorrow
        Database-->>SeedRoute: inserts complete
        SeedRoute-->>DashboardPage: 200 { success: true, contactId }
        DashboardPage->>DashboardPage: queryClient.invalidateQueries()
        DashboardPage-->>DevUser: Dashboard refresh shows Alex Testfield in Coming Up and Contacts
      end
    end
  end
Loading

Entity relationship diagram for contacts, scheduled sends, and cards

erDiagram
  tenants {
    string id PK
  }

  contacts {
    string id PK
    string tenantId FK
    string email
    string firstName
    string lastName
  }

  contactChildren {
    string id PK
    string contactId FK
    string tenantId FK
    string name
  }

  contactSportsTeams {
    string id PK
    string contactId FK
    string tenantId FK
    string sport
    string teamName
  }

  cardTemplates {
    string id PK
    string tenantId FK
    string occasionType
    string name
    string imageUrl
    boolean isSystem
    boolean isActive
  }

  scheduledSends {
    string id PK
    string tenantId FK
    string contactId FK
    string cardTemplateId FK
    string occasionType
    string occasionLabel
    date scheduledDate
    string status
    string emailSubject
  }

  tenants ||--o{ contacts : has
  tenants ||--o{ contactChildren : has
  tenants ||--o{ contactSportsTeams : has
  tenants ||--o{ cardTemplates : has
  tenants ||--o{ scheduledSends : has

  contacts ||--o{ contactChildren : has
  contacts ||--o{ contactSportsTeams : has
  contacts ||--o{ scheduledSends : has

  cardTemplates ||--o{ scheduledSends : used_by
Loading

File-Level Changes

Change Details Files
Redesigned dashboard layout with improved KPI cards, actionable header buttons, and more robust schedule sections including typed models and empty states.
  • Refactored StatCard to use shared accent color maps, top border accent, overflow handling, and truncation to avoid clipping in the 4-column KPI grid.
  • Adjusted KPI labels/values and grid container to ensure readability and prevent overflow while keeping a 4-column layout.
  • Added Add Contact and dev-only Load test data header actions wired via GlassButton and react-query mutation to POST /api/dev/seed, invalidating queries on success.
  • Introduced ScheduledSend and UpcomingItem interfaces and strengthened typing of todaySends/upcoming mappings.
  • Updated Going Out Today and Coming Up panels to use consistent card layout, badges, and CTA-style empty states linking to /schedule and /contacts/import.
src/app/(app)/dashboard/page.tsx
Simplified app shell sidebar to an icon-only navigation rail with tooltip titles and compact user avatar footer.
  • Reduced sidebar width from labeled 224px layout to 56px icon rail and removed text logo/plan details.
  • Changed nav items to centered icon buttons with active teal accent rail and hover states using title tooltips for labels.
  • Reworked user section to center Clerk UserButton in a fixed-height footer row with border separation.
src/components/layout/AppShell.tsx
Added tenant-scoped card gallery upload and delete API routes with file validation and DB integration.
  • Implemented POST /api/cards/upload to accept multipart image uploads, enforce extension/MIME allowlists and 10MB size cap, persist files under public/uploads/cards, and insert a non-system cardTemplates row.
  • Implemented DELETE /api/cards/[id] to authorize via Clerk, scope lookup and deletion by tenantId+id, and forbid deletion of system templates.
  • Ensured both routes use shared getTenantId helper for tenant resolution.
src/app/api/cards/upload/route.ts
src/app/api/cards/[id]/route.ts
src/lib/auth.ts
Introduced a guarded dev-only seed route that inserts a fully-populated test contact, related entities, and a scheduled send for use on the dashboard.
  • Created POST /api/dev/seed that checks NODE_ENV and ENABLE_DEV_ROUTES, authenticates via Clerk, resolves tenant via getTenantId, and returns 403/401/400 as appropriate.
  • Made seeding idempotent by deleting any existing test contact for the tenant before inserting the new Alex Testfield record with extensive McKay fields populated.
  • Inserted associated contactChildren, contactSportsTeams, and scheduledSends records so dashboard Coming Up and contact detail views have realistic data.
  • Integrated the seed route with the dashboard Load test data button via react-query mutation.
src/app/api/dev/seed/route.ts
src/app/(app)/dashboard/page.tsx
src/lib/auth.ts
Extended DB schema relations to support scheduled send joins and added docs/specs and gitignore for uploads.
  • Defined scheduledSendsRelations to relate scheduledSends to contacts, tenants, and cardTemplates so schedule routes can eager-load contact data.
  • Added dashboard redesign spec documentation under docs/superpowers/specs/ for future reference and alignment.
  • Updated .gitignore and repository structure to ignore public/uploads while keeping the cards subdirectory via .gitkeep (per spec, though not shown in diff).
src/lib/db/schema.ts
docs/superpowers/specs/2026-05-07-rapport-dashboard-redesign.md
.gitignore

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In the DELETE /api/cards/[id] handler, the params argument is typed and treated as a Promise, but Next.js app router passes it synchronously, so you can simplify the signature to { params }: { params: { id: string } } and drop the await params to make the handler more idiomatic and type-safe.
  • For StatCard color handling, consider tightening the color type to a union of the supported keys (e.g. 'teal' | 'gold' | 'sky' | 'coral') instead of string so TypeScript can catch invalid values and ensure you always hit a defined accent in ACCENT/ACCENT_RGB.
  • On the dashboard page you’re reading process.env.NODE_ENV in a client component; although this is inlined at build time, using a dedicated NEXT_PUBLIC_... flag (as mentioned in the spec) would make the dev-only button behavior clearer and avoid confusion if you later change build targets or environments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `DELETE /api/cards/[id]` handler, the `params` argument is typed and treated as a `Promise`, but Next.js app router passes it synchronously, so you can simplify the signature to `{ params }: { params: { id: string } }` and drop the `await params` to make the handler more idiomatic and type-safe.
- For `StatCard` color handling, consider tightening the `color` type to a union of the supported keys (e.g. `'teal' | 'gold' | 'sky' | 'coral'`) instead of `string` so TypeScript can catch invalid values and ensure you always hit a defined accent in `ACCENT`/`ACCENT_RGB`.
- On the dashboard page you’re reading `process.env.NODE_ENV` in a client component; although this is inlined at build time, using a dedicated `NEXT_PUBLIC_...` flag (as mentioned in the spec) would make the dev-only button behavior clearer and avoid confusion if you later change build targets or environments.

## Individual Comments

### Comment 1
<location path="src/app/api/cards/[id]/route.ts" line_range="9-18" />
<code_context>
+
+export async function DELETE(
+  _req: Request,
+  { params }: { params: Promise<{ id: string }> }
+) {
+  try {
+    const { userId } = await auth()
+    if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+
+    const tenantId = await getTenantId(userId)
+    if (!tenantId) return NextResponse.json({ error: "No tenant" }, { status: 400 })
+
+    const { id } = await params
+
+    const card = await db.query.cardTemplates.findFirst({
</code_context>
<issue_to_address>
**suggestion:** The `params` type and `await params` usage are inconsistent with Next.js route handler conventions.

In App Router route handlers, `params` is a plain object, not a promise. Typing it as `Promise<{ id: string }>` and `await`-ing it is misleading for both readers and TypeScript. Please update the signature to use a non-promise `params` object and access `id` directly:

```ts
export async function DELETE(
  _req: Request,
  { params }: { params: { id: string } }
) {
  const { id } = params
  // ...
}
```

Suggested implementation:

```typescript
export async function DELETE(
  _req: Request,
  { params }: { params: { id: string } }
) {

```

```typescript
    const { id } = params

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +9 to +18
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { userId } = await auth()
if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })

const tenantId = await getTenantId(userId)
if (!tenantId) return NextResponse.json({ error: "No tenant" }, { status: 400 })

const { id } = await params

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The params type and await params usage are inconsistent with Next.js route handler conventions.

In App Router route handlers, params is a plain object, not a promise. Typing it as Promise<{ id: string }> and await-ing it is misleading for both readers and TypeScript. Please update the signature to use a non-promise params object and access id directly:

export async function DELETE(
  _req: Request,
  { params }: { params: { id: string } }
) {
  const { id } = params
  // ...
}

Suggested implementation:

export async function DELETE(
  _req: Request,
  { params }: { params: { id: string } }
) {
    const { id } = params

- ContactsTable: full 4-side cell borders on every th/td, column widths
  matched to content type, date formatting (May 7 1981), boolean Yes/No,
  zebra striping, font size 12px
- Dashboard: 2-col mobile / 4-col desktop KPI grid, 1-col mobile / 3-col
  desktop content row, min-h-[280px] on panels, min-h-full page fill
- Spec doc added for this rebuild
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant