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
20 changes: 13 additions & 7 deletions frontend/src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, use } from 'react';
import { useThemeStore } from '../stores/themeStore';

export type Theme = 'light' | 'dark';

Expand All @@ -7,10 +7,16 @@ export interface ThemeContextType {
toggleTheme: () => void;
}

export const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

/**
* useTheme hook — reads theme state from the zustand store.
*
* Components that call this hook will re-render when the theme changes.
* Theme persistence is handled by the zustand persist middleware.
*/
export const useTheme = () => {
const context = use(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
};
const theme = useThemeStore((s) => s.theme as Theme);
const toggleTheme = useThemeStore((s) => s.toggleTheme);
const setTheme = useThemeStore((s) => s.setTheme);

return { theme, toggleTheme, setTheme };
};
70 changes: 70 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,41 @@
--accent2: #7c6ff7;
--danger: #ff7b72;
--success: #3fb950;
--warning: #ffd500;
--accent-alpha-06: rgba(74, 240, 184, 0.06);
--accent-alpha-08: rgba(74, 240, 184, 0.08);
--accent-alpha-10: rgba(74, 240, 184, 0.1);
--accent-alpha-12: rgba(74, 240, 184, 0.12);
--accent-alpha-15: rgba(74, 240, 184, 0.15);
--accent-alpha-20: rgba(74, 240, 184, 0.2);
--accent-alpha-30: rgba(74, 240, 184, 0.3);
--accent2-alpha-06: rgba(124, 111, 247, 0.06);
--accent2-alpha-10: rgba(124, 111, 247, 0.1);
--accent2-alpha-12: rgba(124, 111, 247, 0.12);
--accent2-alpha-15: rgba(124, 111, 247, 0.15);
--accent2-alpha-20: rgba(124, 111, 247, 0.2);
--accent2-alpha-25: rgba(124, 111, 247, 0.25);
--accent2-hover: #6657e8;
--danger-alpha-08: rgba(255, 123, 114, 0.08);
--danger-alpha-10: rgba(255, 123, 114, 0.1);
--danger-alpha-12: rgba(255, 123, 114, 0.12);
--danger-alpha-15: rgba(255, 123, 114, 0.15);
--danger-alpha-20: rgba(255, 123, 114, 0.2);
--danger-hover: #ff5b53;
--success-alpha-10: rgba(63, 185, 80, 0.1);
--success-alpha-12: rgba(63, 185, 80, 0.12);
--success-alpha-20: rgba(63, 185, 80, 0.2);
--warning-alpha-10: rgba(255, 213, 0, 0.1);
--warning-alpha-12: rgba(255, 213, 0, 0.12);
--warning-alpha-20: rgba(255, 213, 0, 0.2);
--accent-glow: 0 0 20px rgba(74, 240, 184, 0.3);
--glass-bg: rgba(255, 255, 255, 0.03);
--input-bg: rgba(255, 255, 255, 0.05);
--input-bg-hover: rgba(255, 255, 255, 0.08);
--row-hover: rgba(255, 255, 255, 0.02);
--scrim: rgba(0, 0, 0, 0.2);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.35);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
--header-h: 64px;
--font-head: 'Syne', sans-serif;
--font-body: 'Inter', sans-serif;
Expand All @@ -40,6 +75,41 @@
--accent2: #6c5ce7;
--danger: #d1242f;
--success: #1a7f37;
--warning: #daa520;
--accent-alpha-06: rgba(13, 150, 104, 0.06);
--accent-alpha-08: rgba(13, 150, 104, 0.08);
--accent-alpha-10: rgba(13, 150, 104, 0.1);
--accent-alpha-12: rgba(13, 150, 104, 0.12);
--accent-alpha-15: rgba(13, 150, 104, 0.15);
--accent-alpha-20: rgba(13, 150, 104, 0.2);
--accent-alpha-30: rgba(13, 150, 104, 0.3);
--accent2-alpha-06: rgba(108, 92, 231, 0.06);
--accent2-alpha-10: rgba(108, 92, 231, 0.1);
--accent2-alpha-12: rgba(108, 92, 231, 0.12);
--accent2-alpha-15: rgba(108, 92, 231, 0.15);
--accent2-alpha-20: rgba(108, 92, 231, 0.2);
--accent2-alpha-25: rgba(108, 92, 231, 0.25);
--accent2-hover: #5a4bd6;
--danger-alpha-08: rgba(209, 36, 47, 0.08);
--danger-alpha-10: rgba(209, 36, 47, 0.1);
--danger-alpha-12: rgba(209, 36, 47, 0.12);
--danger-alpha-15: rgba(209, 36, 47, 0.15);
--danger-alpha-20: rgba(209, 36, 47, 0.2);
--danger-hover: #b71b26;
--success-alpha-10: rgba(26, 127, 55, 0.1);
--success-alpha-12: rgba(26, 127, 55, 0.12);
--success-alpha-20: rgba(26, 127, 55, 0.2);
--warning-alpha-10: rgba(218, 165, 32, 0.1);
--warning-alpha-12: rgba(218, 165, 32, 0.12);
--warning-alpha-20: rgba(218, 165, 32, 0.2);
--accent-glow: 0 0 20px rgba(13, 150, 104, 0.3);
--glass-bg: rgba(255, 255, 255, 0.7);
--input-bg: rgba(0, 0, 0, 0.03);
--input-bg-hover: rgba(0, 0, 0, 0.05);
--row-hover: rgba(0, 0, 0, 0.02);
--scrim: rgba(0, 0, 0, 0.4);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--header-h: 64px;
--font-head: 'Syne', sans-serif;
--font-body: 'Inter', sans-serif;
Expand Down
72 changes: 36 additions & 36 deletions frontend/src/pages/CashFlowForecast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default function CashFlowForecast() {
const { direction } = forecast.trendAnalysis;
if (direction === 'increasing') return <TrendingUp className="w-5 h-5 text-emerald-400" />;
if (direction === 'decreasing') return <TrendingDown className="w-5 h-5 text-red-400" />;
return <Minus className="w-5 h-5 text-zinc-400" />;
return <Minus className="w-5 h-5 text-[var(--muted)]" />;
};

