design overhaul + expenses page

- Stripe-style theme: single indigo (#635BFF) brand, 6px radius,
  Inter scale; rewritten themePrimitives + MUI customizations
  (solid primary, near-black secondary, severity-aware Alert)
- Sticky Header (brand-left nav, theme toggle, mobile drawer),
  inline Footer, route fade-in, per-route document.title
- Home hero + feature cards (dead /dashboard,/reports links replaced)
- Split-panel AuthPage with validation and password visibility
- Data pages: PageHeader/EmptyState, breadcrumbed Fetch Request
  pages, admin table polish (numeric alignment, row-hover actions,
  skeletons, empty states), single-accent admin SideMenu
- Global ToastProvider wired into app shell
- New /expenses page: month-grouped feed, single-open accordion
  with inline detail (account, tags, amount, timestamps)
This commit is contained in:
2026-08-17 15:14:57 +05:30
parent 51762f8d18
commit 9808a1f39b
22 changed files with 1567 additions and 790 deletions

View File

@@ -0,0 +1,85 @@
import React from "react";
import { Box, Typography, Chip, Divider } from "@mui/material";
import type { ExpenseItem } from "./types";
import { formatCurrency, formatDate, formatDateTime } from "./types";
function Row({ label, value }: { label: string; value: React.ReactNode }) {
return (
<Box sx={{ display: "flex", gap: 2, alignItems: "baseline", py: 0.75 }}>
<Typography variant="body2" color="text.secondary" sx={{ width: 160, flexShrink: 0 }}>
{label}
</Typography>
<Box sx={{ flex: 1, minWidth: 0 }}>{value}</Box>
</Box>
);
}
export function ExpenseDetail({ item }: { item: ExpenseItem }) {
const account = item.account;
const tags = item.tags ?? [];
const last4 = account?.number ? `${account.number.slice(-4)}` : "";
return (
<Box sx={{ pt: 1, pb: 0.5 }}>
<Divider sx={{ mb: 1.5 }} />
<Row
label="Amount"
value={
<Typography variant="body2" fontWeight={600}>
{formatCurrency(item.amount, account?.currency)}
</Typography>
}
/>
<Row
label="Account"
value={
<Typography variant="body2">
{account ? `${account.name}${last4 ? ` (${last4})` : ""}` : "—"}
{account?.type && (
<Chip
size="small"
label={account.type.replace(/_/g, " ")}
variant="outlined"
sx={{ ml: 1.5, fontSize: 11, height: 20 }}
/>
)}
</Typography>
}
/>
<Row
label="Tags"
value={
tags.length > 0 ? (
<Box sx={{ display: "flex", gap: 0.75, flexWrap: "wrap" }}>
{tags.map((tag, i) => (
<Chip
key={`${tag.name}-${i}`}
size="small"
label={`${tag.icon ?? ""} ${tag.name ?? ""}`.trim()}
sx={{ fontSize: 12, height: 24 }}
/>
))}
</Box>
) : (
<Typography variant="body2" color="text.disabled">
No tags
</Typography>
)
}
/>
<Row label="Date" value={<Typography variant="body2">{formatDate(item.occurred_at)}</Typography>} />
<Row
label="Transaction ID"
value={
<Typography variant="body2" sx={{ fontFamily: "monospace", fontSize: "0.8125rem" }}>
{item.id}
</Typography>
}
/>
<Row
label="Created"
value={<Typography variant="body2" color="text.secondary">{formatDateTime(item.created_at)}</Typography>}
/>
</Box>
);
}