Apollo

Next.js Guru & Modern Web Advisor. Expert guidance on React, TypeScript, Tailwind CSS, shadcn/ui, and modern frontend architecture.


A Quick Note On Working with Apollo

Apollo is your Next.js Guru & Modern Web Advisor — expert guidance on React, TypeScript, Tailwind CSS, shadcn/ui, and modern frontend architecture. In practice, the work he handles is centered on data-driven admin interfaces and web portals — the same domain as Hermes, but with a modern React stack. Think server-rendered admin panels with data tables, filters, charts, document viewers, and payment flows — Next.js 15 with App Router and Server Components, TypeScript 5, Tailwind CSS v4 with shadcn/ui (Radix primitives + lucide-react icons), Drizzle ORM for data persistence, and Stripe/PayPal SDKs for payment processing.

The chat session below is a real-world example of how a conversation with Apollo might look — from discussing requirements, through creating a structured task file, to pair-programming the implementation top to bottom. Feel free to read through it, pick up patterns, get a sense of the rhythm.

But you don't have to follow any of it. You don't need to build a billing portal. You don't need to use pair programming. You don't need to create a task file first. Just open the chat with Apollo and tell him what you need — an admin dashboard, a self-service portal, a payment flow, whatever modern web app you have in mind. Build your own way of working together. He'll adapt.

Go chat with Apollo. Go build stuff.


Billing Portal — Admin Dashboard & Customer Portal UI

Context. The user previously worked with Athena to create a Billing Portal PRD (see Athena → New Billing Portal), then worked with Hephaestus to build the backend payment reminders. ReportBurster handles bill generation and initial email delivery. The missing piece — the admin dashboard and customer-facing portal UI — is Apollo's domain.

The approach: first, the user and Apollo discuss requirements and create a structured tasks file (.org). Then they implement each task one by one in a fast-paced pair-programming session — Apollo guides, provides code snippets, and pushes toward completion while the user stays in full control, understanding where each piece of code comes from and where it goes.

Why pair programming and not a single-shot agent? A capable coding agent like Claude Code could take the tasks file as input and produce the complete working app in one shot — and that is a perfectly legitimate way to work (we fully support this too). But for the purpose of this guide, we deliberately chose the pair-programming approach: it's still fast, and it gives the user a "stay in control and understand what's happening" mode of working.

Here is the PRD document that Athena created — the starting point for this session:

billing-portal-prd.orgView Full Screen

And here are the UI mockups Athena designed — the visual targets for the portal:

bills-list-mockup.htmlView Full Screen
bill-detail-mockup.htmlView Full Screen

Framing the Problem

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
Good evening Apollo. I've been working with Athena on a Billing Portal PRD, and Hephaestus is building the backend reminders. I need you for the UI — same billing portal but with your Next.js/Tailwind/shadcn stack.
K
kraftbot10:18 PM

Good evening! Apollo here — your Next.js Guru & Modern Web Advisor. Happy to jump in on the UI side.

I can see Athena's PRD — billing-portal-prd.org in her artifacts folder — and her two mockups (bills list + bill detail). Great news: the next-playground already has a complete Invoice management system with Drizzle schema, API routes, admin pages (shadcn Table, forms, detail), and a full payment integration (StripeCheckoutForm + PayPalCheckout + InvoicePayment dialog). We follow these exact patterns for Bills.

The mockups are in Bootstrap 5, but we'll translate to Tailwind CSS + shadcn/ui — the semantics are the same. Bootstrap cards become shadcn Card, badges become Badge, tables become Table.

A
Admin10:18 PM
Perfect — let's figure out what we need.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
What pages and components do we need?
K
kraftbot10:18 PM

Here's the architecture:

Schema & API (data layer):

  • bills table in Drizzle schema (follow invoices pattern)
  • /api/bills routes (GET list, POST create, GET/PUT/DELETE by id)

Admin Pages (under app/(admin)/admin/bills/):

  • Bills list — shadcn Table with search, status Badge, actions
  • Bill detail — status banner, amounts, "Pay Now" BillPayment dialog
  • Create/edit forms — shadcn Input, Select, Label

Portal Pages (under app/(main)/bills/):

  • Bills homepage — Tailwind card grid with status badges
  • Bill detail — Server Component with payment button

Payment Component:

  • BillPayment.tsx — follows InvoicePayment.tsx (Dialog + Stripe/PayPal toggle)

