Update Calendar component and update dependencies and updated Agents MD files (#269)

* Update Calendar component and update dependencies

* Refactor AGENTS.md and CLAUDE.md for clarity and consistency

- Streamlined project overview and architecture sections in both AGENTS.md and CLAUDE.md to enhance readability.
- Updated command sections to reflect essential commands and improved formatting for better usability.
- Clarified multi-tenant architecture details and database operations, ensuring accurate representation of the project's structure.
- Enhanced component organization and security guidelines for better developer onboarding and adherence to best practices.

* Refactor code for consistency and readability

- Improved formatting in `env-variables-model.ts` for better description clarity.
- Enhanced readability of `current-plan-alert.tsx` and `current-plan-badge.tsx` by adjusting the status badge mapping.
- Standardized object syntax in `lemon-squeezy-webhook-handler.service.ts` for consistency.
- Refined className ordering in `account-selector.tsx` for better maintainability.
- Ensured proper syntax in `console.ts` with consistent object export.
- Reorganized imports in `calendar.tsx` for clarity and structure.
- Improved className formatting in `sidebar.tsx` for better readability.

* Update package version to 2.10.0 in package.json
This commit is contained in:
Giancarlo Buomprisco
2025-06-08 13:31:49 +07:00
committed by GitHub
parent 67663b32d9
commit 1c31e7ffab
35 changed files with 1369 additions and 430 deletions

575
AGENTS.md
View File

@@ -4,17 +4,15 @@ This Agents.md file provides comprehensive guidance for OpenAI Codex and other A
## Project Overview
Makerkit - Supabase SaaS Starter Kit (Turbo Edition) is a multi-tenant SaaS application built with Next.js, Supabase, and Tailwind CSS. The project uses a Turborepo monorepo structure with distinct apps for the main web application, development tools, and e2e testing.
## Architecture
Makerkit is a multi-tenant SaaS application using a Turborepo monorepo structure with distinct apps for the main web application, development tools, and e2e testing.
### Monorepo Structure
- `/apps/web` - Main Next.js SaaS application
- `/apps/dev-tool` - Development utilities (runs on port 3010)
- `/apps/dev-tool` - Development utilities (port 3010)
- `/apps/e2e` - Playwright end-to-end tests
- `/packages/` - Shared packages and utilities
- `/tooling/` - Build tools, linting, and development scripts
- `/tooling/` - Build tools and development scripts
### Core Technologies
@@ -27,89 +25,101 @@ Makerkit - Supabase SaaS Starter Kit (Turbo Edition) is a multi-tenant SaaS appl
### Multi-Tenant Architecture
The application uses a dual account model:
Uses a dual account model:
- **Personal Accounts**: Individual user accounts (auth.users.id = accounts.id)
- **Personal Accounts**: Individual user accounts (`auth.users.id = accounts.id`)
- **Team Accounts**: Shared workspaces with members, roles, and permissions
- Data is associated with accounts via foreign keys for proper access control
- Data associates with accounts via foreign keys for proper access control
## Common Commands
## Essential Commands
### Development
```bash
# Start all apps in development
pnpm dev
# Start specific app
pnpm --filter web dev # Main app (port 3000)
pnpm --filter dev-tool dev # Dev tools (port 3010)
# Build all apps
pnpm build
# Run tests
pnpm test # All tests
pnpm --filter e2e test # E2E tests only
```
### Code Quality
```bash
# Lint all code
pnpm lint
pnpm lint:fix # Auto-fix issues
# Format code
pnpm format
pnpm format:fix # Auto-fix formatting
# Type checking
pnpm typecheck
pnpm dev # Start all apps
pnpm --filter web dev # Main app (port 3000)
pnpm --filter dev-tool dev # Dev tools (port 3010)
pnpm build # Build all apps
```
### Database Operations
```bash
# Start Supabase locally
pnpm supabase:web:start
# Reset database with latest schema
pnpm supabase:web:reset
# Generate TypeScript types from database
pnpm supabase:web:typegen
# Run database tests
pnpm supabase:web:test
# Create migration from schema changes
pnpm --filter web supabase:db:diff
# Stop Supabase
pnpm supabase:web:stop
pnpm supabase:web:start # Start Supabase locally
pnpm supabase:web:reset # Reset with latest schema
pnpm supabase:web:typegen # Generate TypeScript types
pnpm --filter web supabase:db:diff # Create migration
```
### Code Quality
```bash
pnpm lint && pnpm format # Lint and format
pnpm typecheck # Type checking
pnpm test # Run tests
```
## Application Structure
### Route Organization
```
app/
├── (marketing)/ # Public pages (landing, blog, docs)
├── (auth)/ # Authentication pages
├── home/
│ ├── (user)/ # Personal account context
│ └── [account]/ # Team account context ([account] = team slug)
├── admin/ # Super admin section
└── api/ # API routes
```
See complete structure in @apps/web/app/ with examples like:
- Marketing layout: @apps/web/app/(marketing)/layout.tsx
- Personal dashboard: @apps/web/app/home/(user)/page.tsx
- Team workspace: @apps/web/app/home/[account]/page.tsx
- Admin section: @apps/web/app/admin/page.tsx
### Component Organization
- **Route-specific**: Use `_components/` directories
- **Route utilities**: Use `_lib/` for client, `_lib/server/` for server-side
- **Global components**: Root-level directories
Example organization:
- Team components: @apps/web/app/home/[account]/\_components/
- Team server utils: @apps/web/app/home/[account]/\_lib/server/
- Marketing components: @apps/web/app/(marketing)/\_components/
## Database Guidelines
### Schema Management
- Database schemas are in `apps/web/supabase/schemas/`
- Create new schemas as `<number>-<name>.sql`
- After schema changes: run `pnpm --filter web supabase:db:diff` then `pnpm supabase:web:reset`
### Security & RLS
- **Always enable RLS** on new tables unless explicitly instructed otherwise
- Use helper functions for access control:
- `public.has_role_on_account(account_id, role?)` - Check team membership
- `public.has_permission(user_id, account_id, permission)` - Check specific permissions
- `public.is_account_owner(account_id)` - Verify account ownership
- Associate data with accounts using foreign keys for proper access control
- `public.has_permission(user_id, account_id, permission)` - Check permissions
- `public.is_account_owner(account_id)` - Verify ownership
See RLS examples in database schemas: @apps/web/supabase/schemas/
### Schema Management
- Schemas in `apps/web/supabase/schemas/`
- Create as `<number>-<name>.sql`
- After changes: `pnpm --filter web supabase:db:diff` then `pnpm supabase:web:reset`
Key schema files:
- Accounts: `apps/web/supabase/schemas/03-accounts.sql`
- Memberships: `apps/web/supabase/schemas/05-memberships.sql`
- Permissions: `apps/web/supabase/schemas/06-roles-permissions.sql`
### Type Generation
Import auto-generated types from `@kit/supabase/database`:
Import auto-generated types from @packages/supabase/src/types/database.ts:
```typescript
import { Tables } from '@kit/supabase/database';
@@ -121,50 +131,427 @@ type Account = Tables<'accounts'>;
### Data Fetching
- **Server Components**: Use `getSupabaseServerClient()` directly
- **Server Components**: Use `getSupabaseServerClient()` from @packages/supabase/src/clients/server-client.ts
- **Client Components**: Use `useSupabase()` hook + React Query's `useQuery`
- Prefer Server Components and pass data down to Client Components when needed
- **Admin Operations**: Use `getSupabaseServerAdminClient()` from @packages/supabase/src/clients/server-admin-client.ts (rare cases only - bypasses RLS, use with caution!)
- Prefer Server Components and pass data down when needed
Use the Container/Presenter pattern for complex data components:
```typescript
// Container: handles data fetching
function UserProfileContainer() {
const userData = useUserData();
return <UserProfilePresenter data={userData.data} />;
}
// Presenter: handles UI rendering
function UserProfilePresenter({ data }: { data: UserData }) {
return <div>{data.name}</div>;
}
```
Example server-side data loading:
- User workspace loader: @apps/web/app/home/(user)/\_lib/server/load-user-workspace.ts
- Team workspace loader: @apps/web/app/home/[account]/\_lib/server/team-account-workspace.loader.ts
- Data provider pattern: @packages/features/team-accounts/src/components/members/roles-data-provider.tsx
### Server Actions
- Always use `enhanceAction` from `@kit/next/actions`
- Name files as `server-actions.ts` and functions with `Action` suffix
- Place Zod schemas in separate files for reuse with forms
- Use `'use server'` directive at top of file
Always use `enhanceAction` from @packages/next/src/actions/index.ts:
### Error Handling & Logging
```typescript
'use server';
- Use `@kit/shared/logger` for logging
- Don't swallow errors - handle them appropriately
- Provide context without sensitive data
import { enhanceAction } from '@kit/next/actions';
### Component Organization
export const createNoteAction = enhanceAction(
async function (data, user) {
// data is validated, user is authenticated
return { success: true };
},
{
auth: true,
schema: CreateNoteSchema,
},
);
```
- Route-specific components in `_components/` directories
- Route-specific utilities in `_lib/` directories
- Server-side utilities in `_lib/server/`
- Global components and utilities in root-level directories
Example server actions:
- Team billing: @apps/web/app/home/[account]/billing/\_lib/server/server-actions.ts
- Personal settings: @apps/web/app/home/(user)/settings/\_lib/server/server-actions.ts
### Forms with React Hook Form & Zod
```typescript
// 1. Define schema in separate file
export const CreateNoteSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
// 2. Client component with form
('use client');
const form = useForm({
resolver: zodResolver(CreateNoteSchema),
});
const onSubmit = (data) => {
startTransition(async () => {
await toast.promise(createNoteAction(data), {
loading: 'Creating...',
success: 'Created!',
error: 'Failed!',
});
});
};
```
See form examples:
- Contact form: @apps/web/app/(marketing)/contact/\_components/contact-form.tsx
- Verify OTP form: @packages/otp/src/components/verify-otp-form.tsx
### Route Handlers (API Routes)
Use `enhanceRouteHandler` from @packages/next/src/routes/index.ts:
```typescript
import { enhanceRouteHandler } from '@kit/next/routes';
export const POST = enhanceRouteHandler(
async function ({ body, user, request }) {
// body is validated, user available if auth: true
return NextResponse.json({ success: true });
},
{
auth: true,
schema: ZodSchema,
},
);
```
Example API routes:
- Billing webhook: @apps/web/app/api/billing/webhook/route.ts
- Database webhook: @apps/web/app/api/db/webhook/route.ts
## React & TypeScript Best Practices
### Components
- Use functional components with TypeScript
- Always use `'use client'` directive for client components
- Destructure props with proper TypeScript interfaces
### Conditional Rendering
Use the `If` component from @packages/ui/src/makerkit/if.tsx:
```tsx
import { If } from '@kit/ui/if';
import { Spinner } '@kit/ui/spinner';
<If condition={isLoading} fallback={<Content />}>
<Spinner />
</If>
// With type inference
<If condition={error}>
{(err) => <ErrorMessage error={err} />}
</If>
```
### Testing Attributes
Add data attributes for testing:
```tsx
<button data-test="submit-button">Submit</button>
<div data-test="user-profile" data-user-id={user.id}>Profile</div>
<form data-test="signup-form">Form content</form>
```
### Internationalization
Always use `Trans` component from @packages/ui/src/makerkit/trans.tsx:
```tsx
import { Trans } from '@kit/ui/trans';
// Basic usage
<Trans
i18nKey="user:welcomeMessage"
values={{ name: user.name }}
defaults="Welcome, {name}!"
/>
// With HTML elements
<Trans
i18nKey="terms:agreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
}}
defaults="I agree to the <TermsLink>Terms</TermsLink>."
/>
// Pluralization
<Trans
i18nKey="notifications:count"
count={notifications.length}
defaults="{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
/>
```
Use `LanguageSelector` component from @packages/ui/src/makerkit/language-selector.tsx:
```tsx
import { LanguageSelector } from '@kit/ui/language-selector';
<LanguageSelector />;
```
Adding new languages:
1. Add language code to @apps/web/lib/i18n/i18n.settings.ts
2. Create translation files in @apps/web/public/locales/[new-language]/
3. Copy structure from English files as template
Adding new namespaces:
1. Add namespace to `defaultI18nNamespaces` in @apps/web/lib/i18n/i18n.settings.ts
2. Create corresponding translation files for all supported languages
Translation files located in @apps/web/public/locales/<locale>/<namespace>.json:
- Common translations: @apps/web/public/locales/en/common.json
- Auth translations: @apps/web/public/locales/en/auth.json
- Team translations: @apps/web/public/locales/en/teams.json
## Security Guidelines
### Authentication & Authorization
- Authentication enforced by middleware
- Authorization typically handled by RLS at database level, unless using the admin client
- For rare admin client usage, enforce both manually
- User authentication helper: @apps/web/lib/server/require-user-in-server-component.ts if required or to obtain the authed user
### Data Passing
- **Never pass sensitive data** to Client Components
- **Never expose server environment variables** to client (unless `NEXT_PUBLIC_`)
- Always validate user input before processing
### OTP for Sensitive Operations
Use one-time tokens from @packages/otp/src/api/index.ts for destructive operations:
```tsx
import { VerifyOtpForm } from '@kit/otp/components';
<VerifyOtpForm
purpose="account-deletion"
email={user.email}
onSuccess={(otp) => {
// Proceed with verified operation
}}
/>;
```
OTP schema and functions: @apps/web/supabase/schemas/12-one-time-tokens.sql
### Super Admin Protection
For admin routes, use `AdminGuard` from @packages/features/admin/src/components/admin-guard.tsx:
```tsx
import { AdminGuard } from '@kit/admin/components/admin-guard';
export default AdminGuard(AdminPageComponent);
```
For admin server actions, use `adminAction` wrapper:
```tsx
import { adminAction } from '@kit/admin';
export const yourAdminAction = adminAction(
enhanceAction(
async (data) => {
// Action implementation
},
{ schema: YourActionSchema },
),
);
```
Admin service security pattern:
```typescript
private async assertUserIsNotCurrentSuperAdmin(targetId: string) {
const { data } = await this.client.auth.getUser();
const currentUserId = data.user?.id;
if (currentUserId === targetId) {
throw new Error('Cannot perform destructive action on your own account');
}
}
```
## UI Components
### Core UI Library
Import from @packages/ui/src/:
```tsx
// Shadcn components
import { Button } from '@kit/ui/button';
// @packages/ui/src/shadcn/button.tsx
import { Card } from '@kit/ui/card';
// @packages/ui/src/shadcn/sonner.tsx
// Makerkit components
import { If } from '@kit/ui/if';
// @packages/ui/src/makerkit/trans.tsx
import { ProfileAvatar } from '@kit/ui/profile-avatar';
// @packages/ui/src/shadcn/card.tsx
import { toast } from '@kit/ui/sonner';
// @packages/ui/src/makerkit/if.tsx
import { Trans } from '@kit/ui/trans';
// @packages/ui/src/makerkit/profile-avatar.tsx
```
### Key Component Categories
- **Forms**: Form components in @packages/ui/src/shadcn/form.tsx
- **Navigation**: Navigation menu in @packages/ui/src/shadcn/navigation-menu.tsx
- **Data Display**: Data table in @packages/ui/src/makerkit/data-table.tsx
- **Marketing**: Marketing components in @packages/ui/src/makerkit/marketing/
### Styling
- Use Tailwind CSS with semantic classes
- Prefer `bg-background`, `text-muted-foreground` over fixed colors
- Use `cn()` utility from @packages/ui/src/lib/utils.ts for class merging
## Workspace Contexts
### Personal Account Context (`/home/(user)`)
Use hook from `packages/features/accounts/src/hooks/use-user-workspace.ts`:
```tsx
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
function PersonalComponent() {
const { user, account } = useUserWorkspace();
// Personal account data
}
```
Context provider: `packages/features/accounts/src/components/user-workspace-context-provider.tsx`
### Team Account Context (`/home/[account]`)
Use hook from `packages/features/team-accounts/src/hooks/use-team-account-workspace.ts`:
```tsx
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// Team account data with permissions
}
```
Context provider: `packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx`
## Error Handling & Logging
### Structured Logging
Use logger from `packages/shared/src/logger/logger.ts`:
```typescript
import { getLogger } from '@kit/shared/logger';
const logger = await getLogger();
const ctx = { name: 'myOperation', userId: user.id };
logger.info(ctx, 'Operation started');
// ... operation
logger.error({ ...ctx, error }, 'Operation failed');
```
### Error Boundaries
Use proper error handling with meaningful user messages:
```tsx
try {
await operation();
} catch (error) {
logger.error({ error, context }, 'Operation failed');
return { error: 'Unable to complete operation' }; // Generic message
}
```
## Feature Development Workflow
### Creating New Pages
1. Create page component in appropriate route group
2. Add `withI18n()` HOC from `apps/web/lib/i18n/with-i18n.tsx`
3. Implement `generateMetadata()` for SEO
4. Add loading state with `loading.tsx`
5. Create components in `_components/` directory
6. Add server utilities in `_lib/server/`
Example page structure:
- Marketing page: `apps/web/app/(marketing)/pricing/page.tsx`
- Dashboard page: `apps/web/app/home/(user)/page.tsx`
- Team page: `apps/web/app/home/[account]/members/page.tsx`
### Permission Patterns
- Check permissions before data operations using helper functions
- Guard premium features with subscription checks (`public.has_active_subscription`)
- Use role hierarchy to control member management actions
- Primary account owners have special privileges that cannot be revoked
- Check permissions before data operations
- Guard premium features with `public.has_active_subscription`
- Use role hierarchy for member management
- Primary account owners have special privileges
## Testing
Permission helpers in database: `apps/web/supabase/schemas/06-roles-permissions.sql`
### E2E Tests
### Database Development
```bash
pnpm --filter e2e test # Run all E2E tests
```
1. Create schema file: `apps/web/supabase/schemas/<number>-<name>.sql`
2. Enable RLS and create policies
3. Generate migration: `pnpm --filter web supabase:db:diff`
4. Reset database: `pnpm supabase:web:reset`
5. Generate types: `pnpm supabase:web:typegen`
Test files are in `apps/e2e/tests/` organized by feature area.
## API Services
## Important Notes
### Account Services
- Uses pnpm as package manager
- Database types are auto-generated - don't write manually if shape matches DB
- Always use explicit schema references in SQL (`public.table_name`)
- Documentation available at: https://makerkit.dev/docs/next-supabase-turbo/introduction
- Personal accounts API: `packages/features/accounts/src/server/api.ts`
- Team accounts API: `packages/features/team-accounts/src/server/api.ts`
- Admin service: `packages/features/admin/src/lib/server/services/admin.service.ts`
### Billing Services
- Personal billing: `apps/web/app/home/(user)/billing/_lib/server/user-billing.service.ts`
- Team billing: `apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts`
- Per-seat billing: `packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts`
## Key Configuration Files
- **Feature flags**: @apps/web/config/feature-flags.config.ts
- **i18n settings**: @apps/web/lib/i18n/i18n.settings.ts
- **Supabase local config**: @apps/web/supabase/config.toml@
- **Middleware**: @apps/web/middleware.ts`

