From 1aac14f44b89e632dc0b315a62479cdd43e6f280 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Jul 2025 09:01:11 +0530 Subject: [PATCH] Implement individual agent architecture with simplified template structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace legacy agents system with modular individual agent apps - Add agent_base framework for BaseAgent, processors, and management commands - Create weather_reporter as example individual agent with API integration - Implement simplified template structure: agent_name/templates/detail.html - Fix marketplace to display actual agents instead of placeholder - Add proper authentication flow with login redirect for agent access - Organize project structure: move tests to tests/, docs to docs/ - Update all documentation to reflect new simplified architecture - Fix URL namespace issues throughout templates and views šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ...te Next.js-Supabase Development Guide 2025 | 1535 ----------------- Simplified Hardcoded Agent Pages Plan | 119 -- agent_base/__init__.py | 1 + agent_base/apps.py | 7 + {agents => agent_base/management}/__init__.py | 0 .../management/commands}/__init__.py | 0 .../management/commands/create_agent.py | 243 +++ .../management/commands/test_webhook.py | 53 + .../migrations/0001_initial.py | 15 +- .../migrations}/__init__.py | 0 agent_base/models.py | 86 + agent_base/processors.py | 333 ++++ agents/admin.py | 26 - agents/agent_processors.py | 132 -- agents/apps.py | 6 - agents/management/commands/populate_agents.py | 91 - agents/models.py | 41 - agents/tests.py | 3 - agents/views.py | 3 - authentication/urls.py | 2 + authentication/views.py | 10 +- core/urls.py | 4 +- core/views.py | 166 +- docs/AGENT_SETUP_CHECKLIST.md | 283 +++ docs/CLAUDE.md | 350 ++++ .../DJANGO_RECREATION_GUIDE.md | 0 docs/MANUAL_AGENT_CREATION_GUIDE.md | 896 ++++++++++ docs/STRUCTURE_UPDATES.md | 71 + future agent creation | 48 - netcop_hub/settings.py | 7 +- netcop_hub/urls.py | 3 +- templates/authentication/login.html | 7 +- templates/authentication/profile.html | 14 +- templates/authentication/register.html | 4 +- templates/core/agent_detail.html | 10 +- templates/core/homepage.html | 101 +- templates/core/marketplace.html | 118 ++ templates/core/wallet.html | 12 +- templates/core/wallet_topup.html | 12 +- test_views.py | 34 - tests/check_agents.py | 30 + tests/simple_test.py | 85 + tests/test_final_webhook.py | 95 + tests/test_five_whys_final.py | 153 ++ tests/test_five_whys_webhook.py | 144 ++ tests/test_homepage.py | 74 + tests/test_job_posting_webhook.py | 136 ++ tests/test_weather_agent.py | 78 + tests/test_webhook.py | 115 ++ weather_reporter/__init__.py | 1 + weather_reporter/admin.py | 20 + weather_reporter/apps.py | 7 + weather_reporter/migrations/0001_initial.py | 57 + .../migrations/__init__.py | 0 weather_reporter/models.py | 27 + weather_reporter/processor.py | 127 ++ weather_reporter/templates/detail.html | 933 ++++++++++ weather_reporter/urls.py | 10 + weather_reporter/views.py | 126 ++ 59 files changed, 4819 insertions(+), 2245 deletions(-) delete mode 100644 Complete Next.js-Supabase Development Guide 2025 delete mode 100644 Simplified Hardcoded Agent Pages Plan create mode 100644 agent_base/__init__.py create mode 100644 agent_base/apps.py rename {agents => agent_base/management}/__init__.py (100%) rename {agents/management => agent_base/management/commands}/__init__.py (100%) create mode 100644 agent_base/management/commands/create_agent.py create mode 100644 agent_base/management/commands/test_webhook.py rename {agents => agent_base}/migrations/0001_initial.py (69%) rename {agents/management/commands => agent_base/migrations}/__init__.py (100%) create mode 100644 agent_base/models.py create mode 100644 agent_base/processors.py delete mode 100644 agents/admin.py delete mode 100644 agents/agent_processors.py delete mode 100644 agents/apps.py delete mode 100644 agents/management/commands/populate_agents.py delete mode 100644 agents/models.py delete mode 100644 agents/tests.py delete mode 100644 agents/views.py create mode 100644 docs/AGENT_SETUP_CHECKLIST.md create mode 100644 docs/CLAUDE.md rename DJANGO_RECREATION_GUIDE.md => docs/DJANGO_RECREATION_GUIDE.md (100%) create mode 100644 docs/MANUAL_AGENT_CREATION_GUIDE.md create mode 100644 docs/STRUCTURE_UPDATES.md delete mode 100644 future agent creation create mode 100644 templates/core/marketplace.html delete mode 100644 test_views.py create mode 100644 tests/check_agents.py create mode 100644 tests/simple_test.py create mode 100644 tests/test_final_webhook.py create mode 100644 tests/test_five_whys_final.py create mode 100644 tests/test_five_whys_webhook.py create mode 100644 tests/test_homepage.py create mode 100644 tests/test_job_posting_webhook.py create mode 100644 tests/test_weather_agent.py create mode 100644 tests/test_webhook.py create mode 100644 weather_reporter/__init__.py create mode 100644 weather_reporter/admin.py create mode 100644 weather_reporter/apps.py create mode 100644 weather_reporter/migrations/0001_initial.py rename {agents => weather_reporter}/migrations/__init__.py (100%) create mode 100644 weather_reporter/models.py create mode 100644 weather_reporter/processor.py create mode 100644 weather_reporter/templates/detail.html create mode 100644 weather_reporter/urls.py create mode 100644 weather_reporter/views.py diff --git a/Complete Next.js-Supabase Development Guide 2025 b/Complete Next.js-Supabase Development Guide 2025 deleted file mode 100644 index 0d3ee14..0000000 --- a/Complete Next.js-Supabase Development Guide 2025 +++ /dev/null @@ -1,1535 +0,0 @@ -# Complete Next.js + Supabase Development Guide 2025 - -**A comprehensive, standalone guide for building production-ready applications with Next.js 15 and Supabase** - -This guide covers everything from project setup to deployment, including authentication, database operations, TypeScript integration, and modern best practices. Perfect for beginners and as a reference for experienced developers. - ---- - -## Table of Contents -1. [Prerequisites & Initial Setup](#prerequisites--initial-setup) -2. [Project Creation & Configuration](#project-creation--configuration) -3. [Supabase Integration](#supabase-integration) -4. [Project Structure](#project-structure) -5. [Database Schema & Security](#database-schema--security) -6. [Authentication Implementation](#authentication-implementation) -7. [CRUD Operations](#crud-operations) -8. [UI Components & Styling](#ui-components--styling) -9. [Error Handling & Loading States](#error-handling--loading-states) -10. [TypeScript Configuration](#typescript-configuration) -11. [Deployment & Production](#deployment--production) -12. [Complete Code Examples](#complete-code-examples) - ---- - -## Prerequisites & Initial Setup - -### System Requirements -- **Node.js**: 18.17.0 or higher -- **npm**: 9.0.0 or higher (or yarn/pnpm equivalent) -- **Git**: For version control -- **Code Editor**: VS Code recommended - -### Version Requirements (2025) -- **Next.js**: 15.x (latest) -- **React**: 19 RC -- **Supabase JS**: 2.50.1+ -- **TypeScript**: 5.1.3+ -- **Tailwind CSS**: 4.x - -### Supabase Account Setup -1. Go to [supabase.com](https://supabase.com) -2. Sign up for a free account -3. Create a new project -4. Choose a database password (save it securely) -5. Wait for project initialization (~2 minutes) - ---- - -## Project Creation & Configuration - -### 1. Create Next.js Application - -```bash -npx create-next-app@latest my-supabase-app -``` - -**Choose these options when prompted:** -``` -āœ“ Would you like to use TypeScript? → Yes -āœ“ Would you like to use ESLint? → Yes -āœ“ Would you like to use Tailwind CSS? → Yes -āœ“ Would you like to use `src/` directory? → Yes -āœ“ Would you like to use App Router? → Yes -āœ“ Would you like to use Turbopack for `next dev`? → Yes -āœ“ Would you like to customize the default import alias (@/*)? → Yes -``` - -### 2. Navigate to Project & Install Dependencies - -```bash -cd my-supabase-app -npm install @supabase/supabase-js @supabase/ssr -``` - -**Important**: Don't install `@supabase/auth-helpers-nextjs` - it's deprecated. Use `@supabase/ssr` instead. - -### 3. Environment Variables Setup - -Create `.env.local` in your project root: - -```env -# Supabase Configuration -NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key - -# Optional: For admin operations (keep secure, never expose to client) -SUPABASE_SERVICE_ROLE_KEY=your_service_role_key -``` - -**To find your Supabase credentials:** -1. Go to your Supabase project dashboard -2. Click on "Settings" → "API" -3. Copy "Project URL" and "anon public" key - ---- - -## Supabase Integration - -### 1. Client-Side Supabase Client - -Create `src/lib/supabase/client.ts`: - -```typescript -import { createBrowserClient } from '@supabase/ssr' - -export function createClient() { - return createBrowserClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! - ) -} -``` - -### 2. Server-Side Supabase Client - -Create `src/lib/supabase/server.ts`: - -```typescript -import { createServerClient } from '@supabase/ssr' -import { cookies } from 'next/headers' - -export async function createClient() { - const cookieStore = await cookies() - - return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - getAll() { - return cookieStore.getAll() - }, - setAll(cookiesToSet) { - try { - cookiesToSet.forEach(({ name, value, options }) => { - cookieStore.set(name, value, options) - }) - } catch { - // Server component case - cookies can't be set - } - }, - }, - } - ) -} -``` - -### 3. Session Management Middleware - -Create `middleware.ts` in your project root (same level as `package.json`): - -```typescript -import { type NextRequest } from 'next/server' -import { updateSession } from '@/lib/supabase/middleware' - -export async function middleware(request: NextRequest) { - return await updateSession(request) -} - -export const config = { - matcher: [ - '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', - ], -} -``` - -Create `src/lib/supabase/middleware.ts`: - -```typescript -import { createServerClient } from '@supabase/ssr' -import { NextResponse, type NextRequest } from 'next/server' - -export async function updateSession(request: NextRequest) { - let supabaseResponse = NextResponse.next({ - request, - }) - - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - getAll() { - return request.cookies.getAll() - }, - setAll(cookiesToSet) { - cookiesToSet.forEach(({ name, value, options }) => { - request.cookies.set(name, value) - supabaseResponse.cookies.set(name, value, options) - }) - }, - }, - } - ) - - // Refresh session if needed - const { data: { user } } = await supabase.auth.getUser() - - return supabaseResponse -} -``` - ---- - -## Project Structure - -### Recommended File Organization - -``` -my-supabase-app/ -ā”œā”€ā”€ src/ -│ ā”œā”€ā”€ app/ # App Router (Next.js 13+) -│ │ ā”œā”€ā”€ globals.css # Global styles -│ │ ā”œā”€ā”€ layout.tsx # Root layout -│ │ ā”œā”€ā”€ page.tsx # Home page -│ │ ā”œā”€ā”€ loading.tsx # Global loading UI -│ │ ā”œā”€ā”€ error.tsx # Global error boundary -│ │ ā”œā”€ā”€ not-found.tsx # 404 page -│ │ │ -│ │ ā”œā”€ā”€ (auth)/ # Route groups for organization -│ │ │ ā”œā”€ā”€ login/ -│ │ │ │ ā”œā”€ā”€ page.tsx -│ │ │ │ ā”œā”€ā”€ actions.ts # Server actions -│ │ │ │ └── loading.tsx -│ │ │ └── signup/ -│ │ │ └── page.tsx -│ │ │ -│ │ ā”œā”€ā”€ dashboard/ # Protected routes -│ │ │ ā”œā”€ā”€ layout.tsx # Dashboard layout -│ │ │ ā”œā”€ā”€ page.tsx -│ │ │ ā”œā”€ā”€ actions.ts # Server actions for dashboard -│ │ │ ā”œā”€ā”€ loading.tsx -│ │ │ ā”œā”€ā”€ error.tsx -│ │ │ └── tasks/ -│ │ │ └── page.tsx -│ │ │ -│ │ └── api/ # API routes -│ │ └── auth/ -│ │ └── confirm/ -│ │ └── route.ts # Email confirmation -│ │ -│ ā”œā”€ā”€ components/ # Reusable components -│ │ ā”œā”€ā”€ ui/ # Basic UI components (shadcn/ui) -│ │ │ ā”œā”€ā”€ button.tsx -│ │ │ ā”œā”€ā”€ card.tsx -│ │ │ ā”œā”€ā”€ input.tsx -│ │ │ └── skeleton.tsx -│ │ ā”œā”€ā”€ layout/ # Layout components -│ │ │ ā”œā”€ā”€ header.tsx -│ │ │ ā”œā”€ā”€ nav.tsx -│ │ │ └── footer.tsx -│ │ └── features/ # Feature-specific components -│ │ ā”œā”€ā”€ auth/ -│ │ │ ā”œā”€ā”€ login-form.tsx -│ │ │ └── logout-button.tsx -│ │ └── tasks/ -│ │ ā”œā”€ā”€ task-list.tsx -│ │ └── task-form.tsx -│ │ -│ ā”œā”€ā”€ lib/ # Utility libraries -│ │ ā”œā”€ā”€ utils.ts # General utilities -│ │ ā”œā”€ā”€ dal.ts # Data Access Layer (security) -│ │ ā”œā”€ā”€ validations.ts # Schema validations -│ │ └── supabase/ # Supabase configuration -│ │ ā”œā”€ā”€ client.ts # Client-side client -│ │ ā”œā”€ā”€ server.ts # Server-side client -│ │ └── middleware.ts # Session management -│ │ -│ ā”œā”€ā”€ hooks/ # Custom React hooks -│ │ ā”œā”€ā”€ use-auth.ts -│ │ └── use-local-storage.ts -│ │ -│ └── types/ # TypeScript definitions -│ └── database.types.ts # Generated Supabase types -│ -ā”œā”€ā”€ middleware.ts # Next.js middleware -ā”œā”€ā”€ next.config.ts # Next.js configuration -ā”œā”€ā”€ tailwind.config.ts # Tailwind configuration -ā”œā”€ā”€ tsconfig.json # TypeScript configuration -└── package.json -``` - ---- - -## Database Schema & Security - -### 1. Create Tables in Supabase - -Go to your Supabase project → SQL Editor → New Query, and run: - -```sql --- Create the tasks table -CREATE TABLE tasks ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - completed BOOLEAN DEFAULT FALSE, - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL -); - --- Enable Row Level Security (RLS) -ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; - --- Create security policies -CREATE POLICY "Users can view own tasks" ON tasks - FOR SELECT USING (auth.uid() = user_id); - -CREATE POLICY "Users can insert own tasks" ON tasks - FOR INSERT WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "Users can update own tasks" ON tasks - FOR UPDATE USING (auth.uid() = user_id); - -CREATE POLICY "Users can delete own tasks" ON tasks - FOR DELETE USING (auth.uid() = user_id); - --- Create indexes for performance -CREATE INDEX idx_tasks_user_id ON tasks(user_id); -CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC); - --- Add updated_at trigger -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = TIMEZONE('utc'::text, NOW()); - RETURN NEW; -END; -$$ language 'plpgsql'; - -CREATE TRIGGER update_tasks_updated_at - BEFORE UPDATE ON tasks - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); -``` - -### 2. Generate TypeScript Types - -Run this command to generate types from your database: - -```bash -npx supabase gen types typescript --project-id your_project_id > src/types/database.types.ts -``` - -Replace `your_project_id` with your actual Supabase project ID (found in Project Settings → General). - ---- - -## Authentication Implementation - -### 1. Data Access Layer (Security) - -Create `src/lib/dal.ts`: - -```typescript -import 'server-only' -import { redirect } from 'next/navigation' -import { createClient } from '@/lib/supabase/server' -import { cache } from 'react' - -export const verifySession = cache(async () => { - const supabase = await createClient() - const { data: { user }, error } = await supabase.auth.getUser() - - if (error || !user) { - redirect('/login') - } - - return { isAuth: true, user } -}) - -export const getUser = cache(async () => { - const session = await verifySession() - if (!session) return null - - return session.user -}) -``` - -### 2. Login Page - -Create `src/app/(auth)/login/page.tsx`: - -```typescript -import { login, signup } from './actions' - -export default function LoginPage() { - return ( -
-
-
-