Server Components for data fetching (list, detail), Client Components for interactive elements (forms, payment dialog). The existing InvoicePayment.tsx is a perfect blueprint — same Dialog, same provider wrappers, just different API endpoints.

Send a message...

Reviewing Athena's Mockups

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
Let's look at Athena's Bootstrap mockups — how do we translate them to our stack?
K
kraftbot10:18 PM

Here's the translation for both mockups:

Bills list mockup → Tailwind translation:

  • Bootstrap card → shadcn Card + CardHeader + CardContent + CardFooter
  • Bootstrap badge bg-success/danger/warning → shadcn Badge with variant="default" + Tailwind color classes (bg-emerald-100 text-emerald-800, bg-red-100 text-red-800, bg-amber-100 text-amber-800)
  • Bootstrap grid col-md-6 col-lg-4 → Tailwind grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4
  • Bootstrap btn btn-primary → shadcn Button

Bill detail mockup → Tailwind translation:

  • Bootstrap layout → Tailwind flex/grid with gap-6, p-6, rounded-lg
  • Amount display → text-2xl font-bold with proper formatting
  • Status badge → same Badge component as list
  • "Pay Now" button → shadcn Button that opens BillPayment Dialog

The semantic structure is identical — we're just swapping class names. Our shadcn components (Card, Badge, Button, Table, Dialog) already exist in the project's components/ui/ folder.

Send a message...

Technical Approach

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
How do we structure the implementation? Build from scratch or lean on existing code?
K
kraftbot10:18 PM

Follow the existing Invoice implementation exactly. Everything we need already exists:

  • lib/db/schema.ts → add bills table (copy invoices table, adjust columns)
  • app/api/invoices/ → copy to app/api/bills/ (change schema references)
  • app/(admin)/admin/invoices/ → copy to admin/bills/ (swap components)
  • components/payments/InvoicePayment.tsx → copy to BillPayment.tsx
  • Payment API routes → add bill-specific variants

Architecture:

  1. 1. Schema (Drizzle) — single source of truth
  2. 2. API routes (Next.js Route Handlers) — data access
  3. 3. Admin pages (Server + Client Components) — management
  4. 4. Portal pages (Server Components) — customer-facing
  5. 5. Payment (existing Stripe/PayPal providers) — reuse

Shall we create the task file?

Send a message...

Creating the Task File

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
Let's create it — billing-portal-ui-tasks.org
K
kraftbot10:18 PM

Here's the task file — I'll save it as billing-portal-ui-tasks.org in my artifacts folder.

billing-portal-ui-tasks.orgView Full Screen
1* TODO Task 1: Add bills table to Drizzle schema
2 Update ~lib/db/schema.ts~ (existing)
3 - Follow: invoices table pattern
4 - Fields: billNumber, customer info, amounts, dueDate,
5 status, payment fields, reminder tracking
6 - Value: Foundation — all pages depend on this
7
8* TODO Task 2: Create bills API routes
9 Create ~app/api/bills/route.ts~
10 Create ~app/api/bills/[id]/route.ts~
11 - Follow: invoices API routes (CRUD)
12 - Value: Data layer for all pages
13
14* TODO Task 3: Create admin bills pages
15 Create ~app/(admin)/admin/bills/~ (page, new, [id], [id]/edit)
16 - Follow: admin/invoices/ — shadcn Table, forms, detail
17 - Value: Admin can manage bills
18
19* TODO Task 4: Create portal bills pages
20 Create ~app/(main)/bills/page.tsx~
21 Create ~app/(main)/bills/[id]/page.tsx~
22 - Tailwind card grid, match Athena's mockups
23 - Value: Customer-facing portal
24
25* TODO Task 5: Create BillPayment component
26 Create ~components/payments/BillPayment.tsx~
27 - Follow: InvoicePayment.tsx (Dialog + Stripe/PayPal)
28 - Value: Reusable payment dialog
29
30* TODO Task 6: Wire payment API for bills
31 Create bill-specific Stripe + PayPal routes
32 - Follow: existing invoice payment routes
33 - Value: Full payment pipeline
34
35* TODO Task 7: Navigation & routing
36 Update ~components/admin/AdminSidebar.tsx~
37 Update ~components/layout/Navbar.tsx~
38 - Add "Bills" links to admin + portal nav
39 - Value: Bills discoverable from navigation
40
41* TODO Task 8: Smoke test & cleanup
42 - Add bills to seed.ts, verify full flow
43 - Remove Invoice/Payslip sample code
44 - Value: Confidence before shipping
A
Admin10:18 PM
Clean. Let's go.
Send a message...

