initial commit
This commit is contained in:
216
templates/README.md
Normal file
216
templates/README.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Boilerplate Templates
|
||||
|
||||
This directory contains template files to help you quickly create new components, pages, API routes, hooks, and database models with consistent patterns and best practices.
|
||||
|
||||
## Available Templates
|
||||
|
||||
### 1. Page Template (`page.template.tsx`)
|
||||
|
||||
Use this template to create new Next.js pages with proper metadata and SEO setup.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Metadata generation with SEO optimization
|
||||
- Responsive layout structure
|
||||
- Examples for static and dynamic pages
|
||||
- Proper TypeScript types
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Copy the template
|
||||
cp templates/page.template.tsx app/my-new-page/page.tsx
|
||||
|
||||
# Replace TEMPLATE_NAME with your page name
|
||||
# Update metadata and content
|
||||
```
|
||||
|
||||
### 2. Component Template (`component.template.tsx`)
|
||||
|
||||
Create reusable React components with proper TypeScript interfaces and styling patterns.
|
||||
|
||||
**Features:**
|
||||
|
||||
- TypeScript interface definitions
|
||||
- Multiple component patterns (client/server, compound, forwardRef)
|
||||
- Loading states and error handling
|
||||
- Tailwind CSS with custom UI patterns
|
||||
- Accessibility considerations
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Copy the template
|
||||
cp templates/component.template.tsx components/MyComponent.tsx
|
||||
|
||||
# Replace TEMPLATE_NAME and implement your logic
|
||||
```
|
||||
|
||||
### 3. API Route Template (`api-route.template.ts`)
|
||||
|
||||
Build robust API endpoints with validation, authentication, and error handling.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Full CRUD operations (GET, POST, PUT, DELETE)
|
||||
- Zod validation schemas
|
||||
- Authentication middleware integration
|
||||
- Consistent error handling
|
||||
- Request/response TypeScript types
|
||||
- Pagination support
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Copy the template
|
||||
cp templates/api-route.template.ts app/api/my-endpoint/route.ts
|
||||
|
||||
# Update schemas and implement business logic
|
||||
```
|
||||
|
||||
### 4. Custom Hook Template (`hook.template.ts`)
|
||||
|
||||
Create custom React hooks with proper patterns and optimizations.
|
||||
|
||||
**Features:**
|
||||
|
||||
- TanStack Query integration
|
||||
- State management patterns
|
||||
- Debouncing and cleanup
|
||||
- Multiple hook patterns (simple state, localStorage, API)
|
||||
- TypeScript generics
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Copy the template
|
||||
cp templates/hook.template.ts hooks/useMyHook.ts
|
||||
|
||||
# Replace TEMPLATE_NAME and implement your logic
|
||||
```
|
||||
|
||||
### 5. Database Model Template (`model.template.ts`)
|
||||
|
||||
Build Mongoose models with Zod validation and proper TypeScript types.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Zod schema validation
|
||||
- Mongoose schema with indexes
|
||||
- Instance and static methods
|
||||
- Middleware for data processing
|
||||
- Virtual fields
|
||||
- Full TypeScript support
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Copy the template
|
||||
cp templates/model.template.ts models/MyModel.ts
|
||||
|
||||
# Define your schema and methods
|
||||
```
|
||||
|
||||
## Template Conventions
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Files**: Use descriptive names in kebab-case or camelCase
|
||||
- **Components**: Use PascalCase for component names
|
||||
- **Variables**: Use camelCase for variables and functions
|
||||
- **Constants**: Use UPPER_SNAKE_CASE for constants
|
||||
|
||||
### Code Organization
|
||||
|
||||
- Group related imports together
|
||||
- Define types and interfaces at the top
|
||||
- Implement main logic in the middle
|
||||
- Export statements at the bottom
|
||||
- Add JSDoc comments for complex functions
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Always wrap async operations in try-catch blocks
|
||||
- Use proper HTTP status codes for API routes
|
||||
- Provide meaningful error messages
|
||||
- Log errors for debugging
|
||||
|
||||
### TypeScript Best Practices
|
||||
|
||||
- Define proper interfaces for props and return types
|
||||
- Use generics where appropriate
|
||||
- Avoid `any` type - use `unknown` if needed
|
||||
- Leverage union types for variants
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- Always validate input data with Zod schemas
|
||||
- Use authentication middleware for protected routes
|
||||
- Sanitize user inputs
|
||||
- Follow principle of least privilege
|
||||
|
||||
## Quick Start Guide
|
||||
|
||||
1. **Choose the appropriate template** for your use case
|
||||
2. **Copy the template file** to your desired location
|
||||
3. **Replace placeholder names** (TEMPLATE_NAME, etc.)
|
||||
4. **Update imports** to match your project structure
|
||||
5. **Implement your specific logic**
|
||||
6. **Test your implementation**
|
||||
|
||||
## Example Workflow
|
||||
|
||||
Creating a new feature called "Tasks":
|
||||
|
||||
```bash
|
||||
# 1. Create the model
|
||||
cp templates/model.template.ts models/task.ts
|
||||
# Edit: Define Task schema, add methods
|
||||
|
||||
# 2. Create API routes
|
||||
cp templates/api-route.template.ts app/api/tasks/route.ts
|
||||
# Edit: Implement CRUD operations for tasks
|
||||
|
||||
# 3. Create custom hook
|
||||
cp templates/hook.template.ts hooks/useTask.ts
|
||||
# Edit: Add task-specific logic and API calls
|
||||
|
||||
# 4. Create components
|
||||
cp templates/component.template.tsx components/TaskCard.tsx
|
||||
cp templates/component.template.tsx components/TaskList.tsx
|
||||
# Edit: Implement task UI components
|
||||
|
||||
# 5. Create pages
|
||||
cp templates/page.template.tsx app/tasks/page.tsx
|
||||
cp templates/page.template.tsx app/tasks/[id]/page.tsx
|
||||
# Edit: Build task management pages
|
||||
```
|
||||
|
||||
## Template Customization
|
||||
|
||||
Feel free to modify these templates to match your project's specific needs:
|
||||
|
||||
- Add your own validation patterns
|
||||
- Include additional dependencies
|
||||
- Modify styling approaches
|
||||
- Add project-specific utilities
|
||||
- Update error handling patterns
|
||||
|
||||
## Contributing to Templates
|
||||
|
||||
When improving templates:
|
||||
|
||||
1. Keep them generic and reusable
|
||||
2. Add comprehensive comments and examples
|
||||
3. Follow established patterns in the codebase
|
||||
4. Test templates with real use cases
|
||||
5. Update this README with changes
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues with templates or need additional patterns:
|
||||
|
||||
1. Check existing implementations in the codebase
|
||||
2. Review Next.js and React documentation
|
||||
3. Consult TypeScript best practices
|
||||
4. Ask for help in team discussions
|
||||
298
templates/api-route.template.ts
Normal file
298
templates/api-route.template.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* API Route Template
|
||||
*
|
||||
* Usage: Copy this file to create new API routes
|
||||
* 1. Replace TEMPLATE_NAME with your route name
|
||||
* 2. Implement your route handlers (GET, POST, PUT, DELETE)
|
||||
* 3. Add proper validation and error handling
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
|
||||
// Request validation schemas
|
||||
const GetRequestSchema = z.object({
|
||||
// Define query parameters
|
||||
page: z.string().optional().default('1').transform(Number),
|
||||
limit: z.string().optional().default('10').transform(Number),
|
||||
search: z.string().optional(),
|
||||
})
|
||||
|
||||
const PostRequestSchema = z.object({
|
||||
// Define request body structure
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
description: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
const PutRequestSchema = z.object({
|
||||
// Define update request body
|
||||
id: z.string().min(1, 'ID is required'),
|
||||
name: z.string().min(1, 'Name is required').optional(),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
|
||||
// Type definitions
|
||||
type GetParams = z.infer<typeof GetRequestSchema>
|
||||
type PostBody = z.infer<typeof PostRequestSchema>
|
||||
type PutBody = z.infer<typeof PutRequestSchema>
|
||||
|
||||
/**
|
||||
* GET /api/template-route
|
||||
* Retrieve items with pagination and filtering
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Authentication check (remove if public endpoint)
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Parse and validate query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const rawParams = Object.fromEntries(searchParams.entries())
|
||||
const params = GetRequestSchema.parse(rawParams)
|
||||
|
||||
// Implement your business logic here
|
||||
const { page, limit, search } = params
|
||||
|
||||
// Example: Database query
|
||||
// const items = await db.collection.find({
|
||||
// ...(search && { name: { $regex: search, $options: 'i' } }),
|
||||
// })
|
||||
// .skip((page - 1) * limit)
|
||||
// .limit(limit)
|
||||
// .toArray();
|
||||
|
||||
// Mock response
|
||||
const items = [
|
||||
{ id: '1', name: 'Example Item 1', description: 'Description 1' },
|
||||
{ id: '2', name: 'Example Item 2', description: 'Description 2' },
|
||||
]
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
items,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: items.length,
|
||||
totalPages: Math.ceil(items.length / limit),
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('GET /api/template-route error:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid query parameters', details: error.issues },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/template-route
|
||||
* Create a new item
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Authentication check
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Parse and validate request body
|
||||
const rawBody = await request.json()
|
||||
const body = PostRequestSchema.parse(rawBody)
|
||||
|
||||
// Implement your business logic here
|
||||
const { name, description, tags } = body
|
||||
|
||||
// Example: Database insertion
|
||||
// const newItem = await db.collection.insertOne({
|
||||
// name,
|
||||
// description,
|
||||
// tags,
|
||||
// createdBy: user.id,
|
||||
// createdAt: new Date(),
|
||||
// });
|
||||
|
||||
// Mock response
|
||||
const newItem = {
|
||||
id: 'new-item-id',
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
createdBy: user.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: newItem,
|
||||
},
|
||||
{ status: 201 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('POST /api/template-route error:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request body', details: error.issues },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/template-route
|
||||
* Update an existing item
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
// Authentication check
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Parse and validate request body
|
||||
const rawBody = await request.json()
|
||||
const body = PutRequestSchema.parse(rawBody)
|
||||
|
||||
// Implement your business logic here
|
||||
const { id, name, description } = body
|
||||
|
||||
// Example: Database update
|
||||
// const updatedItem = await db.collection.findOneAndUpdate(
|
||||
// { _id: id, createdBy: user.id }, // Ensure user owns the item
|
||||
// {
|
||||
// $set: {
|
||||
// ...(name && { name }),
|
||||
// ...(description && { description }),
|
||||
// updatedAt: new Date(),
|
||||
// }
|
||||
// },
|
||||
// { returnDocument: 'after' }
|
||||
// );
|
||||
|
||||
// if (!updatedItem.value) {
|
||||
// return NextResponse.json(
|
||||
// { error: 'Item not found or unauthorized' },
|
||||
// { status: 404 }
|
||||
// );
|
||||
// }
|
||||
|
||||
// Mock response
|
||||
const updatedItem = {
|
||||
id,
|
||||
name: name || 'Existing Name',
|
||||
description: description || 'Existing Description',
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: updatedItem,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('PUT /api/template-route error:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request body', details: error.issues },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/template-route/[id]
|
||||
* Delete an item
|
||||
*/
|
||||
export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
|
||||
try {
|
||||
// Authentication check
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = params
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: 'ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Implement your business logic here
|
||||
// Example: Database deletion
|
||||
// const deletedItem = await db.collection.findOneAndDelete({
|
||||
// _id: id,
|
||||
// createdBy: user.id, // Ensure user owns the item
|
||||
// });
|
||||
|
||||
// if (!deletedItem.value) {
|
||||
// return NextResponse.json(
|
||||
// { error: 'Item not found or unauthorized' },
|
||||
// { status: 404 }
|
||||
// );
|
||||
// }
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Item deleted successfully',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('DELETE /api/template-route error:', error)
|
||||
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common Response Types:
|
||||
*
|
||||
* Success Response:
|
||||
* {
|
||||
* success: true,
|
||||
* data: any,
|
||||
* message?: string
|
||||
* }
|
||||
*
|
||||
* Error Response:
|
||||
* {
|
||||
* error: string,
|
||||
* details?: any,
|
||||
* code?: string
|
||||
* }
|
||||
*
|
||||
* Pagination Response:
|
||||
* {
|
||||
* success: true,
|
||||
* data: {
|
||||
* items: any[],
|
||||
* pagination: {
|
||||
* page: number,
|
||||
* limit: number,
|
||||
* total: number,
|
||||
* totalPages: number
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
151
templates/component.template.tsx
Normal file
151
templates/component.template.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Component Template
|
||||
*
|
||||
* Usage: Copy this file to create new components
|
||||
* 1. Replace TEMPLATE_NAME with your component name
|
||||
* 2. Define the props interface
|
||||
* 3. Implement your component logic
|
||||
*/
|
||||
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// Component props interface
|
||||
interface TemplateComponentProps {
|
||||
// Define your props here
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
// Add other props as needed
|
||||
title?: string
|
||||
description?: string
|
||||
variant?: 'default' | 'secondary' | 'outline'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* TEMPLATE_NAME Component
|
||||
*
|
||||
* @param props - Component properties
|
||||
* @returns JSX element
|
||||
*/
|
||||
export function TemplateComponent({
|
||||
className,
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
variant = 'default',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
onClick,
|
||||
...props
|
||||
}: TemplateComponentProps) {
|
||||
// State management
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// Event handlers
|
||||
const handleClick = async () => {
|
||||
if (disabled || isLoading) return
|
||||
|
||||
setIsLoading(true)
|
||||
try {
|
||||
await onClick?.()
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// CSS class variations
|
||||
const variants = {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
}
|
||||
|
||||
const sizes = {
|
||||
sm: 'h-8 px-3 text-sm',
|
||||
md: 'h-10 px-4 py-2',
|
||||
lg: 'h-12 px-8 text-lg',
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
// Base styles
|
||||
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
// Variants
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
// Loading state
|
||||
isLoading && 'cursor-not-allowed opacity-70',
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
{...props}
|
||||
>
|
||||
{/* Loading spinner */}
|
||||
{isLoading && (
|
||||
<svg
|
||||
className="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col items-center">
|
||||
{title && <h3 className="font-semibold">{title}</h3>}
|
||||
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative component patterns:
|
||||
*
|
||||
* 1. Server Component (remove 'use client' and useState)
|
||||
* 2. Compound Components:
|
||||
*
|
||||
* export function TemplateComponent({ children }: { children: React.ReactNode }) {
|
||||
* return <div className="template-wrapper">{children}</div>;
|
||||
* }
|
||||
*
|
||||
* TemplateComponent.Header = function Header({ children }: { children: React.ReactNode }) {
|
||||
* return <header className="template-header">{children}</header>;
|
||||
* };
|
||||
*
|
||||
* TemplateComponent.Body = function Body({ children }: { children: React.ReactNode }) {
|
||||
* return <main className="template-body">{children}</main>;
|
||||
* };
|
||||
*
|
||||
* 3. Forward Ref:
|
||||
*
|
||||
* export const TemplateComponent = forwardRef<HTMLDivElement, TemplateComponentProps>(
|
||||
* ({ className, ...props }, ref) => {
|
||||
* return <div ref={ref} className={cn('template-base', className)} {...props} />;
|
||||
* }
|
||||
* );
|
||||
* TemplateComponent.displayName = "TemplateComponent";
|
||||
*/
|
||||
267
templates/hook.template.ts
Normal file
267
templates/hook.template.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Custom Hook Template
|
||||
*
|
||||
* Usage: Copy this file to create new custom hooks
|
||||
* 1. Replace TEMPLATE_NAME with your hook name (use camelCase)
|
||||
* 2. Define the hook parameters and return type
|
||||
* 3. Implement your hook logic
|
||||
*/
|
||||
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
// Hook parameters interface
|
||||
interface UseTemplateHookParams {
|
||||
// Define your parameters here
|
||||
initialValue?: string
|
||||
autoFetch?: boolean
|
||||
onSuccess?: (data: any) => void
|
||||
onError?: (error: Error) => void
|
||||
debounceMs?: number
|
||||
}
|
||||
|
||||
// Hook return type
|
||||
interface UseTemplateHookReturn {
|
||||
// Define what your hook returns
|
||||
data: any
|
||||
isLoading: boolean
|
||||
error: Error | null
|
||||
refetch: () => void
|
||||
mutate: (data: any) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook template
|
||||
*
|
||||
* @param params - Hook parameters
|
||||
* @returns Hook state and functions
|
||||
*/
|
||||
export function useTemplateHook({
|
||||
initialValue = '',
|
||||
autoFetch = true,
|
||||
onSuccess,
|
||||
onError,
|
||||
debounceMs = 300,
|
||||
}: UseTemplateHookParams = {}): UseTemplateHookReturn {
|
||||
// Local state
|
||||
const [data, setData] = useState<any>(initialValue)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
// Refs for cleanup and debouncing
|
||||
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Query for fetching data
|
||||
const query = useQuery({
|
||||
queryKey: ['template-hook', initialValue],
|
||||
queryFn: async () => {
|
||||
// Implement your data fetching logic
|
||||
const response = await fetch('/api/template-endpoint', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
},
|
||||
enabled: autoFetch,
|
||||
})
|
||||
|
||||
// Handle query success/error with useEffect
|
||||
useEffect(() => {
|
||||
if (query.data && !query.isLoading && !query.error) {
|
||||
setData(query.data)
|
||||
onSuccess?.(query.data)
|
||||
}
|
||||
}, [query.data, query.isLoading, query.error, onSuccess])
|
||||
|
||||
useEffect(() => {
|
||||
if (query.error) {
|
||||
setError(query.error)
|
||||
onError?.(query.error)
|
||||
}
|
||||
}, [query.error, onError])
|
||||
|
||||
// Mutation for updating data
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (newData: any) => {
|
||||
const response = await fetch('/api/template-endpoint', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newData),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
// Handle mutation success/error with useEffect
|
||||
useEffect(() => {
|
||||
if (mutation.data && mutation.isSuccess) {
|
||||
// Update cache
|
||||
queryClient.setQueryData(['template-hook', initialValue], mutation.data)
|
||||
setData(mutation.data)
|
||||
onSuccess?.(mutation.data)
|
||||
}
|
||||
}, [mutation.data, mutation.isSuccess, queryClient, initialValue, onSuccess])
|
||||
|
||||
useEffect(() => {
|
||||
if (mutation.error) {
|
||||
setError(mutation.error)
|
||||
onError?.(mutation.error)
|
||||
}
|
||||
}, [mutation.error, onError])
|
||||
|
||||
// Debounced function
|
||||
const debouncedFunction = useCallback(
|
||||
(value: any) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
// Implement debounced logic here
|
||||
console.log('Debounced value:', value)
|
||||
}, debounceMs)
|
||||
},
|
||||
[debounceMs]
|
||||
)
|
||||
|
||||
// Effect for cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Refetch function
|
||||
const refetch = useCallback(() => {
|
||||
query.refetch()
|
||||
}, [query])
|
||||
|
||||
// Mutate function
|
||||
const mutate = useCallback(
|
||||
(newData: any) => {
|
||||
mutation.mutate(newData)
|
||||
},
|
||||
[mutation]
|
||||
)
|
||||
|
||||
// Reset function
|
||||
const reset = useCallback(() => {
|
||||
setData(initialValue)
|
||||
setError(null)
|
||||
queryClient.removeQueries({ queryKey: ['template-hook', initialValue] })
|
||||
}, [initialValue, queryClient])
|
||||
|
||||
return {
|
||||
data: query.data || data,
|
||||
isLoading: query.isLoading || mutation.isPending,
|
||||
error: query.error || mutation.error || error,
|
||||
refetch,
|
||||
mutate,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative hook patterns:
|
||||
*
|
||||
* 1. Simple State Hook:
|
||||
*
|
||||
* export function useSimpleState<T>(initialValue: T) {
|
||||
* const [value, setValue] = useState<T>(initialValue);
|
||||
*
|
||||
* const reset = useCallback(() => setValue(initialValue), [initialValue]);
|
||||
*
|
||||
* return [value, setValue, reset] as const;
|
||||
* }
|
||||
*
|
||||
* 2. Local Storage Hook:
|
||||
*
|
||||
* export function useLocalStorage<T>(key: string, initialValue: T) {
|
||||
* const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
* try {
|
||||
* const item = window.localStorage.getItem(key);
|
||||
* return item ? JSON.parse(item) : initialValue;
|
||||
* } catch (error) {
|
||||
* return initialValue;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* const setValue = (value: T | ((val: T) => T)) => {
|
||||
* try {
|
||||
* const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
* setStoredValue(valueToStore);
|
||||
* window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
* } catch (error) {
|
||||
* console.error('Error saving to localStorage:', error);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* return [storedValue, setValue] as const;
|
||||
* }
|
||||
*
|
||||
* 3. API Hook with SWR pattern:
|
||||
*
|
||||
* export function useApi<T>(url: string, options?: RequestInit) {
|
||||
* const [data, setData] = useState<T | null>(null);
|
||||
* const [loading, setLoading] = useState(true);
|
||||
* const [error, setError] = useState<Error | null>(null);
|
||||
*
|
||||
* useEffect(() => {
|
||||
* let cancelled = false;
|
||||
*
|
||||
* const fetchData = async () => {
|
||||
* try {
|
||||
* setLoading(true);
|
||||
* const response = await fetch(url, options);
|
||||
*
|
||||
* if (!response.ok) {
|
||||
* throw new Error(`HTTP error! status: ${response.status}`);
|
||||
* }
|
||||
*
|
||||
* const result = await response.json();
|
||||
*
|
||||
* if (!cancelled) {
|
||||
* setData(result);
|
||||
* setError(null);
|
||||
* }
|
||||
* } catch (err) {
|
||||
* if (!cancelled) {
|
||||
* setError(err instanceof Error ? err : new Error('Unknown error'));
|
||||
* }
|
||||
* } finally {
|
||||
* if (!cancelled) {
|
||||
* setLoading(false);
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* fetchData();
|
||||
*
|
||||
* return () => {
|
||||
* cancelled = true;
|
||||
* };
|
||||
* }, [url]);
|
||||
*
|
||||
* return { data, loading, error };
|
||||
* }
|
||||
*/
|
||||
342
templates/model.template.ts
Normal file
342
templates/model.template.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Database Model Template
|
||||
*
|
||||
* Usage: Copy this file to create new database models
|
||||
* 1. Replace TEMPLATE_NAME with your model name
|
||||
* 2. Define the Zod schema for validation
|
||||
* 3. Create the Mongoose schema
|
||||
* 4. Add methods and statics as needed
|
||||
*/
|
||||
|
||||
import mongoose, { Document, Model } from 'mongoose'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Zod schema for validation
|
||||
export const TemplateSchema = z.object({
|
||||
// Define your fields here
|
||||
name: z.string().min(1, 'Name is required').max(100, 'Name too long'),
|
||||
description: z.string().optional(),
|
||||
category: z.enum(['type1', 'type2', 'type3']).default('type1'),
|
||||
tags: z.array(z.string()).optional(),
|
||||
isActive: z.boolean().optional().default(true),
|
||||
metadata: z.record(z.string(), z.any()).optional(),
|
||||
|
||||
// Relationships
|
||||
userId: z.string().min(1, 'User ID is required'),
|
||||
parentId: z.string().optional(),
|
||||
|
||||
// Nested objects
|
||||
settings: z
|
||||
.object({
|
||||
visibility: z.enum(['public', 'private', 'unlisted']).default('public'),
|
||||
allowComments: z.boolean().default(true),
|
||||
featured: z.boolean().default(false),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
// Arrays of objects
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
value: z.union([z.string(), z.number()]),
|
||||
order: z.number().default(0),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
|
||||
// Infer TypeScript type from Zod schema
|
||||
export type TemplateType = z.infer<typeof TemplateSchema>
|
||||
|
||||
// Mongoose document interface
|
||||
export interface ITemplate extends Document {
|
||||
name: string
|
||||
description?: string
|
||||
category: 'type1' | 'type2' | 'type3'
|
||||
tags?: string[]
|
||||
isActive: boolean
|
||||
metadata?: Record<string, any>
|
||||
|
||||
// Relationships
|
||||
userId: string
|
||||
parentId?: string
|
||||
|
||||
// Nested objects
|
||||
settings?: {
|
||||
visibility: 'public' | 'private' | 'unlisted'
|
||||
allowComments: boolean
|
||||
featured: boolean
|
||||
}
|
||||
|
||||
// Arrays of objects
|
||||
items?: Array<{
|
||||
title: string
|
||||
value: string | number
|
||||
order: number
|
||||
}>
|
||||
|
||||
// Timestamps (added by Mongoose)
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
|
||||
// Instance methods
|
||||
toPublicJSON(): Partial<ITemplate>
|
||||
isOwnedBy(userId: string): boolean
|
||||
activate(): Promise<ITemplate>
|
||||
deactivate(): Promise<ITemplate>
|
||||
addTag(tag: string): Promise<ITemplate>
|
||||
removeTag(tag: string): Promise<ITemplate>
|
||||
}
|
||||
|
||||
// Mongoose schema
|
||||
const templateSchema = new mongoose.Schema<ITemplate>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 100,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 500,
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
enum: ['type1', 'type2', 'type3'],
|
||||
default: 'type1',
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
type: String,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
},
|
||||
],
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
metadata: {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
default: {},
|
||||
},
|
||||
|
||||
// Relationships
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
parentId: {
|
||||
type: String,
|
||||
index: true,
|
||||
},
|
||||
|
||||
// Nested objects
|
||||
settings: {
|
||||
visibility: {
|
||||
type: String,
|
||||
enum: ['public', 'private', 'unlisted'],
|
||||
default: 'public',
|
||||
},
|
||||
allowComments: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
featured: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
// Arrays of objects
|
||||
items: [
|
||||
{
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
required: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
toJSON: { virtuals: true },
|
||||
toObject: { virtuals: true },
|
||||
}
|
||||
)
|
||||
|
||||
// Indexes for performance
|
||||
templateSchema.index({ userId: 1, isActive: 1 })
|
||||
templateSchema.index({ category: 1, isActive: 1 })
|
||||
templateSchema.index({ tags: 1 })
|
||||
templateSchema.index({ createdAt: -1 })
|
||||
templateSchema.index({ 'settings.featured': 1, isActive: 1 })
|
||||
|
||||
// Text search index
|
||||
templateSchema.index({
|
||||
name: 'text',
|
||||
description: 'text',
|
||||
tags: 'text',
|
||||
})
|
||||
|
||||
// Virtual fields
|
||||
templateSchema.virtual('itemCount').get(function () {
|
||||
return this.items?.length || 0
|
||||
})
|
||||
|
||||
templateSchema.virtual('url').get(function () {
|
||||
return `/templates/${this._id}`
|
||||
})
|
||||
|
||||
// Instance methods
|
||||
templateSchema.methods.toPublicJSON = function (): Partial<ITemplate> {
|
||||
const obj = this.toObject()
|
||||
|
||||
// Remove sensitive fields
|
||||
delete obj.metadata
|
||||
delete obj.__v
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
templateSchema.methods.isOwnedBy = function (userId: string): boolean {
|
||||
return this.userId === userId
|
||||
}
|
||||
|
||||
templateSchema.methods.activate = async function (): Promise<ITemplate> {
|
||||
this.isActive = true
|
||||
return this.save()
|
||||
}
|
||||
|
||||
templateSchema.methods.deactivate = async function (): Promise<ITemplate> {
|
||||
this.isActive = false
|
||||
return this.save()
|
||||
}
|
||||
|
||||
templateSchema.methods.addTag = async function (tag: string): Promise<ITemplate> {
|
||||
const normalizedTag = tag.toLowerCase().trim()
|
||||
if (!this.tags?.includes(normalizedTag)) {
|
||||
if (!this.tags) this.tags = []
|
||||
this.tags.push(normalizedTag)
|
||||
return this.save()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
templateSchema.methods.removeTag = async function (tag: string): Promise<ITemplate> {
|
||||
const normalizedTag = tag.toLowerCase().trim()
|
||||
if (this.tags?.includes(normalizedTag)) {
|
||||
this.tags = this.tags.filter((t) => t !== normalizedTag)
|
||||
return this.save()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// Static methods
|
||||
templateSchema.statics.findByUser = function (userId: string) {
|
||||
return this.find({ userId, isActive: true }).sort({ createdAt: -1 })
|
||||
}
|
||||
|
||||
templateSchema.statics.findFeatured = function (limit: number = 10) {
|
||||
return this.find({
|
||||
'settings.featured': true,
|
||||
isActive: true,
|
||||
'settings.visibility': 'public',
|
||||
})
|
||||
.limit(limit)
|
||||
.sort({ createdAt: -1 })
|
||||
}
|
||||
|
||||
templateSchema.statics.findByCategory = function (category: string) {
|
||||
return this.find({
|
||||
category,
|
||||
isActive: true,
|
||||
'settings.visibility': 'public',
|
||||
}).sort({ createdAt: -1 })
|
||||
}
|
||||
|
||||
templateSchema.statics.search = function (query: string, limit: number = 20) {
|
||||
return this.find(
|
||||
{
|
||||
$text: { $search: query },
|
||||
isActive: true,
|
||||
'settings.visibility': 'public',
|
||||
},
|
||||
{ score: { $meta: 'textScore' } }
|
||||
)
|
||||
.sort({ score: { $meta: 'textScore' } })
|
||||
.limit(limit)
|
||||
}
|
||||
|
||||
// Pre-save middleware
|
||||
templateSchema.pre('save', function (next) {
|
||||
// Normalize tags
|
||||
if (this.tags) {
|
||||
this.tags = this.tags.map((tag) => tag.toLowerCase().trim()).filter(Boolean)
|
||||
// Remove duplicates
|
||||
this.tags = [...new Set(this.tags)]
|
||||
}
|
||||
|
||||
// Sort items by order
|
||||
if (this.items && this.items.length > 0) {
|
||||
this.items.sort((a, b) => a.order - b.order)
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
// Post-save middleware
|
||||
templateSchema.post('save', function (doc) {
|
||||
console.log(`Template saved: ${doc.name} (${doc._id})`)
|
||||
})
|
||||
|
||||
// Model interface with static methods
|
||||
interface ITemplateModel extends Model<ITemplate> {
|
||||
findByUser(userId: string): Promise<ITemplate[]>
|
||||
findFeatured(limit?: number): Promise<ITemplate[]>
|
||||
findByCategory(category: string): Promise<ITemplate[]>
|
||||
search(query: string, limit?: number): Promise<ITemplate[]>
|
||||
}
|
||||
|
||||
// Export the model
|
||||
export const Template = (mongoose.models.Template ||
|
||||
mongoose.model<ITemplate, ITemplateModel>('Template', templateSchema)) as ITemplateModel
|
||||
|
||||
/**
|
||||
* Usage Examples:
|
||||
*
|
||||
* 1. Create a new template:
|
||||
* const template = new Template({
|
||||
* name: 'My Template',
|
||||
* userId: 'user123',
|
||||
* category: 'type1',
|
||||
* });
|
||||
* await template.save();
|
||||
*
|
||||
* 2. Find user's templates:
|
||||
* const templates = await Template.findByUser('user123');
|
||||
*
|
||||
* 3. Search templates:
|
||||
* const results = await Template.search('react component');
|
||||
*
|
||||
* 4. Validate data before saving:
|
||||
* try {
|
||||
* const validData = TemplateSchema.parse(inputData);
|
||||
* const template = new Template(validData);
|
||||
* await template.save();
|
||||
* } catch (error) {
|
||||
* // Handle validation error
|
||||
* }
|
||||
*/
|
||||
66
templates/page.template.tsx
Normal file
66
templates/page.template.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Page Template
|
||||
*
|
||||
* Usage: Copy this file to create new pages
|
||||
* 1. Replace TEMPLATE_NAME with your page name
|
||||
* 2. Update the metadata
|
||||
* 3. Implement your page logic
|
||||
*/
|
||||
|
||||
import { Metadata } from 'next'
|
||||
import { generateMetadata } from '@/lib/seo'
|
||||
|
||||
// Page metadata
|
||||
export const metadata: Metadata = generateMetadata({
|
||||
title: 'TEMPLATE_NAME - NextJS Boilerplate',
|
||||
description: 'Description for TEMPLATE_NAME page',
|
||||
// Add other SEO properties as needed
|
||||
})
|
||||
|
||||
// Page component
|
||||
export default function TemplatePage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">TEMPLATE_NAME</h1>
|
||||
<p className="text-lg text-muted-foreground">Page description goes here</p>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="space-y-8">
|
||||
{/* Add your content sections here */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-semibold mb-4">Section Title</h2>
|
||||
<p className="text-muted-foreground">Section content goes here</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* For dynamic pages, you can export generateStaticParams or use dynamic segments
|
||||
*
|
||||
* Example for dynamic routes:
|
||||
*
|
||||
* interface Props {
|
||||
* params: { slug: string };
|
||||
* }
|
||||
*
|
||||
* export default function DynamicPage({ params }: Props) {
|
||||
* const { slug } = params;
|
||||
* // Page logic here
|
||||
* }
|
||||
*
|
||||
* // For static generation
|
||||
* export async function generateStaticParams() {
|
||||
* // Return array of params
|
||||
* return [
|
||||
* { slug: 'example-1' },
|
||||
* { slug: 'example-2' },
|
||||
* ];
|
||||
* }
|
||||
*/
|
||||
Reference in New Issue
Block a user