diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f964fb5e..364d6181 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,8 +69,23 @@ jobs: INTERNAL_API_KEY: test_internal_api_key FRONTEND_URL: https://frontend.example.com + migration-paths: + runs-on: ubuntu-latest + outputs: + migrations: ${{ steps.filter.outputs.migrations }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + migrations: + - 'backend/migrations/**' + - 'backend/package.json' + migration-check: - needs: supply-chain-audit + needs: [supply-chain-audit, migration-paths] + if: github.event_name == 'push' || needs.migration-paths.outputs.migrations == 'true' runs-on: ubuntu-latest services: postgres: @@ -97,7 +112,22 @@ jobs: - name: Install dependencies run: npm ci working-directory: backend - - name: Run full migration set against empty database + - name: Migrate up from empty schema + run: npm run migrate:up + working-directory: backend + env: + DATABASE_URL: postgres://pguser:pgpass@localhost:5432/remitlend_test + - name: Migrate all the way down (reversibility check) + run: | + # Count migrations and roll them all back one by one + MIGRATION_COUNT=$(ls migrations/*.js 2>/dev/null | wc -l) + for i in $(seq 1 "$MIGRATION_COUNT"); do + npm run migrate:down -- --count 1 + done + working-directory: backend + env: + DATABASE_URL: postgres://pguser:pgpass@localhost:5432/remitlend_test + - name: Migrate up again (ordering + idempotency check) run: npm run migrate:up working-directory: backend env: diff --git a/frontend/README.md b/frontend/README.md index 4c5353e8..4584c070 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -6,460 +6,338 @@ Next.js web application for the RemitLend platform, providing user interfaces fo The frontend is a modern React application built with Next.js that enables: -- Wallet connection (Freighter, Albedo, etc.) -- Credit score visualization +- Wallet connection via Freighter +- Credit score visualisation - Remittance NFT minting - Loan request and management - Lending pool participation - Real-time transaction tracking +- Notifications and activity streams ## Tech Stack -- **Framework**: Next.js 16 (App Router) -- **React**: 19.2.3 -- **Language**: TypeScript -- **Styling**: Tailwind CSS 4 -- **Wallet Integration**: Stellar Wallet Kit (planned) -- **State Management**: React hooks + Context API (planned) +| Layer | Technology | +|---|---| +| Framework | Next.js 16 (App Router) | +| React | 19.2.3 | +| Language | TypeScript | +| Styling | Tailwind CSS 4 (`@import "tailwindcss"` in globals.css, no config file) | +| State management | Zustand 5 | +| Server state / caching | TanStack React Query 5 | +| Wallet integration | `@stellar/freighter-api` | +| Blockchain SDK | `@stellar/stellar-sdk` | +| Internationalisation | next-intl 4 (locales: `en`, `es`, `tl`) | +| Charts | Recharts | +| Animation | Framer Motion | +| Notifications | Sonner | +| Monitoring | Sentry | +| PWA | Serwist | ## Getting Started ### Prerequisites - Node.js 18 or higher -- npm or yarn -- Stellar wallet (Freighter recommended) +- npm +- Freighter browser extension (for wallet features) ### Installation ```bash -# Install dependencies npm install - -# Start development server npm run dev ``` -### Access the Application - Open [http://localhost:3000](http://localhost:3000) in your browser. ## Available Scripts ```bash -# Development npm run dev # Start dev server with hot reload - -# Production -npm run build # Build for production -npm start # Run production build - -# Code Quality -npm run lint # Check code quality with ESLint +npm run build # Production build +npm start # Serve production build +npm run lint # Check formatting with Prettier (not ESLint) +npm run format # Auto-format with Prettier +npm run typecheck # TypeScript type check (tsc --noEmit) +npm test # Jest unit tests +npm run test:watch # Jest in watch mode +npm run test:e2e # Playwright end-to-end tests +npm run audit:a11y # Build then run axe-playwright accessibility audit ``` +> **Note:** `npm run lint` runs `prettier --check` — it checks formatting, not ESLint rules. +> ESLint runs automatically via `lint-staged` on pre-commit. + ## Project Structure ``` frontend/ +├── e2e/ # Playwright end-to-end tests +│ ├── borrower-loan-flow.spec.ts +│ ├── criticalFlows.spec.ts +│ └── ... +├── messages/ # next-intl translation files +│ ├── en.json +│ ├── es.json +│ └── tl.json ├── src/ -│ └── app/ # Next.js App Router -│ ├── components/ # React components -│ │ └── global_ui/ # Reusable UI components -│ │ └── Spinner.tsx -│ ├── layout.tsx # Root layout -│ ├── page.tsx # Home page -│ ├── not-found.tsx # 404 page -│ ├── globals.css # Global styles -│ └── favicon.ico -├── public/ # Static assets -│ ├── og-image.png -│ └── *.svg -├── next.config.ts # Next.js configuration -├── tailwind.config.ts # Tailwind CSS configuration -├── tsconfig.json # TypeScript configuration -├── package.json -└── README.md +│ └── app/ +│ ├── [locale]/ # Localised routes (en / es / tl) +│ │ ├── layout.tsx +│ │ ├── page.tsx # Landing page +│ │ ├── globals.css # Tailwind 4 entry + CSS custom properties +│ │ ├── loans/ # Borrower loan list + [loanId] detail +│ │ ├── lend/ # Lender dashboard +│ │ ├── wallet/ # Wallet connection & balance +│ │ ├── remittances/ # Remittance NFT viewer +│ │ ├── send-remittance/ +│ │ ├── request-loan/ +│ │ ├── repay/ +│ │ ├── notifications/ +│ │ ├── analytics/ +│ │ ├── activity/ +│ │ ├── settings/ +│ │ ├── liquidations/ +│ │ ├── kingdom/ +│ │ ├── admin/ # Admin governance +│ │ └── ui-demo/ # Dev-only component gallery (404 in production) +│ ├── components/ +│ │ ├── ui/ # Design-system primitives (22 components) +│ │ ├── global_ui/ # App-shell components (Spinner, ErrorBoundary …) +│ │ ├── borrower/ +│ │ ├── lender/ +│ │ └── remittance/ +│ ├── stores/ # Zustand stores +│ │ ├── useWalletStore.ts +│ │ ├── useUserStore.ts +│ │ ├── useThemeStore.ts +│ │ ├── useUIStore.ts +│ │ ├── useToastStore.ts +│ │ └── useGamificationStore.ts +│ ├── hooks/ # Custom React hooks +│ ├── lib/ # API clients and utilities +│ └── utils/ # Pure helpers (cn, stellar, amount, csv …) +├── i18n.config.ts +├── next.config.ts +├── postcss.config.mjs +├── playwright.config.ts +├── jest.config.js +├── tsconfig.json +└── package.json ``` -## Features - -### Current Features - -- Landing page with project overview -- Responsive design for mobile and desktop -- Loading states with spinner component -- SEO optimization with metadata -- Custom 404 page - -### Planned Features - -#### Borrower Dashboard - -- [ ] Wallet connection interface -- [ ] Credit score display -- [ ] Remittance NFT minting -- [ ] Loan request form -- [ ] Active loans management -- [ ] Repayment interface -- [ ] Transaction history - -#### Lender Dashboard - -- [ ] Pool liquidity overview -- [ ] Deposit/withdraw interface -- [ ] Loan approval queue -- [ ] Yield tracking -- [ ] Portfolio analytics - -#### Shared Features - -- [ ] Real-time transaction status -- [ ] Notification system -- [ ] Multi-language support -- [ ] Dark mode toggle -- [ ] Wallet balance display - -## Component Library - -### Global UI Components - -#### Spinner - -Loading indicator component. - -```tsx -import { Spinner } from "@/app/components/global_ui/Spinner"; - -; -``` - -**Props:** - -- `size`: 'sm' | 'md' | 'lg' (default: 'md') - -### Planned Components - -- `Button` - Reusable button with variants -- `Card` - Container component -- `Modal` - Dialog component -- `Input` - Form input with validation -- `WalletConnect` - Wallet connection button -- `TransactionStatus` - Transaction feedback -- `LoanCard` - Loan information display -- `PoolStats` - Pool statistics display - -## Styling - -### Tailwind CSS - -The project uses Tailwind CSS 4 for styling with a custom configuration. - -**Key Features:** - -- Utility-first CSS -- Responsive design utilities -- Custom color palette (planned) -- Dark mode support (planned) +## Routing -**Example:** +All pages are nested under the `[locale]` segment, so every URL is prefixed with the active locale: + +| Route | Description | +|---|---| +| `/[locale]` | Landing page | +| `/[locale]/wallet` | Wallet connection | +| `/[locale]/loans` | Loan list | +| `/[locale]/loans/[loanId]` | Loan detail | +| `/[locale]/lend` | Lender dashboard | +| `/[locale]/request-loan` | New loan request | +| `/[locale]/repay` | Repayment flow | +| `/[locale]/remittances` | Remittance NFT gallery | +| `/[locale]/send-remittance` | Send remittance | +| `/[locale]/notifications` | Notification inbox | +| `/[locale]/activity` | Activity log | +| `/[locale]/analytics` | Analytics dashboard | +| `/[locale]/settings` | User settings | +| `/[locale]/liquidations` | Liquidation queue | +| `/[locale]/kingdom` | Gamification / Kingdom view | +| `/[locale]/admin` | Governance admin | +| `/[locale]/ui-demo` | Dev-only component gallery | +| `/[locale]/not-found` | 404 page | -```tsx -
-

