Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -2,10 +2,12 @@
## Quick Reference
| Function | Import | Purpose |
|----------|--------|---------|
| `enhanceAction` | `@kit/next/actions` | Server actions with auth + validation |
| `enhanceRouteHandler` | `@kit/next/routes` | API routes with auth + validation |
| Function | Import | Purpose |
|-----------------------|-------------------------|------------------------------------|
| `authActionClient` | `@kit/next/safe-action` | Authenticated server actions |
| `publicActionClient` | `@kit/next/safe-action` | Public server actions (no auth) |
| `captchaActionClient` | `@kit/next/safe-action` | Server actions with CAPTCHA + auth |
| `enhanceRouteHandler` | `@kit/next/routes` | API routes with auth + validation |
## Guidelines
@@ -14,29 +16,78 @@
- Authorization via RLS, not application code
- Use `'use server'` at top of file
- Always validate with Zod schema
- Use `useAction` hook from `next-safe-action/hooks` in client components
## Skills
For detailed implementation patterns:
- `/server-action-builder` - Complete server action workflow
## Server Action Pattern
## Server Action Pattern (next-safe-action)
```typescript
'use server';
import { enhanceAction } from '@kit/next/actions';
import { authActionClient } from '@kit/next/safe-action';
export const myAction = enhanceAction(
async function (data, user) {
// data: validated, user: authenticated
// Authenticated action with schema validation
export const myAction = authActionClient
.schema(MySchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
// data: validated input, user: authenticated user
return { success: true };
},
{
auth: true,
schema: MySchema,
},
);
});
// Public action (no auth required)
import { publicActionClient } from '@kit/next/safe-action';
export const publicAction = publicActionClient
.schema(MySchema)
.action(async ({ parsedInput: data }) => {
return { success: true };
});
```
### Admin actions
Admin actions use a dedicated client in `@kit/admin`:
```typescript
import { adminActionClient } from '../utils/admin-action-client';
export const adminAction = adminActionClient
.schema(MySchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
// Only accessible to super admins
return { success: true };
});
```
## Client Component Pattern (useAction)
```typescript
'use client';
import { useAction } from 'next-safe-action/hooks';
import { myAction } from '../server/server-actions';
function MyComponent() {
const { execute, isPending, hasErrored, result } = useAction(myAction, {
onSuccess: ({ data }) => {
// Handle success
},
onError: ({ error }) => {
// Handle error
},
});
return (
<form onSubmit={(e) => { e.preventDefault(); execute(formData); }}>
{/* form fields */}
<button disabled={isPending}>Submit</button>
</form>
);
}
```
## Route Handler Pattern

View File

@@ -11,8 +11,12 @@
"prettier": "@kit/prettier-config",
"exports": {
"./actions": "./src/actions/index.ts",
"./safe-action": "./src/actions/safe-action-client.ts",
"./routes": "./src/routes/index.ts"
},
"dependencies": {
"next-safe-action": "catalog:"
},
"devDependencies": {
"@kit/auth": "workspace:*",
"@kit/eslint-config": "workspace:*",

View File

@@ -20,19 +20,22 @@ export function enhanceAction<
auth?: boolean;
captcha?: boolean;
schema?: z.ZodType<
Config['captcha'] extends true ? Args & { captchaToken: string } : Args,
z.ZodTypeDef
Config['captcha'] extends true ? Args & { captchaToken: string } : Args
>;
},
>(
fn: (
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
params: Config['schema'] extends ZodType
? z.output<Config['schema']>
: Args,
user: Config['auth'] extends false ? undefined : JWTUserData,
) => Response | Promise<Response>,
config: Config,
) {
return async (
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
params: Config['schema'] extends ZodType
? z.output<Config['schema']>
: Args,
) => {
type UserParam = Config['auth'] extends false ? undefined : JWTUserData;
@@ -80,6 +83,11 @@ export function enhanceAction<
user = auth.data as UserParam;
}
return fn(data, user);
return fn(
data as Config['schema'] extends ZodType
? z.output<Config['schema']>
: Args,
user,
);
};
}

View File

@@ -0,0 +1,55 @@
import 'server-only';
import { redirect } from 'next/navigation';
import { createSafeActionClient } from 'next-safe-action';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const baseClient = createSafeActionClient({
handleServerError: (error) => error.message,
});
/**
* @name publicActionClient
* @description Safe action client for public actions that don't require authentication.
*/
export const publicActionClient = baseClient;
/**
* @name authActionClient
* @description Safe action client for authenticated actions. Adds user context.
*/
export const authActionClient = baseClient.use(async ({ next }) => {
const auth = await requireUser(getSupabaseServerClient());
if (!auth.data) {
redirect(auth.redirectTo);
}
return next({ ctx: { user: auth.data } });
});
/**
* @name captchaActionClient
* @description Safe action client for actions that require CAPTCHA and authentication.
*/
export const captchaActionClient = baseClient.use(
async ({ next, clientInput }) => {
const input = clientInput as Record<string, unknown>;
const token =
typeof input?.captchaToken === 'string' ? input.captchaToken : '';
await verifyCaptchaToken(token);
const auth = await requireUser(getSupabaseServerClient());
if (!auth.data) {
redirect(auth.redirectTo);
}
return next({ ctx: { user: auth.data } });
},
);

View File

@@ -3,7 +3,7 @@ import 'server-only';
import { redirect } from 'next/navigation';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import * as z from 'zod';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
@@ -22,7 +22,7 @@ interface HandlerParams<
> {
request: NextRequest;
user: RequireAuth extends false ? undefined : JWTUserData;
body: Schema extends z.ZodType ? z.infer<Schema> : undefined;
body: Schema extends z.ZodType ? z.output<Schema> : undefined;
params: Record<string, string>;
}
@@ -48,7 +48,7 @@ interface HandlerParams<
*/
export const enhanceRouteHandler = <
Body,
Params extends Config<z.ZodType<Body, z.ZodTypeDef>>,
Params extends Config<z.ZodType<Body>>,
>(
// Route handler function
handler: