Optimized agents rules subfolders, dependencies updates (#355)

* Update AGENTS.md and CLAUDE.md for improved clarity and structure
* Added MCP Server
* Added missing triggers to tables that should have used them
* Updated all dependencies
* Fixed rare bug in React present in the Admin layout which prevents navigating to pages (sometimes...)
This commit is contained in:
Giancarlo Buomprisco
2025-09-17 11:36:02 +08:00
committed by GitHub
parent 9fae142f2d
commit 533dfba5b9
83 changed files with 9223 additions and 2974 deletions

289
packages/features/AGENTS.md Normal file
View File

@@ -0,0 +1,289 @@
# Feature Packages Instructions
This file contains instructions for working with feature packages including accounts, teams, billing, auth, and notifications.
## Feature Package Structure
- `accounts/` - Personal account management
- `admin/` - Super admin functionality
- `auth/` - Authentication features
- `notifications/` - Notification system
- `team-accounts/` - Team account management
## Account Services
### Personal Accounts API
Located at: `packages/features/accounts/src/server/api.ts`
```typescript
import { createAccountsApi } from '@kit/accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const client = getSupabaseServerClient();
const api = createAccountsApi(client);
// Get account data
const account = await api.getAccount(accountId);
// Get account workspace
const workspace = await api.getAccountWorkspace();
// Load user accounts
const accounts = await api.loadUserAccounts();
// Get subscription
const subscription = await api.getSubscription(accountId);
// Get customer ID
const customerId = await api.getCustomerId(accountId);
```
### Team Accounts API
Located at: `packages/features/team-accounts/src/server/api.ts`
```typescript
import { createTeamAccountsApi } from '@kit/team-accounts/api';
const api = createTeamAccountsApi(client);
// Get team account by slug
const account = await api.getTeamAccount(slug);
// Get account workspace
const workspace = await api.getAccountWorkspace(slug);
// Check permissions
const hasPermission = await api.hasPermission({
accountId,
userId,
permission: 'billing.manage'
});
// Get members count
const count = await api.getMembersCount(accountId);
// Get invitation
const invitation = await api.getInvitation(adminClient, token);
```
## Workspace Contexts
### Personal Account Context
Use in `apps/web/app/home/(user)` routes:
```tsx
import { useUserWorkspace } from 'kit/accounts/hooks/use-user-workspace';
function PersonalComponent() {
const { user, account } = useUserWorkspace();
// user: authenticated user data
// account: personal account data
return <div>Welcome {user.name}</div>;
}
```
Context provider: `packages/features/accounts/src/components/user-workspace-context-provider.tsx`
### Team Account Context
Use in `apps/web/app/home/[account]` routes:
```tsx
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// account: current team account data
// user: authenticated user data
// accounts: all accounts user has access to
return <div>Team: {account.name}</div>;
}
```
Context provider: `packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx`
## Billing Services
### Personal Billing
Located at: `apps/web/app/home/(user)/billing/_lib/server/user-billing.service.ts`
```typescript
// Personal billing operations
// - Manage individual user subscriptions
// - Handle personal account payments
// - Process individual billing changes
```
### Team Billing
Located at: `apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts`
```typescript
// Team billing operations
// - Manage team subscriptions
// - Handle team payments
// - Process team billing changes
```
### Per-Seat Billing Service
Located at: `packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts`
```typescript
import { createAccountPerSeatBillingService } from '@kit/team-accounts/billing';
const billingService = createAccountPerSeatBillingService(client);
// Increase seats when adding team members
await billingService.increaseSeats(accountId);
// Decrease seats when removing team members
await billingService.decreaseSeats(accountId);
// Get per-seat subscription item
const subscription = await billingService.getPerSeatSubscriptionItem(accountId);
```
## Authentication Features
### OTP for Sensitive Operations
Use one-time tokens from `packages/otp/src/api/index.ts`:
```tsx
import { VerifyOtpForm } from '@kit/otp/components';
<VerifyOtpForm
purpose="account-deletion"
email={user.email}
onSuccess={(otp) => {
// Proceed with verified operation
handleSensitiveOperation(otp);
}}
CancelButton={<Button variant="outline">Cancel</Button>}
/>
```
## Admin Features
### Super Admin Protection
For admin routes, use `AdminGuard`:
```tsx
import { AdminGuard } from '@kit/admin/components/admin-guard';
function AdminPage() {
return (
<div>
<h1>Admin Dashboard</h1>
{/* Admin content */}
</div>
);
}
// Wrap the page component
export default AdminGuard(AdminPage);
```
### Admin Service
Located at: `packages/features/admin/src/lib/server/services/admin.service.ts`
```typescript
// Admin service operations
// - Manage all accounts
// - Handle admin-level operations
// - Access system-wide data
```
### Checking Admin Status
```typescript
import { isSuperAdmin } from '@kit/admin';
function criticalAdminFeature() {
const isAdmin = await isSuperAdmin(client);
if (!isAdmin) {
throw new Error('Access denied: Admin privileges required');
}
// ...
}
```
## Error Handling & Logging
### Structured Logging
Use logger from `packages/shared/src/logger/logger.ts`:
```typescript
import { getLogger } from '@kit/shared/logger';
async function featureOperation() {
const logger = await getLogger();
const ctx = {
name: 'feature-operation',
userId: user.id,
accountId: account.id
};
try {
logger.info(ctx, 'Starting feature operation');
// Perform operation
const result = await performOperation();
logger.info({ ...ctx, result }, 'Feature operation completed');
return result;
} catch (error) {
logger.error({ ...ctx, error }, 'Feature operation failed');
throw error;
}
}
```
## Permission Patterns
### Team Permissions
```typescript
import { createTeamAccountsApi } from '@kit/team-accounts/api';
const api = createTeamAccountsApi(client);
// Check if user has specific permission on account
const canManageBilling = await api.hasPermission({
accountId,
userId,
permission: 'billing.manage'
});
if (!canManageBilling) {
throw new Error('Insufficient permissions');
}
```
### Account Ownership
```typescript
// Check if user is account owner (works for both personal and team accounts)
const isOwner = await client.rpc('is_account_owner', {
account_id: accountId
});
if (!isOwner) {
throw new Error('Only account owners can perform this action');
}
```

289
packages/features/CLAUDE.md Normal file
View File

@@ -0,0 +1,289 @@
# Feature Packages Instructions
This file contains instructions for working with feature packages including accounts, teams, billing, auth, and notifications.
## Feature Package Structure
- `accounts/` - Personal account management
- `admin/` - Super admin functionality
- `auth/` - Authentication features
- `notifications/` - Notification system
- `team-accounts/` - Team account management
## Account Services
### Personal Accounts API
Located at: `packages/features/accounts/src/server/api.ts`
```typescript
import { createAccountsApi } from '@kit/accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const client = getSupabaseServerClient();
const api = createAccountsApi(client);
// Get account data
const account = await api.getAccount(accountId);
// Get account workspace
const workspace = await api.getAccountWorkspace();
// Load user accounts
const accounts = await api.loadUserAccounts();
// Get subscription
const subscription = await api.getSubscription(accountId);
// Get customer ID
const customerId = await api.getCustomerId(accountId);
```
### Team Accounts API
Located at: `packages/features/team-accounts/src/server/api.ts`
```typescript
import { createTeamAccountsApi } from '@kit/team-accounts/api';
const api = createTeamAccountsApi(client);
// Get team account by slug
const account = await api.getTeamAccount(slug);
// Get account workspace
const workspace = await api.getAccountWorkspace(slug);
// Check permissions
const hasPermission = await api.hasPermission({
accountId,
userId,
permission: 'billing.manage'
});
// Get members count
const count = await api.getMembersCount(accountId);
// Get invitation
const invitation = await api.getInvitation(adminClient, token);
```
## Workspace Contexts
### Personal Account Context
Use in `apps/web/app/home/(user)` routes:
```tsx
import { useUserWorkspace } from 'kit/accounts/hooks/use-user-workspace';
function PersonalComponent() {
const { user, account } = useUserWorkspace();
// user: authenticated user data
// account: personal account data
return <div>Welcome {user.name}</div>;
}
```
Context provider: `packages/features/accounts/src/components/user-workspace-context-provider.tsx`
### Team Account Context
Use in `apps/web/app/home/[account]` routes:
```tsx
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// account: current team account data
// user: authenticated user data
// accounts: all accounts user has access to
return <div>Team: {account.name}</div>;
}
```
Context provider: `packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx`
## Billing Services
### Personal Billing
Located at: `apps/web/app/home/(user)/billing/_lib/server/user-billing.service.ts`
```typescript
// Personal billing operations
// - Manage individual user subscriptions
// - Handle personal account payments
// - Process individual billing changes
```
### Team Billing
Located at: `apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts`
```typescript
// Team billing operations
// - Manage team subscriptions
// - Handle team payments
// - Process team billing changes
```
### Per-Seat Billing Service
Located at: `packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts`
```typescript
import { createAccountPerSeatBillingService } from '@kit/team-accounts/billing';
const billingService = createAccountPerSeatBillingService(client);
// Increase seats when adding team members
await billingService.increaseSeats(accountId);
// Decrease seats when removing team members
await billingService.decreaseSeats(accountId);
// Get per-seat subscription item
const subscription = await billingService.getPerSeatSubscriptionItem(accountId);
```
## Authentication Features
### OTP for Sensitive Operations
Use one-time tokens from `packages/otp/src/api/index.ts`:
```tsx
import { VerifyOtpForm } from '@kit/otp/components';
<VerifyOtpForm
purpose="account-deletion"
email={user.email}
onSuccess={(otp) => {
// Proceed with verified operation
handleSensitiveOperation(otp);
}}
CancelButton={<Button variant="outline">Cancel</Button>}
/>
```
## Admin Features
### Super Admin Protection
For admin routes, use `AdminGuard`:
```tsx
import { AdminGuard } from '@kit/admin/components/admin-guard';
function AdminPage() {
return (
<div>
<h1>Admin Dashboard</h1>
{/* Admin content */}
</div>
);
}
// Wrap the page component
export default AdminGuard(AdminPage);
```
### Admin Service
Located at: `packages/features/admin/src/lib/server/services/admin.service.ts`
```typescript
// Admin service operations
// - Manage all accounts
// - Handle admin-level operations
// - Access system-wide data
```
### Checking Admin Status
```typescript
import { isSuperAdmin } from '@kit/admin';
function criticalAdminFeature() {
const isAdmin = await isSuperAdmin(client);
if (!isAdmin) {
throw new Error('Access denied: Admin privileges required');
}
// ...
}
```
## Error Handling & Logging
### Structured Logging
Use logger from `packages/shared/src/logger/logger.ts`:
```typescript
import { getLogger } from '@kit/shared/logger';
async function featureOperation() {
const logger = await getLogger();
const ctx = {
name: 'feature-operation',
userId: user.id,
accountId: account.id
};
try {
logger.info(ctx, 'Starting feature operation');
// Perform operation
const result = await performOperation();
logger.info({ ...ctx, result }, 'Feature operation completed');
return result;
} catch (error) {
logger.error({ ...ctx, error }, 'Feature operation failed');
throw error;
}
}
```
## Permission Patterns
### Team Permissions
```typescript
import { createTeamAccountsApi } from '@kit/team-accounts/api';
const api = createTeamAccountsApi(client);
// Check if user has specific permission on account
const canManageBilling = await api.hasPermission({
accountId,
userId,
permission: 'billing.manage'
});
if (!canManageBilling) {
throw new Error('Insufficient permissions');
}
```
### Account Ownership
```typescript
// Check if user is account owner (works for both personal and team accounts)
const isOwner = await client.rpc('is_account_owner', {
account_id: accountId
});
if (!isOwner) {
throw new Error('Only account owners can perform this action');
}
```

View File

@@ -20,7 +20,7 @@
"nanoid": "^5.1.5"
},
"devDependencies": {
"@hookform/resolvers": "^5.2.1",
"@hookform/resolvers": "^5.2.2",
"@kit/billing-gateway": "workspace:*",
"@kit/email-templates": "workspace:*",
"@kit/eslint-config": "workspace:*",
@@ -34,12 +34,12 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.57.2",
"@tanstack/react-query": "5.87.1",
"@types/react": "19.1.12",
"@supabase/supabase-js": "2.57.4",
"@tanstack/react-query": "5.89.0",
"@types/react": "19.1.13",
"@types/react-dom": "19.1.9",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"lucide-react": "^0.544.0",
"next": "15.5.3",
"next-themes": "0.4.6",
"react": "19.1.1",
"react-dom": "19.1.1",

View File

@@ -10,7 +10,7 @@
},
"prettier": "@kit/prettier-config",
"devDependencies": {
"@hookform/resolvers": "^5.2.1",
"@hookform/resolvers": "^5.2.2",
"@kit/eslint-config": "workspace:*",
"@kit/next": "workspace:*",
"@kit/prettier-config": "workspace:*",
@@ -20,12 +20,12 @@
"@kit/ui": "workspace:*",
"@makerkit/data-loader-supabase-core": "^0.0.10",
"@makerkit/data-loader-supabase-nextjs": "^1.2.5",
"@supabase/supabase-js": "2.57.2",
"@tanstack/react-query": "5.87.1",
"@supabase/supabase-js": "2.57.4",
"@tanstack/react-query": "5.89.0",
"@tanstack/react-table": "^8.21.3",
"@types/react": "19.1.12",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"@types/react": "19.1.13",
"lucide-react": "^0.544.0",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-hook-form": "^7.62.0",

View File

@@ -46,18 +46,18 @@ export function AdminAccountPage(props: {
async function PersonalAccountPage(props: { account: Account }) {
const adminClient = getSupabaseServerAdminClient();
const { data, error } = await adminClient.auth.admin.getUserById(
props.account.id,
);
const [memberships, userResult] = await Promise.all([
getMemberships(props.account.id),
adminClient.auth.admin.getUserById(props.account.id),
]);
if (!data || error) {
throw new Error(`User not found`);
if (userResult.error) {
throw userResult.error;
}
const memberships = await getMemberships(props.account.id);
const isBanned =
'banned_until' in data.user && data.user.banned_until !== 'none';
'banned_until' in userResult.data.user &&
userResult.data.user.banned_until !== 'none';
return (
<>

View File

@@ -168,6 +168,7 @@ function getColumns(): ColumnDef<Account>[] {
cell: ({ row }) => {
return (
<Link
prefetch={false}
className={'hover:underline'}
href={`/admin/accounts/${row.original.id}`}
>

View File

@@ -2,6 +2,8 @@
import { useState, useTransition } from 'react';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
@@ -77,11 +79,9 @@ function BanUserForm(props: { userId: string }) {
onSubmit={form.handleSubmit((data) => {
startTransition(async () => {
try {
const result = await banUserAction(data);
setError(!result.success);
} catch {
setError(true);
await banUserAction(data);
} catch (error) {
setError(!isRedirectError(error));
}
});
})}

View File

@@ -2,6 +2,8 @@
import { useState, useTransition } from 'react';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
@@ -76,11 +78,9 @@ function ReactivateUserForm(props: { userId: string }) {
onSubmit={form.handleSubmit((data) => {
startTransition(async () => {
try {
const result = await reactivateUserAction(data);
setError(!result.success);
} catch {
setError(true);
await reactivateUserAction(data);
} catch (error) {
setError(!isRedirectError(error));
}
});
})}

View File

@@ -47,9 +47,7 @@ export const banUserAction = adminAction(
revalidateAdmin();
return {
success: true,
};
return redirect(`/admin/accounts/${userId}`);
},
{
schema: BanUserSchema,
@@ -83,9 +81,7 @@ export const reactivateUserAction = adminAction(
logger.info({ userId }, `Super Admin has successfully reactivated user`);
return {
success: true,
};
return redirect(`/admin/accounts/${userId}`);
},
{
schema: ReactivateUserSchema,

View File

@@ -20,20 +20,20 @@
"./oauth-provider-logo-image": "./src/components/oauth-provider-logo-image.tsx"
},
"devDependencies": {
"@hookform/resolvers": "^5.2.1",
"@hookform/resolvers": "^5.2.2",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@marsidev/react-turnstile": "^1.3.0",
"@marsidev/react-turnstile": "^1.3.1",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.57.2",
"@tanstack/react-query": "5.87.1",
"@types/react": "19.1.12",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"@supabase/supabase-js": "2.57.4",
"@tanstack/react-query": "5.89.0",
"@types/react": "19.1.13",
"lucide-react": "^0.544.0",
"next": "15.5.3",
"react-hook-form": "^7.62.0",
"react-i18next": "^15.7.3",
"sonner": "^2.0.7",

View File

@@ -19,10 +19,10 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.57.2",
"@tanstack/react-query": "5.87.1",
"@types/react": "19.1.12",
"lucide-react": "^0.542.0",
"@supabase/supabase-js": "2.57.4",
"@tanstack/react-query": "5.89.0",
"@types/react": "19.1.13",
"lucide-react": "^0.544.0",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-i18next": "^15.7.3"

View File

@@ -18,7 +18,7 @@
"nanoid": "^5.1.5"
},
"devDependencies": {
"@hookform/resolvers": "^5.2.1",
"@hookform/resolvers": "^5.2.2",
"@kit/accounts": "workspace:*",
"@kit/billing-gateway": "workspace:*",
"@kit/email-templates": "workspace:*",
@@ -32,15 +32,15 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.57.2",
"@tanstack/react-query": "5.87.1",
"@supabase/supabase-js": "2.57.4",
"@tanstack/react-query": "5.89.0",
"@tanstack/react-table": "^8.21.3",
"@types/react": "19.1.12",
"@types/react": "19.1.13",
"@types/react-dom": "19.1.9",
"class-variance-authority": "^0.7.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"lucide-react": "^0.544.0",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-hook-form": "^7.62.0",