Pair-Programming Session

How this works. The user writes all the code — Apollo provides the snippets, exact file paths, and placement instructions. They work through the 8 tasks top to bottom. Watch how fast it goes once the groundwork (PRD + task file) is in place.

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
One thing before we start coding — I want to be clear about how we work: I write all the code. You guide me, give me the snippets, tell me exactly where to put them. We eat through those 8 tasks top to bottom until it's done.
K
kraftbot10:18 PM

That's exactly how I operate — mentored pair-development. You're in the driver's seat, I navigate.

Here's the rhythm:

  1. 1. I give you the task + code snippet + exact file path
  2. 2. You paste it in
  3. 3. You tell me "done" or "I have a question"
  4. 4. We move to the next task

No detours, no over-engineering. We ship a working billing portal UI today.

Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

One thing before we touch any code. Let's make a backup:

1Copy _apps/flowkraft/next-playground
2 to _apps/flowkraft/next-playground-Copy

That gives us a clean reference of the original code in case we break something. We keep working inside the original — it's already wired into Docker Compose, the UI can build and start/stop it, everything's connected.

Also — my approach while we develop: we only add new code. Don't touch or delete existing classes, even if they look like stuff we don't need. The Invoice and Payslip code is useful as working examples while we build — you might need to reference how they handle schemas, routes, or components.

Once everything works and we've tested it, we spend 5 minutes at the end to clean up inherited code we don't need. Sound good?

A
Admin10:18 PM
Backup done — let's go.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Task 1 — Add bills table to Drizzle schema.

Open lib/db/schema.ts — you'll see the invoices table definition. We add bills right below it, same pattern:

1// Add to lib/db/schema.ts after invoices table
2
3export const bills = sqliteTable('bills', {
4 id: integer('id').primaryKey({ autoIncrement: true }),
5 billNumber: text('bill_number').notNull().unique(),
6 customerName: text('customer_name').notNull(),
7 customerEmail: text('customer_email').notNull(),
8 customerAddress: text('customer_address'),
9
10 subtotal: real('subtotal').default(0),
11 taxRate: real('tax_rate').default(0),
12 taxAmount: real('tax_amount').default(0),
13 discount: real('discount').default(0),
14 totalAmount: real('total_amount').default(0),
15
16 issueDate: text('issue_date').notNull(),
17 dueDate: text('due_date'),
18
19 status: text('status').default('draft'), // draft, sent, paid, overdue, cancelled
20 notes: text('notes'),
21
22 // Payment tracking
23 paidAt: text('paid_at'),
24 paymentMethod: text('payment_method'), // stripe, paypal
25 paymentReference: text('payment_reference'),
26
27 // Reminder tracking (Hephaestus's backend)
28 reminderCount: integer('reminder_count').default(0),
29 lastReminderDate: text('last_reminder_date'),
30
31 createdAt: text('created_at').default(sql`(datetime('now'))`),
32 updatedAt: text('updated_at').default(sql`(datetime('now'))`),
33});

Now create the API route — app/api/bills/route.ts. Copy from app/api/invoices/route.ts and change the table reference:

1import { db } from '@/lib/db';
2import { bills } from '@/lib/db/schema';
3import { desc, like, or } from 'drizzle-orm';
4import { NextResponse } from 'next/server';
5
6export async function GET(request: Request) {
7 const { searchParams } = new URL(request.url);
8 const q = searchParams.get('q');
9
10 let result;
11 if (q) {
12 result = await db.select().from(bills).where(
13 or(
14 like(bills.billNumber, `%${q}%`),
15 like(bills.customerName, `%${q}%`)
16 )
17 ).orderBy(desc(bills.createdAt));
18 } else {
19 result = await db.select().from(bills)
20 .orderBy(desc(bills.createdAt));
21 }
22 return NextResponse.json(result);
23}
24
25export async function POST(request: Request) {
26 const body = await request.json();
27 const result = await db.insert(bills).values(body)
28 .returning();
29 return NextResponse.json(result[0], { status: 201 });
30}
A
Admin10:18 PM
Schema and API done — 1 down, 7 to go.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Tasks 2 and 3 — Admin bills pages.

