Skip to content

Commit 8a75a33

Browse files
borisno2claude
andauthored
Add admin UI setup step to migration assistant (#347)
Adds a new `setup-admin-ui` skill and integrates it into the migration assistant as Phase 5. After completing the core migration steps, the assistant now asks users whether they want the Admin UI set up and at what path, then delegates to the skill to install @opensaas/stack-ui and create the catch-all admin route page (with or without auth). https://claude.ai/code/session_019hziN9HYyNwoTgyHVxRyUC Co-authored-by: Claude <noreply@anthropic.com>
1 parent aa5edec commit 8a75a33

2 files changed

Lines changed: 204 additions & 3 deletions

File tree

claude-plugins/opensaas-migration/agents/migration-assistant.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,22 @@ Change `session.data.id` → `session.userId` in access control functions.
382382
383383
The subagent will search the project, edit all files, and report what it changed.
384384

385-
### Step 8: Run generation and validate
385+
### Step 10: Set up Admin UI — ask the user, then delegate to subagent
386+
387+
**Do not do this yourself.** Ask the user two questions:
388+
389+
1. "Would you like to set up the OpenSaaS Stack Admin UI in this project? It provides a full CRUD interface for all your lists out of the box."
390+
2. "What path should the admin UI be mounted at? (default: `/admin`)"
391+
392+
If the user wants the admin UI:
393+
394+
> Invoke `setup-admin-ui` with: "Project root: /path/to/project. Admin path: /admin (or whatever they chose). Auth enabled: yes/no (based on whether authPlugin was detected in their config)."
395+
396+
The subagent will install `@opensaas/stack-ui`, create `app/{path}/[[...{segment}]]/page.tsx`, and report next steps.
397+
398+
If the user declines, skip this step and proceed to validation.
399+
400+
### Step 11: Run generation and validate
386401

387402
```bash
388403
pnpm opensaas generate # Generates prisma schema
@@ -410,6 +425,7 @@ Your job is to plan and coordinate the migration, not to do all the editing your
410425
- context.graphql/context.query migration → `migrate-context-calls` skill
411426
- Image/file field migration (config + SQL) → `migrate-image-fields` skill
412427
- Document field migration (→ tiptap) → `migrate-document-fields` skill
428+
- Admin UI setup (ask first, then delegate) → `setup-admin-ui` skill
413429

414430
### When the user says "help me migrate" or similar:
415431

@@ -446,7 +462,17 @@ After assessing, show the user a numbered list of exactly what will change and w
446462
- **If virtual fields**: invoke `migrate-virtual-fields` with config path and virtual field code
447463
- **If context.graphql/context.query**: invoke `migrate-context-calls` with project root path
448464

449-
**Phase 5 — Validate:**
465+
**Phase 5 — Admin UI:**
466+
467+
Ask the user two questions (both required before delegating):
468+
469+
1. "Would you like to set up the OpenSaaS Stack Admin UI? It gives you a full CRUD interface for all your lists out of the box, at a path you choose."
470+
2. "What path should it be mounted at?" (default: `/admin`)
471+
472+
If yes → **invoke `setup-admin-ui`** with: project root, chosen admin path, and whether `authPlugin` is in their config.
473+
If no → skip and go to Phase 6.
474+
475+
**Phase 6 — Validate:**
450476

451477
- Run `pnpm opensaas generate` and report any errors
452478
- If image/file fields were found, remind the user to run the SQL migration script BEFORE `prisma db push`
@@ -515,4 +541,5 @@ Guide them through:
515541
2. Run `pnpm opensaas generate`
516542
3. Run `npx prisma db push`
517543
4. Start dev server: `pnpm dev`
518-
5. Visit the admin UI at `http://localhost:3000/admin`
544+
5. If Admin UI was set up: visit `http://localhost:3000/{adminPath}` (e.g. `http://localhost:3000/admin`)
545+
6. If Admin UI was skipped: mention that they can set it up any time — see https://stack.opensaas.au/admin-ui
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
---
2+
name: setup-admin-ui
3+
description: Set up the OpenSaaS Stack Admin UI in an existing Next.js App Router project. Invoke as a forked subagent after migration is complete, passing the project root, desired admin path, and whether auth is enabled.
4+
context: fork
5+
agent: general-purpose
6+
---
7+
8+
Set up the OpenSaaS Stack Admin UI in the Next.js project described below.
9+
10+
$ARGUMENTS
11+
12+
## What This Skill Does
13+
14+
1. Installs `@opensaas/stack-ui` if not already a dependency
15+
2. Creates the catch-all admin route page at `app/{adminPath}/[[...{segmentName}]]/page.tsx`
16+
3. Validates the generated file works with the project structure
17+
18+
## Step 1 — Check if `@opensaas/stack-ui` Is Already Installed
19+
20+
Read `package.json` in the project root. If `@opensaas/stack-ui` is **not** in `dependencies`, install it:
21+
22+
```bash
23+
# Detect package manager
24+
# - If pnpm-lock.yaml exists → pnpm add @opensaas/stack-ui
25+
# - If yarn.lock exists → yarn add @opensaas/stack-ui
26+
# - Otherwise → npm install @opensaas/stack-ui
27+
```
28+
29+
Check existing versions of `@opensaas/stack-core` in `package.json` and install `@opensaas/stack-ui` at the **same version** to avoid mismatches.
30+
31+
## Step 2 — Determine the Admin Path
32+
33+
The admin path comes from `$ARGUMENTS` (e.g. `/admin`, `/dashboard/admin`).
34+
35+
- **Route path**: `app/{adminPath}/[[...{segmentName}]]/page.tsx`
36+
- For `/admin``app/admin/[[...admin]]/page.tsx`
37+
- For `/dashboard/admin``app/dashboard/admin/[[...admin]]/page.tsx`
38+
- For `/cms``app/cms/[[...cms]]/page.tsx`
39+
- **Segment name** (used in both the directory name and the `params.{segmentName}` reference): the last path segment (e.g. `admin`, `cms`)
40+
- **`basePath`** prop on `<AdminUI>`: the full admin path (e.g. `/admin`, `/dashboard/admin`)
41+
42+
Create all intermediate directories as needed.
43+
44+
## Step 3 — Determine if Auth Is Enabled
45+
46+
Check whether auth is configured:
47+
48+
1. Look in `opensaas.config.ts` for `authPlugin` usage
49+
2. Check for a `lib/auth.ts` or `lib/auth/index.ts` that exports `getSession`
50+
51+
This determines which page template to use.
52+
53+
## Step 4 — Create the Admin Page
54+
55+
### Template A — Without Auth
56+
57+
Use this when auth is NOT configured:
58+
59+
```typescript
60+
import { AdminUI } from '@opensaas/stack-ui'
61+
import type { ServerActionInput } from '@opensaas/stack-ui/server'
62+
import { getContext, config } from '@/.opensaas/context'
63+
64+
async function serverAction(props: ServerActionInput) {
65+
'use server'
66+
const context = await getContext()
67+
return await context.serverAction(props)
68+
}
69+
70+
interface AdminPageProps {
71+
params: Promise<{ {SEGMENT_NAME}?: string[] }>
72+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
73+
}
74+
75+
export default async function AdminPage({ params, searchParams }: AdminPageProps) {
76+
const resolvedParams = await params
77+
const resolvedSearchParams = await searchParams
78+
return (
79+
<AdminUI
80+
context={await getContext()}
81+
config={await config}
82+
params={resolvedParams.{SEGMENT_NAME}}
83+
searchParams={resolvedSearchParams}
84+
basePath="{ADMIN_PATH}"
85+
serverAction={serverAction}
86+
/>
87+
)
88+
}
89+
```
90+
91+
### Template B — With Auth
92+
93+
Use this when `authPlugin` is detected and `getSession` is available:
94+
95+
```typescript
96+
import { AdminUI } from '@opensaas/stack-ui'
97+
import type { ServerActionInput } from '@opensaas/stack-ui/server'
98+
import { getContext, config } from '@/.opensaas/context'
99+
import { getSession } from '@/lib/auth'
100+
101+
async function serverAction(props: ServerActionInput) {
102+
'use server'
103+
const context = await getContext({ session: await getSession() })
104+
return await context.serverAction(props)
105+
}
106+
107+
interface AdminPageProps {
108+
params: Promise<{ {SEGMENT_NAME}?: string[] }>
109+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
110+
}
111+
112+
export default async function AdminPage({ params, searchParams }: AdminPageProps) {
113+
const resolvedParams = await params
114+
const resolvedSearchParams = await searchParams
115+
const session = await getSession()
116+
if (!session) {
117+
return (
118+
<div className="p-8">
119+
<div className="bg-destructive/10 border border-destructive text-destructive rounded-lg p-6">
120+
<h2 className="text-lg font-semibold mb-2">Access Denied</h2>
121+
<p>You must be logged in to access the admin interface.</p>
122+
</div>
123+
</div>
124+
)
125+
}
126+
return (
127+
<AdminUI
128+
context={await getContext(session)}
129+
config={await config}
130+
params={resolvedParams.{SEGMENT_NAME}}
131+
searchParams={resolvedSearchParams}
132+
basePath="{ADMIN_PATH}"
133+
serverAction={serverAction}
134+
/>
135+
)
136+
}
137+
```
138+
139+
Replace `{SEGMENT_NAME}` with the last segment of the admin path (e.g. `admin`) and `{ADMIN_PATH}` with the full path (e.g. `/admin`).
140+
141+
**Important**: If `getSession` is not at `@/lib/auth`, check for it at its actual location (e.g. `@/lib/auth/index`, `@/app/lib/auth`) and use the correct import path.
142+
143+
## Step 5 — Check for Missing `.opensaas/context`
144+
145+
The admin page imports from `@/.opensaas/context`. This file is generated by `pnpm opensaas generate` (or `pnpm generate`). If it doesn't exist yet:
146+
147+
- **Do not create it manually** — it's generated from `opensaas.config.ts`
148+
- Remind the user to run `pnpm generate` (or `pnpm opensaas generate`) before starting the dev server
149+
150+
## Step 6 — Report What Was Done
151+
152+
Report to the user:
153+
154+
```
155+
✓ Admin UI set up at: {adminPath}
156+
✓ File created: app/{adminPath}/[[...{segmentName}]]/page.tsx
157+
✓ Auth-aware: yes/no
158+
✓ @opensaas/stack-ui: already installed / installed at version X.Y.Z
159+
160+
Next steps:
161+
1. Run `pnpm generate` to generate the .opensaas/context.ts file (if not already done)
162+
2. Run `pnpm dev` to start the dev server
163+
3. Visit http://localhost:3000{adminPath} to access the admin UI
164+
165+
Docs: https://stack.opensaas.au/admin-ui
166+
```
167+
168+
## Notes
169+
170+
- The `[[...{segmentName}]]` catch-all segment handles all admin routes: the list view, item detail, create, and edit pages — all from one file.
171+
- The `basePath` prop must match exactly the URL path where the admin is mounted.
172+
- The `serverAction` wrapper function is required — it provides the server action bridge between the client-side admin UI components and the database context.
173+
- If the user is using a custom `getSession` approach (not from `@opensaas/stack-auth`), the auth template still works — just update the import path and the session shape passed to `getContext`.
174+
- If Tailwind CSS is not configured in the project, mention that `@opensaas/stack-ui` uses Tailwind for styling and link to the docs for setup: https://stack.opensaas.au/admin-ui#tailwind

0 commit comments

Comments
 (0)