Internal Tools
Documentation for Finnance internal development tools, shared components, and engineering best practices.
Loading System
Finnance uses a unified loading system for creating snappy, consistent loading states across all applications.
Overview
The loading system provides:
- Simple, snappy loading states with optimized animations
- Consistent design patterns following the Finnance design system
- Flexible components for different use cases
- Performance optimizations with minimum loading times
Main Loading Components
Full-screen Loading
import { Loading } from '@app-launch-kit/components/common';
// Used for initial app loading
<Loading />
Page-level Loading
import { PageLoading } from '@app-launch-kit/components/common';
<PageLoading
title="Loading accounts..."
subtitle="Please wait while we fetch your data"
/>
Section-level Loading
import { ContentLoading } from '@app-launch-kit/components/common';
<ContentLoading text="Loading transactions..." />
Spinner Components
Centered Spinner
import { CenteredSpinner } from '@app-launch-kit/components/common';
<CenteredSpinner
text="Loading..."
size="md" // sm, md, lg
/>
Inline Spinner
import { InlineSpinner } from '@app-launch-kit/components/common';
<InlineSpinner size="sm" /> // xs, sm, md
Button Loading
import { ButtonLoading } from '@app-launch-kit/components/common';
<ButtonLoading text="Saving..." size="sm" />
Skeleton Components
Card Skeleton
import { SkeletonCard } from '@app-launch-kit/components/common';
<SkeletonCard showAvatar={true} showActions={true} />
List Skeleton
import { SkeletonList } from '@app-launch-kit/components/common';
<SkeletonList
count={3}
showAvatar={true}
showActions={true}
/>
Table Skeleton
import { SkeletonTable } from '@app-launch-kit/components/common';
<SkeletonTable rows={5} columns={4} />
Form Skeleton
import { SkeletonForm } from '@app-launch-kit/components/common';
<SkeletonForm fields={4} showSubmit={true} />
Loading Hooks
Single Loading State
import { useLoadingState } from '@app-launch-kit/hooks';
const { isLoading, withLoading, startLoading, stopLoading } = useLoadingState({
initialLoading: false,
delay: 300 // Minimum loading time to prevent flash
});
// Wrap async operations
const loadData = async () => {
const result = await withLoading(async () => {
return await fetchData();
});
};
// Manual control
const handleSubmit = async () => {
startLoading();
try {
await submitData();
} finally {
stopLoading();
}
};
Multiple Loading States
import { useMultipleLoadingStates } from '@app-launch-kit/hooks';
const {
isLoading,
isAnyLoading,
withLoading
} = useMultipleLoadingStates(['accounts', 'transactions']);
// Use specific loading state
const loadAccounts = async () => {
await withLoading('accounts', async () => {
return await fetchAccounts();
});
};
// Check if any operation is loading
if (isAnyLoading) {
return <ContentLoading />;
}
Best Practices
Use PageLayout for Page Loading
<PageLayout
title="Accounts"
subtitle="Manage your bank accounts"
isLoading={isLoading}
>
{/* Content */}
</PageLayout>
Prevent Loading Flash
const { isLoading, withLoading } = useLoadingState({
delay: 300 // Minimum 300ms loading time
});
Use Skeleton States for Lists
if (isLoading) {
return <SkeletonList count={5} />;
}
Use Button Loading for Actions
const { isLoading, withLoading } = useLoadingState();
<Button onPress={() => withLoading(handleSubmit)}>
{isLoading ? (
<ButtonLoading text="Saving..." />
) : (
<ButtonText>Save</ButtonText>
)}
</Button>
Admin App Development
The admin dashboard is built with Mantine and follows specific patterns for consistency.
Client Components
Always add "use client" at the top of components that:
- Use React Hooks (
useState,useEffect, etc.) - Handle Browser Events (
onClick,onSubmit, etc.) - Access Browser APIs (
window,document, etc.) - Use third-party libraries requiring client-side execution
'use client';
import { useState } from 'react';
import { Button } from '@mantine/core';
export function MyComponent() {
const [count, setCount] = useState(0);
return (
<Button onClick={() => setCount(count + 1)}>
Count: {count}
</Button>
);
}
Component Compatibility
Working Components
- ✅
Text,Title,Button - ✅
Card,Badge,Group - ✅
Container,Box - ✅
Tooltip,UnstyledButton
Problematic Components
- ❌
Grid.Col- Use CSS Grid instead - ❌
Stack- Use div with flexbox instead
CSS Grid Alternative
// ❌ Don't use this (causes build errors)
<Grid>
<Grid.Col span={3}>Content</Grid.Col>
</Grid>
// ✅ Use this instead
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
gap: '1rem'
}}>
<div>Content</div>
</div>
Design System Compliance
All loading components use the Finnance design system colors:
| Token | Usage |
|---|---|
border-primary-main | Active spinners |
border-surface-2 | Background spinners |
text-typography-secondary | Loading text |
animate-spin | Spinner animations |
animate-pulse | Skeleton animations |
Performance Tips
- Use delay option to prevent loading flash for fast operations
- Prefer skeleton states over spinners for content loading
- Use
withLoadingto automatically handle loading state lifecycle - Combine loading states to reduce component re-renders
- Use
isAnyLoadingfor multiple operations
Migration Guide
Before (Old Pattern)
const [isLoading, setIsLoading] = useState(false);
const loadData = async () => {
setIsLoading(true);
try {
await fetchData();
} finally {
setIsLoading(false);
}
};
if (isLoading) {
return <div className="loading-spinner">Loading...</div>;
}
After (New Pattern)
const { isLoading, withLoading } = useLoadingState({ delay: 300 });
const loadData = async () => {
await withLoading(async () => {
return await fetchData();
});
};
if (isLoading) {
return <ContentLoading text="Loading data..." />;
}
Available Admin Pages
| Page | Route | Description |
|---|---|---|
| Overview | /overview | Company health metrics dashboard |
| Households | /households | User account management |
| Analytics | /analytics | SaaS health metrics and KPIs |
Chart Library (Recharts)
The admin dashboard uses Recharts for data visualization:
import {
LineChart,
AreaChart,
BarChart,
PieChart,
ResponsiveContainer,
} from 'recharts';
Chart Best Practices
- Always wrap charts in
ResponsiveContainer - Use consistent color schemes
- Include proper tooltips and labels
- Test charts with different data sets