Create app/(admin)/admin/bills/page.tsx — follow admin/invoices/page.tsx:

1import Link from 'next/link';
2import { db } from '@/lib/db';
3import { bills } from '@/lib/db/schema';
4import { desc } from 'drizzle-orm';
5import { Badge } from '@/components/ui/badge';
6import { Button } from '@/components/ui/button';
7import {
8 Table, TableBody, TableCell,
9 TableHead, TableHeader, TableRow,
10} from '@/components/ui/table';
11import { Plus, Eye, Pencil } from 'lucide-react';
12
13const statusColors: Record<string, string> = {
14 paid: 'bg-emerald-100 text-emerald-800',
15 overdue: 'bg-red-100 text-red-800',
16 sent: 'bg-blue-100 text-blue-800',
17 draft: 'bg-slate-100 text-slate-800',
18 cancelled: 'bg-gray-100 text-gray-800',
19};
20
21export default async function BillsPage() {
22 const allBills = await db.select().from(bills)
23 .orderBy(desc(bills.createdAt));
24
25 return (
26 <div className="space-y-4">
27 <div className="flex items-center justify-between">
28 <h2 className="text-2xl font-bold">Bills</h2>
29 <Link href="/admin/bills/new">
30 <Button size="sm">
31 <Plus className="h-4 w-4 mr-1" /> New Bill
32 </Button>
33 </Link>
34 </div>
35 <Table>
36 <TableHeader>
37 <TableRow>
38 <TableHead>Bill #</TableHead>
39 <TableHead>Customer</TableHead>
40 <TableHead>Due Date</TableHead>
41 <TableHead className="text-right">Amount</TableHead>
42 <TableHead>Status</TableHead>
43 <TableHead>Actions</TableHead>
44 </TableRow>
45 </TableHeader>
46 <TableBody>
47 {allBills.map((bill) => (
48 <TableRow key={bill.id}>
49 <TableCell>{bill.billNumber}</TableCell>
50 <TableCell>
51 <div>{bill.customerName}</div>
52 <div className="text-sm text-muted-foreground">
53 {bill.customerEmail}</div>
54 </TableCell>
55 <TableCell>{bill.dueDate}</TableCell>
56 <TableCell className="text-right font-medium">
57 ${bill.totalAmount?.toFixed(2)}</TableCell>
58 <TableCell>
59 <Badge className={statusColors[bill.status!]}>
60 {bill.status}</Badge>
61 </TableCell>
62 <TableCell className="flex gap-1">
63 <Link href={`/admin/bills/${bill.id}`}>
64 <Button variant="outline" size="icon">
65 <Eye className="h-4 w-4" />
66 </Button>
67 </Link>
68 <Link href={`/admin/bills/${bill.id}/edit`}>
69 <Button variant="outline" size="icon">
70 <Pencil className="h-4 w-4" />
71 </Button>
72 </Link>
73 </TableCell>
74 </TableRow>
75 ))}
76 </TableBody>
77 </Table>
78 </div>
79 );
80}
A
Admin10:18 PM
Admin list page done — looks clean with shadcn. The status badges use Tailwind colors instead of Bootstrap. Next?
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Tasks 4 and 5 — Portal bills pages.

Create app/(main)/bills/page.tsx — the customer-facing bills list with Tailwind cards:

