Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "nuxt-auto-skills",
"owner": {
"name": "websideproject"
},
"metadata": {
"description": "Claude Code skills for @websideproject/nuxt-auto-api and @websideproject/nuxt-auto-admin — schema-driven REST API and admin panel generation for Nuxt 4",
"version": "0.0.7"
},
"plugins": [
{
"name": "nuxt-auto-skills",
"description": "Skills for nuxt-auto-api and nuxt-auto-admin: resource registration, CRUD composables, authorization, hooks, M2M, aggregation, admin config, widgets, and module authoring",
"source": "./",
"skills": [
"./.claude/skills/nuxt-auto-api",
"./.claude/skills/nuxt-auto-admin"
]
}
]
}
98 changes: 98 additions & 0 deletions .claude/skills/nuxt-auto-admin/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
name: nuxt-auto-admin
description: Use when working with nuxt-auto-admin (@websideproject/nuxt-auto-admin) - auto-generated admin panel from Drizzle ORM schemas, with resource tables, forms, M2M cards, permission integration, and customizable widgets.
license: MIT
---

# nuxt-auto-admin

Auto-generates a full admin panel for Nuxt 4 from Drizzle ORM schemas registered with `@websideproject/nuxt-auto-api`. Introspects table schemas at build time to produce list views, create/edit forms, M2M management, and permission-aware navigation — all via `@nuxt/ui`.

**Requires** `@websideproject/nuxt-auto-api` loaded first in the `modules` array.

## When to Use

- Configuring `autoAdmin` module options (branding, prefix, features, permissions)
- Customizing resource display (`displayName`, `icon`, `listFields`, `formFields`, `hiddenFields`)
- Adding/configuring form field widgets (`WidgetType`, `WidgetOptions`)
- Writing custom actions (`CustomAction`) for single items, bulk, or page-level
- Using admin composables (`useAdminConfig`, `useAdminRegistry`, `useAdminPermissions`, `useAdminActions`)
- Working with `<ResourceTable>`, `<ResourceForm>`, `<AutoForm>`, `<M2MRelationCard>`, `<AutoField>`
- Setting up custom pages (`customPages`)
- Understanding permission integration with nuxt-auto-api

## Quick Start

```ts
// nuxt.config.ts — load API module first
export default defineNuxtConfig({
modules: [
'@websideproject/nuxt-auto-api',
'@websideproject/nuxt-auto-admin',
],
autoApi: {
database: { client: 'd1' },
},
autoAdmin: {
prefix: '/admin',
access: (user) => user?.roles?.includes('admin'),
branding: { title: 'My App Admin', logo: '/logo.svg' },
resources: {
posts: {
displayName: 'Blog Posts',
icon: 'i-heroicons-document-text',
listFields: ['title', 'status', 'author', 'createdAt'],
hiddenFields: ['slug', 'internalNotes'],
},
users: {
displayName: 'Users',
icon: 'i-heroicons-users',
group: 'Team',
readonlyFields: ['email', 'createdAt'],
},
},
},
})
```

## Auto-Generated Routes

All prefixed by `autoAdmin.prefix` (default: `/admin`):

| Route | Page |
|-------|------|
| `/admin` | Dashboard |
| `/admin/:resource` | List with search, filters, bulk actions |
| `/admin/:resource/new` | Create form |
| `/admin/:resource/:id` | Detail view |
| `/admin/:resource/:id/edit` | Edit form |

## Available Guidance

| File | Topics |
|------|--------|
| **[references/config.md](references/config.md)** | ModuleOptions, ResourceConfig, DashboardConfig, ThemeConfig, CustomPageConfig |
| **[references/fields-widgets.md](references/fields-widgets.md)** | FieldConfig, all WidgetTypes with WidgetOptions, auto-widget selection rules |
| **[references/composables.md](references/composables.md)** | useAdminConfig, useAdminRegistry, useAdminResource, useAdminPermissions, useAdminActions, useResourceForm, useM2MDetection |
| **[references/components.md](references/components.md)** | ResourceTable, ResourceForm, AutoForm, AutoField, M2MRelationCard, modals |
| **[references/actions-permissions.md](references/actions-permissions.md)** | CustomAction, ActionContext, permission integration, middleware, unauthorized behavior |
| **[references/module-authoring.md](references/module-authoring.md)** | Injecting admin display config from a Nuxt module: spread pattern, formFields, junction tables |

## Progressive Loading

- Setting up the module or resources? → [references/config.md](references/config.md)
- Configuring form fields / widgets? → [references/fields-widgets.md](references/fields-widgets.md)
- Using composables in custom pages? → [references/composables.md](references/composables.md)
- Using admin UI components? → [references/components.md](references/components.md)
- Custom actions or permission control? → [references/actions-permissions.md](references/actions-permissions.md)
- Building a Nuxt module that ships admin config? → [references/module-authoring.md](references/module-authoring.md)

