Initial commit
This commit is contained in:
152
agents/frontend-architect/templates/component-template.tsx
Normal file
152
agents/frontend-architect/templates/component-template.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* React Component Template
|
||||
*
|
||||
* This template provides a starting point for creating well-structured,
|
||||
* type-safe React components following best practices.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this template to your components directory
|
||||
* 2. Rename file and component
|
||||
* 3. Define props interface
|
||||
* 4. Implement component logic
|
||||
* 5. Add tests and Storybook stories
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils'; // Utility for className merging (clsx + tailwind-merge)
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Props for the ComponentName component
|
||||
*
|
||||
* @property prop1 - Description of prop1
|
||||
* @property prop2 - Description of prop2 (optional)
|
||||
* @property className - Additional CSS classes (optional)
|
||||
* @property children - React children (optional)
|
||||
*/
|
||||
export interface ComponentNameProps {
|
||||
/** Required prop with specific type */
|
||||
prop1: string;
|
||||
|
||||
/** Optional prop with default value */
|
||||
prop2?: number;
|
||||
|
||||
/** Callback function */
|
||||
onAction?: (value: string) => void;
|
||||
|
||||
/** Additional CSS classes for customization */
|
||||
className?: string;
|
||||
|
||||
/** React children */
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONSTANTS
|
||||
// ============================================================================
|
||||
|
||||
const DEFAULT_PROP2 = 42;
|
||||
|
||||
// ============================================================================
|
||||
// COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ComponentName - Brief description of what this component does
|
||||
*
|
||||
* More detailed description of the component's purpose, use cases,
|
||||
* and any important implementation notes.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ComponentName
|
||||
* prop1="value"
|
||||
* prop2={100}
|
||||
* onAction={(value) => console.log(value)}
|
||||
* >
|
||||
* Content
|
||||
* </ComponentName>
|
||||
* ```
|
||||
*/
|
||||
export const ComponentName = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
ComponentNameProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
prop1,
|
||||
prop2 = DEFAULT_PROP2,
|
||||
onAction,
|
||||
className,
|
||||
children,
|
||||
...restProps
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
// ========================================================================
|
||||
// STATE
|
||||
// ========================================================================
|
||||
|
||||
const [internalState, setInternalState] = React.useState<string>('');
|
||||
|
||||
// ========================================================================
|
||||
// EFFECTS
|
||||
// ========================================================================
|
||||
|
||||
React.useEffect(() => {
|
||||
// Side effects here
|
||||
}, [prop1]);
|
||||
|
||||
// ========================================================================
|
||||
// HANDLERS
|
||||
// ========================================================================
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (onAction) {
|
||||
onAction(internalState);
|
||||
}
|
||||
}, [internalState, onAction]);
|
||||
|
||||
// ========================================================================
|
||||
// RENDER
|
||||
// ========================================================================
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
// Base styles
|
||||
'rounded-lg border bg-white p-4 shadow-sm',
|
||||
// Conditional styles
|
||||
prop2 > 50 && 'border-blue-500',
|
||||
// Custom className
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<h2 className="text-lg font-semibold">{prop1}</h2>
|
||||
<p className="text-sm text-gray-600">Value: {prop2}</p>
|
||||
|
||||
{children && <div className="mt-4">{children}</div>}
|
||||
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="mt-4 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
>
|
||||
Click Me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ComponentName.displayName = 'ComponentName';
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export default ComponentName;
|
||||
311
agents/frontend-architect/templates/hook-template.ts
Normal file
311
agents/frontend-architect/templates/hook-template.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Custom React Hook Template
|
||||
*
|
||||
* This template provides a starting point for creating reusable
|
||||
* custom hooks following React best practices.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this template to your hooks directory
|
||||
* 2. Rename file and hook
|
||||
* 3. Define input parameters and return type
|
||||
* 4. Implement hook logic
|
||||
* 5. Add tests and documentation
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for the useCustomHook
|
||||
*/
|
||||
export interface UseCustomHookOptions {
|
||||
/** Enable/disable the hook */
|
||||
enabled?: boolean;
|
||||
|
||||
/** Callback when data is loaded */
|
||||
onSuccess?: (data: string) => void;
|
||||
|
||||
/** Callback when error occurs */
|
||||
onError?: (error: Error) => void;
|
||||
|
||||
/** Retry count */
|
||||
retryCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type of useCustomHook
|
||||
*/
|
||||
export interface UseCustomHookResult {
|
||||
/** Current data */
|
||||
data: string | null;
|
||||
|
||||
/** Loading state */
|
||||
isLoading: boolean;
|
||||
|
||||
/** Error state */
|
||||
error: Error | null;
|
||||
|
||||
/** Refetch function */
|
||||
refetch: () => void;
|
||||
|
||||
/** Reset function */
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONSTANTS
|
||||
// ============================================================================
|
||||
|
||||
const DEFAULT_OPTIONS: Required<UseCustomHookOptions> = {
|
||||
enabled: true,
|
||||
onSuccess: () => {},
|
||||
onError: () => {},
|
||||
retryCount: 3,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HOOK
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useCustomHook - Brief description of what this hook does
|
||||
*
|
||||
* More detailed description of the hook's purpose, use cases,
|
||||
* and any important implementation notes.
|
||||
*
|
||||
* @param param1 - Description of param1
|
||||
* @param options - Configuration options
|
||||
* @returns Hook result with data, loading, error states and utility functions
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function MyComponent() {
|
||||
* const { data, isLoading, error, refetch } = useCustomHook('value', {
|
||||
* enabled: true,
|
||||
* onSuccess: (data) => console.log('Success:', data),
|
||||
* });
|
||||
*
|
||||
* if (isLoading) return <Spinner />;
|
||||
* if (error) return <Error message={error.message} />;
|
||||
*
|
||||
* return <div>{data}</div>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useCustomHook(
|
||||
param1: string,
|
||||
options: UseCustomHookOptions = {}
|
||||
): UseCustomHookResult {
|
||||
// ========================================================================
|
||||
// OPTIONS
|
||||
// ========================================================================
|
||||
|
||||
const {
|
||||
enabled = DEFAULT_OPTIONS.enabled,
|
||||
onSuccess = DEFAULT_OPTIONS.onSuccess,
|
||||
onError = DEFAULT_OPTIONS.onError,
|
||||
retryCount = DEFAULT_OPTIONS.retryCount,
|
||||
} = options;
|
||||
|
||||
// ========================================================================
|
||||
// STATE
|
||||
// ========================================================================
|
||||
|
||||
const [data, setData] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
// ========================================================================
|
||||
// REFS
|
||||
// ========================================================================
|
||||
|
||||
// Use refs for values that shouldn't trigger re-renders
|
||||
const retryCountRef = useRef(0);
|
||||
const mountedRef = useRef(true);
|
||||
|
||||
// ========================================================================
|
||||
// CLEANUP
|
||||
// ========================================================================
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ========================================================================
|
||||
// FETCH LOGIC
|
||||
// ========================================================================
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
if (!enabled) return;
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Simulate async operation
|
||||
const result = await new Promise<string>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (Math.random() > 0.8) {
|
||||
reject(new Error('Random error'));
|
||||
} else {
|
||||
resolve(`Data for ${param1}`);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Only update state if component is still mounted
|
||||
if (mountedRef.current) {
|
||||
setData(result);
|
||||
setIsLoading(false);
|
||||
onSuccess(result);
|
||||
retryCountRef.current = 0; // Reset retry count on success
|
||||
}
|
||||
} catch (err) {
|
||||
if (mountedRef.current) {
|
||||
const error = err instanceof Error ? err : new Error('Unknown error');
|
||||
|
||||
// Retry logic
|
||||
if (retryCountRef.current < retryCount) {
|
||||
retryCountRef.current++;
|
||||
setTimeout(() => fetchData(), 1000 * retryCountRef.current); // Exponential backoff
|
||||
} else {
|
||||
setError(error);
|
||||
setIsLoading(false);
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [enabled, param1, onSuccess, onError, retryCount]);
|
||||
|
||||
// ========================================================================
|
||||
// EFFECTS
|
||||
// ========================================================================
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
// Cancel any pending operations
|
||||
};
|
||||
}, [enabled, fetchData]);
|
||||
|
||||
// ========================================================================
|
||||
// HANDLERS
|
||||
// ========================================================================
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
retryCountRef.current = 0; // Reset retry count
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setData(null);
|
||||
setError(null);
|
||||
setIsLoading(false);
|
||||
retryCountRef.current = 0;
|
||||
}, []);
|
||||
|
||||
// ========================================================================
|
||||
// RETURN
|
||||
// ========================================================================
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPER HOOKS (Optional)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useDebounce - Debounce a value
|
||||
*
|
||||
* Useful for search inputs, window resize handlers, etc.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const [searchTerm, setSearchTerm] = useState('');
|
||||
* const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
*
|
||||
* useEffect(() => {
|
||||
* // API call with debounced value
|
||||
* searchAPI(debouncedSearchTerm);
|
||||
* }, [debouncedSearchTerm]);
|
||||
* ```
|
||||
*/
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* useLocalStorage - Sync state with localStorage
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const [theme, setTheme] = useLocalStorage('theme', 'light');
|
||||
* ```
|
||||
*/
|
||||
export function useLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T
|
||||
): [T, (value: T | ((val: T) => T)) => void] {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch (error) {
|
||||
console.error(`Error reading localStorage key "${key}":`, error);
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = useCallback(
|
||||
(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 setting localStorage key "${key}":`, error);
|
||||
}
|
||||
},
|
||||
[key, storedValue]
|
||||
);
|
||||
|
||||
return [storedValue, setValue];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export default useCustomHook;
|
||||
228
agents/frontend-architect/templates/page-template.tsx
Normal file
228
agents/frontend-architect/templates/page-template.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Next.js App Router Page Template
|
||||
*
|
||||
* This template demonstrates best practices for creating pages
|
||||
* in Next.js 14+ App Router with Server Components.
|
||||
*
|
||||
* Key Features:
|
||||
* - Server Component by default (no 'use client' needed)
|
||||
* - SEO metadata generation
|
||||
* - Suspense boundaries for streaming
|
||||
* - Error handling
|
||||
* - TypeScript types
|
||||
*/
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
// Components
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { DataTable } from '@/components/organisms/DataTable';
|
||||
import { Skeleton } from '@/components/atoms/Skeleton';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
searchParams: {
|
||||
page?: string;
|
||||
sort?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// METADATA (SEO)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate static metadata for SEO
|
||||
*
|
||||
* This function runs on the server and generates metadata
|
||||
* for search engines and social media.
|
||||
*/
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: PageProps): Promise<Metadata> {
|
||||
// Fetch data needed for metadata
|
||||
const data = await fetchData(params.id);
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
title: 'Not Found',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${data.title} | Your App Name`,
|
||||
description: data.description,
|
||||
openGraph: {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
images: [data.imageUrl],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
images: [data.imageUrl],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional: Generate static paths for Static Site Generation (SSG)
|
||||
*
|
||||
* Uncomment this function if you want to pre-render pages at build time.
|
||||
*/
|
||||
// export async function generateStaticParams() {
|
||||
// const items = await fetchAllItems();
|
||||
//
|
||||
// return items.map((item) => ({
|
||||
// id: item.id,
|
||||
// }));
|
||||
// }
|
||||
|
||||
// ============================================================================
|
||||
// DATA FETCHING
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch data on the server
|
||||
*
|
||||
* This runs on the server, so you can safely access databases,
|
||||
* private APIs, etc.
|
||||
*/
|
||||
async function fetchData(id: string) {
|
||||
const response = await fetch(`https://api.example.com/items/${id}`, {
|
||||
next: {
|
||||
revalidate: 3600, // ISR: Revalidate every hour
|
||||
// OR use 'force-cache' for static generation
|
||||
// OR use 'no-store' for dynamic rendering
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function fetchRelatedData(filters: { page: number; sort: string }) {
|
||||
const response = await fetch(
|
||||
`https://api.example.com/related?page=${filters.page}&sort=${filters.sort}`,
|
||||
{
|
||||
next: { revalidate: 60 }, // Revalidate every minute
|
||||
}
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PAGE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Page Component (Server Component by default)
|
||||
*
|
||||
* Server Components:
|
||||
* - Run on the server only
|
||||
* - Can access backend resources directly
|
||||
* - Don't add JavaScript to the client bundle
|
||||
* - Can't use hooks like useState, useEffect
|
||||
* - Can't add event handlers
|
||||
*/
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
// ========================================================================
|
||||
// DATA FETCHING (Server-side)
|
||||
// ========================================================================
|
||||
|
||||
// Parse search params
|
||||
const page = searchParams.page ? parseInt(searchParams.page) : 1;
|
||||
const sort = searchParams.sort || 'desc';
|
||||
|
||||
// Fetch data in parallel for performance
|
||||
const [mainData, relatedData] = await Promise.all([
|
||||
fetchData(params.id),
|
||||
fetchRelatedData({ page, sort }),
|
||||
]);
|
||||
|
||||
// Handle not found
|
||||
if (!mainData) {
|
||||
notFound(); // Renders app/not-found.tsx
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// RENDER
|
||||
// ========================================================================
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* Page Header */}
|
||||
<PageHeader
|
||||
title={mainData.title}
|
||||
description={mainData.description}
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Items', href: '/items' },
|
||||
{ label: mainData.title, href: `/items/${params.id}` },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl font-bold">Details</h2>
|
||||
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<p className="text-gray-600">ID: {mainData.id}</p>
|
||||
<p className="text-gray-600">Created: {mainData.createdAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Related Data with Suspense */}
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-bold">Related Items</h2>
|
||||
<Suspense fallback={<Skeleton className="mt-4 h-96" />}>
|
||||
<DataTable
|
||||
data={relatedData.items}
|
||||
columns={['id', 'name', 'status']}
|
||||
pagination={{
|
||||
currentPage: page,
|
||||
totalPages: relatedData.totalPages,
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CLIENT COMPONENT EXAMPLE (if needed)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* If you need interactivity, create a separate Client Component:
|
||||
*
|
||||
* 'use client';
|
||||
*
|
||||
* export function InteractiveSection({ initialData }) {
|
||||
* const [state, setState] = useState(initialData);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <button onClick={() => setState(...)}>Click me</button>
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* Then use it in the Server Component:
|
||||
* <InteractiveSection initialData={mainData} />
|
||||
*/
|
||||
Reference in New Issue
Block a user