diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 33d57a88..74af80dc 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -2,16 +2,23 @@ "mcpServers": { "shadcn-ui-server": { "command": "npx", - "args": ["-y", "shadcn-ui-mcp-server"] + "args": [ + "-y", + "shadcn-ui-mcp-server" + ] }, "plate": { "description": "Plate editors, plugins, components, and docs", "type": "stdio", "command": "npx", - "args": ["-y", "shadcn@canary", "registry:mcp"], + "args": [ + "-y", + "shadcn@canary", + "registry:mcp" + ], "env": { "REGISTRY_URL": "https://platejs.org/r/registry.json" } } } -} +} \ No newline at end of file diff --git a/.cursor/rules/ai-assisted-coding.mdc b/.cursor/rules/ai-assisted-coding.mdc deleted file mode 100644 index 07154233..00000000 --- a/.cursor/rules/ai-assisted-coding.mdc +++ /dev/null @@ -1,149 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -## AI-Assisted Coding Guidelines - -### Cursor Rules & MCP Best Practices - -- Use `@rules` to reference these workspace rules in your prompts -- Leverage the `.cursor/rules/` directory for consistent AI guidance -- Reference specific rule files: `@rules/typescript`, `@rules/error-handling`, etc. -- Use MCP tools for codebase exploration and understanding -- Keep rules concise but comprehensive for better AI comprehension - -### Free Tier Optimization - -- **Be Specific**: Provide clear, detailed prompts to avoid back-and-forth -- **Use Context Wisely**: Reference existing files and patterns in your requests -- **Batch Requests**: Combine related changes into single comprehensive requests -- **Focus on Quality**: Prefer fewer, well-crafted prompts over many short ones -- **Use Existing Patterns**: Reference established code patterns to reduce explanation needs - -### Effective AI Prompting - -``` -# Good Prompt Structure: -1. Context: "I'm working on [specific feature/component]" -2. Goal: "I need to [specific action]" -3. Constraints: "Following our [specific rule/pattern]" -4. Reference: "Similar to [existing file/pattern]" -``` - -### Model Selection for Free Tier - -- **Claude 3.5 Sonnet**: Best for complex refactoring and architecture decisions -- **GPT-4**: Good for general coding tasks and explanations -- **GPT-3.5**: Suitable for simple tasks and quick fixes -- **Local Models**: Consider for frequent, simple tasks to preserve quota - -### Efficient AI Workflows - -#### For New Features: -1. Start with architecture planning using AI -2. Generate component structure following established patterns -3. Implement core logic with AI assistance -4. Use AI for error handling and edge cases -5. AI-assisted testing and documentation - -#### For Bug Fixes: -1. Use AI to analyze error patterns -2. Reference similar fixes in codebase -3. Apply established error handling patterns -4. Validate fix with AI review - -#### For Refactoring: -1. Use AI to identify improvement opportunities -2. Follow established code organization rules -3. Maintain consistency with existing patterns -4. Use AI for impact analysis - -### Smart Context Usage - -- **File References**: Use `@filename` to include relevant files -- **Rule References**: Use `@rules/rulename` for specific guidelines -- **Pattern Examples**: Reference similar implementations in codebase -- **Error Context**: Include full error messages and stack traces - -### Prompt Templates - -#### New Component Creation: -``` -Create a new [component type] following @rules/ui-and-styling -and @rules/typescript patterns. Reference [similar component] -for consistency. Include proper error handling per @rules/error-handling. -``` - -#### Server Action Implementation: -``` -Implement a server action following @rules/data-management -and @rules/error-handling patterns. Use the established -ServerActionResult pattern from lib/server-action-utils.ts. -``` - -#### Bug Investigation: -``` -Analyze this error following @rules/error-handling. -Check similar patterns in [relevant files]. -Suggest fix maintaining consistency with our codebase. -``` - -### Code Review with AI - -- Use AI to review code against established rules -- Check for consistency with existing patterns -- Validate error handling implementation -- Review performance implications -- Ensure accessibility compliance - -### AI-Assisted Documentation - -- Generate JSDoc comments for complex functions -- Create README updates for new features -- Document API changes and breaking changes -- Generate code examples following established patterns - -### Limitations & Alternatives - -#### When AI Struggles: -- Very large file modifications (break into smaller chunks) -- Complex state management (reference existing patterns) -- Performance optimization (use specific metrics and requirements) -- Security-sensitive code (validate thoroughly) - -#### Free Tier Strategies: -- Plan requests during off-peak hours -- Use AI for planning, implement manually when quota is low -- Focus AI usage on complex problems, not simple syntax -- Cache AI-generated solutions for similar future problems - -### Collaboration Patterns - -#### Team Usage: -- Share effective prompts and patterns -- Document AI-generated solutions for team reference -- Review AI suggestions before implementation -- Maintain consistency across team members' AI usage - -#### Code Quality: -- Always review AI-generated code thoroughly -- Test AI solutions against established patterns -- Validate security and performance implications -- Ensure adherence to project rules and conventions - -### Integration with Development Workflow - -1. **Planning Phase**: Use AI for architecture and approach -2. **Implementation**: AI-assisted coding with rule validation -3. **Review Phase**: AI code review against established patterns -4. **Documentation**: AI-generated docs following project standards -5. **Testing**: AI-assisted test creation and validation - -### Monitoring AI Effectiveness - -- Track which types of requests work best -- Document successful prompt patterns -- Note common AI limitations for your use case -- Share learnings with team for collective improvement - diff --git a/.cursor/rules/ai-integration.mdc b/.cursor/rules/ai-integration.mdc deleted file mode 100644 index 6ca4813d..00000000 --- a/.cursor/rules/ai-integration.mdc +++ /dev/null @@ -1,25 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -## AI Integration Guidelines - -- Use the established Plate.js AI plugins for editor functionality -- Implement AI features using the `/api/ai/` route structure -- Follow the established patterns for AI chat and copilot features -- Use proper error handling for AI API calls with fallbacks -- Implement loading states and progress indicators for AI operations -- Use the established prompt templates and system messages -- Handle AI responses with proper validation and sanitization -- Implement proper rate limiting and usage tracking -- Use streaming responses for better user experience -- Provide clear feedback when AI features are processing -- Implement graceful degradation when AI services are unavailable -- Use proper caching strategies for AI responses when appropriate -- Follow established security practices for AI API interactions -- Implement proper user feedback mechanisms for AI suggestions -- Use the established patterns for AI toolbar buttons and menus -- Handle AI suggestion states (accepting, rejecting, modifying) -- Implement proper accessibility for AI-powered features -- Use consistent styling for AI-generated content indicators diff --git a/.cursor/rules/ai-quick-reference.mdc b/.cursor/rules/ai-quick-reference.mdc deleted file mode 100644 index 036941ba..00000000 --- a/.cursor/rules/ai-quick-reference.mdc +++ /dev/null @@ -1,71 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -## AI Quick Reference Guide - -### Essential Cursor Commands - -- `@rules` - Reference all workspace rules -- `@rules/[rulename]` - Reference specific rule file -- `@filename` - Include specific file in context -- `@folder` - Include folder contents in context -- `Ctrl/Cmd + K` - Quick AI chat -- `Ctrl/Cmd + L` - AI chat with current file context - -### Common AI Prompts for This Project - -#### Component Creation: -``` -@rules Create a new dashboard component for [feature] following our established patterns. Reference components/dashboard/stats-cards.tsx for consistency. -``` - -#### Server Action: -``` -@rules/data-management @rules/error-handling Create a server action to [action] with proper validation and error handling. Use the pattern from actions/user/create-user.ts. -``` - -#### Bug Fix: -``` -@rules/error-handling Fix this error: [error message]. Check similar patterns in the codebase and apply our established error handling approach. -``` - -#### Refactoring: -``` -@rules/code-organization Refactor this component to follow our organization patterns. Split into smaller components if needed and ensure proper separation of concerns. -``` - -### File Reference Shortcuts - -```bash -# Core patterns to reference: -@lib/server-action-utils.ts # Server action patterns -@components/ui/ # UI component patterns -@actions/user/create-user.ts # Server action example -@components/editor/ # Editor component patterns -@lib/validation/ # Validation schema patterns -``` - -### Free Tier Tips - -1. **Combine requests**: "Create component, add error handling, and write tests" -2. **Reference patterns**: Always mention similar existing files -3. **Be specific**: Include exact requirements and constraints -4. **Use rules**: Always reference @rules for consistency - -### Quick Validation Checklist - -- [ ] Follows @rules/typescript patterns? -- [ ] Uses established error handling? -- [ ] Matches @rules/naming-conventions? -- [ ] Includes proper validation? -- [ ] Consistent with existing patterns? - -### Emergency Patterns (When AI Quota Low) - -1. **Copy existing pattern** - Find similar component/function -2. **Manual implementation** - Use rules as checklist -3. **Incremental changes** - Small, focused modifications -4. **Team collaboration** - Ask team members for AI assistance - diff --git a/.cursor/rules/data-management.mdc b/.cursor/rules/data-management.mdc index a8866ca3..b0b7cddc 100644 --- a/.cursor/rules/data-management.mdc +++ b/.cursor/rules/data-management.mdc @@ -1,6 +1,6 @@ --- description: Interaction with the database. -globs: **/*.{js,jsx,ts,tsx} +globs: **/*{.js,.jsx,.ts,.tsx} alwaysApply: false --- - Interact with the database exclusively using Prisma ORM client diff --git a/.cursor/rules/error-handling.mdc b/.cursor/rules/error-handling.mdc index 21fa0383..86f312e7 100644 --- a/.cursor/rules/error-handling.mdc +++ b/.cursor/rules/error-handling.mdc @@ -1,6 +1,6 @@ --- description: Enfore friendly and logical error handling. -globs: **/*.{js,jsx,ts,tsx} +globs: **/*{.js,.jsx,.ts,.tsx} --- - Implement robust error handling and logging mechanisms. - Implement robust error handling using the established `ServerActionResult` pattern diff --git a/.cursor/rules/general-principles.mdc b/.cursor/rules/general-principles.mdc index f6c11f56..92ecfdf6 100644 --- a/.cursor/rules/general-principles.mdc +++ b/.cursor/rules/general-principles.mdc @@ -3,7 +3,6 @@ description: Applies general principles across the project. globs: */** --- - You are an expert in TypeScript, Node.js, Next.js with the app router, React, shadcn/ui, Tailwind, Auth.js and Prisma. -- You are an expert in TypeScript, Node.js, Next.js (App Router), React, Shadcn/UI, Tailwind, and Prisma - Write clean, concise and well-commented TypeScript code - Favor functional and declarative programming patterns over object-oriented approaches - Prioritize code reuse and modularization over duplication diff --git a/.cursor/rules/media-handling.mdc b/.cursor/rules/media-handling.mdc index cc67e847..f82e02c3 100644 --- a/.cursor/rules/media-handling.mdc +++ b/.cursor/rules/media-handling.mdc @@ -5,8 +5,8 @@ alwaysApply: false --- ## Media Handling Guidelines -- Use UploadThing for all file upload operations -- Follow the established patterns in `lib/uploadthing.ts` and `hooks/use-upload-file.ts` +- Use Supabase Storage for all file upload operations +- Follow the established patterns in `lib/supabase/storage.ts` and `hooks/use-upload-file.ts` - Implement proper file validation (type, size, format) - Use the established image processing utilities in `lib/imaging/` - Handle upload errors gracefully with appropriate user feedback diff --git a/.cursor/rules/naming-conventions.mdc b/.cursor/rules/naming-conventions.mdc index 5b6d572f..455c051b 100644 --- a/.cursor/rules/naming-conventions.mdc +++ b/.cursor/rules/naming-conventions.mdc @@ -2,7 +2,6 @@ description: Enforces a consistent naming convention across the project. globs: */** --- -- Use PascalCase for class names and type definitions. - Use PascalCase for React components and type definitions - Utilize camelCase for variables, functions and methods - Employ kebab-case for file and directory names diff --git a/.cursor/rules/nextjs.mdc b/.cursor/rules/nextjs.mdc index 65153cc3..c58c8be4 100644 --- a/.cursor/rules/nextjs.mdc +++ b/.cursor/rules/nextjs.mdc @@ -1,7 +1,8 @@ --- description: Applies optimal Next.js rules and best practices. -globs: **/*.{js,jsx,ts,tsx} +globs: **/*{.js,.jsx,.ts,.tsx} --- + - Favor React Server Components (RSC) where possible. - Minimize `'use client'` directives - only use for interactivity. - Optimize for performance and Web Vitals. diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc index f3f63a53..bd05b8d7 100644 --- a/.cursor/rules/testing.mdc +++ b/.cursor/rules/testing.mdc @@ -1,28 +1,52 @@ --- -description: -globs: -alwaysApply: false +description: Testing standards and conventions for unit, integration, and e2e tests +globs: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "tests/**/*"] +alwaysApply: true --- -## Testing Guidelines - -- Write tests for all critical business logic and server actions -- Use Jest and React Testing Library for component testing -- Implement integration tests for complex user workflows -- Test error handling and edge cases thoroughly -- Use proper mocking for external dependencies and APIs -- Test accessibility features and keyboard navigation -- Implement visual regression testing for UI components -- Use proper test data and factories for consistent testing -- Test responsive design and mobile functionality -- Implement end-to-end tests for critical user journeys -- Test database operations and data integrity -- Use proper setup and teardown for test environments -- Test performance characteristics of critical operations -- Implement proper test coverage reporting + +# Testing Guidelines + +## Test File Conventions + +### Unit & Integration Tests (*.test.ts, *.test.tsx) +- **Framework**: Vitest + React Testing Library +- **Location**: Co-located with source files being tested +- **Purpose**: Test individual components, functions, and integration workflows +- **Examples**: + - `lib/validation/user.test.ts` (co-located with `user.ts`) + - `actions/doc/create-doc.test.ts` (co-located with `create-doc.ts`) + - `tests/integration/user-profile-update.test.tsx` (complex workflows) + +### End-to-End Tests (*.spec.ts) +- **Framework**: Playwright +- **Location**: `tests/e2e/` folder +- **Purpose**: Test complete user workflows in real browser environment +- **Examples**: + - `tests/e2e/auth-login.spec.ts` + - `tests/e2e/dashboard-navigation.spec.ts` + - `tests/e2e/auth-accessibility.spec.ts` + +## Testing Best Practices + +### Unit/Integration Tests - Use descriptive test names and clear assertions +- Mock external dependencies (database, APIs) - Test both happy path and error scenarios -- Implement proper async testing patterns -- Test user interactions and form submissions -- Use proper component testing isolation +- Focus on user interactions and behavior, not implementation +- Use proper async testing patterns for server actions +- Test form submissions and validation thoroughly + +### End-to-End Tests +- Test critical user journeys and workflows +- Include accessibility testing with WCAG compliance +- Test responsive design and mobile functionality +- Validate complete auth flows and permissions +- Test database operations and data integrity + +### Test Organization +- Keep tests simple, reliable, and maintainable +- Use proper test data and fixtures +- Implement proper setup and teardown - Maintain test data separate from production data +- Use custom render functions with providers for component tests diff --git a/.cursor/rules/typescript.mdc b/.cursor/rules/typescript.mdc index ccc8a671..760cbb0b 100644 --- a/.cursor/rules/typescript.mdc +++ b/.cursor/rules/typescript.mdc @@ -1,8 +1,8 @@ --- description: Applies general TypeScript coding standards and best practices across the project. -globs: **/*.{ts,tsx} +globs: **/*{.ts,.tsx} --- -- Use TypeScript for all code; prefer types over interfaces. + - Use TypeScript for all code; prefer `type` over `interface`. - Write concise, technical TypeScript code with accurate examples. - Use functional and declarative programming patterns; avoid classes. diff --git a/.cursor/rules/ui-and-styling.mdc b/.cursor/rules/ui-and-styling.mdc index ae695174..3def7660 100644 --- a/.cursor/rules/ui-and-styling.mdc +++ b/.cursor/rules/ui-and-styling.mdc @@ -1,6 +1,6 @@ --- description: Enforces UI and styling conventions using Shadcn UI, Radix UI and Tailwind CSS for all components. -globs: **/*.{js,jsx,ts,tsx} +globs: **/*{.js,.jsx,.ts,.tsx} alwaysApply: false --- - Use Shadcn UI, Radix UI and Tailwind CSS for all components and styling diff --git a/.env.example b/.env.example index dd7405fb..633eac98 100644 --- a/.env.example +++ b/.env.example @@ -1,49 +1,106 @@ -# CODAC Environment Configuration Example +# ============================================================================= +# CODAC - Environment Configuration Template +# ============================================================================= # Copy this file to .env and fill in your actual values +# Never commit .env to version control - it contains sensitive information -# ============================================================================ +# ============================================================================= # DATABASE CONFIGURATION -# ============================================================================ +# ============================================================================= -# Primary database connection (Supabase with connection pooling) -DATABASE_URL="postgresql://username:password@host:port/database?pgbouncer=true" +# PostgreSQL Database URL +# Format: postgresql://username:password@localhost:5432/database_name +DATABASE_URL="postgresql://username:password@localhost:5432/codac_dev" -# Direct connection to database (used for migrations) -DIRECT_URL="postgresql://username:password@host:port/database" +# ============================================================================= +# AUTHENTICATION & AUTHORIZATION (NextAuth.js) +# ============================================================================= +# Authentication Secret (Required) +# Generate a secure random string (minimum 32 characters) +# You can use: openssl rand -base64 32 +AUTH_SECRET="your-very-secure-secret-key-minimum-32-characters" -# ============================================================================ -# AUTHENTICATION CONFIGURATION -# ============================================================================ - -# NextAuth.js Secret (generate a strong secret for production) -# You can generate one at: https://generate-secret.vercel.app/32 -AUTH_SECRET="your-secret-key-change-this-in-production" - -# Application URL (change for production) +# Application URL AUTH_URL="http://localhost:3000" +# Alternative format for compatibility +NEXTAUTH_URL="http://localhost:3000" -# ============================================================================ +# ============================================================================= # OAUTH PROVIDERS -# ============================================================================ +# ============================================================================= + +# Google OAuth (Required for Google sign-in) +# Get these from Google Cloud Console +AUTH_GOOGLE_ID="your-google-oauth-client-id" +AUTH_GOOGLE_SECRET="your-google-oauth-client-secret" + +# ============================================================================= +# EMAIL SERVICE (Resend) +# ============================================================================= + +# Resend API Key for transactional emails +# Get from: https://resend.com/ +AUTH_RESEND_KEY="re_your-resend-api-key" +EMAIL_FROM="noreply@yourdomain.com" + +# ============================================================================= +# SUPABASE CONFIGURATION +# ============================================================================= + +# Supabase Project URL and API Key +# Get these from your Supabase project settings +NEXT_PUBLIC_SUPABASE_URL="https://your-project-id.supabase.co" +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY="your-supabase-anon-key" + +# ============================================================================= +# AI SERVICES +# ============================================================================= + +# OpenAI API Key for AI features +# Get from: https://platform.openai.com/api-keys +OPENAI_API_KEY="sk-your-openai-api-key" + +# Google Gemini API Key for AI features +# Get from: https://makersuite.google.com/app/apikey +GEMINI_API_KEY="your-gemini-api-key" + +# ============================================================================= +# APPLICATION CONFIGURATION +# ============================================================================= + +# Environment (automatically set by Node.js) +NODE_ENV="development" + +# Application Host URL (public-facing) +NEXT_PUBLIC_APP_URL="http://localhost:3000" +NEXT_PUBLIC_HOST="localhost:3000" + +# Server Port (optional, defaults to 3000) +PORT="3000" -# Google OAuth Configuration -# Get credentials at: https://console.developers.google.com/ -AUTH_GOOGLE_ID="your-google-client-id" -AUTH_GOOGLE_SECRET="your-google-client-secret" +# ============================================================================= +# DEPLOYMENT (Vercel) +# ============================================================================= -# GitHub OAuth Configuration (optional) -# Get credentials at: https://github.com/settings/applications/new -# AUTH_GITHUB_ID="your-github-client-id" -# AUTH_GITHUB_SECRET="your-github-client-secret" +# Vercel URL (automatically set by Vercel) +# VERCEL_URL="your-app.vercel.app" -# ============================================================================ -# EMAIL PROVIDER -# ============================================================================ +# ============================================================================= +# DEVELOPMENT & TESTING +# ============================================================================= -# Resend API (Edge Runtime compatible email provider) -# Get free API key at: https://resend.com -AUTH_RESEND_KEY="your-resend-api-key-here" +# CI Environment (set by CI/CD systems) +# CI="true" -# Email sender address (must be verified domain with Resend) -EMAIL_FROM="auth@yourdomain.com" \ No newline at end of file +# ============================================================================= +# NOTES +# ============================================================================= +# +# 1. Replace all placeholder values with your actual credentials +# 2. Never commit this file with real values to version control +# 3. For production, use your hosting platform's environment variable system +# 4. Some variables are optional and have defaults in the application +# 5. Public variables (NEXT_PUBLIC_*) are exposed to the browser +# 6. Keep your AUTH_SECRET secure and unique per environment +# diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..20b8fbcf --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,69 @@ +# CODAC — AI assistant working guide + +Use these repo-specific rules and patterns; copy existing shapes over inventing new ones. + +- Architecture + + - Next.js 15 App Router (RSC by default), TypeScript strict. + - UI: Tailwind v4 + shadcn/ui + Radix. ORM: Prisma 6 (Postgres). Auth: NextAuth v5. + - Writes = server actions in `actions/*`; reads = queries in `data/*`; shared utils in `lib/*`. + +- Key boundaries (place code accordingly) + + - `app/`: routes/layouts (server-first). API subroutes only when server actions don’t fit. Auth pages in `app/auth/*`. + - `actions/`: mutations (e.g., `actions/doc/update-doc.ts`, `actions/job/*`, `actions/lms/*`). + - `data/`: queries (e.g., `data/docs.ts`, `data/dashboard.ts`). + - `lib/`: `auth/*`, `db/prisma.ts`, `server-action-utils.ts`, `permissions.ts`, `logger.ts`. + - `components/`: primitives in `components/ui/*`, features in `components/{editor,community,profile,...}`. + +- Server action contract (follow this pattern) + + - Validate input with Zod (`schemas/*`). Authorize via `lib/auth/*` + `lib/permissions.ts`. + - Use `lib/db/prisma.ts` with narrow `select`s; wrap multi-step writes in a transaction. + - Handle errors via `lib/server-action-utils.ts`; return `ServerActionResult`; revalidate paths. + +- RSC and client code + + - Default to RSC; add `'use client'` only when interactive. Use `next/image`; `cn()` for classes. + - No `console.*`; use `lib/logger.ts`. Prefer named exports and `type` aliases; path alias `@/*` is enabled. + +- Auth and routing + + - Use `auth()` from `lib/auth/auth` on the server; `next-auth/react` hooks on the client. + - `middleware.ts` defines public routes; update allowlist if adding public pages. + +- Data model highlights (see `prisma/schema.prisma`) + + - LMS: Course → Project → Lesson (+ Assignment, Enrollment, LessonProgress). + - Community: CommunityPost/Comment/Like/Tag. Docs: Document + versions/collaborators/favorites/suggestions. + - Jobs: Job + JobApplication. Mentorship: Mentorship + MentorSession. Portfolio: ProjectProfile/Showcase/Skill/Experience. + +- Editor & content + + - Plate.js v49 editor with local-first autosave; draft key `doc_draft_{documentId}`. See `dev-docs/AUTO_SAVE_STRATEGY.md`. + - Import/export flows in `dev-docs/IMPORT_LMS_CONTENT.md` and `dev-docs/EXPORT_DOCS_TO_MARKDOWN.md`. + +- Build, test, and DB + + - Dev `pnpm dev`; Build `pnpm build`; Lint `pnpm lint`; Types `pnpm ts:check`. + - Prisma: `pnpm db:generate`, `pnpm db:push`, seed via `pnpm db:seed`, inspect with `pnpm db:studio`. + - E2E: Playwright via `pnpm test` / `pnpm test:ui`. + +- CI expectations + + - `.github/workflows/ci.yml` runs lint, TS check, build, and posts tips on large PRs. Keep PRs focused and typed. + +- Integration specifics + + - Images allowed from `utfs.io` (see `next.config.ts`); server actions body limit 2 MB. + - External: UploadThing (files), Resend (email), Vercel AI SDK + OpenAI. Reuse env names from `README.md`. + +- Where to put new code (examples) + + - New job mutation: `actions/job/create-job.ts` mirroring `actions/job/update-job.ts` with Zod + role checks; revalidate list/detail. + - New cohort query: `data/cohort/get-cohorts.ts` with minimal `select`; consume from RSC in `app/community/*`. + +- Quick pre-commit gate + - Run `pnpm lint && pnpm ts:check && pnpm build`. If Prisma changed: `pnpm db:generate` and verify with seed or Studio. + +If any guideline here is unclear or a pattern seems missing, ask and we’ll extend this file. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13bee523..5cdff883 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,14 +19,16 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "18" - cache: "npm" - - - name: Install pnpm - run: npm install -g pnpm + node-version: "22" + cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile @@ -40,8 +42,8 @@ jobs: - name: Run TypeScript check run: pnpm ts:check - build: - name: Build Application + unit-tests: + name: Unit Tests runs-on: ubuntu-latest needs: lint-and-typecheck @@ -49,14 +51,139 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "18" - cache: "npm" + node-version: "22" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate Prisma client + run: pnpm db:generate + + - name: Run unit tests + run: pnpm test:unit --reporter=verbose --reporter=junit --outputFile=test-results/unit-tests.xml + env: + VITEST_REPORTER: junit + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: unit-test-results + path: test-results/ + + - name: Generate test coverage + run: pnpm test:unit:coverage + continue-on-error: true + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-report + path: coverage/ + + e2e-tests: + name: E2E Tests + runs-on: ubuntu-latest + needs: lint-and-typecheck + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + env: + DATABASE_URL: "postgresql://testuser:testpass@localhost:5432/testdb" + NEXTAUTH_SECRET: "test-secret-key-for-ci" + NEXTAUTH_URL: "http://localhost:3001" + GEMINI_API_KEY: "dummy-key-for-testing" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest - - name: Install pnpm - run: npm install -g pnpm + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate Prisma client + run: pnpm db:generate + + - name: Setup test database + run: | + pnpm db:push + pnpm db:seed:clean + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + run: pnpm test:e2e --reporter=junit + env: + PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/e2e-results.xml + + - name: Upload E2E test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: e2e-test-results + path: test-results/ + + - name: Upload Playwright report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + + build: + name: Build Application + runs-on: ubuntu-latest + needs: [unit-tests, e2e-tests] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile @@ -77,13 +204,19 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "18" + node-version: "22" + cache: "pnpm" - - name: Install pnpm - run: npm install -g pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Run security audit run: pnpm audit --audit-level moderate @@ -97,13 +230,19 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "18" + node-version: "22" + cache: "pnpm" - - name: Install pnpm - run: npm install -g pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Check for outdated dependencies run: pnpm outdated diff --git a/.gitignore b/.gitignore index a6ed4d28..9fab2dc6 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,9 @@ node_modules/ /playwright-report/ /blob-report/ /playwright/.cache/ + +**/*.icloud +/playwright-report/ + +# temp +/temp/ diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..1d975bef --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.0.0 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.playwright-mcp/auth-signin-1440px.png b/.playwright-mcp/auth-signin-1440px.png deleted file mode 100644 index dda6f093..00000000 Binary files a/.playwright-mcp/auth-signin-1440px.png and /dev/null differ diff --git a/.playwright-mcp/current-dashboard-before-changes.png b/.playwright-mcp/current-dashboard-before-changes.png deleted file mode 100644 index 861e5461..00000000 Binary files a/.playwright-mcp/current-dashboard-before-changes.png and /dev/null differ diff --git a/.playwright-mcp/current-projects-page-before-changes.png b/.playwright-mcp/current-projects-page-before-changes.png deleted file mode 100644 index 6a9fdb96..00000000 Binary files a/.playwright-mcp/current-projects-page-before-changes.png and /dev/null differ diff --git a/.playwright-mcp/dashboard-homepage-1440px.png b/.playwright-mcp/dashboard-homepage-1440px.png deleted file mode 100644 index e3393716..00000000 Binary files a/.playwright-mcp/dashboard-homepage-1440px.png and /dev/null differ diff --git a/.playwright-mcp/dashboard-homepage-375px-mobile.png b/.playwright-mcp/dashboard-homepage-375px-mobile.png deleted file mode 100644 index 6d9f3e15..00000000 Binary files a/.playwright-mcp/dashboard-homepage-375px-mobile.png and /dev/null differ diff --git a/.playwright-mcp/dashboard-homepage-768px-tablet.png b/.playwright-mcp/dashboard-homepage-768px-tablet.png deleted file mode 100644 index de94e829..00000000 Binary files a/.playwright-mcp/dashboard-homepage-768px-tablet.png and /dev/null differ diff --git a/.playwright-mcp/dashboard-homepage-768px.png b/.playwright-mcp/dashboard-homepage-768px.png deleted file mode 100644 index c5f71460..00000000 Binary files a/.playwright-mcp/dashboard-homepage-768px.png and /dev/null differ diff --git a/.playwright-mcp/final-dashboard-after-changes.png b/.playwright-mcp/final-dashboard-after-changes.png deleted file mode 100644 index cbf78c44..00000000 Binary files a/.playwright-mcp/final-dashboard-after-changes.png and /dev/null differ diff --git a/.playwright-mcp/final-projects-page-after-changes.png b/.playwright-mcp/final-projects-page-after-changes.png deleted file mode 100644 index b7c9e451..00000000 Binary files a/.playwright-mcp/final-projects-page-after-changes.png and /dev/null differ diff --git a/.playwright-mcp/lms-dashboard-layout-verification.png b/.playwright-mcp/lms-dashboard-layout-verification.png deleted file mode 100644 index 4689442e..00000000 Binary files a/.playwright-mcp/lms-dashboard-layout-verification.png and /dev/null differ diff --git a/.playwright-mcp/mobile-dashboard-375px.png b/.playwright-mcp/mobile-dashboard-375px.png deleted file mode 100644 index ff522403..00000000 Binary files a/.playwright-mcp/mobile-dashboard-375px.png and /dev/null differ diff --git a/.playwright-mcp/mobile-dashboard-390px.png b/.playwright-mcp/mobile-dashboard-390px.png deleted file mode 100644 index 61906fdd..00000000 Binary files a/.playwright-mcp/mobile-dashboard-390px.png and /dev/null differ diff --git a/.playwright-mcp/mobile-dashboard-414px.png b/.playwright-mcp/mobile-dashboard-414px.png deleted file mode 100644 index b1d465c8..00000000 Binary files a/.playwright-mcp/mobile-dashboard-414px.png and /dev/null differ diff --git a/.playwright-mcp/updated-dashboard-after-changes.png b/.playwright-mcp/updated-dashboard-after-changes.png deleted file mode 100644 index 2706a5c5..00000000 Binary files a/.playwright-mcp/updated-dashboard-after-changes.png and /dev/null differ diff --git a/.playwright-mcp/updated-projects-page-after-changes.png b/.playwright-mcp/updated-projects-page-after-changes.png deleted file mode 100644 index 3ff85db4..00000000 Binary files a/.playwright-mcp/updated-projects-page-after-changes.png and /dev/null differ diff --git a/.vscode/copilot/instructions.json b/.vscode/copilot/instructions.json new file mode 100644 index 00000000..c0d04577 --- /dev/null +++ b/.vscode/copilot/instructions.json @@ -0,0 +1,63 @@ +{ + "default": { + "tone": "professional", + "format": "typescript", + "repoGuide": ".github/copilot-instructions.md", + "maxTokens": 1500, + "rules": [ + "Follow Next.js App Router with RSC-first approach", + "Place code in correct boundaries (app/, actions/, data/, lib/, components/)", + "Use Zod for validation and return ServerActionResult for actions", + "Use lib/db/prisma.ts with narrow selects; wrap multi-step operations in transactions", + "Ensure WCAG AA accessibility for all UI components", + "Add comprehensive error handling with user-friendly messages", + "Use named exports; avoid default exports", + "Follow TypeScript best practices: strict types, prefer interfaces for public APIs", + "Use logger.ts instead of console.log", + "Implement proper security checks with auth() and permissions.ts", + "Follow existing code organization patterns" + ] + }, + "overrides": { + "markdown": { + "format": "markdown", + "maxTokens": 800, + "rules": [ + "Use clear headings and lists", + "Include code examples where appropriate", + "Be concise and focused on actionable information" + ] + }, + "typescript": { + "format": "typescript", + "maxTokens": 2000, + "rules": [ + "Use strict TypeScript with explicit types", + "Avoid any and type assertions when possible", + "Use type aliases for complex types", + "Prefer readonly for immutable data", + "Use proper error handling with typed errors" + ] + }, + "css": { + "format": "css", + "maxTokens": 800, + "rules": [ + "Use Tailwind utility classes following the project pattern", + "Follow mobile-first responsive design", + "Ensure sufficient color contrast (4.5:1 minimum)", + "Use CSS variables for themeable properties" + ] + }, + "test": { + "format": "typescript", + "maxTokens": 1500, + "rules": [ + "Follow AAA pattern (Arrange-Act-Assert)", + "Test both happy paths and error scenarios", + "Mock external dependencies", + "Use descriptive test names" + ] + } + } +} diff --git a/.vscode/copilot/modes.json b/.vscode/copilot/modes.json new file mode 100644 index 00000000..1cbfa5b1 --- /dev/null +++ b/.vscode/copilot/modes.json @@ -0,0 +1,153 @@ +{ + "refactor": { + "promptFile": ".vscode/copilot/prompts/refactor.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "generate-tests": { + "promptFile": ".vscode/copilot/prompts/testing.md", + "instruction": "test", + "toolSets": ["testing"], + "runOnSave": false + }, + "review": { + "promptFile": ".vscode/copilot/prompts/review.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "feature": { + "promptFile": ".vscode/copilot/prompts/feature.md", + "instruction": "default", + "toolSets": ["quick-check", "prisma"], + "runOnSave": false + }, + "accessibility": { + "promptFile": ".vscode/copilot/prompts/accessibility.md", + "instruction": "default", + "toolSets": ["accessibility"], + "runOnSave": false + }, + "performance": { + "promptFile": ".vscode/copilot/prompts/performance.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "ai-integration": { + "promptFile": ".vscode/copilot/prompts/ai-integration.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "full-check": { + "promptFile": ".vscode/copilot/prompts/review.md", + "instruction": "default", + "toolSets": ["quick-check", "testing", "accessibility"], + "runOnSave": false + }, + "create-route": { + "promptFile": ".vscode/copilot/prompts/create-route.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "create-server-action": { + "promptFile": ".vscode/copilot/prompts/create-server-action.md", + "instruction": "default", + "toolSets": ["quick-check", "prisma"], + "runOnSave": false + }, + "create-component": { + "promptFile": ".vscode/copilot/prompts/create-component.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "create-data-module": { + "promptFile": ".vscode/copilot/prompts/create-data-module.md", + "instruction": "default", + "toolSets": ["quick-check", "prisma"], + "runOnSave": false + }, + "planner": { + "promptFile": ".vscode/copilot/prompts/planner.md", + "instruction": "default", + "toolSets": ["quick-check", "prisma"], + "runOnSave": false + } + , + "ai-assisted-coding": { + "promptFile": ".vscode/copilot/prompts/ai-assisted-coding.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "ai-quick-reference": { + "promptFile": ".vscode/copilot/prompts/ai-quick-reference.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "code-organization": { + "promptFile": ".vscode/copilot/prompts/code-organization.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "data-management": { + "promptFile": ".vscode/copilot/prompts/data-management.md", + "instruction": "default", + "toolSets": ["quick-check", "prisma"], + "runOnSave": false + }, + "error-handling": { + "promptFile": ".vscode/copilot/prompts/error-handling.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "general-principles": { + "promptFile": ".vscode/copilot/prompts/general-principles.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "media-handling": { + "promptFile": ".vscode/copilot/prompts/media-handling.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "naming-conventions": { + "promptFile": ".vscode/copilot/prompts/naming-conventions.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "nextjs": { + "promptFile": ".vscode/copilot/prompts/nextjs.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "security": { + "promptFile": ".vscode/copilot/prompts/security.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "typescript": { + "promptFile": ".vscode/copilot/prompts/typescript.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + }, + "ui-and-styling": { + "promptFile": ".vscode/copilot/prompts/ui-and-styling.md", + "instruction": "default", + "toolSets": ["quick-check"], + "runOnSave": false + } +} diff --git a/.vscode/copilot/prompts/accessibility.md b/.vscode/copilot/prompts/accessibility.md new file mode 100644 index 00000000..d831c741 --- /dev/null +++ b/.vscode/copilot/prompts/accessibility.md @@ -0,0 +1,31 @@ +# Accessibility Review & Enhancement + +Context: + +- File: {{file}} +- Component: {{selection}} + +Requirements: + +- Meet WCAG 2.1 AA standards +- Support keyboard navigation +- Work with screen readers +- Handle color contrast requirements +- Provide appropriate aria attributes + +Check these accessibility concerns: + +1. Semantic HTML (use proper heading levels, lists, etc.) +2. Keyboard navigation and focus management +3. ARIA roles, states, and properties +4. Text alternatives for non-text content +5. Color contrast ratios (minimum 4.5:1 for normal text) +6. Form labels and error messages +7. Responsive behavior at various zoom levels + +Reference: + +- Use radix-ui primitives for complex interactive components +- Ensure sufficient color contrast with Tailwind's text-\* classes +- Add skip links for keyboard users +- Test with screen readers and keyboard-only navigation diff --git a/.vscode/copilot/prompts/ai-assisted-coding.md b/.vscode/copilot/prompts/ai-assisted-coding.md new file mode 100644 index 00000000..0ae6e061 --- /dev/null +++ b/.vscode/copilot/prompts/ai-assisted-coding.md @@ -0,0 +1,18 @@ +# AI-Assisted Coding + +Follow these guidelines for effective AI-assisted coding in this project: + +- Reference workspace rules using `@rules` in prompts +- Use `.cursor/rules/` for consistent guidance +- Reference specific rule files as needed (e.g., `@rules/typescript`) +- Use MCP tools for codebase exploration +- Be specific and batch related requests +- Prefer quality, context-rich prompts +- Reference established code patterns + +Model selection: + +- Claude 3.5 Sonnet: complex refactoring/architecture +- GPT-4: general coding +- GPT-3.5: simple/quick tasks +- Local: frequent/simple tasks diff --git a/.vscode/copilot/prompts/ai-integration.md b/.vscode/copilot/prompts/ai-integration.md new file mode 100644 index 00000000..2d823b12 --- /dev/null +++ b/.vscode/copilot/prompts/ai-integration.md @@ -0,0 +1,35 @@ +# AI Feature Implementation + +Context: + +- File: {{file}} +- Selection: {{selection}} +- AI feature type: {{input:type|completion|analysis|generation|search}} + +Requirements: + +- Follow Vercel AI SDK patterns +- Add appropriate error handling and fallbacks +- Implement user-friendly UX for AI interactions +- Consider rate limiting and cost management +- Respect user privacy and data handling guidelines + +Implementation guide: + +1. Define clear AI feature boundaries and requirements +2. Design the user experience (input, processing, output) +3. Implement with appropriate client/server split +4. Add fallbacks and graceful degradation +5. Include comprehensive error handling + +Security considerations: + +- Validate and sanitize all user inputs +- Add rate limiting to prevent abuse +- Don't expose API keys in client-side code +- Use streaming for responsive UX during processing + +Reference implementation: + +- See `components/editor/ai-suggestions.tsx` +- See `lib/ai/generation-utils.ts` diff --git a/.vscode/copilot/prompts/ai-quick-reference.md b/.vscode/copilot/prompts/ai-quick-reference.md new file mode 100644 index 00000000..93297e37 --- /dev/null +++ b/.vscode/copilot/prompts/ai-quick-reference.md @@ -0,0 +1,15 @@ +# AI Quick Reference + +## Cursor Commands + +- `@rules` - Reference all workspace rules +- `@rules/[rulename]` - Reference specific rule file +- `@filename` - Include file in context +- `@folder` - Include folder contents + +## Common Prompts + +- Component: `@rules Create a dashboard component for [feature]` +- Server Action: `@rules/data-management @rules/error-handling Create a server action to [action]` +- Bug Fix: `@rules/error-handling Fix this error: [error message]` +- Refactor: `@rules/code-organization Refactor this component to follow our patterns.` diff --git a/.vscode/copilot/prompts/code-organization.md b/.vscode/copilot/prompts/code-organization.md new file mode 100644 index 00000000..a57ea478 --- /dev/null +++ b/.vscode/copilot/prompts/code-organization.md @@ -0,0 +1,26 @@ +# Code Organization + +- Structure files logically, group related code +- Prefer named exports for components +- Favor small, single-purpose components +- Separate presentational/container components +- Use kebab-case for files, PascalCase for components + +## Project Structure + +- /actions: server mutations +- /app: Next.js App Router +- /components: UI components by feature +- /data: queries +- /hooks: custom hooks +- /lib: shared utils +- /public: static assets +- /schemas: validation +- /types: types +- /prisma: schema/migrations + +## Component Organization + +- Group by feature +- Use index files for imports +- Co-locate related files when beneficial diff --git a/.vscode/copilot/prompts/create-component.md b/.vscode/copilot/prompts/create-component.md new file mode 100644 index 00000000..91f5647c --- /dev/null +++ b/.vscode/copilot/prompts/create-component.md @@ -0,0 +1,225 @@ +# Create UI Component + +Context: + +- Component type: {{input:type|card|form|list|modal|dashboard}} +- Feature area: {{input:area|user|doc|community|job|course}} +- Client component: {{input:client|yes|no}} + +Requirements: + +- Follow shadcn/ui and Radix UI patterns +- Ensure WCAG 2.1 AA accessibility +- Implement responsive design with Tailwind +- Add proper TypeScript typing +- Include appropriate loading/error states +- Use established component organization patterns + +Implementation guide: + +1. Create component in appropriate directory (components/[feature]/) +2. Use shadcn/ui primitives when possible +3. Add 'use client' directive only if interactivity is needed +4. Implement proper props interface +5. Add appropriate aria attributes for accessibility + +Component template: + +```tsx +// components/[feature]/[component-name].tsx +import { cn } from "@/lib/utils"; + +// Define component props with TypeScript +interface ComponentProps { + // Add your props here + title: string; + description?: string; + className?: string; + children?: React.ReactNode; +} + +/** + * Component description + */ +export function ComponentName({ + title, + description, + className, + children, +}: ComponentProps) { + return ( +
+