575
CLAUDE.md
View File

@@ -4,17 +4,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
Makerkit - Supabase SaaS Starter Kit (Turbo Edition) is a multi-tenant SaaS application built with Next.js, Supabase, and Tailwind CSS. The project uses a Turborepo monorepo structure with distinct apps for the main web application, development tools, and e2e testing.
## Architecture
Makerkit is a multi-tenant SaaS application using a Turborepo monorepo structure with distinct apps for the main web application, development tools, and e2e testing.
### Monorepo Structure
- `/apps/web` - Main Next.js SaaS application
- `/apps/dev-tool` - Development utilities (runs on port 3010)
- `/apps/dev-tool` - Development utilities (port 3010)
- `/apps/e2e` - Playwright end-to-end tests
- `/packages/` - Shared packages and utilities
- `/tooling/` - Build tools, linting, and development scripts
- `/tooling/` - Build tools and development scripts
### Core Technologies
@@ -27,89 +25,101 @@ Makerkit - Supabase SaaS Starter Kit (Turbo Edition) is a multi-tenant SaaS appl
### Multi-Tenant Architecture
The application uses a dual account model:
Uses a dual account model:
- **Personal Accounts**: Individual user accounts (auth.users.id = accounts.id)
- **Personal Accounts**: Individual user accounts (`auth.users.id = accounts.id`)
- **Team Accounts**: Shared workspaces with members, roles, and permissions
- Data is associated with accounts via foreign keys for proper access control
- Data associates with accounts via foreign keys for proper access control
## Common Commands
## Essential Commands
### Development
```bash
# Start all apps in development
pnpm dev
# Start specific app
pnpm --filter web dev # Main app (port 3000)
pnpm --filter dev-tool dev # Dev tools (port 3010)
# Build all apps
pnpm build
# Run tests
pnpm test # All tests
pnpm --filter e2e test # E2E tests only
```
### Code Quality
```bash
# Lint all code
pnpm lint
pnpm lint:fix # Auto-fix issues
# Format code
pnpm format
pnpm format:fix # Auto-fix formatting
# Type checking
pnpm typecheck
pnpm dev # Start all apps
pnpm --filter web dev # Main app (port 3000)
pnpm --filter dev-tool dev # Dev tools (port 3010)
pnpm build # Build all apps
```
### Database Operations
```bash
# Start Supabase locally
pnpm supabase:web:start
# Reset database with latest schema
pnpm supabase:web:reset
# Generate TypeScript types from database
pnpm supabase:web:typegen
# Run database tests
pnpm supabase:web:test
# Create migration from schema changes
pnpm --filter web supabase:db:diff
# Stop Supabase
pnpm supabase:web:stop
pnpm supabase:web:start # Start Supabase locally
pnpm supabase:web:reset # Reset with latest schema
pnpm supabase:web:typegen # Generate TypeScript types
pnpm --filter web supabase:db:diff # Create migration
```
### Code Quality
```bash
pnpm lint && pnpm format # Lint and format
pnpm typecheck # Type checking
pnpm test # Run tests
```
## Application Structure
### Route Organization
```
app/
├── (marketing)/ # Public pages (landing, blog, docs)
├── (auth)/ # Authentication pages
├── home/
│ ├── (user)/ # Personal account context
│ └── [account]/ # Team account context ([account] = team slug)
├── admin/ # Super admin section
└── api/ # API routes
```
See complete structure in @apps/web/app/ with examples like:
- Marketing layout: @apps/web/app/(marketing)/layout.tsx
- Personal dashboard: @apps/web/app/home/(user)/page.tsx
- Team workspace: @apps/web/app/home/[account]/page.tsx
- Admin section: @apps/web/app/admin/page.tsx
### Component Organization
- **Route-specific**: Use `_components/` directories
- **Route utilities**: Use `_lib/` for client, `_lib/server/` for server-side
- **Global components**: Root-level directories
Example organization:
- Team components: @apps/web/app/home/[account]/\_components/
- Team server utils: @apps/web/app/home/[account]/\_lib/server/
- Marketing components: @apps/web/app/(marketing)/\_components/
## Database Guidelines
### Schema Management
- Database schemas are in `apps/web/supabase/schemas/`
- Create new schemas as `<number>-<name>.sql`
- After schema changes: run `pnpm --filter web supabase:db:diff` then `pnpm supabase:web:reset`
### Security & RLS
- **Always enable RLS** on new tables unless explicitly instructed otherwise
- Use helper functions for access control:
- `public.has_role_on_account(account_id, role?)` - Check team membership
- `public.has_permission(user_id, account_id, permission)` - Check specific permissions
- `public.is_account_owner(account_id)` - Verify account ownership
- Associate data with accounts using foreign keys for proper access control
- `public.has_permission(user_id, account_id, permission)` - Check permissions
- `public.is_account_owner(account_id)` - Verify ownership
See RLS examples in database schemas: @apps/web/supabase/schemas/
### Schema Management
- Schemas in `apps/web/supabase/schemas/`
- Create as `<number>-<name>.sql`
- After changes: `pnpm --filter web supabase:db:diff` then `pnpm supabase:web:reset`
Key schema files:
- Accounts: `apps/web/supabase/schemas/03-accounts.sql`
- Memberships: `apps/web/supabase/schemas/05-memberships.sql`
- Permissions: `apps/web/supabase/schemas/06-roles-permissions.sql`
### Type Generation
Import auto-generated types from `@kit/supabase/database`:
Import auto-generated types from @packages/supabase/src/types/database.ts:
```typescript
import { Tables } from '@kit/supabase/database';
@@ -121,50 +131,427 @@ type Account = Tables<'accounts'>;
### Data Fetching
- **Server Components**: Use `getSupabaseServerClient()` directly
- **Server Components**: Use `getSupabaseServerClient()` from @packages/supabase/src/clients/server-client.ts
- **Client Components**: Use `useSupabase()` hook + React Query's `useQuery`
- Prefer Server Components and pass data down to Client Components when needed
- **Admin Operations**: Use `getSupabaseServerAdminClient()` from @packages/supabase/src/clients/server-admin-client.ts (rare cases only - bypasses RLS, use with caution!)
- Prefer Server Components and pass data down when needed
Use the Container/Presenter pattern for complex data components:
```typescript
// Container: handles data fetching
function UserProfileContainer() {
const userData = useUserData();
return <UserProfilePresenter data={userData.data} />;
}
// Presenter: handles UI rendering
function UserProfilePresenter({ data }: { data: UserData }) {
return <div>{data.name}</div>;
}
```
Example server-side data loading:
- User workspace loader: @apps/web/app/home/(user)/\_lib/server/load-user-workspace.ts
- Team workspace loader: @apps/web/app/home/[account]/\_lib/server/team-account-workspace.loader.ts
- Data provider pattern: @packages/features/team-accounts/src/components/members/roles-data-provider.tsx
### Server Actions
- Always use `enhanceAction` from `@kit/next/actions`
- Name files as `server-actions.ts` and functions with `Action` suffix
- Place Zod schemas in separate files for reuse with forms
- Use `'use server'` directive at top of file
Always use `enhanceAction` from @packages/next/src/actions/index.ts:
### Error Handling & Logging
```typescript
'use server';
- Use `@kit/shared/logger` for logging
- Don't swallow errors - handle them appropriately
- Provide context without sensitive data
import { enhanceAction } from '@kit/next/actions';
### Component Organization
export const createNoteAction = enhanceAction(
async function (data, user) {
// data is validated, user is authenticated
return { success: true };
},
{
auth: true,
schema: CreateNoteSchema,
},
);
```
- Route-specific components in `_components/` directories
- Route-specific utilities in `_lib/` directories
- Server-side utilities in `_lib/server/`
- Global components and utilities in root-level directories
Example server actions:
- Team billing: @apps/web/app/home/[account]/billing/\_lib/server/server-actions.ts
- Personal settings: @apps/web/app/home/(user)/settings/\_lib/server/server-actions.ts
### Forms with React Hook Form & Zod
```typescript
// 1. Define schema in separate file
export const CreateNoteSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
// 2. Client component with form
('use client');
const form = useForm({
resolver: zodResolver(CreateNoteSchema),
});
const onSubmit = (data) => {
startTransition(async () => {
await toast.promise(createNoteAction(data), {
loading: 'Creating...',
success: 'Created!',
error: 'Failed!',
});
});
};
```
See form examples:
- Contact form: @apps/web/app/(marketing)/contact/\_components/contact-form.tsx
- Verify OTP form: @packages/otp/src/components/verify-otp-form.tsx
### Route Handlers (API Routes)
Use `enhanceRouteHandler` from @packages/next/src/routes/index.ts:
```typescript
import { enhanceRouteHandler } from '@kit/next/routes';
export const POST = enhanceRouteHandler(
async function ({ body, user, request }) {
// body is validated, user available if auth: true
return NextResponse.json({ success: true });
},
{
auth: true,
schema: ZodSchema,
},
);
```
Example API routes:
- Billing webhook: @apps/web/app/api/billing/webhook/route.ts
- Database webhook: @apps/web/app/api/db/webhook/route.ts
## React & TypeScript Best Practices
### Components
- Use functional components with TypeScript
- Always use `'use client'` directive for client components
- Destructure props with proper TypeScript interfaces
### Conditional Rendering
Use the `If` component from @packages/ui/src/makerkit/if.tsx:
```tsx
import { If } from '@kit/ui/if';
import { Spinner } '@kit/ui/spinner';
<If condition={isLoading} fallback={<Content />}>
<Spinner />
</If>
// With type inference
<If condition={error}>
{(err) => <ErrorMessage error={err} />}
</If>
```
### Testing Attributes
Add data attributes for testing:
```tsx
<button data-test="submit-button">Submit</button>
<div data-test="user-profile" data-user-id={user.id}>Profile</div>
<form data-test="signup-form">Form content</form>
```
### Internationalization
Always use `Trans` component from @packages/ui/src/makerkit/trans.tsx:
```tsx
import { Trans } from '@kit/ui/trans';
// Basic usage
<Trans
i18nKey="user:welcomeMessage"
values={{ name: user.name }}
defaults="Welcome, {name}!"
/>
// With HTML elements
<Trans
i18nKey="terms:agreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
}}
defaults="I agree to the <TermsLink>Terms</TermsLink>."
/>
// Pluralization
<Trans
i18nKey="notifications:count"
count={notifications.length}
defaults="{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
/>
```
Use `LanguageSelector` component from @packages/ui/src/makerkit/language-selector.tsx:
```tsx
import { LanguageSelector } from '@kit/ui/language-selector';
<LanguageSelector />;
```
Adding new languages:
1. Add language code to @apps/web/lib/i18n/i18n.settings.ts
2. Create translation files in @apps/web/public/locales/[new-language]/
3. Copy structure from English files as template
Adding new namespaces:
1. Add namespace to `defaultI18nNamespaces` in @apps/web/lib/i18n/i18n.settings.ts
2. Create corresponding translation files for all supported languages
Translation files located in @apps/web/public/locales/<locale>/<namespace>.json:
- Common translations: @apps/web/public/locales/en/common.json
- Auth translations: @apps/web/public/locales/en/auth.json
- Team translations: @apps/web/public/locales/en/teams.json
## Security Guidelines
### Authentication & Authorization
- Authentication enforced by middleware
- Authorization typically handled by RLS at database level, unless using the admin client
- For rare admin client usage, enforce both manually
- User authentication helper: @apps/web/lib/server/require-user-in-server-component.ts if required or to obtain the authed user
### Data Passing
- **Never pass sensitive data** to Client Components
- **Never expose server environment variables** to client (unless `NEXT_PUBLIC_`)
- Always validate user input before processing
### OTP for Sensitive Operations
Use one-time tokens from @packages/otp/src/api/index.ts for destructive operations:
```tsx
import { VerifyOtpForm } from '@kit/otp/components';
<VerifyOtpForm
purpose="account-deletion"
email={user.email}
onSuccess={(otp) => {
// Proceed with verified operation
}}
/>;
```
OTP schema and functions: @apps/web/supabase/schemas/12-one-time-tokens.sql
### Super Admin Protection
For admin routes, use `AdminGuard` from @packages/features/admin/src/components/admin-guard.tsx:
```tsx
import { AdminGuard } from '@kit/admin/components/admin-guard';
export default AdminGuard(AdminPageComponent);
```
For admin server actions, use `adminAction` wrapper:
```tsx
import { adminAction } from '@kit/admin';
export const yourAdminAction = adminAction(
enhanceAction(
async (data) => {
// Action implementation
},
{ schema: YourActionSchema },
),
);
```
Admin service security pattern:
```typescript
private async assertUserIsNotCurrentSuperAdmin(targetId: string) {
const { data } = await this.client.auth.getUser();
const currentUserId = data.user?.id;
if (currentUserId === targetId) {
throw new Error('Cannot perform destructive action on your own account');
}
}
```
## UI Components
### Core UI Library
Import from @packages/ui/src/:
```tsx
// Shadcn components
import { Button } from '@kit/ui/button';
// @packages/ui/src/shadcn/button.tsx
import { Card } from '@kit/ui/card';
// @packages/ui/src/shadcn/sonner.tsx
// Makerkit components
import { If } from '@kit/ui/if';
// @packages/ui/src/makerkit/trans.tsx
import { ProfileAvatar } from '@kit/ui/profile-avatar';
// @packages/ui/src/shadcn/card.tsx
import { toast } from '@kit/ui/sonner';
// @packages/ui/src/makerkit/if.tsx
import { Trans } from '@kit/ui/trans';
// @packages/ui/src/makerkit/profile-avatar.tsx
```
### Key Component Categories
- **Forms**: Form components in @packages/ui/src/shadcn/form.tsx
- **Navigation**: Navigation menu in @packages/ui/src/shadcn/navigation-menu.tsx
- **Data Display**: Data table in @packages/ui/src/makerkit/data-table.tsx
- **Marketing**: Marketing components in @packages/ui/src/makerkit/marketing/
### Styling
- Use Tailwind CSS with semantic classes
- Prefer `bg-background`, `text-muted-foreground` over fixed colors
- Use `cn()` utility from @packages/ui/src/lib/utils.ts for class merging
## Workspace Contexts
### Personal Account Context (`/home/(user)`)
Use hook from `packages/features/accounts/src/hooks/use-user-workspace.ts`:
```tsx
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
function PersonalComponent() {
const { user, account } = useUserWorkspace();
// Personal account data
}
```
Context provider: `packages/features/accounts/src/components/user-workspace-context-provider.tsx`
### Team Account Context (`/home/[account]`)
Use hook from `packages/features/team-accounts/src/hooks/use-team-account-workspace.ts`:
```tsx
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// Team account data with permissions
}
```
Context provider: `packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx`
## Error Handling & Logging
### Structured Logging
Use logger from `packages/shared/src/logger/logger.ts`:
```typescript
import { getLogger } from '@kit/shared/logger';
const logger = await getLogger();
const ctx = { name: 'myOperation', userId: user.id };
logger.info(ctx, 'Operation started');
// ... operation
logger.error({ ...ctx, error }, 'Operation failed');
```
### Error Boundaries
Use proper error handling with meaningful user messages:
```tsx
try {
await operation();
} catch (error) {
logger.error({ error, context }, 'Operation failed');
return { error: 'Unable to complete operation' }; // Generic message
}
```
## Feature Development Workflow
### Creating New Pages
1. Create page component in appropriate route group
2. Add `withI18n()` HOC from `apps/web/lib/i18n/with-i18n.tsx`
3. Implement `generateMetadata()` for SEO
4. Add loading state with `loading.tsx`
5. Create components in `_components/` directory
6. Add server utilities in `_lib/server/`
Example page structure:
- Marketing page: `apps/web/app/(marketing)/pricing/page.tsx`
- Dashboard page: `apps/web/app/home/(user)/page.tsx`
- Team page: `apps/web/app/home/[account]/members/page.tsx`
### Permission Patterns
- Check permissions before data operations using helper functions
- Guard premium features with subscription checks (`public.has_active_subscription`)
- Use role hierarchy to control member management actions
- Primary account owners have special privileges that cannot be revoked
- Check permissions before data operations
- Guard premium features with `public.has_active_subscription`
- Use role hierarchy for member management
- Primary account owners have special privileges
## Testing
Permission helpers in database: `apps/web/supabase/schemas/06-roles-permissions.sql`
### E2E Tests
### Database Development
```bash
pnpm --filter e2e test # Run all E2E tests
```
1. Create schema file: `apps/web/supabase/schemas/<number>-<name>.sql`
2. Enable RLS and create policies
3. Generate migration: `pnpm --filter web supabase:db:diff`
4. Reset database: `pnpm supabase:web:reset`
5. Generate types: `pnpm supabase:web:typegen`
Test files are in `apps/e2e/tests/` organized by feature area.
## API Services
## Important Notes
### Account Services
- Uses pnpm as package manager
- Database types are auto-generated - don't write manually if shape matches DB
- Always use explicit schema references in SQL (`public.table_name`)
- Documentation available at: https://makerkit.dev/docs/next-supabase-turbo/introduction
- Personal accounts API: `packages/features/accounts/src/server/api.ts`
- Team accounts API: `packages/features/team-accounts/src/server/api.ts`
- Admin service: `packages/features/admin/src/lib/server/services/admin.service.ts`
### Billing Services
- Personal billing: `apps/web/app/home/(user)/billing/_lib/server/user-billing.service.ts`
- Team billing: `apps/web/app/home/[account]/billing/_lib/server/team-billing.service.ts`
- Per-seat billing: `packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts`
## Key Configuration Files
- **Feature flags**: @apps/web/config/feature-flags.config.ts
- **i18n settings**: @apps/web/lib/i18n/i18n.settings.ts
- **Supabase local config**: @apps/web/supabase/config.toml@
- **Middleware**: @apps/web/middleware.ts`