1import Link from 'next/link';
2import { db } from '@/lib/db';
3import { bills } from '@/lib/db/schema';
4import { desc } from 'drizzle-orm';
5import { Badge } from '@/components/ui/badge';
6import { Button } from '@/components/ui/button';
7import {
8 Card, CardContent, CardFooter, CardHeader,
9} from '@/components/ui/card';
10
11const statusColors: Record<string, string> = {
12 paid: 'bg-emerald-100 text-emerald-800',
13 overdue: 'bg-red-100 text-red-800',
14 sent: 'bg-blue-100 text-blue-800',
15 draft: 'bg-slate-100 text-slate-800',
16};
17
18export default async function BillsPortalPage() {
19 const allBills = await db.select().from(bills)
20 .orderBy(desc(bills.createdAt));
21
22 return (
23 <div className="container mx-auto py-8">
24 <h2 className="text-2xl font-bold mb-6">My Bills</h2>
25 <div className="grid grid-cols-1 md:grid-cols-2
26 lg:grid-cols-3 gap-4">
27 {allBills.map((bill) => (
28 <Card key={bill.id}>
29 <CardHeader className="pb-2">
30 <div className="flex justify-between items-start">
31 <span className="font-semibold">
32 {bill.billNumber}</span>
33 <Badge className={statusColors[bill.status!]}>
34 {bill.status}</Badge>
35 </div>
36 <p className="text-sm text-muted-foreground">
37 {bill.customerName}</p>
38 </CardHeader>
39 <CardContent>
40 <p className="text-2xl font-bold">
41 ${bill.totalAmount?.toFixed(2)}</p>
42 <p className="text-sm text-muted-foreground">
43 Due: {bill.dueDate}</p>
44 </CardContent>
45 <CardFooter className="gap-2">
46 <Link href={`/bills/${bill.id}`}>
47 <Button variant="outline" size="sm">
48 View</Button>
49 </Link>
50 {['sent', 'overdue'].includes(bill.status!) && (
51 <Link href={`/bills/${bill.id}`}>
52 <Button size="sm">Pay Now</Button>
53 </Link>
54 )}
55 </CardFooter>
56 </Card>
57 ))}
58 </div>
59 </div>
60 );
61}

Notice how the grid layout maps exactly to Athena's mockup — 3 columns on large screens, 2 on medium, 1 on mobile. The cards use shadcn Card components instead of Bootstrap cards, but the structure is identical.

A
Admin10:18 PM
Portal done. The card layout is clean — shadcn gives it that polished look.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Task 5 and 6 — BillPayment component + payment API.

Create components/payments/BillPayment.tsx — this is a copy of InvoicePayment.tsx with bill references:

1'use client';
2
3import { useState, useEffect } from 'react';
4import { Button } from '@/components/ui/button';
5import {
6 Dialog, DialogContent, DialogHeader,
7 DialogTitle, DialogTrigger,
8} from '@/components/ui/dialog';
9import { StripeProvider } from './StripeProvider';
10import { StripeCheckoutForm } from './StripeCheckoutForm';
11import { PayPalProvider } from './PayPalProvider';
12import { PayPalCheckout } from './PayPalCheckout';
13
14interface BillPaymentProps {
15 billId: number;
16 billNumber: string;
17 amount: number;
18}
19
20export function BillPayment({
21 billId, billNumber, amount
22}: BillPaymentProps) {
23 const [method, setMethod] = useState<'stripe'|'paypal'>('stripe');
24 const [clientSecret, setClientSecret] = useState('');
25 const [open, setOpen] = useState(false);
26
27 useEffect(() => {
28 if (open && method === 'stripe') {
29 fetch('/api/payments/stripe/create-bill-intent', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ billId }),
33 })
34 .then(r => r.json())
35 .then(data => setClientSecret(data.clientSecret));
36 }
37 }, [open, method, billId]);
38
39 return (
40 <Dialog open={open} onOpenChange={setOpen}>
41 <DialogTrigger asChild>
42 <Button>Pay Now</Button>
43 </DialogTrigger>
44 <DialogContent>
45 <DialogHeader>
46 <DialogTitle>
47 Pay Bill {billNumber} — ${amount.toFixed(2)}
48 </DialogTitle>
49 </DialogHeader>
50 <div className="flex gap-2 mb-4">
51 <Button variant={method === 'stripe' ? 'default' : 'outline'}
52 onClick={() => setMethod('stripe')}>Card</Button>
53 <Button variant={method === 'paypal' ? 'default' : 'outline'}
54 onClick={() => setMethod('paypal')}>PayPal</Button>
55 </div>
56 {method === 'stripe' && clientSecret && (
57 <StripeProvider clientSecret={clientSecret}>
58 <StripeCheckoutForm
59 amount={amount}
60 onSuccess={() => setOpen(false)} />
61 </StripeProvider>
62 )}
63 {method === 'paypal' && (
64 <PayPalProvider>
65 <PayPalCheckout
66 billId={billId}
67 onSuccess={() => setOpen(false)} />
68 </PayPalProvider>
69 )}
70 </DialogContent>
71 </Dialog>
72 );
73}
A
Admin10:18 PM
That's almost identical to InvoicePayment — just swapped "invoice" for "bill". The Stripe/PayPal providers handle everything.
K
kraftbot10:18 PM

Exactly — same providers, same checkout forms, different API endpoint. The payment routes follow the same pattern too.

A
Admin10:18 PM
Tasks 5 and 6 done.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Task 7 — Navigation.

