Founding price: $35, then $99 after 23:59 UTC on 4 November, then $99
Get ProPro starter app. Needs a tealmac UI Pro licence. Set it up once on /pro/login, then see Install Pro.
Launchpad is a whole product, not a page. It is a fictional SaaS for publishing product updates, with everything a real SaaS needs around the core feature. Open the live preview.
| Page | Route | Highlights |
|---|---|---|
| Landing | /templates/launchpad | Live 3D hero, product preview, features, social proof, FAQ |
| Pricing | /pricing | Monthly/yearly toggle, three plans, comparison table, FAQ |
| Sign in | /sign-in | Validation, single sign-on buttons, continues to two-factor |
| Sign up | /sign-up | Password strength, terms, continues to onboarding |
| Reset password | /reset-password | Request link, resend timer, choose a new password |
| Two-factor | /two-factor | One-time code input, recovery code fallback, error state |
| Onboarding | /onboarding | Four-step wizard: workspace, team, channels and plan, summary |
| Dashboard | /app | KPIs with sparklines, charts, top updates, activity, checklist |
| Team | /app/team | Searchable members, role dialog, remove, invites |
| Billing | /app/billing | Plan, usage meters, invoices, upgrade dialog, checkout stub |
| Settings | /app/settings | Profile, security and sessions, API keys, notification switches |
| Notifications | /app/notifications | Grouped inbox, filters, read and archive with undo |
The signed-in app has a sidebar (a drawer on phones), a command menu opened with Ctrl K (⌘ K on Apple devices) and a notifications popover.
Set up your licence and the Pro registry once per project. The steps are in Install Pro:
TEALMAC_UI_LICENSE_KEY=your-licence-key{
"registries": {
"@tealmac-pro": {
"url": "https://ui.tealmac.com/r/pro/{name}.json",
"headers": {
"Authorization": "Bearer ${TEALMAC_UI_LICENSE_KEY}"
}
}
}
}Then add the app:
The CLI installs the free components Launchpad uses, plus recharts, sonner
and three-based 3D components for the hero.
components/tealmac-pro/launchpad/*: the shell, data, billing helpers and
one file per page.app/templates/launchpad/**/page.tsx: a route for each page, for example
app/templates/launchpad/app/billing/page.tsx.Move the routes wherever you like, then change the paths in the lp object at
the top of launchpad-shell.tsx. Every link in the app reads from it.
All sample data lives in launchpad-data.ts. Each page reads from it at the top
of the component, so you can swap an export for a fetch without touching the
layout:
// Before
const [members, setMembers] = React.useState<Member[]>(seedMembers)
// After: server component fetches, passes data to the client page
export default async function Page() {
const members = await getMembers()
return <LaunchpadTeam initialMembers={members} />
}List pages show loading, empty and error states through StateView. Add
?state=loading, ?state=empty or ?state=error to a page URL in the preview
to see them. In your app, drive them from your data fetching status.
launchpad-billing-server.ts has three example helpers and no secrets:
createCheckoutSession() creates a hosted checkout and returns its URL.verifyDodoWebhook() checks a webhook's signature with Web Crypto.handleDodoEvent() is a switch you fill in with your database writes.Add your keys to the environment:
DODO_PAYMENTS_API_KEY=
DODO_PAYMENTS_WEBHOOK_SECRET=
DODO_PAYMENTS_ENVIRONMENT=test_mode
NEXT_PUBLIC_APP_URL=http://localhost:3000
DODO_PRODUCT_GROWTH_MONTHLY=
DODO_PRODUCT_GROWTH_YEARLY=
DODO_PRODUCT_SCALE_MONTHLY=
DODO_PRODUCT_SCALE_YEARLY=Create one product per plan and interval in the Dodo Payments dashboard and paste the product IDs. Then add two route handlers.
import { NextResponse } from "next/server"
import { createCheckoutSession } from "@/components/tealmac-pro/launchpad/launchpad-billing-server"
export async function POST(request: Request) {
// Get the signed-in user from your auth library; never trust the body for identity.
const user = await getUser(request)
if (!user)
return NextResponse.json({ error: "unauthorised" }, { status: 401 })
const { planId, interval } = await request.json()
if (
!["growth", "scale"].includes(planId) ||
!["monthly", "yearly"].includes(interval)
) {
return NextResponse.json({ error: "bad request" }, { status: 400 })
}
const session = await createCheckoutSession({
planId,
interval,
email: user.email,
metadata: { workspaceId: user.workspaceId },
})
return NextResponse.json({ checkoutUrl: session.checkoutUrl })
}import { NextResponse } from "next/server"
import {
handleDodoEvent,
verifyDodoWebhook,
} from "@/components/tealmac-pro/launchpad/launchpad-billing-server"
export async function POST(request: Request) {
const result = await verifyDodoWebhook(request)
if (!result.ok) {
return NextResponse.json({ error: result.error }, { status: result.status })
}
// Dodo retries failed deliveries: skip ids you have already handled.
await handleDodoEvent(result.event)
return NextResponse.json({ received: true })
}Point a webhook endpoint at https://your-domain/api/webhooks/dodo in the
dashboard and copy its signing secret into DODO_PAYMENTS_WEBHOOK_SECRET. In
launchpad-billing.tsx, set PREVIEW_MODE to false so the upgrade dialog
posts to your checkout route and redirects.
Change the plan from the webhook, not from the redirect back to your site. The redirect can be closed before it happens; the webhook always arrives.
Launchpad uses only semantic tokens (background, card, primary, muted,
border, chart-1 to chart-5 and friends) and the rounded-md, rounded-lg
and rounded-xl radii. It has no palette of its own, so it follows your theme
and works with every theme preset, in light and dark mode.
h1 per page, labelled landmarks, a skip link in the app shell and
aria-current on the active navigation link.aria-describedby, and
validation moves focus to the first invalid field.aria-live regions.prefers-reduced-motion, and the 3D hero shows a still frame in that case.