diff --git a/claude-plugins/opensaas-migration/agents/migration-assistant.md b/claude-plugins/opensaas-migration/agents/migration-assistant.md index 8be9a1af..36855e9f 100644 --- a/claude-plugins/opensaas-migration/agents/migration-assistant.md +++ b/claude-plugins/opensaas-migration/agents/migration-assistant.md @@ -382,7 +382,22 @@ Change `session.data.id` → `session.userId` in access control functions. The subagent will search the project, edit all files, and report what it changed. -### Step 8: Run generation and validate +### Step 10: Set up Admin UI — ask the user, then delegate to subagent + +**Do not do this yourself.** Ask the user two questions: + +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." +2. "What path should the admin UI be mounted at? (default: `/admin`)" + +If the user wants the admin UI: + +> 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)." + +The subagent will install `@opensaas/stack-ui`, create `app/{path}/[[...{segment}]]/page.tsx`, and report next steps. + +If the user declines, skip this step and proceed to validation. + +### Step 11: Run generation and validate ```bash 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 - context.graphql/context.query migration → `migrate-context-calls` skill - Image/file field migration (config + SQL) → `migrate-image-fields` skill - Document field migration (→ tiptap) → `migrate-document-fields` skill +- Admin UI setup (ask first, then delegate) → `setup-admin-ui` skill ### When the user says "help me migrate" or similar: @@ -446,7 +462,17 @@ After assessing, show the user a numbered list of exactly what will change and w - **If virtual fields**: invoke `migrate-virtual-fields` with config path and virtual field code - **If context.graphql/context.query**: invoke `migrate-context-calls` with project root path -**Phase 5 — Validate:** +**Phase 5 — Admin UI:** + +Ask the user two questions (both required before delegating): + +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." +2. "What path should it be mounted at?" (default: `/admin`) + +If yes → **invoke `setup-admin-ui`** with: project root, chosen admin path, and whether `authPlugin` is in their config. +If no → skip and go to Phase 6. + +**Phase 6 — Validate:** - Run `pnpm opensaas generate` and report any errors - 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: 2. Run `pnpm opensaas generate` 3. Run `npx prisma db push` 4. Start dev server: `pnpm dev` -5. Visit the admin UI at `http://localhost:3000/admin` +5. If Admin UI was set up: visit `http://localhost:3000/{adminPath}` (e.g. `http://localhost:3000/admin`) +6. If Admin UI was skipped: mention that they can set it up any time — see https://stack.opensaas.au/admin-ui diff --git a/claude-plugins/opensaas-migration/skills/setup-admin-ui/SKILL.md b/claude-plugins/opensaas-migration/skills/setup-admin-ui/SKILL.md new file mode 100644 index 00000000..a60b7e69 --- /dev/null +++ b/claude-plugins/opensaas-migration/skills/setup-admin-ui/SKILL.md @@ -0,0 +1,174 @@ +--- +name: setup-admin-ui +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. +context: fork +agent: general-purpose +--- + +Set up the OpenSaaS Stack Admin UI in the Next.js project described below. + +$ARGUMENTS + +## What This Skill Does + +1. Installs `@opensaas/stack-ui` if not already a dependency +2. Creates the catch-all admin route page at `app/{adminPath}/[[...{segmentName}]]/page.tsx` +3. Validates the generated file works with the project structure + +## Step 1 — Check if `@opensaas/stack-ui` Is Already Installed + +Read `package.json` in the project root. If `@opensaas/stack-ui` is **not** in `dependencies`, install it: + +```bash +# Detect package manager +# - If pnpm-lock.yaml exists → pnpm add @opensaas/stack-ui +# - If yarn.lock exists → yarn add @opensaas/stack-ui +# - Otherwise → npm install @opensaas/stack-ui +``` + +Check existing versions of `@opensaas/stack-core` in `package.json` and install `@opensaas/stack-ui` at the **same version** to avoid mismatches. + +## Step 2 — Determine the Admin Path + +The admin path comes from `$ARGUMENTS` (e.g. `/admin`, `/dashboard/admin`). + +- **Route path**: `app/{adminPath}/[[...{segmentName}]]/page.tsx` + - For `/admin` → `app/admin/[[...admin]]/page.tsx` + - For `/dashboard/admin` → `app/dashboard/admin/[[...admin]]/page.tsx` + - For `/cms` → `app/cms/[[...cms]]/page.tsx` +- **Segment name** (used in both the directory name and the `params.{segmentName}` reference): the last path segment (e.g. `admin`, `cms`) +- **`basePath`** prop on ``: the full admin path (e.g. `/admin`, `/dashboard/admin`) + +Create all intermediate directories as needed. + +## Step 3 — Determine if Auth Is Enabled + +Check whether auth is configured: + +1. Look in `opensaas.config.ts` for `authPlugin` usage +2. Check for a `lib/auth.ts` or `lib/auth/index.ts` that exports `getSession` + +This determines which page template to use. + +## Step 4 — Create the Admin Page + +### Template A — Without Auth + +Use this when auth is NOT configured: + +```typescript +import { AdminUI } from '@opensaas/stack-ui' +import type { ServerActionInput } from '@opensaas/stack-ui/server' +import { getContext, config } from '@/.opensaas/context' + +async function serverAction(props: ServerActionInput) { + 'use server' + const context = await getContext() + return await context.serverAction(props) +} + +interface AdminPageProps { + params: Promise<{ {SEGMENT_NAME}?: string[] }> + searchParams: Promise<{ [key: string]: string | string[] | undefined }> +} + +export default async function AdminPage({ params, searchParams }: AdminPageProps) { + const resolvedParams = await params + const resolvedSearchParams = await searchParams + return ( + + ) +} +``` + +### Template B — With Auth + +Use this when `authPlugin` is detected and `getSession` is available: + +```typescript +import { AdminUI } from '@opensaas/stack-ui' +import type { ServerActionInput } from '@opensaas/stack-ui/server' +import { getContext, config } from '@/.opensaas/context' +import { getSession } from '@/lib/auth' + +async function serverAction(props: ServerActionInput) { + 'use server' + const context = await getContext({ session: await getSession() }) + return await context.serverAction(props) +} + +interface AdminPageProps { + params: Promise<{ {SEGMENT_NAME}?: string[] }> + searchParams: Promise<{ [key: string]: string | string[] | undefined }> +} + +export default async function AdminPage({ params, searchParams }: AdminPageProps) { + const resolvedParams = await params + const resolvedSearchParams = await searchParams + const session = await getSession() + if (!session) { + return ( +
+
+

