From 7b0f6f4280509a1cebd867da82ddfef0d6bb2e5c Mon Sep 17 00:00:00 2001 From: Claude Code Date: Tue, 26 Aug 2025 13:34:00 +0000 Subject: [PATCH 1/2] feat: Build Deepseek Finance fullstack app with complete finance management system - Created comprehensive database schema for assets, investments, expenses, and income - Built RESTful API endpoints with authentication and validation - Updated landing page with Deepseek Finance branding and features - Developed finance dashboard components with summary cards and forms - Implemented tabbed finance dashboard with management capabilities All components include TypeScript types, proper error handling, and integrate with the existing auth system. --- CLAUDE.md | 64 ++++ app/api/finance/assets/route.ts | 136 +++++++++ app/api/finance/expenses/route.ts | 138 +++++++++ app/api/finance/income/route.ts | 137 +++++++++ app/api/finance/investments/route.ts | 140 +++++++++ app/dashboard/page.tsx | 244 ++++++++++++++- app/page.tsx | 150 +++++----- components/finance-data-table.tsx | 199 +++++++++++++ components/finance-form-modal.tsx | 280 ++++++++++++++++++ components/finance-summary-cards.tsx | 165 +++++++++++ db/index.ts | 12 +- db/schema/finance.ts | 141 +++++++++ documentation/app_flow_document.md | 41 +++ documentation/app_flowchart.md | 14 + documentation/backend_structure_document.md | 179 +++++++++++ documentation/frontend_guidelines_document.md | 180 +++++++++++ .../project_requirements_document.md | 117 ++++++++ documentation/security_guideline_document.md | 116 ++++++++ ...ullstack-app_2025-08-26_13-05-07-725Z.json | 82 +++++ documentation/tech_stack_document.md | 90 ++++++ lib/api-utils.ts | 39 +++ lib/validation.ts | 60 ++++ 22 files changed, 2640 insertions(+), 84 deletions(-) create mode 100644 CLAUDE.md create mode 100644 app/api/finance/assets/route.ts create mode 100644 app/api/finance/expenses/route.ts create mode 100644 app/api/finance/income/route.ts create mode 100644 app/api/finance/investments/route.ts create mode 100644 components/finance-data-table.tsx create mode 100644 components/finance-form-modal.tsx create mode 100644 components/finance-summary-cards.tsx create mode 100644 db/schema/finance.ts create mode 100644 documentation/app_flow_document.md create mode 100644 documentation/app_flowchart.md create mode 100644 documentation/backend_structure_document.md create mode 100644 documentation/frontend_guidelines_document.md create mode 100644 documentation/project_requirements_document.md create mode 100644 documentation/security_guideline_document.md create mode 100644 documentation/tasks/build-deepseek-finance-fullstack-app_2025-08-26_13-05-07-725Z.json create mode 100644 documentation/tech_stack_document.md create mode 100644 lib/api-utils.ts create mode 100644 lib/validation.ts diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..783a84f --- /dev/null +++ b/CLAUDE.md @@ -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 +``` + +### **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 "Brief description of what was implemented" +# or +task-manager cancel-task "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 ` (mark as in progress) +3. 💻 **IMPLEMENT**: Do the actual coding/implementation work +4. ✅ **COMPLETE**: `task-manager complete-task "What was done"` (or cancel with `task-manager cancel-task "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)** \ No newline at end of file diff --git a/app/api/finance/assets/route.ts b/app/api/finance/assets/route.ts new file mode 100644 index 0000000..2ab7c63 --- /dev/null +++ b/app/api/finance/assets/route.ts @@ -0,0 +1,136 @@ +import { NextRequest } from "next/server"; +import { db } from "@/db"; +import { assets } from "@/db/schema/finance"; +import { eq, and } from "drizzle-orm"; +import { assetCreateSchema, assetUpdateSchema } from "@/lib/validation"; +import { createApiResponse, handleApiError, handleValidationError } from "@/lib/api-utils"; +import { getSession } from "@/lib/auth-client"; +import { z } from "zod"; + +// GET /api/finance/assets - Get all assets for current user +export async function GET(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const userAssets = await db + .select() + .from(assets) + .where(eq(assets.userId, session.user.id)) + .orderBy(assets.createdAt); + + return createApiResponse(true, userAssets); + } catch (error) { + return handleApiError(error); + } +} + +// POST /api/finance/assets - Create a new asset +export async function POST(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const validatedData = assetCreateSchema.parse(body); + + const [newAsset] = await db + .insert(assets) + .values({ + id: crypto.randomUUID(), + userId: session.user.id, + name: validatedData.name, + type: validatedData.type, + value: validatedData.value.toString(), + purchaseDate: validatedData.purchaseDate ? new Date(validatedData.purchaseDate) : undefined, + description: validatedData.description, + }) + .returning(); + + return createApiResponse(true, newAsset, undefined, 201); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// PUT /api/finance/assets - Update an asset +export async function PUT(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const { id, ...updateData } = body; + + if (!id) { + return createApiResponse(false, undefined, "Asset ID is required", 400); + } + + const validatedData = assetUpdateSchema.parse(updateData); + + const [updatedAsset] = await db + .update(assets) + .set({ + ...validatedData, + value: validatedData.value ? validatedData.value.toString() : undefined, + purchaseDate: validatedData.purchaseDate ? new Date(validatedData.purchaseDate) : undefined, + updatedAt: new Date(), + }) + .where(and(eq(assets.id, id), eq(assets.userId, session.user.id))) + .returning(); + + if (!updatedAsset) { + return createApiResponse(false, undefined, "Asset not found", 404); + } + + return createApiResponse(true, updatedAsset); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// DELETE /api/finance/assets - Delete an asset +export async function DELETE(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const { searchParams } = new URL(request.url); + const id = searchParams.get("id"); + + if (!id) { + return createApiResponse(false, undefined, "Asset ID is required", 400); + } + + const [deletedAsset] = await db + .delete(assets) + .where(and(eq(assets.id, id), eq(assets.userId, session.user.id))) + .returning(); + + if (!deletedAsset) { + return createApiResponse(false, undefined, "Asset not found", 404); + } + + return createApiResponse(true, { message: "Asset deleted successfully" }); + } catch (error) { + return handleApiError(error); + } +} \ No newline at end of file diff --git a/app/api/finance/expenses/route.ts b/app/api/finance/expenses/route.ts new file mode 100644 index 0000000..ef0bde0 --- /dev/null +++ b/app/api/finance/expenses/route.ts @@ -0,0 +1,138 @@ +import { NextRequest } from "next/server"; +import { db } from "@/db"; +import { expenses } from "@/db/schema/finance"; +import { eq, and } from "drizzle-orm"; +import { expenseCreateSchema, expenseUpdateSchema } from "@/lib/validation"; +import { createApiResponse, handleApiError, handleValidationError } from "@/lib/api-utils"; +import { getSession } from "@/lib/auth-client"; +import { z } from "zod"; + +// GET /api/finance/expenses - Get all expenses for current user +export async function GET(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const userExpenses = await db + .select() + .from(expenses) + .where(eq(expenses.userId, session.user.id)) + .orderBy(expenses.date); + + return createApiResponse(true, userExpenses); + } catch (error) { + return handleApiError(error); + } +} + +// POST /api/finance/expenses - Create a new expense +export async function POST(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const validatedData = expenseCreateSchema.parse(body); + + const [newExpense] = await db + .insert(expenses) + .values({ + id: crypto.randomUUID(), + userId: session.user.id, + description: validatedData.description, + amount: validatedData.amount.toString(), + category: validatedData.category, + date: new Date(validatedData.date), + isRecurring: validatedData.isRecurring, + recurringFrequency: validatedData.recurringFrequency, + notes: validatedData.notes, + }) + .returning(); + + return createApiResponse(true, newExpense, undefined, 201); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// PUT /api/finance/expenses - Update an expense +export async function PUT(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const { id, ...updateData } = body; + + if (!id) { + return createApiResponse(false, undefined, "Expense ID is required", 400); + } + + const validatedData = expenseUpdateSchema.parse(updateData); + + const [updatedExpense] = await db + .update(expenses) + .set({ + ...validatedData, + amount: validatedData.amount ? validatedData.amount.toString() : undefined, + date: validatedData.date ? new Date(validatedData.date) : undefined, + updatedAt: new Date(), + }) + .where(and(eq(expenses.id, id), eq(expenses.userId, session.user.id))) + .returning(); + + if (!updatedExpense) { + return createApiResponse(false, undefined, "Expense not found", 404); + } + + return createApiResponse(true, updatedExpense); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// DELETE /api/finance/expenses - Delete an expense +export async function DELETE(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const { searchParams } = new URL(request.url); + const id = searchParams.get("id"); + + if (!id) { + return createApiResponse(false, undefined, "Expense ID is required", 400); + } + + const [deletedExpense] = await db + .delete(expenses) + .where(and(eq(expenses.id, id), eq(expenses.userId, session.user.id))) + .returning(); + + if (!deletedExpense) { + return createApiResponse(false, undefined, "Expense not found", 404); + } + + return createApiResponse(true, { message: "Expense deleted successfully" }); + } catch (error) { + return handleApiError(error); + } +} \ No newline at end of file diff --git a/app/api/finance/income/route.ts b/app/api/finance/income/route.ts new file mode 100644 index 0000000..78c22da --- /dev/null +++ b/app/api/finance/income/route.ts @@ -0,0 +1,137 @@ +import { NextRequest } from "next/server"; +import { db } from "@/db"; +import { income } from "@/db/schema/finance"; +import { eq, and } from "drizzle-orm"; +import { incomeCreateSchema, incomeUpdateSchema } from "@/lib/validation"; +import { createApiResponse, handleApiError, handleValidationError } from "@/lib/api-utils"; +import { getSession } from "@/lib/auth-client"; +import { z } from "zod"; + +// GET /api/finance/income - Get all income records for current user +export async function GET(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const userIncome = await db + .select() + .from(income) + .where(eq(income.userId, session.user.id)) + .orderBy(income.date); + + return createApiResponse(true, userIncome); + } catch (error) { + return handleApiError(error); + } +} + +// POST /api/finance/income - Create a new income record +export async function POST(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const validatedData = incomeCreateSchema.parse(body); + + const [newIncome] = await db + .insert(income) + .values({ + id: crypto.randomUUID(), + userId: session.user.id, + source: validatedData.source, + amount: validatedData.amount.toString(), + frequency: validatedData.frequency, + date: new Date(validatedData.date), + isRecurring: validatedData.isRecurring, + description: validatedData.description, + }) + .returning(); + + return createApiResponse(true, newIncome, undefined, 201); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// PUT /api/finance/income - Update an income record +export async function PUT(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const { id, ...updateData } = body; + + if (!id) { + return createApiResponse(false, undefined, "Income ID is required", 400); + } + + const validatedData = incomeUpdateSchema.parse(updateData); + + const [updatedIncome] = await db + .update(income) + .set({ + ...validatedData, + amount: validatedData.amount ? validatedData.amount.toString() : undefined, + date: validatedData.date ? new Date(validatedData.date) : undefined, + updatedAt: new Date(), + }) + .where(and(eq(income.id, id), eq(income.userId, session.user.id))) + .returning(); + + if (!updatedIncome) { + return createApiResponse(false, undefined, "Income record not found", 404); + } + + return createApiResponse(true, updatedIncome); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// DELETE /api/finance/income - Delete an income record +export async function DELETE(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const { searchParams } = new URL(request.url); + const id = searchParams.get("id"); + + if (!id) { + return createApiResponse(false, undefined, "Income ID is required", 400); + } + + const [deletedIncome] = await db + .delete(income) + .where(and(eq(income.id, id), eq(income.userId, session.user.id))) + .returning(); + + if (!deletedIncome) { + return createApiResponse(false, undefined, "Income record not found", 404); + } + + return createApiResponse(true, { message: "Income record deleted successfully" }); + } catch (error) { + return handleApiError(error); + } +} \ No newline at end of file diff --git a/app/api/finance/investments/route.ts b/app/api/finance/investments/route.ts new file mode 100644 index 0000000..958bd94 --- /dev/null +++ b/app/api/finance/investments/route.ts @@ -0,0 +1,140 @@ +import { NextRequest } from "next/server"; +import { db } from "@/db"; +import { investments } from "@/db/schema/finance"; +import { eq, and } from "drizzle-orm"; +import { investmentCreateSchema, investmentUpdateSchema } from "@/lib/validation"; +import { createApiResponse, handleApiError, handleValidationError } from "@/lib/api-utils"; +import { getSession } from "@/lib/auth-client"; +import { z } from "zod"; + +// GET /api/finance/investments - Get all investments for current user +export async function GET(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const userInvestments = await db + .select() + .from(investments) + .where(eq(investments.userId, session.user.id)) + .orderBy(investments.createdAt); + + return createApiResponse(true, userInvestments); + } catch (error) { + return handleApiError(error); + } +} + +// POST /api/finance/investments - Create a new investment +export async function POST(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const validatedData = investmentCreateSchema.parse(body); + + const [newInvestment] = await db + .insert(investments) + .values({ + id: crypto.randomUUID(), + userId: session.user.id, + name: validatedData.name, + type: validatedData.type, + amount: validatedData.amount.toString(), + currentValue: validatedData.currentValue.toString(), + purchaseDate: new Date(validatedData.purchaseDate), + tickerSymbol: validatedData.tickerSymbol, + description: validatedData.description, + isActive: validatedData.isActive, + }) + .returning(); + + return createApiResponse(true, newInvestment, undefined, 201); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// PUT /api/finance/investments - Update an investment +export async function PUT(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const body = await request.json(); + const { id, ...updateData } = body; + + if (!id) { + return createApiResponse(false, undefined, "Investment ID is required", 400); + } + + const validatedData = investmentUpdateSchema.parse(updateData); + + const [updatedInvestment] = await db + .update(investments) + .set({ + ...validatedData, + amount: validatedData.amount ? validatedData.amount.toString() : undefined, + currentValue: validatedData.currentValue ? validatedData.currentValue.toString() : undefined, + purchaseDate: validatedData.purchaseDate ? new Date(validatedData.purchaseDate) : undefined, + updatedAt: new Date(), + }) + .where(and(eq(investments.id, id), eq(investments.userId, session.user.id))) + .returning(); + + if (!updatedInvestment) { + return createApiResponse(false, undefined, "Investment not found", 404); + } + + return createApiResponse(true, updatedInvestment); + } catch (error) { + if (error instanceof z.ZodError) { + return handleValidationError(error); + } + return handleApiError(error); + } +} + +// DELETE /api/finance/investments - Delete an investment +export async function DELETE(request: NextRequest) { + try { + const session = await getSession(); + + if (!session?.user) { + return createApiResponse(false, undefined, "Unauthorized", 401); + } + + const { searchParams } = new URL(request.url); + const id = searchParams.get("id"); + + if (!id) { + return createApiResponse(false, undefined, "Investment ID is required", 400); + } + + const [deletedInvestment] = await db + .delete(investments) + .where(and(eq(investments.id, id), eq(investments.userId, session.user.id))) + .returning(); + + if (!deletedInvestment) { + return createApiResponse(false, undefined, "Investment not found", 404); + } + + return createApiResponse(true, { message: "Investment deleted successfully" }); + } catch (error) { + return handleApiError(error); + } +} \ No newline at end of file diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d1765ef..28000e9 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,18 +1,242 @@ -import { ChartAreaInteractive } from "@//components/chart-area-interactive" -import { DataTable } from "@//components/data-table" -import { SectionCards } from "@//components/section-cards" -import data from "@/app/dashboard/data.json" +"use client"; + +import { FinanceSummaryCards } from "@/components/finance-summary-cards"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Plus, Landmark, TrendingUp, Wallet, DollarSign } from "lucide-react"; +import { FinanceFormModal } from "@/components/finance-form-modal"; export default function Page() { return (
- -
- -
- + {/* Financial Summary Cards */} + + + {/* Main Content Tabs */} + + + Overview + Assets + Investments + Expenses + Income + + + {/* Overview Tab */} + +
+ + + + + Recent Assets + + Your most valuable possessions + + +
+

No assets added yet

+ + + Add Asset + + } + onSubmit={async () => {}} + /> +
+
+
+ + + + + + Recent Investments + + Your investment portfolio + + +
+

No investments added yet

+ + + Add Investment + + } + onSubmit={async () => {}} + /> +
+
+
+
+ +
+ + + + + Recent Expenses + + Your spending activity + + +
+

No expenses added yet

+ + + Add Expense + + } + onSubmit={async () => {}} + /> +
+
+
+ + + + + + Recent Income + + Your earnings + + +
+

No income records added yet

+ + + Add Income + + } + onSubmit={async () => {}} + /> +
+
+
+
+
+ + {/* Individual Category Tabs */} + + + + Assets Management + + Manage your assets including real estate, vehicles, and valuables + + + +
+

Assets management interface coming soon

+ + + Add New Asset + + } + onSubmit={async () => {}} + /> +
+
+
+
+ + + + + Investments Management + + Manage your investment portfolio including stocks, bonds, and crypto + + + +
+

Investments management interface coming soon

+ + + Add New Investment + + } + onSubmit={async () => {}} + /> +
+
+
+
+ + + + + Expenses Management + + Track and categorize your spending habits + + + +
+

Expenses management interface coming soon

+ + + Add New Expense + + } + onSubmit={async () => {}} + /> +
+
+
+
+ + + + + Income Management + + Track your income sources and earnings + + + +
+

Income management interface coming soon

+ + + Add New Income + + } + onSubmit={async () => {}} + /> +
+
+
+
+
- ) + ); } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index e7ebec9..5e8bdca 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,6 +10,12 @@ import { Globe, Palette, Package, + Landmark, + TrendingUp, + Wallet, + DollarSign, + PiggyBank, + BarChart3, } from "lucide-react"; import { ThemeToggle } from "@/components/theme-toggle"; import { AuthButtons, HeroAuthButtons } from "@/components/auth-buttons"; @@ -28,19 +34,15 @@ export default function Home() {
- CodeGuide Logo -

- CodeGuide Starter +
+ +
+

+ Deepseek Finance

- A modern full-stack TypeScript starter with authentication, database, and UI components + Take control of your financial future. Track assets, investments, expenses, and income in one powerful platform.

@@ -49,122 +51,124 @@ export default function Home() {
{/* Project Overview */}
-
🚀
-
Modern Full-Stack Starter
+
💰
+
Comprehensive Financial Management
- This project includes everything you need to build a modern web application with TypeScript, - authentication, database integration, and a beautiful UI component library. + Manage your entire financial life in one place. Track assets, monitor investments, + analyze expenses, and optimize your income with powerful analytics and insights.
- {/* Tech Stack Grid */} + {/* Features Grid */}
- {/* Frontend */} + {/* Assets Tracking */}
- -

Frontend

+ +

Assets Tracking

    -
  • Next.js 15 - React framework with App Router
  • -
  • React 19 - Latest React with concurrent features
  • -
  • TypeScript - Type-safe development
  • -
  • Turbopack - Fast bundling and dev server
  • +
  • • Track real estate, vehicles, and valuables
  • +
  • • Monitor asset appreciation over time
  • +
  • • Categorize by type and location
  • +
  • • Calculate total net worth
- {/* UI & Styling */} - + {/* Investment Management */} +
- -

UI & Styling

+ +

Investment Management

    -
  • Tailwind CSS 4 - Utility-first CSS framework
  • -
  • Radix UI - Accessible component primitives
  • -
  • Lucide Icons - Beautiful icon library
  • -
  • Dark Mode - Built-in theme switching
  • +
  • • Monitor stocks, bonds, and crypto
  • +
  • • Track portfolio performance
  • +
  • • Set investment goals
  • +
  • • Analyze returns and dividends
- {/* Authentication */} - + {/* Expense Tracking */} +
- -

Authentication

+ +

Expense Tracking

    -
  • Better Auth - Modern auth solution
  • -
  • Session Management - Secure user sessions
  • -
  • Type Safety - Fully typed auth hooks
  • -
  • Multiple Providers - Social login support
  • +
  • • Categorize spending habits
  • +
  • • Set monthly budgets
  • +
  • • Identify cost-saving opportunities
  • +
  • • Track recurring payments
- {/* Database */} + {/* Income Management */}
- -

Database

+ +

Income Management

    -
  • PostgreSQL - Robust relational database
  • -
  • Drizzle ORM - Type-safe database toolkit
  • -
  • Docker Setup - Containerized development
  • -
  • Migrations - Schema version control
  • +
  • • Track multiple income sources
  • +
  • • Monitor salary and bonuses
  • +
  • • Analyze income trends
  • +
  • • Plan for tax optimization
- {/* Development */} + {/* Savings Goals */}
- -

Development

+ +

Savings Goals

    -
  • ESLint - Code linting and formatting
  • -
  • Hot Reload - Instant development feedback
  • -
  • Docker - Consistent dev environment
  • -
  • npm Scripts - Automated workflows
  • +
  • • Set financial targets
  • +
  • • Track progress towards goals
  • +
  • • Automate savings plans
  • +
  • • Celebrate milestones
- {/* Components */} + {/* Analytics & Reports */}
- -

Components

+ +

Analytics & Reports

    -
  • Form Handling - React Hook Form + Zod
  • -
  • Data Visualization - Recharts integration
  • -
  • Date Pickers - Beautiful date components
  • -
  • Notifications - Toast and alert systems
  • +
  • • Visual financial dashboards
  • +
  • • Customizable reports
  • +
  • • Historical trend analysis
  • +
  • • Export to PDF/CSV
- {/* Getting Started */} - + {/* Get Started Today */} +

- - Quick Start + + Start Your Financial Journey

-

Development

-
-
npm install
-
npm run db:dev
-
npm run dev
+

New to Deepseek Finance?

+
+
• Sign up for free account
+
• Connect your financial data
+
• Set up your first budget
+
• Start tracking immediately
-

Production

-
-
npm run build
-
npm run start
-
npm run docker:up
+

Existing User?

+
+
• Sign in to your dashboard
+
• Review your financial health
+
• Update recent transactions
+
• Check progress on goals
diff --git a/components/finance-data-table.tsx b/components/finance-data-table.tsx new file mode 100644 index 0000000..d02d107 --- /dev/null +++ b/components/finance-data-table.tsx @@ -0,0 +1,199 @@ +"use client"; + +import { useState } from "react"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + getPaginationRowModel, + getSortedRowModel, + SortingState, + ColumnFiltersState, + getFilteredRowModel, +} from "@tanstack/react-table"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ChevronLeft, ChevronRight, Settings, Search } from "lucide-react"; + +interface FinanceDataTableProps { + columns: ColumnDef[]; + data: TData[]; + title: string; + description?: string; +} + +export function FinanceDataTable({ + columns, + data, + title, + description, +}: FinanceDataTableProps) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [rowSelection, setRowSelection] = useState({}); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + rowSelection, + }, + }); + + return ( +
+
+
+

{title}

+ {description && ( +

{description}

+ )} +
+ +
+
+ + + table.getColumn("name")?.setFilterValue(event.target.value) + } + className="pl-8 w-64" + /> +
+ + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results found. + + + )} + +
+
+ +
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+ +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/components/finance-form-modal.tsx b/components/finance-form-modal.tsx new file mode 100644 index 0000000..f0467a4 --- /dev/null +++ b/components/finance-form-modal.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; + +interface FinanceFormModalProps { + type: "asset" | "investment" | "expense" | "income"; + trigger?: React.ReactNode; + onSubmit: (data: any) => Promise; + defaultValues?: any; +} + +const formSchemas = { + asset: z.object({ + name: z.string().min(1, "Name is required"), + type: z.enum(["cash", "real_estate", "vehicle", "jewelry", "electronics", "other"]), + value: z.number().positive("Value must be positive"), + purchaseDate: z.string().optional(), + description: z.string().optional(), + }), + investment: z.object({ + name: z.string().min(1, "Name is required"), + type: z.enum(["stocks", "bonds", "mutual_funds", "crypto", "real_estate", "retirement", "other"]), + amount: z.number().positive("Amount must be positive"), + currentValue: z.number().positive("Current value must be positive"), + purchaseDate: z.string(), + tickerSymbol: z.string().optional(), + description: z.string().optional(), + isActive: z.boolean().default(true), + }), + expense: z.object({ + description: z.string().min(1, "Description is required"), + amount: z.number().positive("Amount must be positive"), + category: z.enum(["housing", "food", "transportation", "utilities", "healthcare", "entertainment", "education", "clothing", "personal_care", "debt", "savings", "gifts", "other"]), + date: z.string(), + isRecurring: z.boolean().default(false), + recurringFrequency: z.string().optional(), + notes: z.string().optional(), + }), + income: z.object({ + source: z.string().min(1, "Source is required"), + amount: z.number().positive("Amount must be positive"), + frequency: z.enum(["weekly", "biweekly", "monthly", "quarterly", "yearly", "one_time"]), + date: z.string(), + isRecurring: z.boolean().default(true), + description: z.string().optional(), + }), +}; + +const typeLabels = { + asset: "Asset", + investment: "Investment", + expense: "Expense", + income: "Income", +}; + +const typeOptions = { + asset: [ + { value: "cash", label: "Cash" }, + { value: "real_estate", label: "Real Estate" }, + { value: "vehicle", label: "Vehicle" }, + { value: "jewelry", label: "Jewelry" }, + { value: "electronics", label: "Electronics" }, + { value: "other", label: "Other" }, + ], + investment: [ + { value: "stocks", label: "Stocks" }, + { value: "bonds", label: "Bonds" }, + { value: "mutual_funds", label: "Mutual Funds" }, + { value: "crypto", label: "Cryptocurrency" }, + { value: "real_estate", label: "Real Estate" }, + { value: "retirement", label: "Retirement" }, + { value: "other", label: "Other" }, + ], + expense: [ + { value: "housing", label: "Housing" }, + { value: "food", label: "Food" }, + { value: "transportation", label: "Transportation" }, + { value: "utilities", label: "Utilities" }, + { value: "healthcare", label: "Healthcare" }, + { value: "entertainment", label: "Entertainment" }, + { value: "education", label: "Education" }, + { value: "clothing", label: "Clothing" }, + { value: "personal_care", label: "Personal Care" }, + { value: "debt", label: "Debt" }, + { value: "savings", label: "Savings" }, + { value: "gifts", label: "Gifts" }, + { value: "other", label: "Other" }, + ], + income: [ + { value: "weekly", label: "Weekly" }, + { value: "biweekly", label: "Bi-weekly" }, + { value: "monthly", label: "Monthly" }, + { value: "quarterly", label: "Quarterly" }, + { value: "yearly", label: "Yearly" }, + { value: "one_time", label: "One-time" }, + ], +}; + +export function FinanceFormModal({ + type, + trigger, + onSubmit, + defaultValues, +}: FinanceFormModalProps) { + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(formSchemas[type]), + defaultValues: defaultValues || { + isRecurring: false, + isActive: true, + }, + }); + + const handleSubmit = async (data: any) => { + try { + setLoading(true); + await onSubmit(data); + setOpen(false); + form.reset(); + } catch (error) { + console.error("Error submitting form:", error); + } finally { + setLoading(false); + } + }; + + return ( + + + {trigger || ( + + )} + + + + + {defaultValues ? "Edit" : "Add New"} {typeLabels[type]} + + + +
+ + {type === "asset" && ( + <> + ( + + Asset Name + + + + + + )} + /> + ( + + Asset Type + + + + )} + /> + ( + + Value + + field.onChange(parseFloat(e.target.value))} + /> + + + + )} + /> + ( + + Purchase Date (Optional) + + + + + + )} + /> + ( + + Description (Optional) + +