Files
myeasycms-v2/apps/web/AGENTS.md
Giancarlo Buomprisco fa2fa9a15c chore: improve invitation flow, update project dependencies and documentation for Next.js 16 (#408)
* chore: update project dependencies and documentation for Next.js 16

- Upgraded Next.js from version 15 to 16 across various documentation files and components.
- Updated references to Next.js 16 in AGENTS.md and CLAUDE.md for consistency.
- Incremented application version to 2.21.0 in package.json.
- Refactored identity setup components to improve user experience and added confirmation dialogs for authentication methods.
- Enhanced invitation flow with new logic for handling user redirection and token generation.

* refactor: streamline invitation flow in e2e tests

- Simplified the invitation flow test by using a predefined email instead of generating a random one.
- Removed unnecessary steps such as clearing cookies and reloading the page before user sign-up.
- Enhanced clarity by eliminating commented-out code related to identity verification and user membership checks.

* refactor: improve code readability in IdentitiesPage and UpdatePasswordForm components

- Enhanced formatting of JSX elements in IdentitiesPage and UpdatePasswordForm for better readability.
- Adjusted indentation and line breaks to maintain consistent coding style across components.

* refactor: enhance LinkAccountsList component with user redirection logic

- Updated the LinkAccountsList component to include a redirectToPath option in the useLinkIdentityWithProvider hook for improved user experience.
- Removed redundant user hook declaration to streamline the code structure.

* refactor: update account setup logic in JoinTeamAccountPage

- Introduced a check for email-only authentication support to streamline account setup requirements.
- Adjusted the conditions for determining if a new account should set up additional authentication methods, enhancing user experience for new users.
2025-11-05 11:39:08 +07:00

8.5 KiB

Web Application Instructions

This file contains instructions specific to the main Next.js web application.

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

Key Examples:

  • Marketing layout: app/(marketing)/layout.tsx
  • Personal dashboard: app/home/(user)/page.tsx
  • Team workspace: app/home/[account]/page.tsx
  • Admin section: 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:

  • Team components: app/home/[account]/_components/
  • Team server utils: app/home/[account]/_lib/server/
  • Marketing components: app/(marketing)/_components/

The [account] parameter is the accounts.slug property, not the ID

React Server Components - Async Pattern

CRITICAL: In Next.js 16, always await params directly in async server components:

// ❌ WRONG - Don't use React.use() in async functions
async function Page({ params }: Props) {
  const { account } = use(params);
}

// ✅ CORRECT - await params directly in Next.js 16
async function Page({ params }: Props) {
  const { account } = await params; // ✅ Server component pattern
}

// ✅ CORRECT - "use" in non-async functions in Next.js 16
function Page({ params }: Props) {
  const { account } = use(params); // ✅ Server component pattern
}

Data Fetching Strategy

Quick Decision Framework:

  • Server Components: Default choice for initial data loading
  • Client Components: For interactive features requiring hooks or real-time updates
  • Admin Client: Only for bypassing RLS (rare cases - requires manual auth/authorization)

Server Components (Preferred)

import { getSupabaseServerClient } from '@kit/supabase/server-client';

async function NotesPage() {
  const client = getSupabaseServerClient();
  const { data, error } = await client.from('notes').select('*');

  if (error) return <ErrorMessage error={error} />;
  return <NotesList notes={data} />;
}

Key Insight: Server Components automatically inherit RLS protection - no additional authorization checks needed!

Client Components (Interactive) 🖱️

'use client';
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { useQuery } from '@tanstack/react-query';

function InteractiveNotes() {
  const supabase = useSupabase();
  const { data, isLoading } = useQuery({
    queryKey: ['notes'],
    queryFn: () => supabase.from('notes').select('*')
  });

  if (isLoading) return <Spinner />;
  return <NotesList notes={data} />;
}

Performance Optimization - Parallel Data Fetching 🚀

Sequential (Slow) Pattern

async function SlowDashboard() {
  const userData = await loadUserData();
  const notifications = await loadNotifications();
  const metrics = await loadMetrics();
  // Total time: sum of all requests
}

Parallel (Optimized) Pattern

async function FastDashboard() {
  // Execute all requests simultaneously
  const [userData, notifications, metrics] = await Promise.all([
    loadUserData(),
    loadNotifications(),
    loadMetrics()
  ]);
  // Total time: longest single request

  return <Dashboard user={userData} notifications={notifications} metrics={metrics} />;
}

Performance Impact: Parallel fetching can reduce page load time by 60-80% for multi-data pages!

Authorization Patterns - Critical Understanding 🔐

RLS-Protected Data Fetching (Standard)

async function getUserNotes(userId: string) {
  const client = getSupabaseServerClient();

  // RLS automatically ensures user can only access their own notes
  // NO additional authorization checks needed!
  const { data } = await client.from('notes').select('*').eq('user_id', userId); // RLS validates this automatically

  return data;
}

Admin Client Usage (Dangerous - Rare Cases Only) ⚠️

async function adminGetUserNotes(userId: string) {
  const adminClient = getSupabaseServerAdminClient();

  // CRITICAL: Manual authorization required - bypasses RLS!
  const currentUser = await getCurrentUser();
  if (!(await isSuperAdmin(currentUser))) {
    throw new Error('Unauthorized: Admin access required');
  }

  // Additional validation: ensure current admin isn't targeting themselves
  if (currentUser.id === userId) {
    throw new Error('Cannot perform admin action on own account');
  }

  // Now safe to proceed with admin privileges
  const { data } = await adminClient
    .from('notes')
    .select('*')
    .eq('user_id', userId);

  return data;
}

Rule of thumb: If using standard Supabase client, trust RLS. If using admin client, validate everything manually.

Internationalization

Always use Trans component from @kit/ui/trans:

import { Trans } from '@kit/ui/trans';

<Trans
  i18nKey="user:welcomeMessage"
  values={{ name: user.name }}
/>

// With HTML elements
<Trans
  i18nKey="terms:agreement"
  components={{
    TermsLink: <a href="/terms" className="underline" />,
  }}
/>

Adding New Languages

  1. Add language code to lib/i18n/i18n.settings.ts
  2. Create translation files in public/locales/[new-language]/
  3. Copy structure from English files

Adding new namespaces

  1. Translation files: public/locales/<locale>/<namespace>.json
  2. Add namespace to defaultI18nNamespaces in apps/web/lib/i18n/i18n.settings.ts

Workspace Contexts 🏢

Personal Account Context (app/home/(user))

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 (app/home/[account])

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

Key Configuration Files

  • Feature flags: config/feature-flags.config.ts
  • i18n settings: lib/i18n/i18n.settings.ts
  • Supabase config: supabase/config.toml
  • Middleware: middleware.ts

Route Handlers (API Routes)

Use enhanceRouteHandler from @packages/next/src/routes/index.ts:

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,
  },
);