View File

@@ -883,7 +883,8 @@ export const envVariables: EnvVariableModel[] = [
},
{
name: 'EMAIL_TLS',
description: 'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.',
description:
'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.',
category: 'Email',
type: 'boolean',
contextualValidation: {

View File

@@ -9,7 +9,7 @@
},
"dependencies": {
"@ai-sdk/openai": "^1.3.22",
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@tanstack/react-query": "5.80.6",
"ai": "4.3.16",
"lucide-react": "^0.513.0",
@@ -36,7 +36,7 @@
"tailwindcss": "4.1.8",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.8.3",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"prettier": "@kit/prettier-config",
"browserslist": [

View File

@@ -32,7 +32,7 @@
},
"dependencies": {
"@edge-csrf/nextjs": "2.5.3-cloudflare-rc1",
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/accounts": "workspace:*",
"@kit/admin": "workspace:*",
"@kit/analytics": "workspace:*",
@@ -56,7 +56,7 @@
"@marsidev/react-turnstile": "^1.1.0",
"@nosecone/next": "1.0.0-beta.8",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@tanstack/react-table": "^8.21.3",
"date-fns": "^4.1.0",
@@ -71,7 +71,7 @@
"recharts": "2.15.3",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",

View File

@@ -1,6 +1,6 @@
{
"name": "next-supabase-saas-kit-turbo",
"version": "2.9.0",
"version": "2.10.0",
"private": true,
"sideEffects": false,
"engines": {

View File

@@ -21,7 +21,7 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -16,7 +16,7 @@
"./marketing": "./src/components/marketing.tsx"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/billing": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/lemon-squeezy": "workspace:*",
@@ -26,7 +26,7 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@types/react": "19.1.6",
"date-fns": "^4.1.0",
"lucide-react": "^0.513.0",
@@ -34,7 +34,7 @@
"react": "19.1.0",
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -2,7 +2,10 @@ import { Enums } from '@kit/supabase/database';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Trans } from '@kit/ui/trans';
const statusBadgeMap: Record<Enums<'subscription_status'>, `success` | `destructive` | `warning`> = {
const statusBadgeMap: Record<
Enums<'subscription_status'>,
`success` | `destructive` | `warning`
> = {
active: 'success',
trialing: 'success',
past_due: 'destructive',
@@ -35,4 +38,4 @@ export function CurrentPlanAlert(
</AlertDescription>
</Alert>
);
}
}

View File

@@ -16,7 +16,7 @@ const statusBadgeMap: Record<Status, `success` | `destructive` | `warning`> = {
pending: 'warning',
incomplete_expired: 'destructive',
paused: 'warning',
}
};
export function CurrentPlanBadge(
props: React.PropsWithoutRef<{

View File

@@ -27,7 +27,7 @@
"@types/react": "19.1.6",
"next": "15.3.3",
"react": "19.1.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -454,7 +454,7 @@ export class LemonSqueezyWebhookHandlerService
pending: 'pending',
failed: 'failed',
refunded: 'failed',
} satisfies Record<OrderStatus, UpsertOrderParams['status']>
} satisfies Record<OrderStatus, UpsertOrderParams['status']>;
return statusMap[status] ?? 'pending';
}

View File

@@ -31,7 +31,7 @@
"date-fns": "^4.1.0",
"next": "15.3.3",
"react": "19.1.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -29,7 +29,7 @@
"@types/node": "^22.15.30",
"@types/react": "19.1.6",
"react": "19.1.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -22,8 +22,8 @@
"@kit/supabase": "workspace:*",
"@kit/team-accounts": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "2.49.10",
"zod": "^3.25.53"
"@supabase/supabase-js": "2.50.0",
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -20,7 +20,7 @@
"nanoid": "^5.1.5"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/billing-gateway": "workspace:*",
"@kit/email-templates": "workspace:*",
"@kit/eslint-config": "workspace:*",
@@ -34,7 +34,7 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@types/react": "19.1.6",
"@types/react-dom": "19.1.6",
@@ -46,7 +46,7 @@
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"sonner": "^2.0.5",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"prettier": "@kit/prettier-config",
"typesVersions": {

View File

@@ -100,7 +100,7 @@ export function AccountSelector({
role="combobox"
aria-expanded={open}
className={cn(
'dark:shadow-primary/10 group w-full min-w-0 px-2 lg:w-auto lg:max-w-fit mr-1',
'dark:shadow-primary/10 group mr-1 w-full min-w-0 px-2 lg:w-auto lg:max-w-fit',
{
'justify-start': !collapsed,
'm-auto justify-center px-2 lg:w-full': collapsed,

View File

@@ -10,7 +10,7 @@
},
"prettier": "@kit/prettier-config",
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/eslint-config": "workspace:*",
"@kit/next": "workspace:*",
"@kit/prettier-config": "workspace:*",
@@ -20,7 +20,7 @@
"@kit/ui": "workspace:*",
"@makerkit/data-loader-supabase-core": "^0.0.10",
"@makerkit/data-loader-supabase-nextjs": "^1.2.5",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@tanstack/react-table": "^8.21.3",
"@types/react": "19.1.6",
@@ -29,7 +29,7 @@
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.57.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"exports": {
".": "./src/index.ts",

View File

@@ -19,7 +19,7 @@
"./resend-email-link": "./src/components/resend-auth-link-form.tsx"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
@@ -28,7 +28,7 @@
"@kit/ui": "workspace:*",
"@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@types/react": "19.1.6",
"lucide-react": "^0.513.0",
@@ -36,7 +36,7 @@
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"sonner": "^2.0.5",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"prettier": "@kit/prettier-config",
"typesVersions": {

View File

@@ -19,7 +19,7 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@types/react": "19.1.6",
"lucide-react": "^0.513.0",

View File

@@ -18,7 +18,7 @@
"nanoid": "^5.1.5"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/accounts": "workspace:*",
"@kit/billing-gateway": "workspace:*",
"@kit/email-templates": "workspace:*",
@@ -32,7 +32,7 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@tanstack/react-table": "^8.21.3",
"@types/react": "19.1.6",
@@ -46,7 +46,7 @@
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"sonner": "^2.0.5",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"prettier": "@kit/prettier-config",
"typesVersions": {

View File

@@ -21,7 +21,7 @@
"@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.15.30",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -21,7 +21,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/nodemailer": "6.4.17",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -18,7 +18,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.15.30",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -16,7 +16,7 @@
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -26,7 +26,7 @@
"@kit/tsconfig": "workspace:*",
"@types/react": "19.1.6",
"react": "19.1.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -26,7 +26,7 @@
"@kit/tsconfig": "workspace:*",
"@types/react": "19.1.6",
"react": "19.1.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -20,9 +20,9 @@
"@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"next": "15.3.3",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -14,7 +14,7 @@
"./components": "./src/components/index.ts"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@kit/email-templates": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/mailers": "workspace:*",
@@ -25,13 +25,13 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@types/react": "19.1.6",
"@types/react-dom": "19.1.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.57.0",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -4,6 +4,6 @@ const Logger = {
warn: console.warn,
debug: console.debug,
fatal: console.error,
}
};
export { Logger };
export { Logger };

View File

@@ -25,13 +25,13 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "2.49.10",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@types/react": "19.1.6",
"next": "15.3.3",
"react": "19.1.0",
"server-only": "^0.0.1",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"typesVersions": {
"*": {

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@hookform/resolvers": "^5.1.0",
"@radix-ui/react-accordion": "1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.10",
@@ -53,16 +53,21 @@
"next": "15.3.3",
"next-themes": "0.4.6",
"prettier": "^3.5.3",
"react-day-picker": "^8.10.1",
"react-day-picker": "^9.7.0",
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"sonner": "^2.0.5",
"tailwindcss": "4.1.8",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.8.3",
"zod": "^3.25.53"
"zod": "^3.25.56"
},
"prettier": "@kit/prettier-config",
"imports": {
"#utils": [
"./src/lib/utils/index.ts"
]
},
"exports": {
"./accordion": "./src/shadcn/accordion.tsx",
"./alert-dialog": "./src/shadcn/alert-dialog.tsx",

View File

@@ -2,69 +2,210 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from 'lucide-react';
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
import { cn } from '../lib/utils';
import { buttonVariants } from './button';
export type { DateRange } from 'react-day-picker';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
import { Button, buttonVariants } from './button';
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = 'label',
buttonVariant = 'ghost',
formatters,
components,
...props
}: CalendarProps) {
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
}) {
const defaultClassNames = getDefaultClassNames();
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
className={cn(
'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString('default', { month: 'short' }),
...formatters,
}}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'gap-x-2 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
root: cn('w-fit', defaultClassNames.root),
months: cn(
'relative flex flex-col gap-4 md:flex-row',
defaultClassNames.months,
),
month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
nav: cn(
'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',
defaultClassNames.button_next,
),
month_caption: cn(
'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',
defaultClassNames.month_caption,
),
dropdowns: cn(
'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium',
defaultClassNames.dropdowns,
),
dropdown_root: cn(
'has-focus:border-ring border-input has-focus:ring-ring/50 relative rounded-md border shadow-xs has-focus:ring-[3px]',
defaultClassNames.dropdown_root,
),
dropdown: cn('absolute inset-0 opacity-0', defaultClassNames.dropdown),
caption_label: cn(
'font-medium select-none',
captionLayout === 'label'
? 'text-sm'
: '[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5',
defaultClassNames.caption_label,
),
table: 'w-full border-collapse',
weekdays: cn('flex', defaultClassNames.weekdays),
weekday: cn(
'text-muted-foreground flex-1 rounded-md text-[0.8rem] font-normal select-none',
defaultClassNames.weekday,
),
week: cn('mt-2 flex w-full', defaultClassNames.week),
week_number_header: cn(
'w-(--cell-size) select-none',
defaultClassNames.week_number_header,
),
week_number: cn(
'text-muted-foreground text-[0.8rem] select-none',
defaultClassNames.week_number,
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell:
'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100',
'group/day relative aspect-square h-full w-full p-0 text-center select-none [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md',
defaultClassNames.day,
),
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside: 'text-muted-foreground opacity-50',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle:
'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
range_start: cn(
'bg-accent rounded-l-md',
defaultClassNames.range_start,
),
range_middle: cn('rounded-none', defaultClassNames.range_middle),
range_end: cn('bg-accent rounded-r-md', defaultClassNames.range_end),
today: cn(
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
defaultClassNames.today,
),
outside: cn(
'text-muted-foreground aria-selected:text-muted-foreground',
defaultClassNames.outside,
),
disabled: cn(
'text-muted-foreground opacity-50',
defaultClassNames.disabled,
),
hidden: cn('invisible', defaultClassNames.hidden),
...classNames,
}}
components={{
IconLeft: ({ ...props }) => (
<ChevronLeft className="h-4 w-4" {...props} />
),
IconRight: ({ ...props }) => (
<ChevronRight className="h-4 w-4" {...props} />
),
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
);
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === 'left') {
return (
<ChevronLeftIcon className={cn('size-4', className)} {...props} />
);
}
if (orientation === 'right') {
return (
<ChevronRightIcon
className={cn('size-4', className)}
{...props}
/>
);
}
return (
<ChevronDownIcon className={cn('size-4', className)} {...props} />
);
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
},
...components,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };
function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]);
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
defaultClassNames.day,
className,
)}
{...props}
/>
);
}
export { Calendar, CalendarDayButton };

