admin-dashboard
<!-- Loaded by nextjs-ecommerce-engineer when task involves admin UI, order management, product CRUD, inventory tracking, analytics, or role-based access -->
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
<!-- Loaded by nextjs-ecommerce-engineer when task involves admin UI, order management, product CRUD, inventory tracking, analytics, or role-based access -->
Agent definition
admin-dashboard.mdAdmin Dashboard Reference
<!-- Loaded by nextjs-ecommerce-engineer when task involves admin UI, order management, product CRUD, inventory tracking, analytics, or role-based access -->
Every destructive action needs confirmation; every mutation needs authorization.
Role-Based Access Control
**When to use:** Any admin route. Gate at middleware and repeat in Server Components (defense in depth).
// lib/auth.ts — extend NextAuth session with role
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: DefaultSession['user'] & {
id: string
role: 'customer' | 'admin' | 'staff'
}
}
}
export const authOptions: NextAuthOptions = {
callbacks: {
session({ session, token }) {
return {
...session,
user: {
...session.user,
id: token.sub!,
role: token.role as 'customer' | 'admin' | 'staff',
},
}
},
jwt({ token, user }) {
if (user) token.role = (user as any).role
return token
},
},
}// middleware.ts — block non-admin at the edge
export async function middleware(request: NextRequest) {
if (!request.nextUrl.pathname.startsWith('/admin')) return NextResponse.next()
const token = await getToken({ req: request })
if (!token || token.role !== 'admin') {
return NextResponse.redirect(new URL('/', request.url))
}
return NextResponse.next()
}
export const config = { matcher: ['/admin/:path*'] }// app/admin/layout.tsx — secondary check in Server Component
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession(authOptions)
// Belt and suspenders — middleware already blocked, but verify again
if (session?.user?.role !== 'admin') notFound()
return <div className="admin-shell">{children}</div>
}---
Order Management
**When to use:** Admin view of all orders with filtering, status updates, and fulfillment tracking.
// app/admin/orders/page.tsx
import { db } from '@/lib/db'
interface OrdersPageProps {
searchParams: { status?: string; page?: string }
}
export default async function OrdersPage({ searchParams }: OrdersPageProps) {
const page = parseInt(searchParams.page ?? '1')
const pageSize = 25
const status = searchParams.status
const [orders, total] = await Promise.all([
db.order.findMany({
where: status ? { status: status as OrderStatus } : undefined,
include: {
user: { select: { name: true, email: true } },
items: {
include: { product: { select: { name: true } } },
},
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
db.order.count({ where: status ? { status: status as OrderStatus } : undefined }),
])
return (
<div>
<OrderFilters currentStatus={status} />
<OrderTable orders={orders} />
<Pagination page={page} total={total} pageSize={pageSize} />
</div>
)
}// actions/admin/orders.ts — update order status
'use server'
import { z } from 'zod'
import { requireAdmin } from '@/lib/auth-helpers'
const UpdateOrderSchema = z.object({
orderId: z.string().cuid(),
status: z.enum(['pending', 'processing', 'shipped', 'delivered', 'refunded', 'cancelled']),
note: z.string().max(500).optional(),
})
export async function updateOrderStatus(formData: FormData) {
await requireAdmin() // throws if not admin
const parsed = UpdateOrderSchema.safeParse({
orderId: formData.get('orderId'),
status: formData.get('status'),
note: formData.get('note'),
})
if (!parsed.success) throw new Error('Invalid form data')
await db.$transaction(async (tx) => {
await tx.order.update({
where: { id: parsed.data.orderId },
data: { status: parsed.data.status },
})
// Audit log — every status change is traceable
await tx.orderAuditLog.create({
data: {
orderId: parsed.data.orderId,
previousStatus: 'unknown', // fetch before update in real usage
newStatus: parsed.data.status,
note: parsed.data.note,
adminId: (await getServerSession(authOptions))!.user.id,
},
})
})
revalidatePath('/admin/orders')
}---
Product CRUD
**When to use:** Admin product management — create, edit, archive products. Use `archive` instead of `delete` to preserve order history references.
// actions/admin/products.ts
'use server'
import { z } from 'zod'
import { requireAdmin } from '@/lib/auth-helpers'
const ProductSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().max(5000),
price: z.number().positive().multipleOf(0.01),
stock: z.number().int().min(0),
categoryId: z.string().cuid(),
published: z.boolean().default(false),
})
export async function createProduct(formData: FormData) {
await requireAdmin()
const parsed = ProductSchema.safeParse({
name: formData.get('name'),
description: formData.get('description'),
price: parseFloat(formData.get('price') as string),
stock: parseInt(formData.get('stock') as string),
categoryId: formData.get('categoryId'),
published: formData.get('published') === 'true',
})
if (!parsed.success) {
return { success: false, errors: parsed.error.flatten().fieldErrors }
}
const product = await db.product.create({ data: parsed.data })
revalidatePath('/admin/products')
revalidatePath('/products') // invalidate public product listing
return { success: true, productId: product.id }
}
export async function archiveProduct(productId: string) {
await requireAdmin()
// Soft delete — preserve for order history
await db.product.update({
where: { id: productId },
data: { archivedAt: new Date(), published: false },
})
revalidatePath('/admin/products')
revalidateRead more
Admin Dashboard Reference
<!-- Loaded by nextjs-ecommerce-engineer when task involves admin UI, order management, product CRUD, inventory tracking, analytics, or role-based access -->
Every destructive action needs confirmation; every mutation needs authorization.
Role-Based Access Control
**When to use:** Any admin route. Gate at middleware and repeat in Server Components (defense in depth).
// lib/auth.ts — extend NextAuth session with role
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: DefaultSession['user'] & {
id: string
role: 'customer' | 'admin' | 'staff'
}
}
}
export const authOptions: NextAuthOptions = {
callbacks: {
session({ session, token }) {
return {
...session,
user: {
...session.user,
id: token.sub!,
role: token.role as 'customer' | 'admin' | 'staff',
},
}
},
jwt({ token, user }) {
if (user) token.role = (user as any).role
return token
},
},
}// middleware.ts — block non-admin at the edge
export async function middleware(request: NextRequest) {
if (!request.nextUrl.pathname.startsWith('/admin')) return NextResponse.next()
const token = await getToken({ req: request })
if (!token || token.role !== 'admin') {
return NextResponse.redirect(new URL('/', request.url))
}
return NextResponse.next()
}
export const config = { matcher: ['/admin/:path*'] }// app/admin/layout.tsx — secondary check in Server Component
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession(authOptions)
// Belt and suspenders — middleware already blocked, but verify again
if (session?.user?.role !== 'admin') notFound()
return <div className="admin-shell">{children}</div>
}---
Order Management
**When to use:** Admin view of all orders with filtering, status updates, and fulfillment tracking.
// app/admin/orders/page.tsx
import { db } from '@/lib/db'
interface OrdersPageProps {
searchParams: { status?: string; page?: string }
}
export default async function OrdersPage({ searchParams }: OrdersPageProps) {
const page = parseInt(searchParams.page ?? '1')
const pageSize = 25
const status = searchParams.status
const [orders, total] = await Promise.all([
db.order.findMany({
where: status ? { status: status as OrderStatus } : undefined,
include: {
user: { select: { name: true, email: true } },
items: {
include: { product: { select: { name: true } } },
},
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
db.order.count({ where: status ? { status: status as OrderStatus } : undefined }),
])
return (
<div>
<OrderFilters currentStatus={status} />
<OrderTable orders={orders} />
<Pagination page={page} total={total} pageSize={pageSize} />
</div>
)
}// actions/admin/orders.ts — update order status
'use server'
import { z } from 'zod'
import { requireAdmin } from '@/lib/auth-helpers'
const UpdateOrderSchema = z.object({
orderId: z.string().cuid(),
status: z.enum(['pending', 'processing', 'shipped', 'delivered', 'refunded', 'cancelled']),
note: z.string().max(500).optional(),
})
export async function updateOrderStatus(formData: FormData) {
await requireAdmin() // throws if not admin
const parsed = UpdateOrderSchema.safeParse({
orderId: formData.get('orderId'),
status: formData.get('status'),
note: formData.get('note'),
})
if (!parsed.success) throw new Error('Invalid form data')
await db.$transaction(async (tx) => {
await tx.order.update({
where: { id: parsed.data.orderId },
data: { status: parsed.data.status },
})
// Audit log — every status change is traceable
await tx.orderAuditLog.create({
data: {
orderId: parsed.data.orderId,
previousStatus: 'unknown', // fetch before update in real usage
newStatus: parsed.data.status,
note: parsed.data.note,
adminId: (await getServerSession(authOptions))!.user.id,
},
})
})
revalidatePath('/admin/orders')
}---
Product CRUD
**When to use:** Admin product management — create, edit, archive products. Use `archive` instead of `delete` to preserve order history references.
// actions/admin/products.ts
'use server'
import { z } from 'zod'
import { requireAdmin } from '@/lib/auth-helpers'
const ProductSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().max(5000),
price: z.number().positive().multipleOf(0.01),
stock: z.number().int().min(0),
categoryId: z.string().cuid(),
published: z.boolean().default(false),
})
export async function createProduct(formData: FormData) {
await requireAdmin()
const parsed = ProductSchema.safeParse({
name: formData.get('name'),
description: formData.get('description'),
price: parseFloat(formData.get('price') as string),
stock: parseInt(formData.get('stock') as string),
categoryId: formData.get('categoryId'),
published: formData.get('published') === 'true',
})
if (!parsed.success) {
return { success: false, errors: parsed.error.flatten().fieldErrors }
}
const product = await db.product.create({ data: parsed.data })
revalidatePath('/admin/products')
revalidatePath('/products') // invalidate public product listing
return { success: true, productId: product.id }
}
export async function archiveProduct(productId: string) {
await requireAdmin()
// Soft delete — preserve for order history
await db.product.update({
where: { id: productId },
data: { archivedAt: new Date(), published: false },
})
revalidatePath('/admin/products')
revalidateEssays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

