# Typescript - Write clean, clear, well-designed, explicit Typescript - Make sure types are validated strictly - Use implicit type inference, unless impossible - Consider using classes for server-side services, but export a function instead of the class ```tsx // service.ts class UserService { getUser(id: number) { // ... implementation ... return { id, name: 'Example User' }; } } export function createUserService() { return new UserService(); } ``` - Follow the Single Responsibility Principle (SRP). Each module/function/class should have one reason to change. - Favor composition over inheritance. - Handle errors gracefully using try/catch and appropriate error types. - Keep functions short and focused. - Use descriptive names for variables, functions, and classes. - Avoid unnecessary complexity. - Avoid using `any` type as much as possible. If necessary, use `unknown` - Use enums only when appropriate. Consider union types of string literals as an alternative. - Be aware of performance implications of your code. # React ## Core Principles - **Component-Driven Development**: Build applications as a composition of isolated, reusable components - **One-Way Data Flow**: Follow React's unidirectional data flow pattern - **Single Responsibility**: Each component should have a clear, singular purpose - **TypeScript First**: Use TypeScript for type safety and better developer experience - **Internationalization (i18n) By Default**: All user-facing text should be translatable ## React Components ### Component Structure - Always use functional components with TypeScript - Name components using PascalCase (e.g., `UserProfile`) - Use named exports for components, not default exports - Split components by responsibility and avoid "god components" - Name files to match their component name (e.g., `user-profile.tsx`) ### Props - Always type props using TypeScript interfaces or type aliases - Use discriminated unions for complex prop types with conditional rendering - Destructure props at the start of component functions - Use prop spreading cautiously and only when appropriate - Provide default props for optional parameters when it makes sense ```typescript type ButtonProps = { variant: 'primary' | 'secondary' | 'ghost'; size?: 'sm' | 'md' | 'lg'; children: React.ReactNode; disabled?: boolean; onClick?: () => void; }; function Button({ variant, size = 'md', children, disabled = false, onClick }: ButtonProps) { // Component implementation } ``` ### State Management - Keep state as local as possible - Lift state up when multiple components need access - Use Context sparingly and only for truly global state - Prefer the "Container/Presenter" pattern when separating data and UI ```typescript // Container component (manages data) function UserProfileContainer() { const userData = useUserData(); if (userData.isLoading) { return ; } if (userData.error) { return ; } return ; } // Presenter component (renders UI) function UserProfilePresenter({ data }: { data: UserData }) { return (

{data.name}