View File

@@ -392,7 +392,10 @@ const SidebarHeader: React.FC<React.ComponentPropsWithRef<'div'>> = ({
return (
<div
data-sidebar="header"
className={cn('flex flex-col gap-2 p-2 group-data-[state=collapsed]:group-data-[collapsible=offcanvas]:hidden', className)}
className={cn(
'flex flex-col gap-2 p-2 group-data-[state=collapsed]:group-data-[collapsible=offcanvas]:hidden',
className,
)}
{...props}
/>
);

306
pnpm-lock.yaml generated
View File

@@ -35,16 +35,16 @@ importers:
dependencies:
'@ai-sdk/openai':
specifier: ^1.3.22
version: 1.3.22(zod@3.25.53)
version: 1.3.22(zod@3.25.56)
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
ai:
specifier: 4.3.16
version: 4.3.16(react@19.1.0)(zod@3.25.53)
version: 4.3.16(react@19.1.0)(zod@3.25.56)
lucide-react:
specifier: ^0.513.0
version: 0.513.0(react@19.1.0)
@@ -113,8 +113,8 @@ importers:
specifier: ^5.8.3
version: 5.8.3
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
apps/e2e:
devDependencies:
@@ -140,8 +140,8 @@ importers:
specifier: 2.5.3-cloudflare-rc1
version: 2.5.3-cloudflare-rc1(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/accounts':
specifier: workspace:*
version: link:../../packages/features/accounts
@@ -198,10 +198,10 @@ importers:
version: link:../../packages/ui
'@makerkit/data-loader-supabase-core':
specifier: ^0.0.10
version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)
version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)
'@makerkit/data-loader-supabase-nextjs':
specifier: ^1.2.5
version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
'@marsidev/react-turnstile':
specifier: ^1.1.0
version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -212,8 +212,8 @@ importers:
specifier: ^1.3.2
version: 1.3.2(react@19.1.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -257,8 +257,8 @@ importers:
specifier: ^3.3.0
version: 3.3.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
devDependencies:
'@kit/eslint-config':
specifier: workspace:*
@@ -342,14 +342,14 @@ importers:
specifier: workspace:*
version: link:../../ui
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/billing/gateway:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/billing':
specifier: workspace:*
version: link:../core
@@ -378,8 +378,8 @@ importers:
specifier: workspace:*
version: link:../../ui
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@types/react':
specifier: 19.1.6
version: 19.1.6
@@ -402,8 +402,8 @@ importers:
specifier: ^15.5.2
version: 15.5.2(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/billing/lemon-squeezy:
dependencies:
@@ -442,8 +442,8 @@ importers:
specifier: 19.1.0
version: 19.1.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/billing/stripe:
dependencies:
@@ -491,8 +491,8 @@ importers:
specifier: 19.1.0
version: 19.1.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/cms/core:
devDependencies:
@@ -558,8 +558,8 @@ importers:
specifier: 19.1.0
version: 19.1.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/cms/types:
devDependencies:
@@ -630,11 +630,11 @@ importers:
specifier: workspace:*
version: link:../../tooling/typescript
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/email-templates:
dependencies:
@@ -662,8 +662,8 @@ importers:
version: 5.1.5
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/billing-gateway':
specifier: workspace:*
version: link:../../billing/gateway
@@ -704,8 +704,8 @@ importers:
specifier: ^1.3.2
version: 1.3.2(react@19.1.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -740,14 +740,14 @@ importers:
specifier: ^2.0.5
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/features/admin:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/eslint-config':
specifier: workspace:*
version: link:../../../tooling/eslint
@@ -771,13 +771,13 @@ importers:
version: link:../../ui
'@makerkit/data-loader-supabase-core':
specifier: ^0.0.10
version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)
version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)
'@makerkit/data-loader-supabase-nextjs':
specifier: ^1.2.5
version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -803,14 +803,14 @@ importers:
specifier: ^7.57.0
version: 7.57.0(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/features/auth:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/eslint-config':
specifier: workspace:*
version: link:../../../tooling/eslint
@@ -836,8 +836,8 @@ importers:
specifier: ^1.3.2
version: 1.3.2(react@19.1.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -860,8 +860,8 @@ importers:
specifier: ^2.0.5
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/features/notifications:
devDependencies:
@@ -881,8 +881,8 @@ importers:
specifier: workspace:*
version: link:../../ui
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -909,8 +909,8 @@ importers:
version: 5.1.5
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/accounts':
specifier: workspace:*
version: link:../accounts
@@ -951,8 +951,8 @@ importers:
specifier: workspace:*
version: link:../../ui
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -993,8 +993,8 @@ importers:
specifier: ^2.0.5
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/i18n:
dependencies:
@@ -1063,8 +1063,8 @@ importers:
specifier: ^22.15.30
version: 22.15.30
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/mailers/nodemailer:
dependencies:
@@ -1088,8 +1088,8 @@ importers:
specifier: 6.4.17
version: 6.4.17
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/mailers/resend:
devDependencies:
@@ -1109,8 +1109,8 @@ importers:
specifier: ^22.15.30
version: 22.15.30
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/mailers/shared:
devDependencies:
@@ -1124,8 +1124,8 @@ importers:
specifier: workspace:*
version: link:../../../tooling/typescript
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/monitoring/api:
devDependencies:
@@ -1157,8 +1157,8 @@ importers:
specifier: 19.1.0
version: 19.1.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/monitoring/baselime:
dependencies:
@@ -1188,8 +1188,8 @@ importers:
specifier: 19.1.0
version: 19.1.0
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/monitoring/core:
devDependencies:
@@ -1258,20 +1258,20 @@ importers:
specifier: workspace:*
version: link:../../tooling/typescript
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
next:
specifier: 15.3.3
version: 15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/otp:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@kit/email-templates':
specifier: workspace:*
version: link:../email-templates
@@ -1303,8 +1303,8 @@ importers:
specifier: ^1.3.2
version: 1.3.2(react@19.1.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@types/react':
specifier: 19.1.6
version: 19.1.6
@@ -1321,8 +1321,8 @@ importers:
specifier: ^7.57.0
version: 7.57.0(react@19.1.0)
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/shared:
dependencies:
@@ -1356,10 +1356,10 @@ importers:
version: link:../../tooling/typescript
'@supabase/ssr':
specifier: ^0.6.1
version: 0.6.1(@supabase/supabase-js@2.49.10)
version: 0.6.1(@supabase/supabase-js@2.50.0)
'@supabase/supabase-js':
specifier: 2.49.10
version: 2.49.10
specifier: 2.50.0
version: 2.50.0
'@tanstack/react-query':
specifier: 5.80.6
version: 5.80.6(react@19.1.0)
@@ -1376,14 +1376,14 @@ importers:
specifier: ^0.0.1
version: 0.0.1
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
packages/ui:
dependencies:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.57.0(react@19.1.0))
specifier: ^5.1.0
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
'@radix-ui/react-accordion':
specifier: 1.2.11
version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1509,8 +1509,8 @@ importers:
specifier: ^3.5.3
version: 3.5.3
react-day-picker:
specifier: ^8.10.1
version: 8.10.1(date-fns@4.1.0)(react@19.1.0)
specifier: ^9.7.0
version: 9.7.0(react@19.1.0)
react-hook-form:
specifier: ^7.57.0
version: 7.57.0(react@19.1.0)
@@ -1530,8 +1530,8 @@ importers:
specifier: ^5.8.3
version: 5.8.3
zod:
specifier: ^3.25.53
version: 3.25.53
specifier: ^3.25.56
version: 3.25.56
tooling/eslint:
dependencies:
@@ -1568,13 +1568,13 @@ importers:
dependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: 5.2.2
version: 5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.14)
version: 5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.17)
prettier:
specifier: ^3.5.3
version: 3.5.3
prettier-plugin-tailwindcss:
specifier: ^0.6.12
version: 0.6.12(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.14))(prettier@3.5.3)
version: 0.6.12(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.17))(prettier@3.5.3)
devDependencies:
'@kit/tsconfig':
specifier: workspace:*
@@ -1777,6 +1777,9 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@date-fns/tz@1.2.0':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
@@ -1916,8 +1919,8 @@ packages:
engines: {node: '>=6'}
hasBin: true
'@hookform/resolvers@5.0.1':
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
'@hookform/resolvers@5.1.0':
resolution: {integrity: sha512-A5tY8gxqvvR0lFfropqpy/gUDOxjwT7LZCxJ8lNA9puK7nFNRl/O0egGKxzdF4JSz/pnnT1O8g76P/YMyTBEFw==}
peerDependencies:
react-hook-form: ^7.55.0
@@ -4175,8 +4178,8 @@ packages:
resolution: {integrity: sha512-pTzb864TQWDRQBPLgSPFRoyjSDUqpCkbEgTzpsjiTjGz1Z5SxZNXJek28w1s6Dyry4CyW4/Izj5jHE/J9hCJYQ==}
engines: {node: '>=12.16'}
'@supabase/auth-js@2.69.1':
resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==}
'@supabase/auth-js@2.70.0':
resolution: {integrity: sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==}
'@supabase/functions-js@2.4.4':
resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==}
@@ -4199,8 +4202,8 @@ packages:
'@supabase/storage-js@2.7.1':
resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==}
'@supabase/supabase-js@2.49.10':
resolution: {integrity: sha512-IRPcIdncuhD2m1eZ2Fkg0S1fq9SXlHfmAetBxPN66kVFtTucR8b01xKuVmKqcIJokB17umMf1bmqyS8yboXGsw==}
'@supabase/supabase-js@2.50.0':
resolution: {integrity: sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==}
'@sveltejs/acorn-typescript@1.0.5':
resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
@@ -5322,6 +5325,9 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
date-fns-jalali@4.1.0-0:
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
@@ -7484,11 +7490,11 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-day-picker@8.10.1:
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
react-day-picker@9.7.0:
resolution: {integrity: sha512-urlK4C9XJZVpQ81tmVgd2O7lZ0VQldZeHzNejbwLWZSkzHH498KnArT0EHNfKBOWwKc935iMLGZdxXPRISzUxQ==}
engines: {node: '>=18'}
peerDependencies:
date-fns: ^2.28.0 || ^3.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react: '>=16.8.0'
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
@@ -8020,8 +8026,8 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
svelte@5.33.14:
resolution: {integrity: sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA==}
svelte@5.33.17:
resolution: {integrity: sha512-y453Ac1Xi/MFXbGIDK89YH1CeXlFk+aTsDQ1uMc7iE+iVau6ZnE4t3iWsr0oxKnIApjfI7CL4R1NEHzVMUrkVg==}
engines: {node: '>=18'}
svgo@3.3.2:
@@ -8551,8 +8557,8 @@ packages:
peerDependencies:
zod: ^3.24.1
zod@3.25.53:
resolution: {integrity: sha512-BKOKoY3XcGUVkqaalCtFK15LhwR0G0i65AClFpWSXLN2gJNBGlTktukHgwexCTa/dAacPPp9ReryXPWyeZF4LQ==}
zod@3.25.56:
resolution: {integrity: sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==}
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -8563,39 +8569,39 @@ snapshots:
optionalDependencies:
graphql: 16.10.0
'@ai-sdk/openai@1.3.22(zod@3.25.53)':
'@ai-sdk/openai@1.3.22(zod@3.25.56)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.53)
zod: 3.25.53
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.56)
zod: 3.25.56
'@ai-sdk/provider-utils@2.2.8(zod@3.25.53)':
'@ai-sdk/provider-utils@2.2.8(zod@3.25.56)':
dependencies:
'@ai-sdk/provider': 1.1.3
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.25.53
zod: 3.25.56
'@ai-sdk/provider@1.1.3':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.2.12(react@19.1.0)(zod@3.25.53)':
'@ai-sdk/react@1.2.12(react@19.1.0)(zod@3.25.56)':
dependencies:
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.53)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.53)
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.56)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.56)
react: 19.1.0
swr: 2.3.3(react@19.1.0)
throttleit: 2.1.0
optionalDependencies:
zod: 3.25.53
zod: 3.25.56
'@ai-sdk/ui-utils@1.2.11(zod@3.25.53)':
'@ai-sdk/ui-utils@1.2.11(zod@3.25.56)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.53)
zod: 3.25.53
zod-to-json-schema: 3.24.5(zod@3.25.53)
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.56)
zod: 3.25.56
zod-to-json-schema: 3.24.5(zod@3.25.56)
'@alloc/quick-lru@5.2.0': {}
@@ -8805,6 +8811,8 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@date-fns/tz@1.2.0': {}
'@discoveryjs/json-ext@0.5.7': {}
'@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
@@ -8994,7 +9002,7 @@ snapshots:
protobufjs: 7.4.0
yargs: 17.7.2
'@hookform/resolvers@5.0.1(react-hook-form@7.57.0(react@19.1.0))':
'@hookform/resolvers@5.1.0(react-hook-form@7.57.0(react@19.1.0))':
dependencies:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.57.0(react@19.1.0)
@@ -9333,16 +9341,16 @@ snapshots:
'@lemonsqueezy/lemonsqueezy.js@4.0.0': {}
'@makerkit/data-loader-supabase-core@0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)':
'@makerkit/data-loader-supabase-core@0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)':
dependencies:
'@supabase/postgrest-js': 1.19.4
'@supabase/supabase-js': 2.49.10
'@supabase/supabase-js': 2.50.0
ts-case-convert: 2.1.0
'@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
'@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)(@tanstack/react-query@5.80.6(react@19.1.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
dependencies:
'@makerkit/data-loader-supabase-core': 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.10)
'@supabase/supabase-js': 2.49.10
'@makerkit/data-loader-supabase-core': 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.50.0)
'@supabase/supabase-js': 2.50.0
'@tanstack/react-query': 5.80.6(react@19.1.0)
next: 15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
@@ -11884,7 +11892,7 @@ snapshots:
'@stripe/stripe-js@7.3.1': {}
'@supabase/auth-js@2.69.1':
'@supabase/auth-js@2.70.0':
dependencies:
'@supabase/node-fetch': 2.6.15
@@ -11910,18 +11918,18 @@ snapshots:
- bufferutil
- utf-8-validate
'@supabase/ssr@0.6.1(@supabase/supabase-js@2.49.10)':
'@supabase/ssr@0.6.1(@supabase/supabase-js@2.50.0)':
dependencies:
'@supabase/supabase-js': 2.49.10
'@supabase/supabase-js': 2.50.0
cookie: 1.0.2
'@supabase/storage-js@2.7.1':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/supabase-js@2.49.10':
'@supabase/supabase-js@2.50.0':
dependencies:
'@supabase/auth-js': 2.69.1
'@supabase/auth-js': 2.70.0
'@supabase/functions-js': 2.4.4
'@supabase/node-fetch': 2.6.15
'@supabase/postgrest-js': 1.19.4
@@ -12042,7 +12050,7 @@ snapshots:
'@tootallnate/quickjs-emscripten@0.23.0': {}
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.14)':
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.17)':
dependencies:
'@babel/generator': 7.27.0
'@babel/parser': 7.27.0
@@ -12053,7 +12061,7 @@ snapshots:
prettier: 3.5.3
optionalDependencies:
'@vue/compiler-sfc': 3.5.16
svelte: 5.33.14
svelte: 5.33.17
transitivePeerDependencies:
- supports-color
@@ -12586,15 +12594,15 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@4.3.16(react@19.1.0)(zod@3.25.53):
ai@4.3.16(react@19.1.0)(zod@3.25.56):
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.53)
'@ai-sdk/react': 1.2.12(react@19.1.0)(zod@3.25.53)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.53)
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.56)
'@ai-sdk/react': 1.2.12(react@19.1.0)(zod@3.25.56)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.56)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
zod: 3.25.53
zod: 3.25.56
optionalDependencies:
react: 19.1.0
@@ -13189,6 +13197,8 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
date-fns-jalali@4.1.0-0: {}
date-fns@4.1.0: {}
dateformat@4.6.3: {}
@@ -15567,11 +15577,11 @@ snapshots:
prelude-ls@1.2.1: {}
prettier-plugin-tailwindcss@0.6.12(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.14))(prettier@3.5.3):
prettier-plugin-tailwindcss@0.6.12(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.17))(prettier@3.5.3):
dependencies:
prettier: 3.5.3
optionalDependencies:
'@trivago/prettier-plugin-sort-imports': 5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.14)
'@trivago/prettier-plugin-sort-imports': 5.2.2(@vue/compiler-sfc@3.5.16)(prettier@3.5.3)(svelte@5.33.17)
prettier@3.5.3: {}
@@ -15693,9 +15703,11 @@ snapshots:
minimist: 1.2.8
strip-json-comments: 2.0.1
react-day-picker@8.10.1(date-fns@4.1.0)(react@19.1.0):
react-day-picker@9.7.0(react@19.1.0):
dependencies:
'@date-fns/tz': 1.2.0
date-fns: 4.1.0
date-fns-jalali: 4.1.0-0
react: 19.1.0
react-dom@19.1.0(react@19.1.0):
@@ -16316,7 +16328,7 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
svelte@5.33.14:
svelte@5.33.17:
dependencies:
'@ampproject/remapping': 2.3.0
'@jridgewell/sourcemap-codec': 1.5.0
@@ -16922,10 +16934,10 @@ snapshots:
zimmerframe@1.1.2:
optional: true
zod-to-json-schema@3.24.5(zod@3.25.53):
zod-to-json-schema@3.24.5(zod@3.25.56):
dependencies:
zod: 3.25.53
zod: 3.25.56
zod@3.25.53: {}
zod@3.25.56: {}
zwitch@2.0.4: {}