Index: frontend/package-lock.json
===================================================================
--- frontend/package-lock.json	(revision 708af9efba48db0445f07728b5d091a3d8898876)
+++ frontend/package-lock.json	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -17,4 +17,5 @@
         "react-dom": "^19.2.0",
         "react-icons": "^5.5.0",
+        "react-is": "^19.2.5",
         "react-router-dom": "^7.13.0"
       },
@@ -9541,4 +9542,10 @@
       }
     },
+    "node_modules/react-is": {
+      "version": "19.2.5",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz",
+      "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==",
+      "license": "MIT"
+    },
     "node_modules/react-refresh": {
       "version": "0.18.0",
Index: frontend/package.json
===================================================================
--- frontend/package.json	(revision 708af9efba48db0445f07728b5d091a3d8898876)
+++ frontend/package.json	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -19,4 +19,5 @@
     "react-dom": "^19.2.0",
     "react-icons": "^5.5.0",
+    "react-is": "^19.2.5",
     "react-router-dom": "^7.13.0"
   },
Index: frontend/src/App.jsx
===================================================================
--- frontend/src/App.jsx	(revision 708af9efba48db0445f07728b5d091a3d8898876)
+++ frontend/src/App.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -13,4 +13,6 @@
 import NewWeightIntake from "./pages/Dashboard/pages/Weight/NewWeightIntake.jsx";
 import Finance from "./pages/Dashboard/pages/Finance/Finance.jsx";
+import FinanceTracking from "./pages/Dashboard/pages/Finance/FinanceTracking.jsx";
+import NewIncome from "./pages/Dashboard/pages/Finance/NewIncome.jsx";
 import Investing from "./pages/Dashboard/pages/Investing/Investing.jsx";
 import InvestingTracking from "./pages/Dashboard/pages/Investing/InvestingTracking.jsx";
@@ -51,4 +53,6 @@
           <Route path="weight/intakes/new" element={<NewWeightIntake />} />
           <Route path="finance" element={<Finance />} />
+           <Route path="finance/tracking" element={<FinanceTracking />} />
+           <Route path="finance/incomes/new" element={<NewIncome />} />
           <Route path="investing" element={<Investing />} />
           <Route path="investing/tracking" element={<InvestingTracking />} />
