321 lines
8.3 KiB
Plaintext
321 lines
8.3 KiB
Plaintext
---
|
|
description: Creating new Pages in the app
|
|
globs: apps/**
|
|
alwaysApply: false
|
|
---
|
|
# 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 (
|
|
<>
|
|
<MyFeatureHeader
|
|
title={<Trans i18nKey={'common:routes.myFeature'} />}
|
|
description={<Trans i18nKey={'common:myFeatureDescription'} />}
|
|
/>
|
|
|
|
<PageBody>
|
|
{/* Main page content */}
|
|
</PageBody>
|
|
</>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<UserWorkspaceContextProvider value={workspace}>
|
|
<Page>
|
|
<PageNavigation>
|
|
<MyFeatureNavigation workspace={workspace} />
|
|
</PageNavigation>
|
|
|
|
{children}
|
|
</Page>
|
|
</UserWorkspaceContextProvider>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<UserWorkspaceContextProvider value={workspace}>
|
|
<Page>
|
|
{/* Navigation components */}
|
|
{children}
|
|
</Page>
|
|
</UserWorkspaceContextProvider>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<TeamAccountWorkspaceContextProvider value={workspace}>
|
|
<Page>
|
|
{/* Navigation components */}
|
|
{children}
|
|
</Page>
|
|
</TeamAccountWorkspaceContextProvider>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<PageHeader
|
|
title={title}
|
|
description={description}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
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 (
|
|
<div>
|
|
{/* Component content */}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 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 (
|
|
<NavigationMenu>
|
|
{/* Navigation items */}
|
|
</NavigationMenu>
|
|
);
|
|
}
|
|
```
|
|
|
|
## 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 `<Trans>` component for translated text
|
|
- Define translation keys in the appropriate namespace in `apps/web/public/locales/<locale>/<namespace>.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 |