Add Bills to AdminSidebar.tsx:

1// Add to the navigation items array in AdminSidebar.tsx
2{
3 label: 'Bills',
4 href: '/admin/bills',
5 icon: Receipt, // from lucide-react
6},

And add "My Bills" to Navbar.tsx:

1// Add to the nav links in Navbar.tsx
2<Link href="/bills"
3 className="text-sm font-medium hover:text-primary">
4 My Bills
5</Link>
A
Admin10:18 PM
Done — Bills shows up in both admin sidebar and portal nav.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Task 8 — Smoke test.

Add bills to lib/db/seed.ts — follow the invoices seeding pattern:

1// Add to seed.ts after invoice seeding
2const sampleBills = Array.from({ length: 10 }, (_, i) => ({
3 billNumber: `BILL-${String(i + 1).padStart(3, '0')}`,
4 customerName: faker.company.name(),
5 customerEmail: faker.internet.email(),
6 subtotal: parseFloat(faker.commerce.price({ min: 100, max: 5000 })),
7 taxRate: 10,
8 taxAmount: 0, // calculated below
9 totalAmount: 0,
10 issueDate: faker.date.recent({ days: 30 }).toISOString(),
11 dueDate: faker.date.soon({ days: 30 }).toISOString(),
12 status: faker.helpers.arrayElement(
13 ['draft', 'sent', 'paid', 'overdue']),
14}));
15
16for (const bill of sampleBills) {
17 bill.taxAmount = bill.subtotal * (bill.taxRate / 100);
18 bill.totalAmount = bill.subtotal + bill.taxAmount;
19 await db.insert(bills).values(bill);
20}

Run the app — npm run dev — and check:

  1. 1. /admin/bills — shadcn table with 10 seeded bills
  2. 2. /admin/bills/1 — detail page with status banner
  3. 3. /bills — portal card grid
  4. 4. Click "Pay Now" on an overdue bill — payment dialog opens
A
Admin10:18 PM
All working. The shadcn components look great.
K
kraftbot10:18 PM

All passing. The billing portal is working end to end — admin CRUD, portal views, and payment flow. We still have the Invoice and Payslip code sitting around as reference. We'll do a proper cleanup next.

Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

All tasks complete. Here's what you built today:

Admin Dashboard: Full CRUD for bills using shadcn/ui — Table with search and status badges, detail page with payment info, create/edit forms.

Customer Portal: Bills homepage with responsive card grid (Tailwind CSS), bill detail with payment button.

Payment Integration: BillPayment dialog with Stripe + PayPal toggle — reused existing providers and checkout forms.

The stack: Next.js 15 App Router + TypeScript + Tailwind CSS v4 + shadcn/ui + Drizzle ORM. ~250 lines of new code across schema, routes, pages, and components.

A
Admin10:18 PM
Fast work. The reminder backend (Hephaestus) stores reminderCount and lastReminderDate on the bills table — I could show that in the admin detail page.
K
kraftbot10:18 PM

Easy — just add a "Reminders" card to the bill detail page. Something like {bill.reminderCount} reminders sent, last on {bill.lastReminderDate}. Drizzle already has the columns in the schema. Have a good evening!

Send a message...

Cleaning Session

What happens here. The pair-programming session kept existing Invoice/Payslip code around as working reference. Now that the billing portal is verified, the user and Apollo do a thorough project audit — removing all non-billing feature code (Charts, Reports, Pivot Tables, Data Warehouse, Tabulator, Canvas, Settings), updating shared files (sidebar, navbar, schema, seed data), and verifying the app compiles and runs as a slim billing-only portal.

Apollo (Next.js Guru & Modern Web Advisor)
A
Admin10:18 PM
Time to clean the project. First — the Invoice and Payslip code we kept as working reference. What exactly needs to go?
K
kraftbot10:18 PM

Right. Now that Bills works end to end, remove the old feature code:

  • Remove invoices + payslips table definitions from schema.ts (keep bills)
  • Remove app/api/invoices/ and app/api/payslips/ routes
  • Remove app/(admin)/admin/invoices/ and payslips/ pages
  • Remove InvoicePayment.tsx (we have BillPayment.tsx now)

Keep: StripeProvider, PayPalProvider, StripeCheckoutForm, PayPalCheckout, BillPayment, components/ui/ (shared), AdminSidebar, Navbar, layouts.

