initial commit

This commit is contained in:
Kar k1
2025-08-30 18:18:57 +05:30
commit 7219108342
270 changed files with 70221 additions and 0 deletions

216
templates/README.md Normal file
View 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

View 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
* }
* }
* }
*/

View 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
View 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
View 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
* }
*/

View 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' },
* ];
* }
*/