Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Claude Code Task Management Guide

## Documentation Available

πŸ“š **Project Documentation**: Check the documentation files in this directory for project-specific setup instructions and guides.
**Project Tasks**: Check the tasks directory in documentation/tasks for the list of tasks to be completed. Use the CLI commands below to interact with them.

## MANDATORY Task Management Workflow

🚨 **YOU MUST FOLLOW THIS EXACT WORKFLOW - NO EXCEPTIONS** 🚨

### **STEP 1: DISCOVER TASKS (MANDATORY)**
You MUST start by running this command to see all available tasks:
```bash
task-manager list-tasks
```

### **STEP 2: START EACH TASK (MANDATORY)**
Before working on any task, you MUST mark it as started:
```bash
task-manager start-task <task_id>
```

### **STEP 3: COMPLETE OR CANCEL EACH TASK (MANDATORY)**
After finishing implementation, you MUST mark the task as completed, or cancel if you cannot complete it:
```bash
task-manager complete-task <task_id> "Brief description of what was implemented"
# or
task-manager cancel-task <task_id> "Reason for cancellation"
```

## Task Files Location

πŸ“ **Task Data**: Your tasks are organized in the `documentation/tasks/` directory:
- Task JSON files contain complete task information
- Use ONLY the `task-manager` commands listed above
- Follow the mandatory workflow sequence for each task

## MANDATORY Task Workflow Sequence

πŸ”„ **For EACH individual task, you MUST follow this sequence:**

1. πŸ“‹ **DISCOVER**: `task-manager list-tasks` (first time only)
2. πŸš€ **START**: `task-manager start-task <task_id>` (mark as in progress)
3. πŸ’» **IMPLEMENT**: Do the actual coding/implementation work
4. βœ… **COMPLETE**: `task-manager complete-task <task_id> "What was done"` (or cancel with `task-manager cancel-task <task_id> "Reason"`)
5. πŸ” **REPEAT**: Go to next task (start from step 2)

## Task Status Options

- `pending` - Ready to work on
- `in_progress` - Currently being worked on
- `completed` - Successfully finished
- `blocked` - Cannot proceed (waiting for dependencies)
- `cancelled` - No longer needed

## CRITICAL WORKFLOW RULES

❌ **NEVER skip** the `task-manager start-task` command
❌ **NEVER skip** the `task-manager complete-task` command (use `task-manager cancel-task` if a task is not planned, not required, or you must stop it)
❌ **NEVER work on multiple tasks simultaneously**
βœ… **ALWAYS complete one task fully before starting the next**
βœ… **ALWAYS provide completion details in the complete command**
βœ… **ALWAYS follow the exact 3-step sequence: list β†’ start β†’ complete (or cancel if not required)**
57 changes: 57 additions & 0 deletions app/api/finance/assets/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NextRequest, NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { db } from "@/db"
import { assets } from "@/db/schema/finance"
import { eq } from "drizzle-orm"
import { z } from "zod"

const assetSchema = z.object({
name: z.string().min(1, "Name is required"),
type: z.enum(["cash", "real_estate", "vehicle", "other"]),
value: z.number().positive("Value must be positive"),
description: z.string().optional(),
purchaseDate: z.string().optional(),
})

export async function GET() {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const userAssets = await db
.select()
.from(assets)
.where(eq(assets.userId, session.user.id))

return NextResponse.json(userAssets)
} catch (error) {
return NextResponse.json({ error: "Failed to fetch assets" }, { status: 500 })
}
}

export async function POST(request: NextRequest) {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const body = await request.json()
const validatedData = assetSchema.parse(body)

const newAsset = await db.insert(assets).values({
...validatedData,
userId: session.user.id,
id: crypto.randomUUID(),
}).returning()

return NextResponse.json(newAsset[0], { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: error.errors }, { status: 400 })
}
return NextResponse.json({ error: "Failed to create asset" }, { status: 500 })
}
}
61 changes: 61 additions & 0 deletions app/api/finance/expenses/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { NextRequest, NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { db } from "@/db"
import { expenses } from "@/db/schema/finance"
import { eq } from "drizzle-orm"
import { z } from "zod"

const expenseSchema = z.object({
amount: z.number().positive("Amount must be positive"),
category: z.enum([
"food", "transport", "housing", "utilities", "entertainment",
"healthcare", "education", "shopping", "other"
]),
description: z.string().optional(),
date: z.string(),
paymentMethod: z.enum(["cash", "credit_card", "debit_card", "bank_transfer", "other"]).optional(),
})

export async function GET() {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const userExpenses = await db
.select()
.from(expenses)
.where(eq(expenses.userId, session.user.id))
.orderBy(expenses.date)

return NextResponse.json(userExpenses)
} catch (error) {
return NextResponse.json({ error: "Failed to fetch expenses" }, { status: 500 })
}
}