{title}

+ {description && ( +

{description}

+ )} + {children &&
{children}
} +
+ ); +} +``` + +Client component template: + +```tsx +"use client"; + +// components/[feature]/[component-name].tsx +import { useState } from "react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +interface InteractiveComponentProps { + initialValue: string; + onAction: (value: string) => void; + className?: string; +} + +/** + * Interactive component description + */ +export function InteractiveComponent({ + initialValue, + onAction, + className, +}: InteractiveComponentProps) { + const [value, setValue] = useState(initialValue); + + return ( +
+ setValue(e.target.value)} + className="w-full px-3 py-2 border rounded-md" + aria-label="Input description" + /> + + +
+ ); +} +``` + +Form component example: + +```tsx +"use client"; + +// components/[feature]/[name]-form.tsx +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { createEntity } from "@/actions/entity/create-entity"; + +// Form schema using Zod +const formSchema = z.object({ + title: z.string().min(3, { + message: "Title must be at least 3 characters.", + }), + description: z.string().min(10, { + message: "Description must be at least 10 characters.", + }), +}); + +// Define form values type +type FormValues = z.infer; + +// Props +interface EntityFormProps { + defaultValues?: Partial; + entityId?: string; +} + +export function EntityForm({ defaultValues, entityId }: EntityFormProps) { + const router = useRouter(); + const [isPending, setIsPending] = useState(false); + + // Initialize form + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: defaultValues || { + title: "", + description: "", + }, + }); + + // Form submission handler + async function onSubmit(values: FormValues) { + setIsPending(true); + + try { + const result = await createEntity({ + ...values, + id: entityId, + }); + + if (result.ok) { + toast.success("Entity saved successfully"); + router.push(`/entities/${result.data.id}`); + } else { + toast.error("Failed to save entity"); + } + } catch (error) { + toast.error("An error occurred"); + console.error(error); + } finally { + setIsPending(false); + } + } + + return ( +
+ + ( + + Title + + + + + + )} + /> + + ( + + Description + +