**DO NOT read all files at once.**

## Related Skills

- **`nuxt-auto-api`** — required backend; provides the endpoints this admin consumes
- **`nuxt-ui`** — UI component library used by the admin panel
- **`nuxt`** — Nuxt 4 patterns for custom pages and middleware

_Token efficiency: Main skill ~350 tokens. Each reference ~800–1200 tokens._
165 changes: 165 additions & 0 deletions .claude/skills/nuxt-auto-admin/references/actions-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Custom Actions & Permissions

## Custom Actions

Add custom buttons to resource rows, toolbars, or detail pages.

```ts
resources: {
posts: {
actions: {
publish: {
label: 'Publish',
icon: 'i-heroicons-paper-airplane',
type: 'single', // 'single' | 'bulk' | 'page-level'
location: 'row', // 'row' | 'toolbar' | 'detail'
permission: (ctx) => ctx.user?.roles?.includes('editor'),
confirm: 'Publish this post?', // confirmation dialog message
// or: confirm: (item) => `Publish "${item.title}"?`
handler: async (item, ctx) => {
await $fetch(`/api/posts/${item.id}`, {
method: 'PATCH',
body: { status: 'published', publishedAt: new Date() },
})
await ctx.refresh()
ctx.toast.add({ title: 'Post published', color: 'success' })
},
variant: 'ghost',
color: 'success',
},
},
},
}
```

### `CustomAction` interface

```ts
interface CustomAction {
label: string
icon?: string // Iconify icon
type: 'single' | 'bulk' | 'page-level'
location: 'row' | 'toolbar' | 'detail'
permission?: (ctx: ActionContext) => boolean | Promise<boolean>
handler: (item: any | any[], ctx: ActionContext) => Promise<void> | void
confirm?: string | ((item: any | any[]) => string)
variant?: 'primary' | 'secondary' | 'ghost' | 'link'
color?: string
}
```

### `ActionContext`

```ts
interface ActionContext {
user: any // Current authenticated user
resource: string // Resource name
refresh: () => Promise<void> // Re-fetch the current list/detail
toast: any // @nuxt/ui useToast() instance
}
```

### Action types

| Type | Receives | Shown in |
|------|----------|----------|
| `single` | Single item | Row actions, detail page |
| `bulk` | Array of selected items | Toolbar (bulk actions) |
| `page-level` | N/A | Page toolbar only |

### Action locations

| Location | Where |
|----------|-------|
| `row` | Per-row action buttons in the table |
| `toolbar` | Above the table (for bulk and page-level) |
| `detail` | On the detail/edit page |

---

## Permission Integration

nuxt-auto-admin integrates directly with the `nuxt-auto-api` permission system.

### How permissions flow

1. `useAdminPermissions('posts')` calls `usePermissions('posts')` from nuxt-auto-api
2. The admin UI reads `canCreate`, `canRead`, `canUpdate`, `canDelete`
3. Buttons/sidebar items are **hidden or disabled** based on `autoAdmin.permissions` config

### Configure unauthorized behavior

```ts
autoAdmin: {
permissions: {
unauthorizedButtons: 'disable', // 'hide' | 'disable' — default: 'disable'
unauthorizedSidebarItems: 'hide', // 'hide' | 'disable' — default: 'hide'
},
}
```

- `'disable'` — shows the button but makes it unclickable
- `'hide'` — removes the element entirely

### Custom action permissions

```ts
permission: async (ctx) => {
// ctx.user is the authenticated user from the session
return ctx.user?.roles?.includes('editor') && !ctx.user?.isSuspended
}
```

When `permission` returns `false`, the action button respects the `unauthorizedButtons` setting.

---

## Admin Access Guard

The `access` function in module config controls who can reach any admin route at all:

```ts
autoAdmin: {
access: (user) => {
if (!user) return false
return user.roles?.includes('admin') || user.roles?.includes('moderator')
},
}
```

The `admin-auth` middleware enforces this on every `/admin/*` route.

---

## Middleware

### `admin-auth`

Use in custom pages to enforce the global `access` check:

```ts
// pages/admin/custom-page.vue
definePageMeta({ middleware: 'admin-auth' })
```

### `permissions.global`

Automatically runs on all admin routes. Checks per-resource permissions and shows `<PermissionDeniedPage>` if the user lacks the required permission for the current route's resource.

---

## Custom Page Access

Each custom page can define its own access check independent of the global one:

```ts
customPages: [
{
name: 'reports',
label: 'Reports',
path: '/reports',
icon: 'i-heroicons-chart-bar',
canAccess: (user) => user?.permissions?.includes('view:reports'),
},
]
```
Loading
Loading