From 4f41304be4f0e404a714cfdb65bf972f2f0132f5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Tue, 22 Apr 2025 06:36:34 +0700 Subject: [PATCH] Allow super admin to create users and reset password (#238) 1. Add user creation and password reset dialog functionalities; added Junie guidelines Introduced new `AdminCreateUserDialog` and `AdminResetPasswordDialog` components for managing user accounts in the admin panel. Updated the `AdminAccountsTable` page with a button for user creation and implemented backend logic for password resets with robust error handling. 2. Added Jetbrains AI guidelines --- .aiignore | 27 + .junie/guidelines.md | 1715 +++++++++++++++++ apps/web/app/admin/accounts/page.tsx | 10 +- .../src/components/admin-accounts-table.tsx | 87 +- .../components/admin-create-user-dialog.tsx | 178 ++ .../admin-impersonate-user-dialog.tsx | 13 +- .../admin-reset-password-dialog.tsx | 163 ++ .../src/lib/server/admin-server-actions.ts | 80 +- .../lib/server/schema/create-user.schema.ts | 11 + .../server/schema/reset-password.schema.ts | 9 + .../services/admin-auth-user.service.ts | 45 + 11 files changed, 2291 insertions(+), 47 deletions(-) create mode 100644 .aiignore create mode 100644 .junie/guidelines.md create mode 100644 packages/features/admin/src/components/admin-create-user-dialog.tsx create mode 100644 packages/features/admin/src/components/admin-reset-password-dialog.tsx create mode 100644 packages/features/admin/src/lib/server/schema/create-user.schema.ts create mode 100644 packages/features/admin/src/lib/server/schema/reset-password.schema.ts diff --git a/.aiignore b/.aiignore new file mode 100644 index 000000000..7d17f6fe3 --- /dev/null +++ b/.aiignore @@ -0,0 +1,27 @@ +# An .aiignore file follows the same syntax as a .gitignore file. +# .gitignore documentation: https://git-scm.com/docs/gitignore + +# you can ignore files +.DS_Store +*.log +*.tmp + +# or folders +dist/ +build/ +out/ + +.cursor +.cursorignore +database.types.ts +playwright-report +test-results +web/supabase/migrations +pnpm-lock.yaml +.env.local +.env.production.local +.idea +.vscode +.zed +tsconfig.tsbuildinfo +.windsurfrules \ No newline at end of file diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 000000000..1074a3018 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1,1715 @@ +# 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 + + + +