Index: frontend/src/api/finance.js
===================================================================
--- frontend/src/api/finance.js	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/api/finance.js	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,26 @@
+import api from "./axios";
+
+export async function getFinanceStatus() {
+  const res = await api.get("/finance/status");
+  return Boolean(res?.data?.tracking);
+}
+
+export async function startFinanceTracking(payload) {
+  return api.post("/finance/start", payload);
+}
+
+export async function getFinanceProfile() {
+  const res = await api.get("/finance/profile");
+  return res?.data;
+}
+
+export async function getIncomes({ page = 0, size = 5 } = {}) {
+  const res = await api.get("/finance/incomes", { params: { page, size } });
+  return res?.data;
+}
+
+export async function createIncome(payload) {
+  const res = await api.post("/finance/incomes", payload);
+  return res?.data;
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/Finance.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/Finance.jsx	(revision 708af9efba48db0445f07728b5d091a3d8898876)
+++ frontend/src/pages/Dashboard/pages/Finance/Finance.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -1,16 +1,125 @@
-import React from "react";
+import React, { useEffect, useMemo, useState } from "react";
+import { useNavigate } from "react-router-dom";
 
-const Finance = () => {
+import { getFinanceStatus, startFinanceTracking } from "../../../../api/finance";
+
+import FinanceCenteredCard from "./components/FinanceCenteredCard.jsx";
+import FinanceStartCtaCard from "./components/FinanceStartCtaCard.jsx";
+import FinanceStartForm from "./components/FinanceStartForm.jsx";
+
+export default function Finance() {
+  const navigate = useNavigate();
+
+  const [isLoading, setIsLoading] = useState(true);
+  const [isTracking, setIsTracking] = useState(false);
+  const [step, setStep] = useState("cta"); // cta | form
+  const [error, setError] = useState("");
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  const initialForm = useMemo(
+    () => ({
+      spendingBudget: "50",
+      savingBudget: "20",
+      investingBudget: "20",
+      donationBudget: "5",
+      credit: "5",
+    }),
+    [],
+  );
+
+  const [form, setForm] = useState(initialForm);
+
+  useEffect(() => {
+    let isMounted = true;
+    const run = async () => {
+      setIsLoading(true);
+      setError("");
+      try {
+        const tracking = await getFinanceStatus();
+        if (!isMounted) return;
+        setIsTracking(tracking);
+        if (tracking) {
+          navigate("/dashboard/finance/tracking", { replace: true });
+        }
+      } catch (err) {
+        if (!isMounted) return;
+        const message =
+          err?.response?.data?.message || "Failed to load finance status";
+        setError(message);
+      } finally {
+        if (isMounted) setIsLoading(false);
+      }
+    };
+    run();
+    return () => {
+      isMounted = false;
+    };
+  }, [navigate]);
+
+  const onStart = () => {
+    setError("");
+    setStep("form");
+  };
+
+  const onCancel = () => {
+    setStep("cta");
+    setForm(initialForm);
+    setError("");
+  };
+
+  const onSubmit = async (e) => {
+    e.preventDefault();
+    setError("");
+    setIsSubmitting(true);
+    try {
+      await startFinanceTracking({
+        spendingBudget:
+          form.spendingBudget === "" ? null : Number(form.spendingBudget),
+        savingBudget: form.savingBudget === "" ? null : Number(form.savingBudget),
+        investingBudget:
+          form.investingBudget === "" ? null : Number(form.investingBudget),
+        donationBudget:
+          form.donationBudget === "" ? null : Number(form.donationBudget),
+        credit: form.credit === "" ? null : Number(form.credit),
+      });
+
+      setIsTracking(true);
+      navigate("/dashboard/finance/tracking", { replace: true });
+    } catch (err) {
+      const message =
+        err?.response?.data?.message ||
+        Object.values(err?.response?.data?.errors ?? {})[0] ||
+        "Failed to start tracking";
+      setError(message);
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  if (isLoading) {
+    return <FinanceCenteredCard title="Finance" message="Loading…" />;
+  }
+
+  if (isTracking) {
+    return <FinanceCenteredCard title="Finance" message="Redirecting…" />;
+  }
+
   return (
-    <div className="space-y-4">
-      <h1 className="text-2xl font-bold">Finance</h1>
-      <div className="card bg-base-200 border border-base-300">
-        <div className="card-body">
-          <p className="opacity-80">Finance dashboard content goes here.</p>
-        </div>
+    <div className="min-h-[70vh] w-full flex items-center justify-center">
+      <div className="w-full max-w-3xl">
+        {step === "cta" ? (
+          <FinanceStartCtaCard error={error} onStart={onStart} />
+        ) : (
+          <FinanceStartForm
+            form={form}
+            setForm={setForm}
+            onSubmit={onSubmit}
+            onCancel={onCancel}
+            error={error}
+            isSubmitting={isSubmitting}
+          />
+        )}
       </div>
     </div>
   );
-};
-
-export default Finance;
+}
Index: frontend/src/pages/Dashboard/pages/Finance/FinanceTracking.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/FinanceTracking.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/FinanceTracking.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,229 @@
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+import { useNavigate } from "react-router-dom";
+
+import graphPlaceholder from "../../../../assets/graph-placeholder.svg";
+import {
+  getFinanceProfile,
+  getIncomes,
+  startFinanceTracking,
+} from "../../../../api/finance";
+
+import FinanceSummaryCard from "./components/FinanceSummaryCard.jsx";
+import FinanceIncomesTable from "./components/FinanceIncomesTable.jsx";
+import FinanceStartForm from "./components/FinanceStartForm.jsx";
+
+function toNumber(value) {
+  const n = Number(value);
+  return Number.isFinite(n) ? n : 0;
+}
+
+export default function FinanceTracking() {
+  const navigate = useNavigate();
+  const pageSize = 5;
+
+  const [profile, setProfile] = useState(null);
+  const [incomes, setIncomes] = useState([]);
+  const [page, setPage] = useState(0);
+  const [hasMore, setHasMore] = useState(false);
+
+  const [isLoading, setIsLoading] = useState(true);
+  const [isLoadingMore, setIsLoadingMore] = useState(false);
+  const [error, setError] = useState("");
+
+  const [isEditing, setIsEditing] = useState(false);
+  const [editError, setEditError] = useState("");
+  const [isSavingProfile, setIsSavingProfile] = useState(false);
+  const [editForm, setEditForm] = useState({
+    spendingBudget: "",
+    savingBudget: "",
+    investingBudget: "",
+    donationBudget: "",
+    credit: "",
+  });
+
+  const segments = useMemo(() => {
+    const p = profile ?? {};
+    return [
+      { key: "spending", label: "Spending", value: toNumber(p.spendingBudget), color: "#22c55e" },
+      { key: "saving", label: "Saving", value: toNumber(p.savingBudget), color: "#3b82f6" },
+      { key: "investing", label: "Investing", value: toNumber(p.investingBudget), color: "#a855f7" },
+      { key: "donation", label: "Donation", value: toNumber(p.donationBudget), color: "#f97316" },
+      { key: "credit", label: "Credit", value: toNumber(p.credit), color: "#ef4444" },
+    ];
+  }, [profile]);
+
+  const totalThisMonth = useMemo(() => {
+    const now = new Date();
+    const y = now.getFullYear();
+    const m = now.getMonth();
+
+    return (incomes ?? []).reduce((acc, i) => {
+      const d = new Date(i?.date);
+      if (Number.isNaN(d.getTime())) return acc;
+      if (d.getFullYear() !== y || d.getMonth() !== m) return acc;
+      const amount = Number(i?.amount);
+      if (!Number.isFinite(amount)) return acc;
+      return acc + amount;
+    }, 0);
+  }, [incomes]);
+
+  const fetchPage = useCallback(async (nextPage) => {
+    const data = await getIncomes({ page: nextPage, size: pageSize });
+    return {
+      nextIncomes: Array.isArray(data?.incomes) ? data.incomes : [],
+      nextHasMore: Boolean(data?.hasMore),
+    };
+  }, []);
+
+  useEffect(() => {
+    let cancelled = false;
+    (async () => {
+      try {
+        setIsLoading(true);
+        setError("");
+
+        const [p, inc] = await Promise.all([getFinanceProfile(), fetchPage(0)]);
+        if (cancelled) return;
+        setProfile(p);
+        setEditForm({
+          spendingBudget: p?.spendingBudget ?? "",
+          savingBudget: p?.savingBudget ?? "",
+          investingBudget: p?.investingBudget ?? "",
+          donationBudget: p?.donationBudget ?? "",
+          credit: p?.credit ?? "",
+        });
+        setIncomes(inc.nextIncomes);
+        setHasMore(inc.nextHasMore);
+        setPage(0);
+      } catch (e) {
+        if (cancelled) return;
+        setError(e?.response?.data?.message || "Failed to load finance dashboard.");
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    })();
+
+    return () => {
+      cancelled = true;
+    };
+  }, [fetchPage]);
+
+  const onEdit = () => {
+    setEditError("");
+    setIsEditing(true);
+  };
+
+  const onCancelEdit = () => {
+    setEditError("");
+    const p = profile ?? {};
+    setEditForm({
+      spendingBudget: p?.spendingBudget ?? "",
+      savingBudget: p?.savingBudget ?? "",
+      investingBudget: p?.investingBudget ?? "",
+      donationBudget: p?.donationBudget ?? "",
+      credit: p?.credit ?? "",
+    });
+    setIsEditing(false);
+  };
+
+  const onSaveProfile = async (e) => {
+    e.preventDefault();
+    setEditError("");
+    setIsSavingProfile(true);
+    try {
+      await startFinanceTracking({
+        spendingBudget:
+          editForm.spendingBudget === "" ? null : Number(editForm.spendingBudget),
+        savingBudget:
+          editForm.savingBudget === "" ? null : Number(editForm.savingBudget),
+        investingBudget:
+          editForm.investingBudget === "" ? null : Number(editForm.investingBudget),
+        donationBudget:
+          editForm.donationBudget === "" ? null : Number(editForm.donationBudget),
+        credit: editForm.credit === "" ? null : Number(editForm.credit),
+      });
+
+      const refreshed = await getFinanceProfile();
+      setProfile(refreshed);
+      setEditForm({
+        spendingBudget: refreshed?.spendingBudget ?? "",
+        savingBudget: refreshed?.savingBudget ?? "",
+        investingBudget: refreshed?.investingBudget ?? "",
+        donationBudget: refreshed?.donationBudget ?? "",
+        credit: refreshed?.credit ?? "",
+      });
+      setIsEditing(false);
+    } catch (err) {
+      const message =
+        err?.response?.data?.message ||
+        Object.values(err?.response?.data?.errors ?? {})[0] ||
+        "Failed to update finance profile";
+      setEditError(message);
+    } finally {
+      setIsSavingProfile(false);
+    }
+  };
+
+  const onLoadMore = async () => {
+    if (isLoadingMore || !hasMore) return;
+    const nextPage = page + 1;
+    try {
+      setIsLoadingMore(true);
+      setError("");
+      const { nextIncomes, nextHasMore } = await fetchPage(nextPage);
+      setIncomes((prev) => [...prev, ...nextIncomes]);
+      setHasMore(nextHasMore);
+      setPage(nextPage);
+    } catch (e) {
+      setError(e?.response?.data?.message || "Failed to load more incomes.");
+    } finally {
+      setIsLoadingMore(false);
+    }
+  };
+
+  return (
+    <div className="space-y-6">
+      <div className="flex items-center justify-between gap-3">
+        <h1 className="text-2xl font-bold">Finance</h1>
+      </div>
+
+      {isEditing ? (
+        <FinanceStartForm
+          form={editForm}
+          setForm={setEditForm}
+          onSubmit={onSaveProfile}
+          onCancel={onCancelEdit}
+          error={editError}
+          isSubmitting={isSavingProfile}
+        />
+      ) : (
+        <FinanceSummaryCard
+          segments={segments}
+          totalThisMonth={totalThisMonth}
+          onEdit={onEdit}
+        />
+      )}
+
+      <div className="card bg-base-200 border border-base-300">
+        <div className="card-body">
+          <h2 className="card-title">Progress</h2>
+          <p className="text-sm opacity-80">(Placeholder graph for now)</p>
+          <div className="mt-4">
+            <img src={graphPlaceholder} alt="Finance graph placeholder" className="w-full" />
+          </div>
+        </div>
+      </div>
+
+      <FinanceIncomesTable
+        incomes={incomes}
+        isLoading={isLoading}
+        error={error}
+        hasMore={hasMore}
+        isLoadingMore={isLoadingMore}
+        onLoadMore={onLoadMore}
+        onAddIncome={() => navigate("/dashboard/finance/incomes/new")}
+      />
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/NewIncome.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/NewIncome.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/NewIncome.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,50 @@
+import React, { useState } from "react";
+import { useNavigate } from "react-router-dom";
+
+import { createIncome } from "../../../../api/finance";
+import NewIncomeForm from "./components/NewIncomeForm.jsx";
+
+export default function NewIncome() {
+  const navigate = useNavigate();
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [error, setError] = useState("");
+
+  const onSubmit = async ({ date, amount }) => {
+    setError("");
+    setIsSubmitting(true);
+    try {
+      await createIncome({
+        date,
+        amount: amount === "" ? null : Number(amount),
+      });
+      navigate("/dashboard/finance/tracking", { replace: true });
+    } catch (e) {
+      const message =
+        e?.response?.data?.message ||
+        Object.values(e?.response?.data?.errors ?? {})[0] ||
+        "Failed to add income";
+      setError(message);
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  return (
+    <div className="space-y-4">
+      <div className="flex items-center justify-between">
+        <h1 className="text-2xl font-bold">Finance</h1>
+        <button className="btn btn-ghost" onClick={() => navigate(-1)}>
+          Back
+        </button>
+      </div>
+
+      <NewIncomeForm
+        onSubmit={onSubmit}
+        onCancel={() => navigate("/dashboard/finance/tracking")}
+        error={error}
+        isSubmitting={isSubmitting}
+      />
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceCenteredCard.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceCenteredCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceCenteredCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,17 @@
+import React from "react";
+
+export default function FinanceCenteredCard({ title, message }) {
+  return (
+    <div className="min-h-[70vh] w-full flex items-center justify-center">
+      <div className="w-full max-w-3xl">
+        <div className="card bg-base-200 border border-base-300">
+          <div className="card-body">
+            <h1 className="card-title text-2xl">{title}</h1>
+            <p className="opacity-80">{message}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceIncomesTable.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceIncomesTable.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceIncomesTable.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,79 @@
+import React from "react";
+
+function formatDate(value) {
+  if (!value) return "—";
+  const d = new Date(value);
+  if (Number.isNaN(d.getTime())) return String(value);
+  return d.toLocaleDateString(undefined, {
+    year: "numeric",
+    month: "short",
+    day: "2-digit",
+  });
+}
+
+export default function FinanceIncomesTable({
+  incomes,
+  isLoading,
+  error,
+  hasMore,
+  isLoadingMore,
+  onLoadMore,
+  onAddIncome,
+}) {
+  return (
+    <div className="card bg-base-200 border border-base-300">
+      <div className="card-body">
+        <div className="flex items-center justify-between gap-3">
+          <h2 className="card-title">Incomes</h2>
+          <button className="btn btn-primary btn-sm" onClick={onAddIncome}>
+            Add income
+          </button>
+        </div>
+
+        {error ? <p className="text-error text-sm mt-2">{error}</p> : null}
+
+        <div className="mt-4 overflow-x-auto">
+          <table className="table table-zebra">
+            <thead>
+              <tr>
+                <th>Date</th>
+                <th className="text-right">Amount</th>
+              </tr>
+            </thead>
+            <tbody>
+              {isLoading ? (
+                <tr>
+                  <td colSpan={2} className="opacity-70">
+                    Loading…
+                  </td>
+                </tr>
+              ) : (incomes ?? []).length === 0 ? (
+                <tr>
+                  <td colSpan={2} className="opacity-70">
+                    No incomes yet.
+                  </td>
+                </tr>
+              ) : (
+                (incomes ?? []).map((i) => (
+                  <tr key={i.incomeId}>
+                    <td>{formatDate(i.date)}</td>
+                    <td className="text-right tabular-nums">{Number(i.amount).toFixed?.(2) ?? i.amount}</td>
+                  </tr>
+                ))
+              )}
+            </tbody>
+          </table>
+        </div>
+
+        <div className="mt-4">
+          {hasMore ? (
+            <button className="btn btn-outline btn-sm" disabled={isLoadingMore} onClick={onLoadMore}>
+              {isLoadingMore ? "Loading…" : "Load more"}
+            </button>
+          ) : null}
+        </div>
+      </div>
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceLegend.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceLegend.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceLegend.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,21 @@
+import React from "react";
+
+export default function FinanceLegend({ segments }) {
+  return (
+    <div className="space-y-2 text-sm">
+      {(segments ?? []).map((s) => (
+        <div key={s.key} className="flex items-center justify-between gap-3">
+          <div className="flex items-center gap-2">
+            <span
+              className="inline-block h-3 w-3 rounded"
+              style={{ backgroundColor: s.color }}
+            />
+            <span className="opacity-80">{s.label}</span>
+          </div>
+          <span className="font-medium tabular-nums">{Number(s.value) || 0}%</span>
+        </div>
+      ))}
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinancePieChart.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinancePieChart.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinancePieChart.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,67 @@
+import React, { useMemo } from "react";
+
+function clamp01(n) {
+  if (!Number.isFinite(n)) return 0;
+  return Math.max(0, Math.min(1, n));
+}
+
+function polarToCartesian(cx, cy, r, angleDeg) {
+  const rad = ((angleDeg - 90) * Math.PI) / 180;
+  return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
+}
+
+function arcPath(cx, cy, r, startAngle, endAngle) {
+  const start = polarToCartesian(cx, cy, r, endAngle);
+  const end = polarToCartesian(cx, cy, r, startAngle);
+  const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
+  return `M ${cx} ${cy} L ${start.x} ${start.y} A ${r} ${r} 0 ${largeArcFlag} 0 ${end.x} ${end.y} Z`;
+}
+
+export default function FinancePieChart({ segments, size = 180 }) {
+  const r = size / 2;
+  const cx = r;
+  const cy = r;
+
+  const normalized = useMemo(() => {
+    const values = (segments ?? []).map((s) => ({
+      ...s,
+      value: Math.max(0, Number(s.value) || 0),
+    }));
+    const sum = values.reduce((acc, s) => acc + s.value, 0);
+    if (sum <= 0) return values.map((s) => ({ ...s, frac: 0 }));
+    return values.map((s) => ({ ...s, frac: clamp01(s.value / sum) }));
+  }, [segments]);
+
+  let currentAngle = 0;
+
+  return (
+    <svg
+      width={size}
+      height={size}
+      viewBox={`0 0 ${size} ${size}`}
+      role="img"
+      aria-label="Budget distribution pie chart"
+    >
+      <circle cx={cx} cy={cy} r={r} fill="#111827" opacity="0.15" />
+      {normalized.map((s) => {
+        const start = currentAngle;
+        const sweep = s.frac * 360;
+        const end = start + sweep;
+        currentAngle = end;
+
+        if (sweep <= 0.001) return null;
+        return (
+          <path
+            key={s.key}
+            d={arcPath(cx, cy, r, start, end)}
+            fill={s.color}
+            stroke="rgba(0,0,0,0.2)"
+            strokeWidth="1"
+          />
+        );
+      })}
+      <circle cx={cx} cy={cy} r={r * 0.55} fill="var(--fallback-b1, oklch(var(--b1)/1))" />
+    </svg>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartCtaCard.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartCtaCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartCtaCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,22 @@
+import React from "react";
+
+export default function FinanceStartCtaCard({ error, onStart }) {
+  return (
+    <div className="card bg-base-200 border border-base-300">
+      <div className="card-body">
+        <h1 className="card-title text-2xl">Finance</h1>
+        <p className="opacity-80">
+          Start tracking your finances by setting your yearly budget distribution
+          percentages.
+        </p>
+        {error ? <p className="text-error text-sm mt-2">{error}</p> : null}
+        <div className="mt-4">
+          <button className="btn btn-primary" onClick={onStart}>
+            Start tracking
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartForm.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartForm.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceStartForm.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,155 @@
+import React, { useMemo } from "react";
+import FinanceLegend from "./FinanceLegend.jsx";
+import FinancePieChart from "./FinancePieChart.jsx";
+
+function toNumber(value) {
+  if (value === "" || value === null || value === undefined) return 0;
+  const n = Number(value);
+  return Number.isFinite(n) ? n : 0;
+}
+
+export default function FinanceStartForm({ form, setForm, onSubmit, onCancel, error, isSubmitting }) {
+  const segments = useMemo(
+    () => [
+      { key: "spending", label: "Spending", value: toNumber(form.spendingBudget), color: "#22c55e" },
+      { key: "saving", label: "Saving", value: toNumber(form.savingBudget), color: "#3b82f6" },
+      { key: "investing", label: "Investing", value: toNumber(form.investingBudget), color: "#a855f7" },
+      { key: "donation", label: "Donation", value: toNumber(form.donationBudget), color: "#f97316" },
+      { key: "credit", label: "Credit", value: toNumber(form.credit), color: "#ef4444" },
+    ],
+    [form],
+  );
+
+  const sum = segments.reduce((acc, s) => acc + (Number(s.value) || 0), 0);
+  const sumOk = Math.abs(sum - 100) <= 0.01;
+
+  const onChange = (key) => (e) => {
+    const value = e.target.value;
+    setForm((prev) => ({ ...prev, [key]: value }));
+  };
+
+  return (
+    <div className="card bg-base-200 border border-base-300">
+      <div className="card-body gap-6">
+        <div>
+          <h2 className="card-title text-xl">Set your budget distribution</h2>
+          <p className="text-sm opacity-80">
+            Enter 5 percentages that sum to 100. You can fine-tune later.
+          </p>
+        </div>
+
+        <form onSubmit={onSubmit} className="grid grid-cols-1 gap-6 lg:grid-cols-2">
+          <div className="space-y-4">
+            <label className="form-control">
+              <div className="label">
+                <span className="label-text">Spending (%)</span>
+              </div>
+              <input
+                className="input input-bordered w-full"
+                type="number"
+                min="0"
+                max="100"
+                step="0.01"
+                value={form.spendingBudget}
+                onChange={onChange("spendingBudget")}
+                required
+              />
+            </label>
+
+            <label className="form-control">
+              <div className="label">
+                <span className="label-text">Saving (%)</span>
+              </div>
+              <input
+                className="input input-bordered w-full"
+                type="number"
+                min="0"
+                max="100"
+                step="0.01"
+                value={form.savingBudget}
+                onChange={onChange("savingBudget")}
+                required
+              />
+            </label>
+
+            <label className="form-control">
+              <div className="label">
+                <span className="label-text">Investing (%)</span>
+              </div>
+              <input
+                className="input input-bordered w-full"
+                type="number"
+                min="0"
+                max="100"
+                step="0.01"
+                value={form.investingBudget}
+                onChange={onChange("investingBudget")}
+                required
+              />
+            </label>
+
+            <label className="form-control">
+              <div className="label">
+                <span className="label-text">Donation (%)</span>
+              </div>
+              <input
+                className="input input-bordered w-full"
+                type="number"
+                min="0"
+                max="100"
+                step="0.01"
+                value={form.donationBudget}
+                onChange={onChange("donationBudget")}
+                required
+              />
+            </label>
+
+            <label className="form-control">
+              <div className="label">
+                <span className="label-text">Credit (%)</span>
+              </div>
+              <input
+                className="input input-bordered w-full"
+                type="number"
+                min="0"
+                max="100"
+                step="0.01"
+                value={form.credit}
+                onChange={onChange("credit")}
+                required
+              />
+            </label>
+
+            <div className="text-sm">
+              <span className={sumOk ? "text-success" : "text-warning"}>
+                Total: {sum.toFixed(2)}%
+              </span>
+              {!sumOk ? (
+                <span className="opacity-70"> (must equal 100%)</span>
+              ) : null}
+            </div>
+
+            {error ? <p className="text-error text-sm">{error}</p> : null}
+
+            <div className="flex gap-2">
+              <button className="btn btn-primary" type="submit" disabled={isSubmitting || !sumOk}>
+                {isSubmitting ? "Saving…" : "Start tracking"}
+              </button>
+              <button className="btn" type="button" onClick={onCancel} disabled={isSubmitting}>
+                Cancel
+              </button>
+            </div>
+          </div>
+
+          <div className="grid grid-cols-1 gap-4 md:grid-cols-[auto_1fr] items-center">
+            <div className="flex justify-center md:justify-start">
+              <FinancePieChart segments={segments} size={200} />
+            </div>
+            <FinanceLegend segments={segments} />
+          </div>
+        </form>
+      </div>
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/FinanceSummaryCard.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/FinanceSummaryCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/FinanceSummaryCard.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,87 @@
+import React from "react";
+import FinanceLegend from "./FinanceLegend.jsx";
+import FinancePieChart from "./FinancePieChart.jsx";
+
+function formatMoney(value) {
+  const n = Number(value);
+  if (!Number.isFinite(n)) return "—";
+  try {
+    return new Intl.NumberFormat(undefined, {
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2,
+    }).format(n);
+  } catch {
+    return n.toFixed(2);
+  }
+}
+
+export default function FinanceSummaryCard({ segments, totalThisMonth, onEdit }) {
+  return (
+    <div className="card bg-base-200 border border-base-300">
+      <div className="card-body">
+        <div className="flex items-start justify-between gap-6 flex-wrap">
+          <div>
+            <h2 className="card-title">Budget distribution</h2>
+            <p className="text-sm opacity-80">
+              Your current percentages (stored in your finance profile).
+            </p>
+          </div>
+
+          <div className="flex items-center gap-2">
+            <button className="btn btn-outline btn-sm" onClick={onEdit}>
+              Edit
+            </button>
+          </div>
+        </div>
+
+        <div className="mt-4">
+          <div className="rounded-lg border border-base-300 bg-base-100 p-4">
+            <div className="text-sm opacity-70">Total money earned this month</div>
+            <div className="text-2xl font-bold tabular-nums">
+              {formatMoney(totalThisMonth)}
+            </div>
+          </div>
+        </div>
+
+        <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-[auto_1fr] items-center">
+          <div className="flex justify-center md:justify-start">
+            <FinancePieChart segments={segments} size={180} />
+          </div>
+
+          <div className="space-y-4">
+            <FinanceLegend segments={segments} />
+
+            <div className="divider my-0" />
+
+            <div className="space-y-2 text-sm">
+              {segments.map((s) => {
+                const pct = Number(s.value) || 0;
+                const allocated = ((Number(totalThisMonth) || 0) * pct) / 100;
+                return (
+                  <div
+                    key={s.key}
+                    className="flex items-center justify-between gap-3"
+                  >
+                    <div className="flex items-center gap-2">
+                      <span
+                        className="inline-block h-3 w-3 rounded"
+                        style={{ backgroundColor: s.color }}
+                      />
+                      <span className="opacity-80">
+                        {s.label} allocation
+                      </span>
+                    </div>
+                    <span className="font-medium tabular-nums">
+                      {formatMoney(allocated)}
+                    </span>
+                  </div>
+                );
+              })}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
Index: frontend/src/pages/Dashboard/pages/Finance/components/NewIncomeForm.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/Finance/components/NewIncomeForm.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ frontend/src/pages/Dashboard/pages/Finance/components/NewIncomeForm.jsx	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
@@ -0,0 +1,65 @@
+import React, { useState } from "react";
+
+export default function NewIncomeForm({ onSubmit, onCancel, error, isSubmitting }) {
+  const [date, setDate] = useState(() => {
+    const d = new Date();
+    return d.toISOString().slice(0, 10);
+  });
+  const [amount, setAmount] = useState("");
+
+  return (
+    <div className="card bg-base-200 border border-base-300">
+      <div className="card-body">
+        <h2 className="card-title">Add income</h2>
+
+        <form
+          className="mt-4 grid grid-cols-1 gap-4 max-w-md"
+          onSubmit={(e) => {
+            e.preventDefault();
+            onSubmit({ date, amount });
+          }}
+        >
+          <label className="form-control">
+            <div className="label">
+              <span className="label-text">Date</span>
+            </div>
+            <input
+              className="input input-bordered"
+              type="date"
+              value={date}
+              onChange={(e) => setDate(e.target.value)}
+              required
+            />
+          </label>
+
+          <label className="form-control">
+            <div className="label">
+              <span className="label-text">Amount</span>
+            </div>
+            <input
+              className="input input-bordered"
+              type="number"
+              min="0.01"
+              step="0.01"
+              value={amount}
+              onChange={(e) => setAmount(e.target.value)}
+              required
+            />
+          </label>
+
+          {error ? <p className="text-error text-sm">{error}</p> : null}
+
+          <div className="flex gap-2">
+            <button className="btn btn-primary" type="submit" disabled={isSubmitting}>
+              {isSubmitting ? "Saving…" : "Save"}
+            </button>
+            <button className="btn" type="button" onClick={onCancel} disabled={isSubmitting}>
+              Cancel
+            </button>
+          </div>
+        </form>
+      </div>
+    </div>
+  );
+}
+
