* 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...)
6.4 KiB
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 managementadmin/- Super admin functionalityauth/- Authentication featuresnotifications/- Notification systemteam-accounts/- Team account management
Account Services
Personal Accounts API
Located at: packages/features/accounts/src/server/api.ts
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
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:
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:
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
// 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
// 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
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:
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:
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
// Admin service operations
// - Manage all accounts
// - Handle admin-level operations
// - Access system-wide data
Checking Admin Status
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:
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
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
// 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');
}