export async function POST(request: NextRequest) {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const body = await request.json()
const validatedData = expenseSchema.parse(body)

const newExpense = await db.insert(expenses).values({
...validatedData,
userId: session.user.id,
id: crypto.randomUUID(),
}).returning()

return NextResponse.json(newExpense[0], { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: error.errors }, { status: 400 })
}
return NextResponse.json({ error: "Failed to create expense" }, { status: 500 })
}
}
61 changes: 61 additions & 0 deletions app/api/finance/income/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { NextRequest, NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { db } from "@/db"
import { income } from "@/db/schema/finance"
import { eq } from "drizzle-orm"
import { z } from "zod"

const incomeSchema = z.object({
amount: z.number().positive("Amount must be positive"),
source: z.enum([
"salary", "freelance", "investment", "business", "gift", "other"
]),
description: z.string().optional(),
date: z.string(),
recurring: z.boolean().default(false),
frequency: z.enum(["weekly", "monthly", "yearly"]).optional(),
})

export async function GET() {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const userIncome = await db
.select()
.from(income)
.where(eq(income.userId, session.user.id))
.orderBy(income.date)

return NextResponse.json(userIncome)
} catch (error) {
return NextResponse.json({ error: "Failed to fetch income" }, { status: 500 })
}
}

export async function POST(request: NextRequest) {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const body = await request.json()
const validatedData = incomeSchema.parse(body)

const newIncome = await db.insert(income).values({
...validatedData,
userId: session.user.id,
id: crypto.randomUUID(),
}).returning()

return NextResponse.json(newIncome[0], { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: error.errors }, { status: 400 })
}
return NextResponse.json({ error: "Failed to create income" }, { status: 500 })
}
}
62 changes: 62 additions & 0 deletions app/api/finance/investments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { db } from "@/db"
import { investments } from "@/db/schema/finance"
import { eq } from "drizzle-orm"
import { z } from "zod"

const investmentSchema = z.object({
name: z.string().min(1, "Name is required"),
type: z.enum(["stocks", "bonds", "crypto", "mutual_funds", "etf", "other"]),
symbol: z.string().optional(),
quantity: z.number().positive("Quantity must be positive"),
purchasePrice: z.number().positive("Purchase price must be positive"),
currentPrice: z.number().positive("Current price must be positive"),
purchaseDate: z.string().optional(),
})

export async function GET() {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const userInvestments = await db
.select()
.from(investments)
.where(eq(investments.userId, session.user.id))

return NextResponse.json(userInvestments)
} catch (error) {
return NextResponse.json({ error: "Failed to fetch investments" }, { status: 500 })
}
}

export async function POST(request: NextRequest) {
try {
const session = await auth.api.getSession({ headers: new Headers() })
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const body = await request.json()
const validatedData = investmentSchema.parse(body)

const totalValue = validatedData.quantity * validatedData.currentPrice

const newInvestment = await db.insert(investments).values({
...validatedData,
totalValue,
userId: session.user.id,
id: crypto.randomUUID(),
}).returning()

return NextResponse.json(newInvestment[0], { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: error.errors }, { status: 400 })
}
return NextResponse.json({ error: "Failed to create investment" }, { status: 500 })
}
}
34 changes: 34 additions & 0 deletions app/dashboard/assets/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AddAssetForm } from "@/components/forms/add-asset-form"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { ArrowLeft, Plus } from "lucide-react"
import Link from "next/link"

export default function AssetsPage() {
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<Button variant="ghost" asChild>
<Link href="/dashboard">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Dashboard
</Link>
</Button>
</div>

<div className="max-w-2xl mx-auto">
<Card>
<CardHeader>
<CardTitle>Add New Asset</CardTitle>
<CardDescription>
Track your valuable possessions and cash holdings
</CardDescription>
</CardHeader>
<CardContent>
<AddAssetForm />
</CardContent>
</Card>
</div>
</div>
)
}
34 changes: 34 additions & 0 deletions app/dashboard/expenses/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AddExpenseForm } from "@/components/forms/add-expense-form"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { ArrowLeft, Plus } from "lucide-react"
import Link from "next/link"

export default function ExpensesPage() {
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<Button variant="ghost" asChild>
<Link href="/dashboard">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Dashboard
</Link>
</Button>
</div>

<div className="max-w-2xl mx-auto">
<Card>
<CardHeader>
<CardTitle>Add New Expense</CardTitle>
<CardDescription>
Track and categorize your spending habits
</CardDescription>
</CardHeader>
<CardContent>
<AddExpenseForm />
</CardContent>
</Card>
</div>
</div>
)
}
Loading