project-standalo-todo-super/.workflow/versions/v001/design/design_document.json

1196 lines
30 KiB
JSON

{
"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<void>",
"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<void>",
"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)"
}
}
}