From 6a31533f1c67b721fc59d0c28c8dfb39475edba2 Mon Sep 17 00:00:00 2001 From: Ermolz <00ermzahar@gmail.com> Date: Thu, 25 Jun 2026 01:22:15 +0300 Subject: [PATCH] Redesign dashboard UI and add micro-animations - Dashboard: hero Balance card (2/3 width) with sparkline + month delta badge, Income/Expense metric cards with colored glow orbs - Card: add elevated/metric/glass/danger variants; base transition for hover-lift/shadow/border animations - Button: active:scale-[0.98] press feedback - Modal: animate-backdrop-in + animate-modal-in fade+scale entrance - Skeleton: replace animate-pulse with shimmer gradient animation - TransactionsHistory: bank-style rows with circle icon, category, meta string, colored amounts - DashboardAnalytics: drawer slide-in animation, hover:border-brand on chart cards, UX label renames, backdrop overlay - CreateTransactionModal: segmented pill control for Expense/Income kind - EmptyState: SVG line-art illustrations (wallet, chart, receipt, spreadsheet, sync) - SetupPage: split landing layout with branding panel, feature bullets, decorative preview, connect card --- app/src/app/styles/index.css | 56 +++++ .../ui/CreateTransactionModal.tsx | 60 ++++-- app/src/pages/dashboard/ui/DashboardPage.tsx | 185 +++++++++++++++- app/src/pages/setup/ui/SetupPage.tsx | 114 ++++++++-- app/src/shared/ui/button/button-variants.ts | 2 +- app/src/shared/ui/card/Card.tsx | 26 ++- app/src/shared/ui/empty-state/EmptyState.tsx | 89 ++++++++ app/src/shared/ui/modal/Modal.tsx | 4 +- app/src/shared/ui/skeleton/Skeleton.tsx | 6 +- .../ui/DashboardAnalytics.tsx | 179 ++++++++++------ .../ui/TransactionsHistory.tsx | 200 ++++++++---------- 11 files changed, 681 insertions(+), 240 deletions(-) diff --git a/app/src/app/styles/index.css b/app/src/app/styles/index.css index ae5153d..5641b67 100644 --- a/app/src/app/styles/index.css +++ b/app/src/app/styles/index.css @@ -38,6 +38,11 @@ --color-info: #6366f1; --color-info-soft: #eef2ff; + + /* Animations */ + --animate-modal-in: modal-in 0.2s cubic-bezier(0.16, 1, 0.3, 1); + --animate-backdrop-in: backdrop-in 0.15s ease-out; + --animate-drawer-in-right: drawer-in-right 0.25s cubic-bezier(0.16, 1, 0.3, 1); } /* Dark theme overrides */ @@ -105,3 +110,54 @@ select, textarea { font: inherit; } + +@keyframes modal-in { + from { + opacity: 0; + transform: scale(0.97) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes backdrop-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes drawer-in-right { + from { + opacity: 0; + transform: translateX(20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes shimmer { + from { + background-position: 200% center; + } + to { + background-position: -200% center; + } +} + +@utility animate-shimmer { + animation: shimmer 1.6s linear infinite; + background-image: linear-gradient( + 90deg, + var(--color-border) 0%, + var(--color-surface-hover) 50%, + var(--color-border) 100% + ); + background-size: 200% 100%; +} diff --git a/app/src/features/create-transaction/ui/CreateTransactionModal.tsx b/app/src/features/create-transaction/ui/CreateTransactionModal.tsx index 131c9ad..bcace78 100644 --- a/app/src/features/create-transaction/ui/CreateTransactionModal.tsx +++ b/app/src/features/create-transaction/ui/CreateTransactionModal.tsx @@ -11,7 +11,7 @@ import { DatePicker } from '@shared/ui/date-picker'; import { Input } from '@shared/ui/input'; import { localStorageService } from '@shared/lib/storage/local-storage.service'; import { Modal } from '@shared/ui/modal'; -import { Select } from '@shared/ui/select'; +import { cn } from '@shared/lib/classnames/cn'; import { mapFormToTransaction } from '../lib/map-form-to-transaction'; import { createTransactionFormSchema } from '../model/transaction-form.schema'; @@ -144,6 +144,16 @@ export function CreateTransactionModal({ ); const [formError, setFormError] = useState(null); + function switchKind(kind: 'income' | 'expense') { + setValues({ + ...values, + categoryId: selectedCategory?.kind === kind ? values.categoryId : '', + categoryName: selectedCategory?.kind === kind ? values.categoryName : '', + kind, + }); + setSelectedCategory((current) => (current?.kind === kind ? current : null)); + } + function selectCategory(category: Category) { setSelectedCategory(category); setValues({ @@ -222,28 +232,32 @@ export function CreateTransactionModal({ {visibleError} ) : null} - updateFilters({ container })} options={[ { label: 'All containers', value: '' }, - ...containerOptions.map((container) => ({ label: container, value: container })), + ...containerOptions.map((container) => ({ + label: container, + value: container, + })), ]} value={filters.container} /> @@ -427,6 +400,8 @@ export function TransactionsHistory({ ) : null} + + {/* List */} {isInitialLoading ? ( ) : !filteredTransactions.length ? ( @@ -438,72 +413,79 @@ export function TransactionsHistory({ title="No matching transactions" /> ) : ( -
-
- Date - Type - Category - Amount - Payment method - Comment - Container - Status - Actions -
-
- {filteredTransactions.map((transaction) => ( +
+ {filteredTransactions.map((transaction) => { + const isDeleted = Boolean(transaction.deletedAt); + const isIncome = transaction.kind === 'income'; + + const metaParts = [ + transaction.date, + transaction.paymentMethod, + transaction.comment, + containersEnabled ? transaction.containerName : undefined, + ] + .filter(Boolean) + .join(' · '); + + return (
- -
{transaction.date}
-
- -
{formatKind(transaction.kind)}
-
- -
{transaction.categoryName}
-
- -
+ {transaction.categoryName.charAt(0)} +
+ +
+

+ {transaction.categoryName} +

+ {metaParts ? ( +

{metaParts}

+ ) : null} +
+ +
+ {formatMoney(transaction.signedAmount, transaction.currency)} -
-
- -
{transaction.paymentMethod || '-'}
-
- - - - -
{transaction.containerName || '-'}
-
- -
- {transaction.deletedAt ? Deleted : null} + +
+ {isDeleted ? Deleted : null} {formatSyncStatus(transaction.syncStatus)}
- +
+ onDeleteTransaction(transaction.id)} onEdit={() => onEditTransaction(transaction)} />
- ))} -
+ ); + })}
)}