* refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stack and architecture details - Merged content from CLAUDE.md into AGENTS.md for better organization. - Updated tech stack section to reflect the current technologies used, including Next.js, Supabase, and Tailwind CSS. - Enhanced monorepo structure documentation with detailed directory purposes. - Streamlined multi-tenant architecture explanation and essential commands. - Added key patterns for naming conventions and server actions. - Removed outdated agent files related to Playwright and PostgreSQL, ensuring a cleaner codebase. - Bumped version to 2.23.7 to reflect changes.
195 lines
4.5 KiB
Markdown
195 lines
4.5 KiB
Markdown
# Server Action Examples
|
|
|
|
Real examples from the Makerkit codebase.
|
|
|
|
## Team Billing Action
|
|
|
|
Location: `apps/web/app/home/[account]/billing/_lib/server/server-actions.ts`
|
|
|
|
```typescript
|
|
'use server';
|
|
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { getLogger } from '@kit/shared/logger';
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
import { UpdateBillingSchema } from '../schemas/billing.schema';
|
|
import { createBillingService } from './billing.service';
|
|
|
|
export const updateBillingAction = enhanceAction(
|
|
async function (data, user) {
|
|
const logger = await getLogger();
|
|
const ctx = { name: 'update-billing', userId: user.id, accountId: data.accountId };
|
|
|
|
logger.info(ctx, 'Updating billing settings');
|
|
|
|
const service = createBillingService();
|
|
await service.updateBilling(data);
|
|
|
|
logger.info(ctx, 'Billing settings updated');
|
|
|
|
revalidatePath(`/home/${data.accountSlug}/billing`);
|
|
|
|
return { success: true };
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: UpdateBillingSchema,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Personal Settings Action
|
|
|
|
Location: `apps/web/app/home/(user)/settings/_lib/server/server-actions.ts`
|
|
|
|
```typescript
|
|
'use server';
|
|
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { getLogger } from '@kit/shared/logger';
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
import { UpdateProfileSchema } from '../schemas/profile.schema';
|
|
|
|
export const updateProfileAction = enhanceAction(
|
|
async function (data, user) {
|
|
const logger = await getLogger();
|
|
const ctx = { name: 'update-profile', userId: user.id };
|
|
|
|
logger.info(ctx, 'Updating user profile');
|
|
|
|
const client = getSupabaseServerClient();
|
|
|
|
const { error } = await client
|
|
.from('accounts')
|
|
.update({ name: data.name })
|
|
.eq('id', user.id);
|
|
|
|
if (error) {
|
|
logger.error({ ...ctx, error }, 'Failed to update profile');
|
|
throw error;
|
|
}
|
|
|
|
logger.info(ctx, 'Profile updated successfully');
|
|
|
|
revalidatePath('/home/settings');
|
|
|
|
return { success: true };
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: UpdateProfileSchema,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Action with Redirect
|
|
|
|
```typescript
|
|
'use server';
|
|
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { redirect } from 'next/navigation';
|
|
|
|
import { CreateProjectSchema } from '../schemas/project.schema';
|
|
import { createProjectService } from './project.service';
|
|
|
|
export const createProjectAction = enhanceAction(
|
|
async function (data, user) {
|
|
const service = createProjectService();
|
|
const project = await service.create(data);
|
|
|
|
// Redirect after creation
|
|
redirect(`/home/${data.accountSlug}/projects/${project.id}`);
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: CreateProjectSchema,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Delete Action with Confirmation
|
|
|
|
```typescript
|
|
'use server';
|
|
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { getLogger } from '@kit/shared/logger';
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
import { DeleteItemSchema } from '../schemas/item.schema';
|
|
|
|
export const deleteItemAction = enhanceAction(
|
|
async function (data, user) {
|
|
const logger = await getLogger();
|
|
const ctx = { name: 'delete-item', userId: user.id, itemId: data.itemId };
|
|
|
|
logger.info(ctx, 'Deleting item');
|
|
|
|
const client = getSupabaseServerClient();
|
|
|
|
const { error } = await client
|
|
.from('items')
|
|
.delete()
|
|
.eq('id', data.itemId)
|
|
.eq('account_id', data.accountId); // RLS will also validate
|
|
|
|
if (error) {
|
|
logger.error({ ...ctx, error }, 'Failed to delete item');
|
|
throw error;
|
|
}
|
|
|
|
logger.info(ctx, 'Item deleted successfully');
|
|
|
|
revalidatePath(`/home/${data.accountSlug}/items`);
|
|
|
|
return { success: true };
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: DeleteItemSchema,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Error Handling with isRedirectError
|
|
|
|
```typescript
|
|
'use server';
|
|
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
import { isRedirectError } from 'next/dist/client/components/redirect-error';
|
|
import { redirect } from 'next/navigation';
|
|
|
|
export const submitFormAction = enhanceAction(
|
|
async function (data, user) {
|
|
const logger = await getLogger();
|
|
const ctx = { name: 'submit-form', userId: user.id };
|
|
|
|
try {
|
|
logger.info(ctx, 'Submitting form');
|
|
|
|
// Process form
|
|
await processForm(data);
|
|
|
|
logger.info(ctx, 'Form submitted, redirecting');
|
|
|
|
redirect('/success');
|
|
} catch (error) {
|
|
// Don't treat redirects as errors
|
|
if (!isRedirectError(error)) {
|
|
logger.error({ ...ctx, error }, 'Form submission failed');
|
|
throw error;
|
|
}
|
|
throw error; // Re-throw redirect
|
|
}
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: FormSchema,
|
|
},
|
|
);
|
|
```
|