A
Admin10:18 PM
Done — all Invoice and Payslip code removed. But the project still has Charts, Reports, Pivot Tables, Data Warehouse, Tabulator, Canvas, Settings, Stats — all the demo pages from the original template. I want a clean billing-only app. Walk me through everything.
K
kraftbot10:18 PM

Let's do a full audit. Here's every remaining file that needs attention:

DELETE — these pages and routes are not part of the billing portal:

Pages to delete (8 directories):

  • app/(main)/charts/
  • app/(main)/data-warehouse/
  • app/(main)/pivot-tables/
  • app/(main)/report-parameters/
  • app/(main)/reports/
  • app/(main)/tabulator/
  • app/(main)/your-canvas/
  • app/(admin)/admin/settings/

API routes to delete (2 directories):

  • app/api/settings/
  • app/api/stats/

Components to delete (4 files):

  • components/Placeholder.tsx — generic placeholder, not used by billing
  • components/ComponentDemo.tsx — demo component, not needed
  • components/CodeBlock.tsx — code display, not part of billing portal
  • components/RbWebComponentsLoader.tsx — not needed for billing

Other files to delete:

  • types/rb-components.d.ts — type declarations for RbWebComponentsLoader
  • hooks/use-settings.ts — settings hook, goes with Settings page

UPDATE — these shared files reference the old features:

  • components/admin/AdminSidebar.tsx — remove nav links to Dashboard, Invoices, Payslips, Settings. Keep only: Bills
  • components/layout/Navbar.tsx — remove nav links to Charts, Reports, Pivot Tables, Data Warehouse, Tabulator, Canvas. Keep: My Bills
  • app/(admin)/admin/page.tsx — update the admin dashboard to show billing stats instead of generic dashboard content
  • app/(main)/page.tsx — update the home page for billing portal (remove feature grid, add billing welcome content)
  • lib/db/seed.ts — remove any remaining invoice/payslip seeding code. Keep the bills seeding
  • components/payments/index.ts — update barrel export: remove InvoicePayment, keep BillPayment

KEEP — billing portal code + infrastructure:

  • app/(admin)/admin/bills/ — admin bill pages
  • app/(main)/bills/ — portal bill pages
  • app/api/bills/ — bill API routes
  • app/api/payments/ — Stripe + PayPal endpoints
  • app/api/health/ — health check
  • app/api/auth/ — authentication
  • components/payments/ (BillPayment, StripeProvider, StripeCheckoutForm, PayPalProvider, PayPalCheckout)
  • components/ui/ — all shadcn components (shared)
  • components/admin/ (AdminHeader, AdminSidebar — after update)
  • components/layout/ (Footer, Navbar — after update)
  • components/ThemeProvider.tsx, ThemeSelector.tsx — theme system
  • hooks/use-toast.ts, useThemeSettings.ts
  • lib/ (db, utils, themes, rb-config)
  • middleware.ts — auth/routing
  • All config (next.config.ts, tsconfig.json, tailwind, postcss, etc.)
  • All Docker files, package.json

That's 8 page directories + 2 API directories + 4 components + 2 other files to delete, and 6 files to update. Ready?

A
Admin10:18 PM
Let's go.
Send a message...
Apollo (Next.js Guru & Modern Web Advisor)
K
kraftbot10:18 PM

Delete in this order — pages, API routes, components, other files:

1# Step 1: Delete page directories (8)
2app/(main)/charts/
3app/(main)/data-warehouse/
4app/(main)/pivot-tables/
5app/(main)/report-parameters/
6app/(main)/reports/
7app/(main)/tabulator/
8app/(main)/your-canvas/
9app/(admin)/admin/settings/
10
11# Step 2: Delete API route directories (2)
12app/api/settings/
13app/api/stats/
14
15# Step 3: Delete components (4)
16components/Placeholder.tsx
17components/ComponentDemo.tsx
18components/CodeBlock.tsx
19components/RbWebComponentsLoader.tsx
20
21# Step 4: Delete other files (2)
22types/rb-components.d.ts
23hooks/use-settings.ts
A
Admin10:18 PM
All deleted — 16 items gone. Now the updates.
K
kraftbot10:18 PM

Start with AdminSidebar.tsx — strip the nav items down to billing:

1// AdminSidebar.tsx — navigation items
2const navItems = [
3 {
4 label: 'Bills',
5 href: '/admin/bills',
6 icon: Receipt,
7 },
8];