Welcome to RemitLend

-
-``` +## State Management -### Global Styles +Global state is managed with **Zustand 5** stores: -Global styles are defined in `src/app/globals.css`: +| Store | Responsibility | +|---|---| +| `useWalletStore` | Freighter wallet connection, public key, signing | +| `useUserStore` | User profile and credit score | +| `useThemeStore` | Light / dark / system theme with `localStorage` persistence | +| `useUIStore` | Modal and sidebar open/close state | +| `useToastStore` | Toast queue used by `useToast` hook | +| `useGamificationStore` | Kingdom / XP state | -- CSS reset -- Tailwind directives -- Custom CSS variables -- Typography defaults +TanStack React Query handles server-state caching and background refetching for all API calls. ## Wallet Integration -### Stellar Wallet Kit (Planned) - -Integration with Stellar wallets for transaction signing. - -**Supported Wallets:** - -- Freighter -- Albedo -- Rabet -- xBull - -**Example Usage:** +Wallet integration uses **`@stellar/freighter-api`**: ```tsx -import { StellarWalletKit } from "@stellar/wallet-kit"; - -const kit = new StellarWalletKit({ - network: "testnet", - selectedWallet: "freighter", -}); +import { isConnected, getPublicKey, signTransaction } from "@stellar/freighter-api"; -// Connect wallet -await kit.connect(); - -// Sign transaction -const signedTx = await kit.sign(transaction); +const connected = await isConnected(); +const publicKey = await getPublicKey(); +const signedXdr = await signTransaction(xdr, { networkPassphrase }); ``` -## State Management +Wallet state (public key, connection status) is managed by `useWalletStore`. -### React Context (Planned) - -Global state management using React Context API. - -**Contexts:** +## Component Library -- `WalletContext` - Wallet connection state -- `UserContext` - User profile and credit score -- `LoanContext` - Active loans data -- `PoolContext` - Lending pool information +Design-system primitives live in [`src/app/components/ui/`](src/app/components/ui/). There are 22 components: + +### Core primitives + +| Component | Props | Description | +|---|---|---| +| `Button` | `variant` (`primary`\|`secondary`\|`outline`\|`ghost`\|`danger`), `size` (`sm`\|`md`\|`lg`\|`icon`), `isLoading`, `leftIcon`, `rightIcon` | Polymorphic button with loading spinner | +| `Input` | `label`, `error`, `helperText`, `leftIcon`, `rightIcon` | Labelled text input with accessible error/helper text | +| `Card` / `CardHeader` / `CardTitle` / `CardDescription` / `CardContent` / `CardFooter` | `className` | Compound card layout | +| `Modal` | `isOpen`, `onClose`, `title`, `size` (`sm`\|`md`\|`lg`\|`xl`), `ariaLabel` | Focus-trapped animated dialog | + +### Feedback & status + +| Component | Props | Description | +|---|---|---| +| `Skeleton` / `SkeletonText` / `SkeletonCard` / `SkeletonRow` / `SkeletonChart` / `SkeletonAvatar` | `className`, `lines` (SkeletonText) | Loading placeholders | +| `EmptyState` | `icon`, `title`, `description`, `actionLabel`, `actionHref` \| `onAction`, `actionIcon` | Zero-state placeholder with optional CTA | +| `StatusIndicator` | `label`, `tone` (`success`\|`danger`\|`warning`\|`info`\|`neutral`), `icon`, `iconOnly` | Coloured badge pill | +| `LoanStatusBadge` | `status` (`active`\|`pending`\|`repaid`\|`defaulted`\|`liquidated`) | Domain-specific status badge | +| `Toast` / `Toaster` | — | Toast notification components backed by `useToastStore` | + +### Controls & utilities + +| Component | Props | Description | +|---|---|---| +| `Tooltip` | `content`, `label`, `iconClassName` | Hover/focus tooltip with info icon | +| `PaginationControls` | `currentPage`, `totalPages`, `hasPrevious`, `hasNext`, `onPageChange`, `onPrevious`, `onNext`, `summary` | Page navigation with ellipsis windowing | +| `CopyButton` | `value` | Clipboard copy with check feedback | +| `TxHashLink` | `txHash`, `chars` | Truncated hash with copy + Stellar Explorer link | +| `ThemeToggle` | — | Light / dark / system switcher | +| `ConfirmTransactionDialog` | — | Pre-submit transaction confirmation modal | +| `OperationProgress` | — | Multi-step operation stepper | +| `RepaymentProgress` | — | Loan repayment progress bar | +| `LoanTimeline` | — | Chronological loan event list | +| `TransactionStatusTracker` | — | Live transaction polling status | +| `CreditScoreGauge` | — | Circular credit score visualisation | +| `CreditScoreBreakdown` | — | Score factor breakdown chart | + +> A live, interactive gallery is available in development at `/[locale]/ui-demo` (returns 404 in production). -**Example:** +## Styling -```tsx -import { useWallet } from "@/contexts/WalletContext"; +Tailwind CSS 4 is configured via CSS-first `@import "tailwindcss"` in +[`src/app/[locale]/globals.css`](src/app/%5Blocale%5D/globals.css) — there is no +`tailwind.config.ts` file. Design tokens are declared as CSS custom properties in `:root`: -function MyComponent() { - const { address, connected, connect, disconnect } = useWallet(); +```css +@import "tailwindcss"; - return ( - - ); +:root { + --background: #ffffff; + --foreground: #171717; + --focus-ring: #2563eb; } -``` - -## API Integration - -### Backend API -The frontend communicates with the Express backend for off-chain data. - -**Base URL:** `http://localhost:3001/api` - -**Example:** - -```tsx -async function fetchCreditScore(userId: string) { - const response = await fetch(`http://localhost:3001/api/score/${userId}`); - const data = await response.json(); - return data.score; +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); } ``` -### Blockchain Integration +Dark mode is driven by the `.dark` class (set by `useThemeStore`) and also respects +`prefers-color-scheme`. -Direct interaction with Soroban smart contracts via Stellar SDK. - -**Example:** - -```tsx -import { Contract, SorobanRpc } from "@stellar/stellar-sdk"; +## Testing -const contract = new Contract(contractId); -const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org"); +### Unit tests — Jest + React Testing Library -// Call contract method -const result = await contract.call("get_score", [nftId]); +```bash +npm test # run once +npm run test:watch # interactive watch mode ``` -## Routing - -### App Router Structure - -Next.js 13+ App Router with file-based routing. - -**Current Routes:** - -- `/` - Landing page -- `/404` - Not found page - -**Planned Routes:** +Test files sit alongside source files (`*.test.ts` / `*.test.tsx`). Key test suites: -- `/borrower` - Borrower dashboard -- `/lender` - Lender dashboard -- `/loans` - Loan management -- `/loans/[id]` - Loan details -- `/pool` - Pool overview -- `/profile` - User profile +- `src/app/stores/stores.test.ts` — Zustand store logic +- `src/app/utils/*.test.ts` — pure utility functions +- `src/app/components/**/*.test.tsx` — component rendering -## SEO & Metadata +### End-to-end tests — Playwright -### Metadata Configuration - -```tsx -export const metadata = { - title: "RemitLend - Credit from Remittances", - description: "Turn your remittance history into credit history", - openGraph: { - title: "RemitLend", - description: "Decentralized lending for migrant workers", - images: ["/og-image.png"], - }, -}; +```bash +npm run test:e2e ``` -## Performance Optimization - -### Next.js Features +Specs live in `e2e/` and cover critical user flows: -- **Static Generation**: Pre-render pages at build time -- **Image Optimization**: Automatic image optimization -- **Code Splitting**: Automatic code splitting per route -- **Font Optimization**: Automatic font optimization +- `borrower-loan-flow.spec.ts` +- `borrower-repay-flow.spec.ts` +- `lender-withdraw-flow.spec.ts` +- `notifications-inbox.spec.ts` +- `remittance-nft-viewer.spec.ts` +- `admin-governance.spec.ts` +- `criticalFlows.spec.ts` -### Best Practices +### Accessibility audit -- Use `next/image` for images -- Implement lazy loading for heavy components -- Minimize client-side JavaScript -- Use server components when possible -- Implement proper caching strategies - -## Testing (Planned) - -### Testing Stack +```bash +npm run audit:a11y # builds then runs axe-playwright +``` -- **Unit Tests**: Jest + React Testing Library -- **E2E Tests**: Playwright or Cypress -- **Component Tests**: Storybook +## API Integration -### Example Test +The frontend talks to the Express backend for off-chain data and uses `@stellar/stellar-sdk` +directly for on-chain calls. -```tsx -import { render, screen } from "@testing-library/react"; -import { Spinner } from "@/app/components/global_ui/Spinner"; - -describe("Spinner", () => { - it("renders spinner", () => { - render(); - expect(screen.getByRole("status")).toBeInTheDocument(); - }); -}); -``` +**Backend base URL:** `NEXT_PUBLIC_API_URL` (default: `http://localhost:3001`) ## Deployment -### Vercel (Recommended) +### Vercel (recommended) ```bash -# Install Vercel CLI npm i -g vercel - -# Deploy vercel ``` ### Docker ```bash -# Build image docker build -t remitlend-frontend . - -# Run container docker run -p 3000:3000 remitlend-frontend ``` ### Environment Variables -Create `.env.local` for local development: - ```env NEXT_PUBLIC_API_URL=http://localhost:3001 NEXT_PUBLIC_STELLAR_NETWORK=testnet NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org ``` -## Accessibility - -### WCAG Compliance - -The application aims for WCAG 2.1 Level AA compliance: - -- Semantic HTML elements -- ARIA labels where needed -- Keyboard navigation support -- Color contrast ratios -- Screen reader compatibility - -**Note:** Full WCAG compliance requires manual testing with assistive technologies. - -## Browser Support - -- Chrome (latest 2 versions) -- Firefox (latest 2 versions) -- Safari (latest 2 versions) -- Edge (latest 2 versions) - -## Contributing - -See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. - -### Code Style - -- Use functional components with hooks -- Prefer TypeScript interfaces over types -- Use descriptive component names -- Keep components small and focused -- Extract reusable logic into custom hooks -- Follow Next.js best practices - -### Before Submitting PR - -```bash -npm run lint -npm run build -``` - ## Troubleshooting -### Port Already in Use - ```bash -# Kill process on port 3000 +# Port in use lsof -ti:3000 | xargs kill -9 -``` -### Build Errors +# Stale Next.js cache +rm -rf .next/ && npm run build -```bash -# Clean Next.js cache -rm -rf .next/ -npm run build -``` - -### Module Not Found - -```bash # Reinstall dependencies -rm -rf node_modules package-lock.json -npm install +rm -rf node_modules package-lock.json && npm install ``` ## Resources - [Next.js Documentation](https://nextjs.org/docs) -- [React Documentation](https://react.dev) -- [Tailwind CSS Documentation](https://tailwindcss.com/docs) +- [Tailwind CSS v4 Documentation](https://tailwindcss.com/docs) +- [Zustand Documentation](https://zustand.docs.pmnd.rs) +- [TanStack React Query](https://tanstack.com/query/latest) - [Stellar Documentation](https://developers.stellar.org) -- [Soroban Documentation](https://soroban.stellar.org/docs) +- [next-intl Documentation](https://next-intl-docs.vercel.app) ## License -ISC License - See LICENSE file for details. - -## Support - -- Open an issue for bug reports -- Check existing issues before creating new ones -- Provide browser and OS information -- Include screenshots for UI issues +ISC License — see LICENSE file for details. diff --git a/frontend/src/app/[locale]/ui-demo/page.tsx b/frontend/src/app/[locale]/ui-demo/page.tsx index 45d8b02d..e6fd6702 100644 --- a/frontend/src/app/[locale]/ui-demo/page.tsx +++ b/frontend/src/app/[locale]/ui-demo/page.tsx @@ -1,5 +1,8 @@ "use client"; +// This route is intentionally gated from production — it is a development-only +// component gallery. If accidentally shipped, requests are bounced to 404. +import { notFound } from "next/navigation"; import React, { useState } from "react"; import { Button } from "../../components/ui/Button"; import { Input } from "../../components/ui/Input"; @@ -12,11 +15,32 @@ import { CardFooter, } from "../../components/ui/Card"; import { Modal } from "../../components/ui/Modal"; -import { Search, Mail, Lock, User, Terminal, ChevronRight } from "lucide-react"; +import { Skeleton, SkeletonText, SkeletonCard, SkeletonRow } from "../../components/ui/Skeleton"; +import { EmptyState } from "../../components/ui/EmptyState"; +import { StatusIndicator } from "../../components/ui/StatusIndicator"; +import { LoanStatusBadge } from "../../components/ui/LoanStatusBadge"; +import { Tooltip } from "../../components/ui/Tooltip"; +import { PaginationControls } from "../../components/ui/PaginationControls"; +import { CopyButton } from "../../components/ui/CopyButton"; +import { ThemeToggle } from "../../components/ui/ThemeToggle"; +import { + Search, + Mail, + Lock, + User, + Terminal, + ChevronRight, + Inbox, + FileQuestion, +} from "lucide-react"; export default function UIDemoPage() { + if (process.env.NODE_ENV === "production") notFound(); + const [isModalOpen, setIsModalOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(3); + const totalPages = 10; const handleAction = () => { setIsLoading(true); @@ -26,21 +50,22 @@ export default function UIDemoPage() { return (
-
-