const getAlertIcon = (severity: BudgetAlert['severity']) => {
Expand All @@ -146,7 +146,7 @@ export default function CashFlowForecast() {
<header className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-white mb-2">Cash Flow Forecast</h1>
<p className="text-sm text-zinc-400">
<p className="text-sm text-[var(--muted)]">
Analyze historical payroll data and project future cash flow requirements
</p>
</div>
Expand All @@ -156,14 +156,14 @@ export default function CashFlowForecast() {
placeholder="Distribution Account"
value={params.distributionAccount}
onChange={(e) => setParams({ ...params, distributionAccount: e.target.value })}
className="px-4 py-2 bg-zinc-900 border border-zinc-800 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[44px]"
className="px-4 py-2 bg-zinc-900 border border-[var(--border-hi)] rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[44px]"
/>
<input
type="text"
placeholder="Asset Issuer"
value={params.assetIssuer}
onChange={(e) => setParams({ ...params, assetIssuer: e.target.value })}
className="px-4 py-2 bg-zinc-900 border border-zinc-800 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[44px]"
className="px-4 py-2 bg-zinc-900 border border-[var(--border-hi)] rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[44px]"
/>
<input
type="number"
Expand All @@ -172,7 +172,7 @@ export default function CashFlowForecast() {
onChange={(e) =>
setParams({ ...params, forecastDays: parseInt(e.target.value, 10) || 90 })
}
className="px-4 py-2 bg-zinc-900 border border-zinc-800 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 w-24 min-h-[44px]"
className="px-4 py-2 bg-zinc-900 border border-[var(--border-hi)] rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 w-24 min-h-[44px]"
/>
<button
onClick={() => {
Expand All @@ -192,48 +192,48 @@ export default function CashFlowForecast() {
{[1, 2, 3].map((i) => (
<div
key={i}
className="animate-pulse bg-zinc-900 border border-zinc-800 rounded-xl p-6 h-64"
className="animate-pulse bg-zinc-900 border border-[var(--border-hi)] rounded-xl p-6 h-64"
/>
))}
</div>
) : forecast ? (
<>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-zinc-400">Current Balance</span>
<DollarSign className="w-5 h-5 text-zinc-500" />
<span className="text-sm text-[var(--muted)]">Current Balance</span>
<DollarSign className="w-5 h-5 text-[var(--muted)]" />
</div>
<p className="text-2xl font-bold text-white">
{parseFloat(forecast.currentBalance).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</p>
<p className="text-xs text-zinc-500 mt-1">ORGUSD</p>
<p className="text-xs text-[var(--muted)] mt-1">ORGUSD</p>
</div>

<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-zinc-400">Projected Outflow</span>
<Calendar className="w-5 h-5 text-zinc-500" />
<span className="text-sm text-[var(--muted)]">Projected Outflow</span>
<Calendar className="w-5 h-5 text-[var(--muted)]" />
</div>
<p className="text-2xl font-bold text-white">
{forecast.totalProjectedOutflow.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</p>
<p className="text-xs text-zinc-500 mt-1">
<p className="text-xs text-[var(--muted)] mt-1">
{forecast.projections.length} scheduled payments
</p>
</div>

<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-zinc-400">Projected Balance</span>
<TrendingDown className="w-5 h-5 text-zinc-500" />
<span className="text-sm text-[var(--muted)]">Projected Balance</span>
<TrendingDown className="w-5 h-5 text-[var(--muted)]" />
</div>
<p
className={`text-2xl font-bold ${
Expand All @@ -245,18 +245,18 @@ export default function CashFlowForecast() {
maximumFractionDigits: 2,
})}
</p>
<p className="text-xs text-zinc-500 mt-1">After all projections</p>
<p className="text-xs text-[var(--muted)] mt-1">After all projections</p>
</div>

<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-zinc-400">Trend</span>
<span className="text-sm text-[var(--muted)]">Trend</span>
{getTrendIcon()}
</div>
<p className="text-2xl font-bold text-white capitalize">
{forecast.trendAnalysis.direction}
</p>
<p className="text-xs text-zinc-500 mt-1">
<p className="text-xs text-[var(--muted)] mt-1">
{forecast.trendAnalysis.changePercent > 0 ? '+' : ''}
{forecast.trendAnalysis.changePercent.toFixed(1)}% vs historical
</p>
Expand All @@ -265,7 +265,7 @@ export default function CashFlowForecast() {

{/* Budget Alerts */}
{alerts.length > 0 && (
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<AlertTriangle className="w-5 h-5 text-yellow-400" />
Budget Alerts ({alerts.length})
Expand Down Expand Up @@ -297,7 +297,7 @@ export default function CashFlowForecast() {
>
{alert.severity}
</span>
<span className="text-xs text-zinc-400">
<span className="text-xs text-[var(--muted)]">
{new Date(alert.projectedDate).toLocaleDateString()}
</span>
</div>
Expand All @@ -318,7 +318,7 @@ export default function CashFlowForecast() {
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Cash Flow Projection Chart */}
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<h3 className="text-lg font-bold text-white mb-4">Cash Flow Projection</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={balanceProjectionData}>
Expand All @@ -333,7 +333,7 @@ export default function CashFlowForecast() {
<YAxis stroke="#9ca3af" fontSize={12} />
<Tooltip
contentStyle={{
backgroundColor: '#18181b',
backgroundColor: 'var(--surface)',
border: '1px solid #27272a',
borderRadius: '8px',
}}
Expand All @@ -350,7 +350,7 @@ export default function CashFlowForecast() {
</div>

{/* Historical vs Projected Chart */}
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<h3 className="text-lg font-bold text-white mb-4">Historical vs Projected</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData}>
Expand All @@ -359,7 +359,7 @@ export default function CashFlowForecast() {
<YAxis stroke="#9ca3af" fontSize={12} />
<Tooltip
contentStyle={{
backgroundColor: '#18181b',
backgroundColor: 'var(--surface)',
border: '1px solid #27272a',
borderRadius: '8px',
}}
Expand All @@ -373,29 +373,29 @@ export default function CashFlowForecast() {
</div>

{/* Historical Averages */}
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-6">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-6">
<h3 className="text-lg font-bold text-white mb-4">Historical Averages</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-4 bg-zinc-900/50 rounded-lg border border-zinc-800">
<p className="text-sm text-zinc-400 mb-1">Weekly Average</p>
<div className="p-4 bg-zinc-900/50 rounded-lg border border-[var(--border-hi)]">
<p className="text-sm text-[var(--muted)] mb-1">Weekly Average</p>
<p className="text-2xl font-bold text-white">
{forecast.historicalAverage.weekly.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</p>
</div>
<div className="p-4 bg-zinc-900/50 rounded-lg border border-zinc-800">
<p className="text-sm text-zinc-400 mb-1">Biweekly Average</p>
<div className="p-4 bg-zinc-900/50 rounded-lg border border-[var(--border-hi)]">
<p className="text-sm text-[var(--muted)] mb-1">Biweekly Average</p>
<p className="text-2xl font-bold text-white">
{forecast.historicalAverage.biweekly.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</p>
</div>
<div className="p-4 bg-zinc-900/50 rounded-lg border border-zinc-800">
<p className="text-sm text-zinc-400 mb-1">Monthly Average</p>
<div className="p-4 bg-zinc-900/50 rounded-lg border border-[var(--border-hi)]">
<p className="text-sm text-[var(--muted)] mb-1">Monthly Average</p>
<p className="text-2xl font-bold text-white">
{forecast.historicalAverage.monthly.toLocaleString('en-US', {
minimumFractionDigits: 2,
Expand All @@ -407,8 +407,8 @@ export default function CashFlowForecast() {
</div>
</>
) : (
<div className="bg-gradient-to-br from-zinc-900 to-black border border-zinc-800 rounded-xl p-12 text-center">
<p className="text-zinc-400">
<div className="bg-gradient-to-br from-zinc-900 to-black border border-[var(--border-hi)] rounded-xl p-12 text-center">
<p className="text-[var(--muted)]">
Enter distribution account and asset issuer to load forecast
</p>
</div>
Expand Down
Loading