Update Navbar.tsx — remove all the old feature links, keep only My Bills:

1// Navbar.tsx — portal navigation links
2<Link href="/bills"
3 className="text-sm font-medium hover:text-primary">
4 My Bills
5</Link>

Update app/(main)/page.tsx — replace the feature grid with a billing portal landing:

1export default function HomePage() {
2 return (
3 <div className="container mx-auto py-12 text-center">
4 <h1 className="text-4xl font-bold mb-4">
5 Billing Portal
6 </h1>
7 <p className="text-lg text-muted-foreground mb-8">
8 Manage bills, track payments, send reminders.
9 </p>
10 <div className="flex gap-4 justify-center">
11 <Link href="/bills">
12 <Button size="lg">View My Bills</Button>
13 </Link>
14 <Link href="/admin/bills">
15 <Button variant="outline" size="lg">
16 Admin Panel
17 </Button>
18 </Link>
19 </div>
20 </div>
21 );
22}

Update app/(admin)/admin/page.tsx — simplify the admin dashboard:

1export default async function AdminPage() {
2 const allBills = await db.select().from(bills);
3 const overdue = allBills.filter(b => b.status === 'overdue');
4 const paid = allBills.filter(b => b.status === 'paid');
5
6 return (
7 <div className="space-y-4">
8 <h2 className="text-2xl font-bold">Dashboard</h2>
9 <div className="grid grid-cols-3 gap-4">
10 <Card><CardContent className="pt-6">
11 <p className="text-sm text-muted-foreground">
12 Total Bills</p>
13 <p className="text-2xl font-bold">
14 {allBills.length}</p>
15 </CardContent></Card>
16 <Card><CardContent className="pt-6">
17 <p className="text-sm text-muted-foreground">
18 Overdue</p>
19 <p className="text-2xl font-bold text-red-500">
20 {overdue.length}</p>
21 </CardContent></Card>
22 <Card><CardContent className="pt-6">
23 <p className="text-sm text-muted-foreground">
24 Paid</p>
25 <p className="text-2xl font-bold text-emerald-500">
26 {paid.length}</p>
27 </CardContent></Card>
28 </div>
29 </div>
30 );
31}

Finally, clean up lib/db/seed.ts — remove invoice/payslip seeding, keep bills. And update components/payments/index.ts — export BillPayment instead of InvoicePayment.

A
Admin10:18 PM
All done. Let me run npm run dev to verify...
A
Admin10:18 PM
App starts clean. Home page shows the billing portal landing. /admin/bills shows the shadcn table with status badges. /bills shows the card grid. Payment dialog works. Admin dashboard shows bill stats. No broken imports, no dead routes.
K
kraftbot10:18 PM

Here's your clean billing portal:

1next-playground/
2├── app/
3│ ├── (admin)/
4│ │ ├── admin/
5│ │ │ ├── bills/ (page, new, [id], [id]/edit)
6│ │ │ └── page.tsx # Dashboard with stats
7│ │ └── layout.tsx
8│ ├── (main)/
9│ │ ├── bills/ (page, [id])
10│ │ ├── page.tsx # Billing portal landing
11│ │ └── layout.tsx
12│ ├── api/
13│ │ ├── auth/ # Authentication
14│ │ ├── bills/ (route, [id]) # Bill CRUD API
15│ │ ├── health/ # Health check
16│ │ └── payments/ (stripe, paypal)
17│ ├── globals.css
18│ └── layout.tsx
19├── components/
20│ ├── admin/ (AdminHeader, AdminSidebar)
21│ ├── layout/ (Footer, Navbar)
22│ ├── payments/ (BillPayment, Stripe*, PayPal*)
23│ ├── ui/ (badge, button, card, dialog, ...)
24│ ├── ThemeProvider.tsx
25│ └── ThemeSelector.tsx
26├── hooks/ (use-toast, useThemeSettings)
27├── lib/
28│ ├── db/ (index, schema, seed)
29│ ├── utils.ts, themes.ts, rb-config.ts
30├── middleware.ts
31├── package.json, next.config.ts, tsconfig.json
32├── Dockerfile, docker-compose.yml
33└── drizzle.config.ts

From ~50 source files down to ~30. Every file serves the billing portal — schema, API routes, admin pages, portal pages, payment components, and infrastructure. Clean, focused, production-ready.

Send a message...