- Sign in to your account -

-
-
-
-
- - -
-
- - -
-
-
- - -
-
-
-
- ) -} -``` - -### 3. Authentication Server Actions - -Create `src/app/(auth)/login/actions.ts`: - -```typescript -'use server' - -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' -import { createClient } from '@/lib/supabase/server' - -export async function login(formData: FormData) { - const supabase = await createClient() - - const data = { - email: formData.get('email') as string, - password: formData.get('password') as string, - } - - const { error } = await supabase.auth.signInWithPassword(data) - - if (error) { - console.error('Login error:', error) - redirect('/login?error=Invalid credentials') - } - - revalidatePath('/', 'layout') - redirect('/dashboard') -} - -export async function signup(formData: FormData) { - const supabase = await createClient() - - const data = { - email: formData.get('email') as string, - password: formData.get('password') as string, - } - - const { error } = await supabase.auth.signUp(data) - - if (error) { - console.error('Signup error:', error) - redirect('/login?error=Could not create account') - } - - revalidatePath('/', 'layout') - redirect('/login?message=Check your email to confirm your account') -} - -export async function logout() { - const supabase = await createClient() - await supabase.auth.signOut() - revalidatePath('/', 'layout') - redirect('/login') -} -``` - -### 4. Email Confirmation Route - -Create `src/app/api/auth/confirm/route.ts`: - -```typescript -import { createClient } from '@/lib/supabase/server' -import { NextRequest, NextResponse } from 'next/server' - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url) - const token_hash = searchParams.get('token_hash') - const type = searchParams.get('type') - const next = searchParams.get('next') ?? '/dashboard' - - if (token_hash && type) { - const supabase = await createClient() - - const { error } = await supabase.auth.verifyOtp({ - type: type as any, - token_hash, - }) - - if (!error) { - return NextResponse.redirect(new URL(next, request.url)) - } - } - - // Redirect to error page if confirmation fails - return NextResponse.redirect(new URL('/login?error=Could not confirm account', request.url)) -} -``` - ---- - -## CRUD Operations - -### 1. Dashboard Page with Data Fetching - -Create `src/app/dashboard/page.tsx`: - -```typescript -import { verifySession } from '@/lib/dal' -import { createClient } from '@/lib/supabase/server' -import TaskList from '@/components/features/tasks/task-list' -import TaskForm from '@/components/features/tasks/task-form' -import LogoutButton from '@/components/features/auth/logout-button' - -export default async function Dashboard() { - const session = await verifySession() - const supabase = await createClient() - - const { data: tasks, error } = await supabase - .from('tasks') - .select('*') - .order('created_at', { ascending: false }) - - if (error) { - console.error('Error fetching tasks:', error) - return
Error loading tasks
- } - - return ( -
-
-
-
-
-

- Dashboard -

-

Welcome back, {session.user.email}

-
- -
-
-
-
- -
-
- -
-
-
-
-
-
- ) -} -``` - -### 2. Server Actions for CRUD Operations - -Create `src/app/dashboard/actions.ts`: - -```typescript -'use server' - -import { createClient } from '@/lib/supabase/server' -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' - -export async function createTask(formData: FormData) { - const supabase = await createClient() - - const { data: { user } } = await supabase.auth.getUser() - if (!user) redirect('/login') - - const title = formData.get('title') as string - const description = formData.get('description') as string - - if (!title.trim()) { - return { error: 'Title is required' } - } - - const { error } = await supabase - .from('tasks') - .insert([ - { - title: title.trim(), - description: description?.trim() || null, - user_id: user.id, - }, - ]) - - if (error) { - console.error('Error creating task:', error) - return { error: 'Failed to create task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function updateTask(id: string, formData: FormData) { - const supabase = await createClient() - - const title = formData.get('title') as string - const description = formData.get('description') as string - const completed = formData.get('completed') === 'on' - - const { error } = await supabase - .from('tasks') - .update({ - title: title.trim(), - description: description?.trim() || null, - completed, - }) - .eq('id', id) - - if (error) { - console.error('Error updating task:', error) - return { error: 'Failed to update task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function deleteTask(id: string) { - const supabase = await createClient() - - const { error } = await supabase - .from('tasks') - .delete() - .eq('id', id) - - if (error) { - console.error('Error deleting task:', error) - return { error: 'Failed to delete task' } - } - - revalidatePath('/dashboard') - return { success: true } -} - -export async function toggleTask(id: string, completed: boolean) { - const supabase = await createClient() - - const { error } = await supabase - .from('tasks') - .update({ completed }) - .eq('id', id) - - if (error) { - console.error('Error toggling task:', error) - return { error: 'Failed to update task' } - } - - revalidatePath('/dashboard') - return { success: true } -} -``` - ---- - -## UI Components & Styling - -### 1. Utility Functions - -Create `src/lib/utils.ts`: - -```typescript -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} - -export function formatDate(date: string | Date) { - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }).format(new Date(date)) -} -``` - -### 2. Task List Component - -Create `src/components/features/tasks/task-list.tsx`: - -```typescript -import { toggleTask, deleteTask } from '@/app/dashboard/actions' -import { formatDate } from '@/lib/utils' - -type Task = { - id: string - title: string - description: string | null - completed: boolean - created_at: string - updated_at: string -} - -type TaskListProps = { - tasks: Task[] -} - -export default function TaskList({ tasks }: TaskListProps) { - if (tasks.length === 0) { - return ( -
-

No tasks yet. Create your first task!

-
- ) - } - - return ( -
-

Your Tasks

-
- {tasks.map((task) => ( -
-
-
-
- -
-
-

- {task.title} -

- {task.description && ( -

- {task.description} -

- )} -

- Created {formatDate(task.created_at)} -

-
-
-
- -
-
-
- ))} -
-
- ) -} -``` - -### 3. Task Form Component - -Create `src/components/features/tasks/task-form.tsx`: - -```typescript -'use client' - -import { createTask } from '@/app/dashboard/actions' -import { useActionState } from 'react' - -export default function TaskForm() { - const [state, formAction, pending] = useActionState(createTask, null) - - return ( -
-

Add New Task

-
-
- - -
- -
- -