From 08a924fbb6b3f2822c1cf3e73b5c95f5a32be725 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Fri, 27 Mar 2026 19:52:31 +0100 Subject: [PATCH 1/6] style(frontend): standardize shadow tokens across dashboard cards Add --shadow-sm, --shadow-md, --shadow-lg, --shadow-card and --shadow-card-hover custom properties to index.css for both dark and light themes. Apply the card tokens to the global .card class and migrate hardcoded box-shadow values in ContractErrorPanel and FeeEstimationPanel CSS modules to use the new tokens. --- .../src/components/ContractErrorPanel.module.css | 2 +- .../src/components/FeeEstimationPanel.module.css | 4 ++-- frontend/src/index.css | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ContractErrorPanel.module.css b/frontend/src/components/ContractErrorPanel.module.css index 3d57da19..700ba081 100644 --- a/frontend/src/components/ContractErrorPanel.module.css +++ b/frontend/src/components/ContractErrorPanel.module.css @@ -8,7 +8,7 @@ margin-bottom: 1.5rem; overflow: hidden; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-shadow: var(--shadow-md); backdrop-filter: blur(10px); } diff --git a/frontend/src/components/FeeEstimationPanel.module.css b/frontend/src/components/FeeEstimationPanel.module.css index aa88eb55..edc7a7c0 100644 --- a/frontend/src/components/FeeEstimationPanel.module.css +++ b/frontend/src/components/FeeEstimationPanel.module.css @@ -73,12 +73,12 @@ border: 1px solid #e5e7eb; border-radius: 0.75rem; padding: 1.25rem; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); + box-shadow: var(--shadow-sm); transition: box-shadow 0.2s ease; } .card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + box-shadow: var(--shadow-md); } .cardTitle { diff --git a/frontend/src/index.css b/frontend/src/index.css index de44f606..8911d922 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -25,6 +25,11 @@ --font-head: 'Syne', sans-serif; --font-body: 'Inter', sans-serif; --font-mono: 'DM Mono', monospace; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.16); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.24); + --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.18); + --shadow-card-hover: 0 6px 20px rgba(0, 0, 0, 0.28); color-scheme: dark; } @@ -44,6 +49,11 @@ --font-head: 'Syne', sans-serif; --font-body: 'Inter', sans-serif; --font-mono: 'DM Mono', monospace; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.06); + --shadow-card-hover: 0 6px 20px rgba(0, 0, 0, 0.1); color-scheme: light; } @@ -121,13 +131,16 @@ h6 { border: 1px solid var(--border); border-radius: 16px; padding: 24px; + box-shadow: var(--shadow-card); transition: transform 0.2s ease, - border-color 0.2s ease; + border-color 0.2s ease, + box-shadow 0.2s ease; } .card:hover { border-color: var(--border-hi); + box-shadow: var(--shadow-card-hover); transform: translateY(-2px); } From 45d63b4fcada884a0008b396f0e92594224e2d94 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Fri, 27 Mar 2026 22:33:55 +0100 Subject: [PATCH 2/6] perf(frontend): add lazy loading to profile and wallet images Set loading=lazy on Avatar and wallet icon img elements to defer offscreen image requests until users scroll them into view. --- frontend/src/components/Avatar.tsx | 1 + frontend/src/providers/WalletProvider.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Avatar.tsx b/frontend/src/components/Avatar.tsx index 92a8913e..b279bb05 100644 --- a/frontend/src/components/Avatar.tsx +++ b/frontend/src/components/Avatar.tsx @@ -49,6 +49,7 @@ export const Avatar: React.FC = ({ {name} { setHasImageError(true); diff --git a/frontend/src/providers/WalletProvider.tsx b/frontend/src/providers/WalletProvider.tsx index a93fb8b7..38c63f9a 100644 --- a/frontend/src/providers/WalletProvider.tsx +++ b/frontend/src/providers/WalletProvider.tsx @@ -255,7 +255,7 @@ export const WalletProvider: React.FC<{ >
{wallet.icon ? ( - {wallet.name} + {wallet.name} ) : (
)} From 2ad9402d4a246b3cd7a7a39300663e9ab839cc87 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Fri, 27 Mar 2026 22:35:50 +0100 Subject: [PATCH 3/6] refactor(frontend): extract date helpers and add unit tests Move formatDate out of PayrollScheduler into a shared utils/dateHelpers module. Add getRemainingDays helper that computes the day-level difference between now and a target date. Include 12 vitest cases covering empty input, ISO strings, date-only strings, invalid dates, past/future dates, and day-level precision. --- .../src/__tests__/utils/dateHelpers.test.ts | 70 +++++++++++++++++++ frontend/src/pages/PayrollScheduler.tsx | 20 +----- frontend/src/utils/dateHelpers.ts | 31 ++++++++ 3 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 frontend/src/__tests__/utils/dateHelpers.test.ts create mode 100644 frontend/src/utils/dateHelpers.ts diff --git a/frontend/src/__tests__/utils/dateHelpers.test.ts b/frontend/src/__tests__/utils/dateHelpers.test.ts new file mode 100644 index 00000000..b2cd2b92 --- /dev/null +++ b/frontend/src/__tests__/utils/dateHelpers.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest'; +import { formatDate, getRemainingDays } from '../../utils/dateHelpers'; + +describe('formatDate', () => { + it('returns N/A for empty string', () => { + expect(formatDate('')).toBe('N/A'); + }); + + it('formats a date-only string (YYYY-MM-DD) correctly', () => { + const result = formatDate('2024-01-15'); + expect(result).toBe('Jan 15, 2024'); + }); + + it('formats an ISO datetime string correctly', () => { + const result = formatDate('2024-06-01T10:00:00Z'); + expect(result).toContain('2024'); + expect(result).toContain('Jun'); + }); + + it('returns the original string for an invalid date', () => { + expect(formatDate('not-a-date')).toBe('not-a-date'); + }); + + it('handles single-digit month and day', () => { + const result = formatDate('2024-03-05'); + expect(result).toBe('Mar 5, 2024'); + }); + + it('handles Dec 31 edge case', () => { + const result = formatDate('2024-12-31'); + expect(result).toBe('Dec 31, 2024'); + }); +}); + +describe('getRemainingDays', () => { + it('returns 0 for an invalid date string', () => { + expect(getRemainingDays('garbage')).toBe(0); + }); + + it('returns 0 for today', () => { + const today = new Date(); + expect(getRemainingDays(today)).toBe(0); + }); + + it('returns a positive number for a future date', () => { + const future = new Date(); + future.setDate(future.getDate() + 10); + expect(getRemainingDays(future)).toBe(10); + }); + + it('returns a negative number for a past date', () => { + const past = new Date(); + past.setDate(past.getDate() - 5); + expect(getRemainingDays(past)).toBe(-5); + }); + + it('accepts a date string', () => { + const future = new Date(); + future.setDate(future.getDate() + 3); + const iso = future.toISOString(); + expect(getRemainingDays(iso)).toBe(3); + }); + + it('uses day-level precision, not time-level', () => { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + expect(getRemainingDays(tomorrow)).toBe(1); + }); +}); diff --git a/frontend/src/pages/PayrollScheduler.tsx b/frontend/src/pages/PayrollScheduler.tsx index 2f17eb90..7842df9c 100644 --- a/frontend/src/pages/PayrollScheduler.tsx +++ b/frontend/src/pages/PayrollScheduler.tsx @@ -23,6 +23,7 @@ import { ContractErrorPanel } from '../components/ContractErrorPanel'; import { IssuerMultisigBanner } from '../components/IssuerMultisigBanner'; import { HelpLink } from '../components/HelpLink'; import { parseContractError, type ContractErrorDetail } from '../utils/contractErrorParser'; +import { formatDate } from '../utils/dateHelpers'; interface PayrollFormState { employeeName: string; @@ -120,25 +121,6 @@ function computeNextRunDate(config: SchedulingConfig, from: Date = new Date()): return first; } -const formatDate = (dateString: string) => { - if (!dateString) return 'N/A'; - - const dateOnlyMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateString); - const date = dateOnlyMatch - ? new Date( - Number.parseInt(dateOnlyMatch[1], 10), - Number.parseInt(dateOnlyMatch[2], 10) - 1, - Number.parseInt(dateOnlyMatch[3], 10) - ) - : new Date(dateString); - - if (isNaN(date.getTime())) return dateString; - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - }); -}; interface PendingClaim { id: string; diff --git a/frontend/src/utils/dateHelpers.ts b/frontend/src/utils/dateHelpers.ts new file mode 100644 index 00000000..daf44009 --- /dev/null +++ b/frontend/src/utils/dateHelpers.ts @@ -0,0 +1,31 @@ +export function formatDate(dateString: string): string { + if (!dateString) return 'N/A'; + + const dateOnlyMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateString); + const date = dateOnlyMatch + ? new Date( + Number.parseInt(dateOnlyMatch[1], 10), + Number.parseInt(dateOnlyMatch[2], 10) - 1, + Number.parseInt(dateOnlyMatch[3], 10) + ) + : new Date(dateString); + + if (isNaN(date.getTime())) return dateString; + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); +} + +export function getRemainingDays(targetDate: string | Date): number { + const target = typeof targetDate === 'string' ? new Date(targetDate) : targetDate; + if (isNaN(target.getTime())) return 0; + + const now = new Date(); + const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const startOfTarget = new Date(target.getFullYear(), target.getMonth(), target.getDate()); + + const diffMs = startOfTarget.getTime() - startOfToday.getTime(); + return Math.ceil(diffMs / (1000 * 60 * 60 * 24)); +} From 12199bf1b929a37ab7431609aeca31ab385fbc05 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Sun, 29 Mar 2026 09:46:10 +0100 Subject: [PATCH 4/6] style: fix prettier formatting issues Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/providers/WalletProvider.tsx | 7 ++++++- src/pages/PayrollScheduler.tsx | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/providers/WalletProvider.tsx b/frontend/src/providers/WalletProvider.tsx index 38c63f9a..1b10b2e2 100644 --- a/frontend/src/providers/WalletProvider.tsx +++ b/frontend/src/providers/WalletProvider.tsx @@ -255,7 +255,12 @@ export const WalletProvider: React.FC<{ >
{wallet.icon ? ( - {wallet.name} + {wallet.name} ) : (
)} diff --git a/src/pages/PayrollScheduler.tsx b/src/pages/PayrollScheduler.tsx index 23e25bac..46384b4e 100644 --- a/src/pages/PayrollScheduler.tsx +++ b/src/pages/PayrollScheduler.tsx @@ -5,8 +5,12 @@ export default function PayrollScheduler() { return (
-

Payroll Scheduler

-

Automate recurring payments to your workforce.

+

+ Payroll Scheduler +

+

+ Automate recurring payments to your workforce. +

@@ -16,7 +20,8 @@ export default function PayrollScheduler() {

No Active Schedules

- You haven't set up any automated payroll schedules yet. Connect your wallet to get started. + You haven't set up any automated payroll schedules yet. Connect your + wallet to get started.