Access Denied

+

You must be logged in to access the admin interface.

+
+
+ ) + } + return ( + + ) +} +``` + +Replace `{SEGMENT_NAME}` with the last segment of the admin path (e.g. `admin`) and `{ADMIN_PATH}` with the full path (e.g. `/admin`). + +**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. + +## Step 5 — Check for Missing `.opensaas/context` + +The admin page imports from `@/.opensaas/context`. This file is generated by `pnpm opensaas generate` (or `pnpm generate`). If it doesn't exist yet: + +- **Do not create it manually** — it's generated from `opensaas.config.ts` +- Remind the user to run `pnpm generate` (or `pnpm opensaas generate`) before starting the dev server + +## Step 6 — Report What Was Done + +Report to the user: + +``` +✓ Admin UI set up at: {adminPath} +✓ File created: app/{adminPath}/[[...{segmentName}]]/page.tsx +✓ Auth-aware: yes/no +✓ @opensaas/stack-ui: already installed / installed at version X.Y.Z + +Next steps: +1. Run `pnpm generate` to generate the .opensaas/context.ts file (if not already done) +2. Run `pnpm dev` to start the dev server +3. Visit http://localhost:3000{adminPath} to access the admin UI + +Docs: https://stack.opensaas.au/admin-ui +``` + +## Notes + +- The `[[...{segmentName}]]` catch-all segment handles all admin routes: the list view, item detail, create, and edit pages — all from one file. +- The `basePath` prop must match exactly the URL path where the admin is mounted. +- The `serverAction` wrapper function is required — it provides the server action bridge between the client-side admin UI components and the database context. +- 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`. +- 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