- UI Component Library -

+
+
+

+ UI Component Library +

+ +

- A boutique collection of reusable atomic components for RemitLend. + Development-only gallery — not shipped to production. Covers all 22 components in{" "} + src/app/components/ui/.

{/* Buttons */}
-
- -

Buttons

-
+ Button
@@ -66,6 +91,7 @@ export default function UIDemoPage() { +
@@ -73,10 +99,7 @@ export default function UIDemoPage() { {/* Inputs */}
-
- -

Inputs

-
+ Input
@@ -107,10 +130,7 @@ export default function UIDemoPage() { {/* Cards */}
-
- -

Cards

-
+ Card
@@ -156,22 +176,15 @@ export default function UIDemoPage() {
{/* Modals */} -
-
- -

Modals

-
+
+ Modal

- Portals and focus locking verified via hooks. + Focus-trapped, animated, keyboard-dismissible.

- - setIsModalOpen(false)} - title="Privacy Settings" - > + + setIsModalOpen(false)} title="Privacy Settings">

Are you sure you want to update your privacy settings? This will affect how your @@ -188,7 +201,161 @@ export default function UIDemoPage() {

+ + {/* Skeleton */} +
+ Skeleton +
+ + + SkeletonCard + + + + + + + + SkeletonRow × 3 + + + + + + + + + + SkeletonText + + + + + + + + Skeleton (raw) + + + + + + + + +
+
+ + {/* EmptyState */} +
+ EmptyState +
+ + {}} + /> +
+
+ + {/* StatusIndicator + LoanStatusBadge */} +
+ StatusIndicator & LoanStatusBadge + + +
+

+ StatusIndicator tones +

+
+ + + + + +
+
+
+

+ LoanStatusBadge statuses +

+
+ + + + + +
+
+
+
+
+ + {/* Tooltip */} +
+ Tooltip + + + Credit score + + Interest rate + + + +
+ + {/* PaginationControls */} +
+ PaginationControls + + + 1} + hasNext={currentPage < totalPages} + onPageChange={setCurrentPage} + onPrevious={() => setCurrentPage((p) => Math.max(1, p - 1))} + onNext={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} + summary={`Page ${currentPage} of ${totalPages}`} + /> + + +
+ + {/* CopyButton */} +
+ CopyButton + + + + GBDNQ...XKJA + + + Click the icon to copy + + +
); } + +function SectionHeading({ children }: { children: React.ReactNode }) { + return ( +
+ +

{children}

+
+ ); +} diff --git a/frontend/src/app/components/ui/Button.tsx b/frontend/src/app/components/ui/Button.tsx index 9092ad1d..cb0a39c2 100644 --- a/frontend/src/app/components/ui/Button.tsx +++ b/frontend/src/app/components/ui/Button.tsx @@ -1,13 +1,7 @@ "use client"; import * as React from "react"; -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -/** Tool to merge Tailwind classes safely */ -function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} +import { cn } from "@/app/utils/cn"; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: "primary" | "secondary" | "outline" | "ghost" | "danger"; diff --git a/frontend/src/app/components/ui/Card.tsx b/frontend/src/app/components/ui/Card.tsx index 31eec63d..a1d1f6e9 100644 --- a/frontend/src/app/components/ui/Card.tsx +++ b/frontend/src/app/components/ui/Card.tsx @@ -1,13 +1,7 @@ "use client"; import * as React from "react"; -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -/** Tool to merge Tailwind classes safely */ -function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} +import { cn } from "@/app/utils/cn"; const Card = React.forwardRef>( ({ className, ...props }, ref) => ( diff --git a/frontend/src/app/components/ui/EmptyState.tsx b/frontend/src/app/components/ui/EmptyState.tsx index 9ecd5839..e1659959 100644 --- a/frontend/src/app/components/ui/EmptyState.tsx +++ b/frontend/src/app/components/ui/EmptyState.tsx @@ -3,12 +3,7 @@ import type { ElementType, ReactNode } from "react"; import Link from "next/link"; import { ArrowRight } from "lucide-react"; -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} +import { cn } from "@/app/utils/cn"; interface EmptyStateProps { icon: ElementType<{ className?: string }>;