{/* Rest of the UI */}
); } ``` ### Hooks - Follow the Rules of Hooks (only call hooks at the top level, only call them from React functions) - Create custom hooks for reusable logic - Keep custom hooks focused on a single concern - Name custom hooks with a 'use' prefix (e.g., `useUserProfile`) - Extract complex effect logic into separate functions - Always provide a complete dependencies array to `useEffect` ### Performance Optimization - Apply `useMemo` for expensive calculations - Use `useCallback` for functions passed as props to child components - Split code using dynamic imports and `React.lazy()` ```typescript const MemoizedComponent = React.memo(function Component(props: Props) { // Component implementation }); // For expensive calculations const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); // For callback functions passed as props const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); ``` ### Internationalization (i18n) - Always use the `Trans` component for text rendering (no hardcoded strings) - Ensure all i18n keys are available in locale files - Use namespaces to organize translations logically - Include interpolation variables in translation keys - Test UI with different languages, especially those with longer text ```typescript // Correct // Incorrect

Welcome, {user.name}!

``` ## Server Components ### Fundamentals - Server Components render React server-side and never run on the client - Use Server Components as the default choice, especially for data fetching - No use of hooks, browser APIs, or event handlers in Server Components - No use of `useState`, `useEffect`, or any other React hooks - Server Components can render Client Components but not vice versa ### Data Fetching - Fetch data directly using async/await in Server Components - Use Suspense boundaries around data-fetching components - Apply security checks before fetching sensitive data - Never pass sensitive data (API keys, tokens) to Client Components - Use React's `cache()` function for caching data requests ### Error Handling - Implement error boundaries at appropriate levels - Use the Next.js `error.tsx` file for route-level error handling - Create fallback UI for when data fetching fails - Log server errors appropriately without exposing details to clients ### Streaming and Suspense - Use React Suspense for progressive loading experiences if specified - Implement streaming rendering for large or complex pages - Structure components to enable meaningful loading states - Prioritize above-the-fold content when using streaming ## Client Components ### Fundamentals - Add the `'use client'` directive at the top of files for Client Components - Keep Client Components focused on interactivity and browser APIs - Use hooks appropriately following the Rules of Hooks - Implement controlled components for form elements - Handle all browser events in Client Components ### Data Fetching - Use React Query (TanStack Query) for data fetching in Client Components - Create custom hooks for data fetching logic (e.g., `useUserData`) - Always handle loading, success, and error states ### Form Handling - Use libraries like React Hook Form for complex forms - Implement proper validation with libraries like Zod - Create reusable form components - Handle form submissions with loading and error states - Use controlled components for form inputs ### Error Handling - Implement error boundaries to catch and handle component errors if using client components - Always handle network request errors - Provide user-friendly error messages - Log errors appropriately - Implement retry mechanisms where applicable ```typescript 'use client'; import { ErrorBoundary } from 'react-error-boundary'; function ErrorFallback({ error, resetErrorBoundary }) { return (

Something went wrong:

{error.message}
); } export function UserProfileWithErrorHandling() { return ( { // Reset application state here if needed }} > ); } ``` # Project Structure ``` apps/web/app/ # Root directory (apps/web/app) │ ├── (marketing)/ # Marketing pages group │ ├── _components/ # Shared components for marketing routes │ │ ├── site-footer.tsx │ │ ├── site-header.tsx │ │ ├── site-navigation.tsx │ │ └── site-page-header.tsx │ │ │ ├── (legal)/ # Legal pages subgroup │ │ ├── cookie-policy/ │ │ │ └── page.tsx │ │ ├── privacy-policy/ │ │ │ └── page.tsx │ │ └── terms-of-service/ │ │ └── page.tsx │ │ │ ├── blog/ # Blog section │ │ ├── _components/ # Blog-specific components │ │ │ ├── blog-pagination.tsx │ │ │ ├── post-header.tsx │ │ │ └── post-preview.tsx │ │ ├── [slug]/ # Dynamic route for blog posts │ │ │ └── page.tsx │ │ └── page.tsx # Blog listing page │ │ │ ├── contact/ # Contact page │ │ ├── _components/ │ │ │ └── contact-form.tsx │ │ ├── _lib/ # Contact page utilities │ │ │ ├── contact-email.schema.ts │ │ │ └── server/ │ │ │ └── server-actions.ts │ │ └── page.tsx │ │ │ ├── docs/ # Documentation pages │ │ ├── _components/ │ │ ├── _lib/ │ │ │ ├── server/ │ │ │ │ └── docs.loader.ts │ │ │ └── utils.ts │ │ ├── [slug]/ │ │ │ └── page.tsx │ │ ├── layout.tsx # Layout specific to docs section │ │ └── page.tsx │ │ │ ├── faq/ │ │ └── page.tsx │ │ │ ├── pricing/ │ │ └── page.tsx │ │ │ ├── layout.tsx # Layout for all marketing pages │ ├── loading.tsx # Loading state for marketing pages │ └── page.tsx # Home/landing page │ ├── (auth)/ # Authentication pages group │ ├── callback/ # Auth callback routes │ │ ├── error/ │ │ │ └── page.tsx │ │ └── route.ts # API route handler for auth callback │ │ │ ├── confirm/ │ │ └── route.ts │ │ │ ├── password-reset/ │ │ └── page.tsx │ │ │ ├── sign-in/ │ │ └── page.tsx │ │ │ ├── sign-up/ │ │ └── page.tsx │ │ │ ├── verify/ │ │ └── page.tsx │ │ │ ├── layout.tsx # Layout for auth pages │ └── loading.tsx # Loading state for auth pages │ ├── admin/ # Admin section │ ├── _components/ │ │ ├── admin-sidebar.tsx │ │ └── mobile-navigation.tsx │ │ │ ├── accounts/ │ │ ├── [id]/ │ │ │ └── page.tsx │ │ └── page.tsx │ │ │ ├── layout.tsx │ ├── loading.tsx │ └── page.tsx │ ├── api/ # API routes │ ├── billing/ │ │ └── webhook/ │ │ └── route.ts │ │ │ └── db/ │ └── webhook/ │ └── route.ts │ ├── home/ # User dashboard area │ ├── (user)/ # Personal user routes │ │ ├── _components/ # User dashboard components │ │ │ ├── home-account-selector.tsx │ │ │ └── home-sidebar.tsx │ │ │ │ │ ├── _lib/ # User dashboard utilities │ │ │ └── server/ │ │ │ └── load-user-workspace.ts │ │ │ │ │ ├── billing/ # Personal account billing │ │ │ ├── _components/ │ │ │ ├── _lib/ │ │ │ │ ├── schema/ │ │ │ │ │ └── personal-account-checkout.schema.ts │ │ │ │ └── server/ │ │ │ │ ├── personal-account-billing-page.loader.ts │ │ │ │ ├── server-actions.ts │ │ │ │ └── user-billing.service.ts │ │ │ │ │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── return/ │ │ │ └── page.tsx │ │ │ │ │ ├── settings/ │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ │ │ ├── [account]/ # Team account routes (dynamic) │ │ ├── _components/ # Team account components │ │ │ ├── dashboard-demo.tsx │ │ │ ├── team-account-accounts-selector.tsx │ │ │ └── team-account-layout-sidebar.tsx │ │ │ │ │ ├── _lib/ # Team account utilities │ │ │ └── server/ │ │ │ ├── team-account-billing-page.loader.ts │ │ │ └── team-account-workspace.loader.ts │ │ │ │ │ ├── billing/ # Team billing section │ │ │ ├── _components/ │ │ │ ├── _lib/ │ │ │ │ ├── schema/ │ │ │ │ │ └── team-billing.schema.ts │ │ │ │ └── server/ │ │ │ │ ├── server-actions.ts │ │ │ │ └── team-billing.service.ts │ │ │ │ │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── return/ │ │ │ └── page.tsx │ │ │ │ │ ├── members/ # Team members management │ │ │ ├── _lib/ │ │ │ │ └── server/ │ │ │ │ └── members-page.loader.ts │ │ │ └── page.tsx │ │ │ │ │ ├── settings/ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ │ │ └── loading.tsx │ ├── join/ # Team join page │ └── page.tsx │ ├── update-password/ │ └── page.tsx │ ├── error.tsx # Global error page ├── global-error.tsx # Global error component ├── layout.tsx # Root layout ├── not-found.tsx # 404 page ├── robots.ts # Robots.txt config ├── sitemap.xml/ # Sitemap generation │ └── route.ts └── version/ # Version info endpoint └── route.ts ``` ## Key Organization Patterns 1. **Route Groups** - `(marketing)` - Groups all marketing/public pages - `(auth)` - Groups all authentication related pages - `(user)` - Groups all personal user dashboard pages 2. **Component Organization** - `_components/` - Route-specific components - Global components are in the root `/components` directory (not shown) 3. **Utilities & Data** - `_lib/` - Route-specific utilities, types, and helpers - `_lib/server/` - Server-side utilities including data loaders - `/lib/` - Global utilities (not shown) 4. **Data Fetching** - Use of React's `cache()` function for request deduplication 5. **Server Actions** - `server-actions.ts` - Server-side actions for mutating data - Follows 'use server' directive pattern 6. **Special Files** - `layout.tsx` - Define layouts for routes - `loading.tsx` - Loading UI for routes - `error.tsx` - Error handling for routes - `page.tsx` - Page component for routes - `route.ts` - API route handlers 7. **Dynamic Routes** - `[account]` - Dynamic route for team accounts. The [account] property is the account slug in the table `public.accounts`. - `[slug]` - Dynamic route for blog posts and documentation # Creating Pages # Makerkit Page & Layout Guidelines ## Page Structure Overview Makerkit uses Next.js App Router architecture with a clear separation of concerns for layouts and pages. The application's structure reflects the multi-tenant approach with specific routing patterns: ``` - app - home # protected routes - (user) # user workspace (personal account context) - [account] # team workspace (team account context) - (marketing) # marketing pages - auth # auth pages ``` ## Key Components ### Layouts Layouts in Makerkit provide the structure for various parts of the application: 1. **Root Layout**: The base structure for the entire application 2. **Workspace Layouts**: - User Workspace Layout (`app/home/(user)/layout.tsx`): For personal account context - Team Workspace Layout (`app/home/[account]/layout.tsx`): For team account context Layouts handle: - Workspace context providers - Navigation components - Authentication requirements - UI structure (sidebar vs header style) ### Pages Pages represent the actual content for each route and follow a consistent pattern: 1. **Metadata Generation**: Using `generateMetadata()` for SEO and page titles 2. **Content Structure**: - Page headers with titles and descriptions - Page body containing the main content 3. **i18n Implementation**: Wrapped with `withI18n` HOC ## Creating a New Page ### 1. Define the Page Structure Create a new file within the appropriate route folder: ```tsx // app/home/(user)/my-feature/page.tsx import { PageBody } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; // Import components from the _components folder if needed import { MyFeatureHeader } from './_components/my-feature-header'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); const title = i18n.t('account:myFeaturePage'); return { title, }; }; function MyFeaturePage() { return ( <> } description={} /> {/* Main page content */} ); } export default withI18n(MyFeaturePage); ``` - Authentication is enforced already in the middleware - Authorization is normally enforced by RLS at the database level - In the rare case you use the Supabase Admin client, you must enforce both authentication and authorization manually ### 2. Create a Loading State ```tsx // app/home/(user)/my-feature/loading.tsx import { GlobalLoader } from '@kit/ui/global-loader'; export default GlobalLoader; ``` ### 3. Create a Layout (if needed) If the feature requires a specific layout, create a layout file: ```tsx // app/home/(user)/my-feature/layout.tsx import { use } from 'react'; import { UserWorkspaceContextProvider } from '@kit/accounts/components'; import { Page, PageNavigation } from '@kit/ui/page'; import { withI18n } from '~/lib/i18n/with-i18n'; import { loadUserWorkspace } from '../_lib/server/load-user-workspace'; // Import components from the _components folder import { MyFeatureNavigation } from './_components/my-feature-navigation'; function MyFeatureLayout({ children }: React.PropsWithChildren) { const workspace = use(loadUserWorkspace()); return ( {children} ); } export default withI18n(MyFeatureLayout); ``` ## Layout Patterns ### 1. User Workspace Layout For pages in the personal account context, use the user workspace layout pattern: ```tsx import { use } from 'react'; import { UserWorkspaceContextProvider } from '@kit/accounts/components'; import { Page } from '@kit/ui/page'; import { withI18n } from '~/lib/i18n/with-i18n'; import { loadUserWorkspace } from './_lib/server/load-user-workspace'; function MyLayout({ children }: React.PropsWithChildren) { const workspace = use(loadUserWorkspace()); return ( {/* Navigation components */} {children} ); } export default withI18n(MyLayout); ``` ### 2. Team Workspace Layout For pages in the team account context, use the team workspace layout pattern: ```tsx import { use } from 'react'; import { TeamAccountWorkspaceContextProvider } from '@kit/team-accounts/components'; import { Page } from '@kit/ui/page'; import { withI18n } from '~/lib/i18n/with-i18n'; import { loadTeamWorkspace } from './_lib/server/load-team-workspace'; function TeamLayout({ children, params }: LayoutParams) { const workspace = use(loadTeamWorkspace(params.account)); return ( {/* Navigation components */} {children} ); } export default withI18n(TeamLayout); ``` ## UI Components Structure ### Page Components Break down pages into reusable components: 1. **Page Headers**: Create header components for consistent titling: ```tsx // _components/my-feature-header.tsx import { PageHeader } from '@kit/ui/page-header'; export function MyFeatureHeader({ title, description }: { title: React.ReactNode, description: React.ReactNode }) { return ( ); } ``` 2. **Feature Components**: Create components for feature-specific functionality: ```tsx // _components/my-feature-component.tsx 'use client'; import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace'; export function MyFeatureComponent() { const { user } = useUserWorkspace(); return (
{/* Component content */}
); } ``` ### Navigation Components Create navigation components to handle sidebar or header navigation: ```tsx // _components/my-feature-navigation.tsx 'use client'; import { NavigationMenu } from '@kit/ui/navigation-menu'; export function MyFeatureNavigation({ workspace }: { workspace: UserWorkspace }) { return ( {/* Navigation items */} ); } ``` ## Layout Styles Makerkit supports different layout styles that can be toggled by the user: 1. **Sidebar Layout**: A vertical sidebar navigation 2. **Header Layout**: A horizontal header navigation The layout style is stored in cookies and can be accessed server-side: ```tsx async function getLayoutState() { const cookieStore = await cookies(); const layoutStyleCookie = cookieStore.get('layout-style'); return { style: layoutStyleCookie?.value ?? defaultStyle, // Other layout state properties }; } ``` ## Best Practices 1. **Server vs. Client Components**: - Use Server Components for data fetching and initial rendering - Use Client Components ('use client') for interactive elements 2. **Data Loading**: - Load workspace data in layouts using server functions - Pass data down to components that need it - Use React Query for client-side data fetching 3. **Component Organization**: - Place feature-specific components in a `_components` folder - Place feature-specific server utilities in a `_lib/server` folder - Place feature-specific client utilities in a `_lib/client` folder 4. **i18n Support**: - Always use `withI18n` HOC for pages and layouts - Use `` component for translated text - Define translation keys in the appropriate namespace in `apps/web/public/locales//.json` 5. **Metadata**: - Always include `generateMetadata` for SEO and UX - Use translations for page titles and descriptions 6. **Loading States**: - Always provide a loading state for each route - Use the `GlobalLoader` or custom loading components 7. **Error Handling**: - Implement error.tsx files for route error boundaries - Handle data fetching errors gracefully # UI Components - Reusable UI components are defined in the "packages/ui" package named "@kit/ui". - By exporting the component from the "exports" field, we can import it using the "@kit/ui/{component-name}" format. ## Styling - Styling is done using Tailwind CSS. We use the "cn" function from the "@kit/ui/utils" package to generate class names. - Avoid fixes classes such as "bg-gray-500". Instead, use Shadcn classes such as "bg-background", "text-secondary-foreground", "text-muted-foreground", etc. Makerkit leverages two sets of UI components: 1. **Shadcn UI Components**: Base components from the Shadcn UI library 2. **Makerkit-specific Components**: Custom components built on top of Shadcn UI ## Importing Components ```tsx // Import Shadcn UI components import { Button } from '@kit/ui/button'; import { Card } from '@kit/ui/card'; import { toast } from '@kit/ui/sonner'; // Import Makerkit-specific components import { If } from '@kit/ui/if'; import { Trans } from '@kit/ui/trans'; import { ProfileAvatar } from '@kit/ui/profile-avatar'; ``` ## Core Shadcn UI Components | Component | Description | Import Path | |-----------|-------------|-------------| | `Accordion` | Expandable/collapsible content sections | `@kit/ui/accordion` [accordion.tsx](mdc:packages/ui/src/shadcn/accordion.tsx) | | `AlertDialog` | Modal dialog for important actions | `@kit/ui/alert-dialog` [alert-dialog.tsx](mdc:packages/ui/src/shadcn/alert-dialog.tsx) | | `Alert` | Status/notification messages | `@kit/ui/alert` [alert.tsx](mdc:packages/ui/src/shadcn/alert.tsx) | | `Avatar` | User profile images with fallback | `@kit/ui/avatar` [avatar.tsx](mdc:packages/ui/src/shadcn/avatar.tsx) | | `Badge` | Small status indicators | `@kit/ui/badge` [badge.tsx](mdc:packages/ui/src/shadcn/badge.tsx) | | `Breadcrumb` | Navigation path indicators | `@kit/ui/breadcrumb` [breadcrumb.tsx](mdc:packages/ui/src/shadcn/breadcrumb.tsx) | | `Button` | Clickable action elements | `@kit/ui/button` [button.tsx](mdc:packages/ui/src/shadcn/button.tsx) | | `Calendar` | Date picker and date display | `@kit/ui/calendar` [calendar.tsx](mdc:packages/ui/src/shadcn/calendar.tsx) | | `Card` | Container for grouped content | `@kit/ui/card` [card.tsx](mdc:packages/ui/src/shadcn/card.tsx) | | `Checkbox` | Selection input | `@kit/ui/checkbox` [checkbox.tsx](mdc:packages/ui/src/shadcn/checkbox.tsx) | | `Command` | Command palette interface | `@kit/ui/command` [command.tsx](mdc:packages/ui/src/shadcn/command.tsx) | | `DataTable` | Table | `@kit/ui/data-table` [data-table.tsx](mdc:packages/ui/src/shadcn/data-table.tsx) | | `Dialog` | Modal window for focused interactions | `@kit/ui/dialog` [dialog.tsx](mdc:packages/ui/src/shadcn/dialog.tsx) | | `DropdownMenu` | Menu triggered by a button | `@kit/ui/dropdown-menu` [dropdown-menu.tsx](mdc:packages/ui/src/shadcn/dropdown-menu.tsx) | | `Form` | Form components with validation | `@kit/ui/form` [form.tsx](mdc:packages/ui/src/shadcn/form.tsx) | | `Input` | Text input field | `@kit/ui/input` [input.tsx](mdc:packages/ui/src/shadcn/input.tsx) | | `Input OTP` | OTP Text input field | `@kit/ui/input-otp` [input-otp.tsx](mdc:packages/ui/src/shadcn/input-otp.tsx) | | `Label` | Text label for form elements | `@kit/ui/label` [label.tsx](mdc:packages/ui/src/shadcn/label.tsx) | | `NavigationMenu` | Hierarchical navigation component | `@kit/ui/navigation-menu` [navigation-menu.tsx](mdc:packages/ui/src/shadcn/navigation-menu.tsx) | | `Popover` | Floating content triggered by interaction | `@kit/ui/popover` [popover.tsx](mdc:packages/ui/src/shadcn/popover.tsx) | | `RadioGroup` | Radio button selection group | `@kit/ui/radio-group` [radio-group.tsx](mdc:packages/ui/src/shadcn/radio-group.tsx) | | `ScrollArea` | Customizable scrollable area | `@kit/ui/scroll-area` [scroll-area.tsx](mdc:packages/ui/src/shadcn/scroll-area.tsx) | | `Select` | Dropdown selection menu | `@kit/ui/select` [select.tsx](mdc:packages/ui/src/shadcn/select.tsx) | | `Separator` | Visual divider between content | `@kit/ui/separator` [separator.tsx](mdc:packages/ui/src/shadcn/separator.tsx) | | `Sheet` | Sliding panel from screen edge | `@kit/ui/sheet` [sheet.tsx](mdc:packages/ui/src/shadcn/sheet.tsx) | | `Sidebar` | Advanced sidebar navigation | `@kit/ui/shadcn-sidebar` [sidebar.tsx](mdc:packages/ui/src/shadcn/sidebar.tsx) | | `Skeleton` | Loading placeholder | `@kit/ui/skeleton` [skeleton.tsx](mdc:packages/ui/src/shadcn/skeleton.tsx) | | `Switch` | Toggle control | `@kit/ui/switch` [switch.tsx](mdc:packages/ui/src/shadcn/switch.tsx) | | `Toast` | Toaster | `@kit/ui/sonner` [sonner.tsx](mdc:packages/ui/src/shadcn/sonner.tsx) | | `Tabs` | Tab-based navigation | `@kit/ui/tabs` [tabs.tsx](mdc:packages/ui/src/shadcn/tabs.tsx) | | `Textarea` | Multi-line text input | `@kit/ui/textarea` [textarea.tsx](mdc:packages/ui/src/shadcn/textarea.tsx) | | `Tooltip` | Contextual information on hover | `@kit/ui/tooltip` [tooltip.tsx](mdc:packages/ui/src/shadcn/tooltip.tsx) | ## Makerkit-specific Components | Component | Description | Import Path | |-----------|-------------|-------------| | `If` | Conditional rendering component | `@kit/ui/if` [if.tsx](mdc:packages/ui/src/makerkit/if.tsx) | | `Trans` | Internationalization text component | `@kit/ui/trans` [trans.tsx](mdc:packages/ui/src/makerkit/trans.tsx) | | `Page` | Page layout with navigation | `@kit/ui/page` [page.tsx](mdc:packages/ui/src/makerkit/page.tsx) | | `GlobalLoader` | Full-page loading indicator | `@kit/ui/global-loader` [global-loader.tsx](mdc:packages/ui/src/makerkit/global-loader.tsx) | | `ImageUploader` | Image upload component | `@kit/ui/image-uploader` [image-uploader.tsx](mdc:packages/ui/src/makerkit/image-uploader.tsx) | | `ProfileAvatar` | User avatar with fallback | `@kit/ui/profile-avatar` [profile-avatar.tsx](mdc:packages/ui/src/makerkit/profile-avatar.tsx) | | `DataTable` (Enhanced) | Extended data table with pagination | `@kit/ui/enhanced-data-table` [data-table.tsx](mdc:packages/ui/src/makerkit/data-table.tsx) | | `Stepper` | Multi-step process indicator | `@kit/ui/stepper` [stepper.tsx](mdc:packages/ui/src/makerkit/stepper.tsx) | | `CookieBanner` | GDPR-compliant cookie notice | `@kit/ui/cookie-banner` [cookie-banner.tsx](mdc:packages/ui/src/makerkit/cookie-banner.tsx) | | `CardButton` | Card-styled button | `@kit/ui/card-button` [card-button.tsx](mdc:packages/ui/src/makerkit/card-button.tsx) | | `MultiStepForm` | Form with multiple steps | `@kit/ui/multi-step-form` [multi-step-form.tsx](mdc:packages/ui/src/makerkit/multi-step-form.tsx) | | `EmptyState` | Empty data placeholder | `@kit/ui/empty-state` [empty-state.tsx](mdc:packages/ui/src/makerkit/empty-state.tsx) | | `AppBreadcrumbs` | Application path breadcrumbs | `@kit/ui/app-breadcrumbs` [app-breadcrumbs.tsx](mdc:packages/ui/src/makerkit/app-breadcrumbs.tsx) | ## Marketing Components Import all marketing components with: ```tsx import { Hero, HeroTitle, GradientText, // etc. } from '@kit/ui/marketing'; ``` Key marketing components: - `Hero` - Hero sections [hero.tsx](mdc:packages/ui/src/makerkit/marketing/hero.tsx) - `SecondaryHero` [secondary-hero.tsx](mdc:packages/ui/src/makerkit/marketing/secondary-hero.tsx) - `FeatureCard`, `FeatureGrid` - Feature showcases [feature-card.tsx](mdc:packages/ui/src/makerkit/marketing/feature-card.tsx) - `Footer` - Page Footer [footer.tsx](mdc:packages/ui/src/makerkit/marketing/footer.tsx) - `Header` - Page Header [header.tsx](mdc:packages/ui/src/makerkit/marketing/header.tsx) - `NewsletterSignup` - Email collection [newsletter-signup-container.tsx](mdc:packages/ui/src/makerkit/marketing/newsletter-signup-container.tsx) - `ComingSoon` - Coming soon page template [coming-soon.tsx](mdc:packages/ui/src/makerkit/marketing/coming-soon.tsx) # Forms - Use React Hook Form for form validation and submission. - Use Zod for form validation. - Use the `zodResolver` function to resolve the Zod schema to the form. - Use Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) for server-side code handling - Use Sonner for writing toasters for UI feedback Follow the example below to create all forms: ## Define the schema Zod schemas should be defined in the `schema` folder and exported, so we can reuse them across a Server Action and the client-side form: ```tsx // _lib/schema/create-note.schema.ts import { z } from 'zod'; export const CreateNoteSchema = z.object({ title: z.string().min(1), content: z.string().min(1), }); ``` ## Create the Server Action Server Actions [server-actions.mdc](mdc:.cursor/rules/server-actions.mdc) can help us create endpoints for our forms. ```tsx 'use server'; import { z } from 'zod'; import { enhanceAction } from '@kit/next/actions'; import { CreateNoteSchema } from '../schema/create-note.schema'; export const createNoteAction = enhanceAction( async function (data, user) { // 1. "data" has been validated against the Zod schema, and it's safe to use // 2. "user" is the authenticated user // ... your code here return { success: true, }; }, { auth: true, schema: CreateNoteSchema, }, ); ``` ## Create the Form Component Then create a client component to handle the form submission: ```tsx 'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Textarea } from '@kit/ui/textarea'; import { Input } from '@kit/ui/input'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form'; import { toast } from '@kit/ui/sonner'; import { useTranslation } from 'react-i18next'; import { CreateNoteSchema } from '../_lib/schema/create-note.schema'; export function CreateNoteForm() { const [pending, startTransition] = useTransition(); const { t } = useTranslation(); const form = useForm({ resolver: zodResolver(CreateNoteSchema), defaultValues: { title: '', content: '', }, }); const onSubmit = (data) => { startTransition(async () => { await toast.promise(createNoteAction(data), { loading: t('notes:creatingNote`), success: t('notes:createNoteSuccess`), error: t('notes:createNoteError`) }) }); }; return (
( Title )} /> ( Content