{ "workflow_version": "v001", "feature": "a complete to earn app", "created_at": "2025-12-18", "status": "draft", "revision": 1, "data_models": [ { "id": "model_user", "name": "User", "description": "User account and authentication", "file_path": "app/lib/db/schema/user.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique user identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "email", "type": "string", "required": true, "description": "User email address (unique)" }, { "name": "password_hash", "type": "string", "required": true, "description": "Hashed password using bcrypt" }, { "name": "name", "type": "string", "required": true, "description": "User display name" }, { "name": "created_at", "type": "timestamp", "required": true, "description": "Account creation timestamp" }, { "name": "updated_at", "type": "timestamp", "required": true, "description": "Last update timestamp" } ], "indexes": [ { "fields": [ "email" ], "unique": true } ] }, { "id": "model_points", "name": "Points", "description": "Points transaction history", "file_path": "app/lib/db/schema/points.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique transaction identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "user_id", "type": "string", "required": true, "description": "Foreign key to User" }, { "name": "amount", "type": "integer", "required": true, "description": "Points amount (positive for earned, negative for spent)" }, { "name": "type", "type": "enum", "required": true, "description": "Transaction type", "enum_values": [ "earned", "spent" ] }, { "name": "source", "type": "string", "required": true, "description": "Source of points (task_id, purchase_id, etc.)" }, { "name": "created_at", "type": "timestamp", "required": true, "description": "Transaction timestamp" } ], "indexes": [ { "fields": [ "user_id", "created_at" ], "unique": false } ] }, { "id": "model_task", "name": "Task", "description": "Available tasks for earning points", "file_path": "app/lib/db/schema/task.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique task identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "type", "type": "enum", "required": true, "description": "Task category", "enum_values": [ "checkin", "ad", "survey", "referral", "quiz", "video", "reading" ] }, { "name": "title", "type": "string", "required": true, "description": "Task display title" }, { "name": "description", "type": "string", "required": true, "description": "Task description" }, { "name": "points_reward", "type": "integer", "required": true, "description": "Points awarded upon completion" }, { "name": "is_active", "type": "boolean", "required": true, "description": "Whether task is currently available" } ], "indexes": [ { "fields": [ "type", "is_active" ], "unique": false } ] }, { "id": "model_user_task", "name": "UserTask", "description": "User task completion records", "file_path": "app/lib/db/schema/user_task.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique completion record identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "user_id", "type": "string", "required": true, "description": "Foreign key to User" }, { "name": "task_id", "type": "string", "required": true, "description": "Foreign key to Task" }, { "name": "completed_at", "type": "timestamp", "required": true, "description": "Task completion timestamp" }, { "name": "points_earned", "type": "integer", "required": true, "description": "Points awarded for this completion" } ], "indexes": [ { "fields": [ "user_id", "task_id", "completed_at" ], "unique": false } ] }, { "id": "model_badge", "name": "Badge", "description": "Available badges and achievements", "file_path": "app/lib/db/schema/badge.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique badge identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "name", "type": "string", "required": true, "description": "Badge display name" }, { "name": "description", "type": "string", "required": true, "description": "Badge description" }, { "name": "icon", "type": "string", "required": true, "description": "Badge icon identifier or URL" }, { "name": "requirement_type", "type": "enum", "required": true, "description": "Type of requirement to earn badge", "enum_values": [ "points_total", "tasks_completed", "streak_days", "referrals" ] }, { "name": "requirement_value", "type": "integer", "required": true, "description": "Threshold value for requirement" } ] }, { "id": "model_user_badge", "name": "UserBadge", "description": "User earned badges", "file_path": "app/lib/db/schema/user_badge.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique user badge identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "user_id", "type": "string", "required": true, "description": "Foreign key to User" }, { "name": "badge_id", "type": "string", "required": true, "description": "Foreign key to Badge" }, { "name": "earned_at", "type": "timestamp", "required": true, "description": "Badge earned timestamp" } ], "indexes": [ { "fields": [ "user_id", "badge_id" ], "unique": true } ] }, { "id": "model_quiz", "name": "Quiz", "description": "Quiz questions for learning tasks", "file_path": "app/lib/db/schema/quiz.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique quiz identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "task_id", "type": "string", "required": true, "description": "Foreign key to Task" }, { "name": "question", "type": "string", "required": true, "description": "Quiz question text" }, { "name": "options", "type": "json", "required": true, "description": "Array of answer options" }, { "name": "correct_answer", "type": "string", "required": true, "description": "Correct answer identifier" } ], "indexes": [ { "fields": [ "task_id" ], "unique": false } ] }, { "id": "model_referral", "name": "Referral", "description": "Referral tracking and rewards", "file_path": "app/lib/db/schema/referral.ts", "status": "PENDING", "fields": [ { "name": "id", "type": "string", "required": true, "description": "Unique referral identifier (UUID)", "constraints": [ "primary_key" ] }, { "name": "referrer_id", "type": "string", "required": true, "description": "Foreign key to User (who referred)" }, { "name": "referred_id", "type": "string", "required": true, "description": "Foreign key to User (who was referred)" }, { "name": "bonus_points", "type": "integer", "required": true, "description": "Points awarded to referrer" }, { "name": "created_at", "type": "timestamp", "required": true, "description": "Referral creation timestamp" } ], "indexes": [ { "fields": [ "referrer_id" ], "unique": false }, { "fields": [ "referred_id" ], "unique": true } ] } ], "api_endpoints": [ { "id": "api_auth_register", "path": "/api/auth/register", "method": "POST", "description": "Create new user account", "file_path": "app/api/auth/register/route.ts", "status": "PENDING", "request_body": { "email": "string (required)", "password": "string (required, min 8 chars)", "name": "string (required)" }, "responses": [ { "status": 201, "description": "Success", "schema": { "user": { "id": "string", "email": "string", "name": "string" }, "token": "string (JWT)" } }, { "status": 400, "description": "Error", "schema": { "error": "string (validation error or email exists)" } } ] }, { "id": "api_auth_login", "path": "/api/auth/login", "method": "POST", "description": "Login existing user", "file_path": "app/api/auth/login/route.ts", "status": "PENDING", "request_body": { "email": "string (required)", "password": "string (required)" }, "responses": [ { "status": 200, "description": "Success", "schema": { "user": { "id": "string", "email": "string", "name": "string" }, "token": "string (JWT)" } }, { "status": 401, "description": "Error", "schema": { "error": "Invalid credentials" } } ] }, { "id": "api_auth_me", "path": "/api/auth/me", "method": "GET", "description": "Get current authenticated user", "file_path": "app/api/auth/me/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "id": "string", "email": "string", "name": "string", "created_at": "string (ISO date)" } }, { "status": 401, "description": "Error", "schema": { "error": "Unauthorized" } } ] }, { "id": "api_users_points", "path": "/api/users/me/points", "method": "GET", "description": "Get user points balance and transaction history", "file_path": "app/api/users/me/points/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "balance": "integer", "transactions": [ { "id": "string", "amount": "integer", "type": "string", "source": "string", "created_at": "string (ISO date)" } ] } } ] }, { "id": "api_users_badges", "path": "/api/users/me/badges", "method": "GET", "description": "Get user earned badges", "file_path": "app/api/users/me/badges/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "badges": [ { "id": "string", "name": "string", "description": "string", "icon": "string", "earned_at": "string (ISO date)" } ] } } ] }, { "id": "api_tasks_list", "path": "/api/tasks", "method": "GET", "description": "List all available tasks", "file_path": "app/api/tasks/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "tasks": [ { "id": "string", "type": "string", "title": "string", "description": "string", "points_reward": "integer", "is_completed_today": "boolean" } ] } } ] }, { "id": "api_tasks_checkin", "path": "/api/tasks/checkin", "method": "POST", "description": "Complete daily check-in", "file_path": "app/api/tasks/checkin/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "points_earned": "integer", "streak_days": "integer", "new_balance": "integer" } }, { "status": 400, "description": "Error", "schema": { "error": "Already checked in today" } } ] }, { "id": "api_tasks_complete", "path": "/api/tasks/:id/complete", "method": "POST", "description": "Complete a specific task", "file_path": "app/api/tasks/[id]/complete/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "points_earned": "integer", "new_balance": "integer" } }, { "status": 400, "description": "Error", "schema": { "error": "string (task not found or already completed)" } } ] }, { "id": "api_quizzes_get", "path": "/api/quizzes/:taskId", "method": "GET", "description": "Get quiz questions for a task", "file_path": "app/api/quizzes/[taskId]/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "questions": [ { "id": "string", "question": "string", "options": [ "string" ] } ] } } ] }, { "id": "api_quizzes_submit", "path": "/api/quizzes/:taskId/submit", "method": "POST", "description": "Submit quiz answers", "file_path": "app/api/quizzes/[taskId]/submit/route.ts", "status": "PENDING", "auth_required": true, "request_body": { "answers": "object (question_id -> answer mapping)" }, "responses": [ { "status": 200, "description": "Success", "schema": { "score": "integer (0-100)", "passed": "boolean", "points_earned": "integer", "new_balance": "integer" } } ] }, { "id": "api_leaderboard", "path": "/api/leaderboard", "method": "GET", "description": "Get top users by points", "file_path": "app/api/leaderboard/route.ts", "status": "PENDING", "auth_required": true, "query_params": { "limit": "integer (optional, default 100)" }, "responses": [ { "status": 200, "description": "Success", "schema": { "leaderboard": [ { "rank": "integer", "user_id": "string", "name": "string", "total_points": "integer" } ], "current_user_rank": "integer" } } ] }, { "id": "api_referrals_create", "path": "/api/referrals", "method": "POST", "description": "Create referral code", "file_path": "app/api/referrals/route.ts", "status": "PENDING", "auth_required": true, "responses": [ { "status": 200, "description": "Success", "schema": { "referral_code": "string", "referral_url": "string" } } ] }, { "id": "api_referrals_claim", "path": "/api/referrals/claim", "method": "POST", "description": "Claim referral bonus", "file_path": "app/api/referrals/claim/route.ts", "status": "PENDING", "auth_required": true, "request_body": { "referral_code": "string (required)" }, "responses": [ { "status": 200, "description": "Success", "schema": { "bonus_points": "integer", "new_balance": "integer" } }, { "status": 400, "description": "Error", "schema": { "error": "string (invalid code or already used)" } } ] } ], "pages": [ { "id": "page_login", "name": "Login Page", "description": "User login page", "file_path": "app/login/page.tsx", "status": "PENDING", "components_used": [ "AuthForm" ], "features": [ "Email/password login form", "Link to register page", "Form validation", "Error display", "Redirect to dashboard on success" ], "path": "/login" }, { "id": "page_register", "name": "Register Page", "description": "User registration page", "file_path": "app/register/page.tsx", "status": "PENDING", "components_used": [ "AuthForm" ], "features": [ "Registration form with email, password, name", "Link to login page", "Form validation (password min 8 chars)", "Error display", "Redirect to dashboard on success" ], "path": "/register" }, { "id": "page_dashboard", "name": "Dashboard", "description": "Main dashboard with overview", "file_path": "app/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "PointsDisplay", "TaskCard", "DailyCheckinButton", "BadgeCard", "Navbar" ], "features": [ "Points balance display", "Daily check-in button with streak", "Quick task overview (3-5 featured tasks)", "Recent badges earned", "Navigation to other sections" ], "path": "/" }, { "id": "page_tasks", "name": "Tasks Page", "description": "List of all available tasks", "file_path": "app/tasks/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "TaskList", "TaskCard", "Navbar" ], "features": [ "List all available tasks", "Filter by task type", "Show completion status", "Click to complete or view task", "Points reward display" ], "path": "/tasks" }, { "id": "page_quiz", "name": "Quiz Page", "description": "Quiz interface for learning tasks", "file_path": "app/quiz/[id]/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "QuizQuestion", "Navbar" ], "features": [ "Display quiz questions one by one", "Multiple choice answers", "Submit button", "Score display after completion", "Points earned display" ], "path": "/quiz/[id]" }, { "id": "page_profile", "name": "Profile Page", "description": "User profile with points history", "file_path": "app/profile/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "PointsDisplay", "TransactionHistory", "BadgeCard", "Navbar" ], "features": [ "User info display", "Total points balance", "Points transaction history", "Earned badges display", "Account statistics" ], "path": "/profile" }, { "id": "page_leaderboard", "name": "Leaderboard Page", "description": "Rankings of top users", "file_path": "app/leaderboard/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "LeaderboardTable", "Navbar" ], "features": [ "Top 100 users by points", "User rank display", "Current user highlight", "Points totals", "Update in real-time" ], "path": "/leaderboard" }, { "id": "page_referral", "name": "Referral Page", "description": "Referral code sharing", "file_path": "app/referral/page.tsx", "status": "PENDING", "auth_required": true, "components_used": [ "Navbar" ], "features": [ "Generate referral code", "Display shareable link", "Copy to clipboard button", "Show referral statistics", "Bonus points display" ], "path": "/referral" } ], "components": [ { "id": "component_auth_form", "name": "AuthForm", "description": "Reusable login/register form component", "file_path": "app/components/AuthForm.tsx", "status": "PENDING", "props": { "mode": "'login' | 'register'", "onSubmit": "(data: AuthData) => Promise", "error": "string | null" }, "features": [ "Email and password inputs", "Name input (register mode only)", "Client-side validation", "Error display", "Loading state", "Submit button" ] }, { "id": "component_points_display", "name": "PointsDisplay", "description": "Display current points balance", "file_path": "app/components/PointsDisplay.tsx", "status": "PENDING", "props": { "balance": "number", "showAnimation": "boolean (optional)" }, "features": [ "Large points number display", "Coin/points icon", "Optional animation on change", "Dark theme styling" ] }, { "id": "component_task_card", "name": "TaskCard", "description": "Display a single task", "file_path": "app/components/TaskCard.tsx", "status": "PENDING", "props": { "task": "Task object", "onComplete": "() => void", "isCompleted": "boolean" }, "features": [ "Task title and description", "Points reward badge", "Task type icon", "Completion status", "Complete button", "Dark theme card styling" ] }, { "id": "component_task_list", "name": "TaskList", "description": "List of task cards with filtering", "file_path": "app/components/TaskList.tsx", "status": "PENDING", "props": { "tasks": "Task[]", "onTaskComplete": "(taskId: string) => void" }, "features": [ "Grid layout of TaskCard components", "Filter by task type", "Empty state message", "Loading state" ] }, { "id": "component_quiz_question", "name": "QuizQuestion", "description": "Quiz question with multiple choice", "file_path": "app/components/QuizQuestion.tsx", "status": "PENDING", "props": { "question": "string", "options": "string[]", "onAnswer": "(answer: string) => void", "selectedAnswer": "string | null" }, "features": [ "Question text display", "Radio button options", "Highlight selected answer", "Dark theme styling" ] }, { "id": "component_badge_card", "name": "BadgeCard", "description": "Display a badge or achievement", "file_path": "app/components/BadgeCard.tsx", "status": "PENDING", "props": { "badge": "Badge object", "earned": "boolean", "earnedAt": "Date | null" }, "features": [ "Badge icon display", "Badge name and description", "Earned/locked state", "Earned date (if earned)", "Tooltip with requirement" ] }, { "id": "component_leaderboard_table", "name": "LeaderboardTable", "description": "Rankings table", "file_path": "app/components/LeaderboardTable.tsx", "status": "PENDING", "props": { "entries": "LeaderboardEntry[]", "currentUserId": "string" }, "features": [ "Table with rank, name, points columns", "Highlight current user row", "Top 3 special styling", "Responsive design", "Dark theme table styling" ] }, { "id": "component_daily_checkin_button", "name": "DailyCheckinButton", "description": "Check-in button with streak display", "file_path": "app/components/DailyCheckinButton.tsx", "status": "PENDING", "props": { "onCheckIn": "() => Promise", "isCheckedInToday": "boolean", "streakDays": "number" }, "features": [ "Large prominent button", "Streak counter display", "Disabled state if already checked in", "Success animation", "Points earned display" ] }, { "id": "component_transaction_history", "name": "TransactionHistory", "description": "List of points transactions", "file_path": "app/components/TransactionHistory.tsx", "status": "PENDING", "props": { "transactions": "Transaction[]" }, "features": [ "List of transactions with date, amount, source", "Color coding (green for earned, red for spent)", "Pagination or infinite scroll", "Empty state message", "Dark theme styling" ] }, { "id": "component_navbar", "name": "Navbar", "description": "Main navigation bar", "file_path": "app/components/Navbar.tsx", "status": "PENDING", "props": { "currentPath": "string" }, "features": [ "Links to Dashboard, Tasks, Leaderboard, Profile, Referral", "Active link highlighting", "User menu with logout", "Points balance display", "Responsive mobile menu", "Dark theme styling" ] }, { "id": "component_dark_theme_layout", "name": "DarkThemeLayout", "description": "Root layout with dark theme", "file_path": "app/components/DarkThemeLayout.tsx", "status": "PENDING", "props": { "children": "React.ReactNode" }, "features": [ "Dark background colors", "Global dark theme CSS variables", "Consistent spacing and typography", "Tailwind CSS dark mode utilities" ] } ], "technical_notes": { "authentication": { "method": "JWT tokens", "storage": "HTTP-only cookies", "library": "jsonwebtoken + bcrypt" }, "database": { "type": "PostgreSQL (recommended) or SQLite for dev", "orm": "Prisma", "migrations": "Prisma migrate" }, "styling": { "framework": "Tailwind CSS 4", "theme": "Dark mode by default", "responsive": "Mobile-first design" }, "state_management": { "method": "React Context API or Zustand", "server_state": "SWR or TanStack Query" }, "validation": { "frontend": "Zod schemas", "backend": "Zod schemas (shared)" } } }