Skip to content

Commit 9550365

Browse files
committed
Redesign savings streak with flame icon and metric card layout
1 parent a51b2ff commit 9550365

2 files changed

Lines changed: 51 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
* Income matched via payee patterns is marked as received instantly
88
* Webhook signature verification with HMAC-SHA256
99
* Exempt Synci webhook endpoint from auth middleware
10-
* Show 12 days in savings streak
10+
* Redesign savings streak card with flame icon matching other metric cards
11+
* Show 7 days in savings streak
1112

1213
### 1.9.4: 2026-03-25
1314

src/components/dashboard/savings-streak.tsx

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ interface HistoryEntry {
1515
spent: number;
1616
}
1717

18+
function FlameIcon() {
19+
return (
20+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
21+
<path d="M12 2C12 2 6 8.5 6 14C6 17.31 8.69 20 12 20C15.31 20 18 17.31 18 14C18 8.5 12 2 12 2Z" fill="url(#flame-grad)" />
22+
<path d="M12 20C10.34 20 9 18.66 9 17C9 14.5 12 11 12 11C12 11 15 14.5 15 17C15 18.66 13.66 20 12 20Z" fill="url(#flame-inner)" />
23+
<defs>
24+
<linearGradient id="flame-grad" x1="12" y1="2" x2="12" y2="20" gradientUnits="userSpaceOnUse">
25+
<stop stopColor="#fbbf24" />
26+
<stop offset="1" stopColor="#f97316" />
27+
</linearGradient>
28+
<linearGradient id="flame-inner" x1="12" y1="11" x2="12" y2="20" gradientUnits="userSpaceOnUse">
29+
<stop stopColor="#fde68a" />
30+
<stop offset="1" stopColor="#fbbf24" />
31+
</linearGradient>
32+
</defs>
33+
</svg>
34+
);
35+
}
36+
1837
export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
1938
const { locale, fmt } = useLocale();
2039
const [history, setHistory] = useState<HistoryEntry[]>([]);
@@ -44,11 +63,11 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
4463
}).catch(() => {});
4564
}, [dailyBudget, todaySpent]);
4665

47-
// Last 12 days from history
66+
// Last 7 days from history
4867
const days: { day: number; month: number; status: "fire" | "fail" | "today" | "nodata"; budget: number; spent: number }[] = [];
4968
let currentStreak = 0;
5069

51-
for (let d = 11; d >= 0; d--) {
70+
for (let d = 6; d >= 0; d--) {
5271
const date = new Date(now);
5372
date.setDate(today - d);
5473
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
@@ -57,13 +76,11 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
5776
const monthNum = date.getMonth() + 1;
5877

5978
if (d === 0) {
60-
// Today — use current props for spending
6179
days.push({ day: dayNum, month: monthNum, status: "today", budget: dailyBudget, spent: todaySpent });
6280
continue;
6381
}
6482

6583
if (!entry) {
66-
// No data recorded — mark as fail (unknown)
6784
currentStreak = 0;
6885
days.push({ day: dayNum, month: monthNum, status: "nodata", budget: 0, spent: 0 });
6986
continue;
@@ -86,29 +103,36 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
86103

87104
return (
88105
<Card className="metric-card savings-streak-card">
89-
<p className="metric-card-label">{locale === "fi" ? "Säästöputki" : "Savings streak"}</p>
90-
<div className="savings-streak-dots">
91-
{days.map((d, i) => (
92-
<div
93-
key={i}
94-
className={`savings-streak-dot ${d.status === "fire" ? "is-fire" : d.status === "fail" ? "is-fail" : d.status === "today" ? (dailyBudget > 0 && d.spent <= dailyBudget ? "is-fire" : "is-today") : "is-neutral"}`}
95-
>
96-
{(d.status === "fire" || (d.status === "today" && dailyBudget > 0 && d.spent <= dailyBudget)) && <span className="savings-streak-fire-icon" />}
97-
{d.status === "fail" && <span className="savings-streak-x-icon" />}
98-
{d.status === "today" && dailyBudget > 0 && d.spent > dailyBudget && <span className="savings-streak-x-icon" />}
99-
{d.status === "nodata" && <span className="savings-streak-neutral-dot" />}
100-
<span className="savings-streak-tooltip">
101-
<span>{d.day}.{d.month}.</span>
102-
<span>{d.status === "nodata" ? (locale === "fi" ? "ei dataa" : "no data") : `${fmt(d.status === "today" ? todaySpent : d.spent)}/${fmt(d.budget)} €`}</span>
103-
</span>
106+
<div className="metric-card-row">
107+
<div className="metric-card-icon savings-streak-flame">
108+
<FlameIcon />
109+
</div>
110+
<div>
111+
<p className="metric-card-label">{locale === "fi" ? "Säästöputki" : "Savings streak"}</p>
112+
<div className="savings-streak-dots">
113+
{days.map((d, i) => (
114+
<div
115+
key={i}
116+
className={`savings-streak-dot ${d.status === "fire" ? "is-fire" : d.status === "fail" ? "is-fail" : d.status === "today" ? (dailyBudget > 0 && d.spent <= dailyBudget ? "is-fire" : "is-today") : "is-neutral"}`}
117+
>
118+
{(d.status === "fire" || (d.status === "today" && dailyBudget > 0 && d.spent <= dailyBudget)) && <span className="savings-streak-fire-icon" />}
119+
{d.status === "fail" && <span className="savings-streak-x-icon" />}
120+
{d.status === "today" && dailyBudget > 0 && d.spent > dailyBudget && <span className="savings-streak-x-icon" />}
121+
{d.status === "nodata" && <span className="savings-streak-neutral-dot" />}
122+
<span className="savings-streak-tooltip">
123+
<span>{d.day}.{d.month}.</span>
124+
<span>{d.status === "nodata" ? (locale === "fi" ? "ei dataa" : "no data") : `${fmt(d.status === "today" ? todaySpent : d.spent)}/${fmt(d.budget)} €`}</span>
125+
</span>
126+
</div>
127+
))}
104128
</div>
105-
))}
129+
<p className="metric-card-note">
130+
{currentStreak > 0
131+
? (locale === "fi" ? `${currentStreak} päivää putkeen alle budjetin` : `${currentStreak} days in a row under budget`)
132+
: (locale === "fi" ? "Ei putkea vielä" : "No streak yet")}
133+
</p>
134+
</div>
106135
</div>
107-
<p className="metric-card-note">
108-
{currentStreak > 0
109-
? (locale === "fi" ? `Olet onnistunut selviämään alle päiväbudjetin ${currentStreak} päivää putkeen.` : `You've stayed under budget ${currentStreak} days in a row.`)
110-
: (locale === "fi" ? "Ei putkea vielä. Pysy budjetissa tänään!" : "No streak yet. Stay under budget today!")}
111-
</p>
112136
</Card>
113137
);
114138
}

0 commit comments

Comments
 (0)