Navigation Menu Configuration 🗺️

Adding Sidebar Menu Items

Config Files:

  • Personal: config/personal-account-navigation.config.tsx
  • Team: config/team-account-navigation.config.tsx

Add to Personal Navigation:

{
  label: 'common:routes.yourFeature',
  path: pathsConfig.app.yourFeaturePath,
  Icon: <YourIcon className="w-4" />,
  end: true,
},

Add to Team Navigation:

{
  label: 'common:routes.yourTeamFeature',
  path: createPath(pathsConfig.app.yourTeamFeaturePath, account),
  Icon: <YourIcon className="w-4" />,
},

Add Paths:

// config/paths.config.ts
app: {
  yourFeaturePath: '/home/your-feature',
  yourTeamFeaturePath: '/home/[account]/your-feature',
}

Add Translations:

// public/locales/en/common.json
"routes": {
  "yourFeature": "Your Feature"
}

Security Guidelines 🛡️

Authentication & Authorization

  • Authentication already enforced by middleware
  • Authorization handled by RLS at database level (in most cases)
  • Avoid defensive code - use RLS instead
  • When using the Supabase admin client, must enforce both authentication and authorization

Passing data to the client

  • Never pass sensitive data to Client Components
  • Never expose server environment variables to client (unless prefixed with NEXT_PUBLIC)
  • Always validate user input