Update formatting and improve consistency across configuration and documentation files

- Adjusted formatting in .prettierrc for consistent newline handling.
- Enhanced API documentation in BACKEND_PROMPT.md for better readability and structure.
- Updated docker-compose.yml to standardize quotes and improve health check commands.
- Refactored ESLint configuration for better readability and consistency.
- Made minor formatting adjustments in various frontend components for improved user experience and code clarity.
This commit is contained in:
2025-12-11 02:24:01 -05:00
parent 51074d02a9
commit 2cff25c55b
22 changed files with 173 additions and 179 deletions

View File

@@ -57,8 +57,7 @@ export default function Layout() {
</div>
<button
onClick={handleLogout}
className="flex h-9 w-full items-center gap-3 rounded-lg px-2.5 text-muted-foreground hover:bg-accent/50 hover:text-foreground transition-colors"
>
className="flex h-9 w-full items-center gap-3 rounded-lg px-2.5 text-muted-foreground hover:bg-accent/50 hover:text-foreground transition-colors">
<LogOut className="h-[18px] w-[18px] shrink-0" />
<span className="text-sm opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">Log out</span>
</button>

View File

@@ -10,4 +10,3 @@ export default function ProtectedRoute() {
return <Outlet />;
}

View File

@@ -50,7 +50,7 @@ export default function AddAssetDialog({open, onOpenChange}: Props) {
createAsset({
name: sanitizeString(form.name),
type: form.type.toUpperCase() as 'CASH' | 'INVESTMENT' | 'PROPERTY' | 'VEHICLE' | 'OTHER',
value: valueNum,
value: valueNum
})
);
onOpenChange(false);

View File

@@ -23,7 +23,7 @@ export default function AddLiabilityDialog({open, onOpenChange}: Props) {
createLiability({
name: form.name,
type: form.type.toUpperCase() as 'CREDIT_CARD' | 'LOAN' | 'MORTGAGE' | 'OTHER',
currentBalance: parseFloat(form.balance) || 0,
currentBalance: parseFloat(form.balance) || 0
})
);
onOpenChange(false);

View File

@@ -66,7 +66,7 @@ export default function EditAssetDialog({open, onOpenChange, asset}: Props) {
data: {
name: form.name.trim(),
type: form.type.toUpperCase() as 'CASH' | 'INVESTMENT' | 'PROPERTY' | 'VEHICLE' | 'OTHER',
value: valueNum,
value: valueNum
}
})
);

View File

@@ -66,7 +66,7 @@ export default function EditLiabilityDialog({open, onOpenChange, liability}: Pro
data: {
name: form.name.trim(),
type: form.type.toUpperCase() as 'CREDIT_CARD' | 'LOAN' | 'MORTGAGE' | 'OTHER',
currentBalance: balanceNum,
currentBalance: balanceNum
}
})
);

View File

@@ -16,7 +16,7 @@ export default function LoginDialog({open, onOpenChange, onSwitchToSignUp}: Prop
const dispatch = useAppDispatch();
const [form, setForm] = useState({
email: '',
password: '',
password: ''
});
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
@@ -36,7 +36,7 @@ export default function LoginDialog({open, onOpenChange, onSwitchToSignUp}: Prop
await dispatch(
loginUser({
email: form.email,
password: form.password,
password: form.password
})
).unwrap();

View File

@@ -18,7 +18,7 @@ export default function SignUpDialog({open, onOpenChange, onSwitchToLogin}: Prop
name: '',
email: '',
password: '',
confirmPassword: '',
confirmPassword: ''
});
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
@@ -45,7 +45,7 @@ export default function SignUpDialog({open, onOpenChange, onSwitchToLogin}: Prop
registerUser({
email: form.email,
password: form.password,
name: form.name,
name: form.name
})
).unwrap();
@@ -117,9 +117,7 @@ export default function SignUpDialog({open, onOpenChange, onSwitchToLogin}: Prop
/>
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
<p className="text-xs text-muted-foreground">
By signing up, you agree to our Terms of Service and Privacy Policy.
</p>
<p className="text-xs text-muted-foreground">By signing up, you agree to our Terms of Service and Privacy Policy.</p>
</div>
<DialogFooter className="flex-col gap-2 sm:flex-col">
<Button type="submit" className="w-full" disabled={isLoading}>
@@ -134,4 +132,3 @@ export default function SignUpDialog({open, onOpenChange, onSwitchToLogin}: Prop
</Dialog>
);
}

View File

@@ -69,5 +69,5 @@ export const authService = {
} catch {
return null;
}
},
}
};

View File

@@ -52,7 +52,7 @@ export const incomeService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/cashflow/income/${id}`);
},
}
};
export const expenseService = {
@@ -70,7 +70,7 @@ export const expenseService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/cashflow/expenses/${id}`);
},
}
};
export const transactionService = {
@@ -84,5 +84,5 @@ export const transactionService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/cashflow/transactions/${id}`);
},
}
};

View File

@@ -28,7 +28,7 @@ class ApiClient {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
...(options.headers as Record<string, string>)
};
if (token) {
@@ -38,7 +38,7 @@ class ApiClient {
try {
const response = await fetch(url, {
...options,
headers,
headers
});
// Handle non-JSON responses
@@ -48,7 +48,7 @@ class ApiClient {
throw {
message: 'Request failed',
statusCode: response.status,
error: response.statusText,
error: response.statusText
} as ApiError;
}
@@ -61,7 +61,7 @@ class ApiClient {
throw {
message: data.message || 'Request failed',
statusCode: response.status,
error: data.error,
error: data.error
} as ApiError;
}
@@ -73,7 +73,7 @@ class ApiClient {
throw {
message: 'Network error',
statusCode: 0,
error: String(error),
error: String(error)
} as ApiError;
}
}
@@ -85,21 +85,21 @@ class ApiClient {
async post<T>(endpoint: string, data?: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
body: data ? JSON.stringify(data) : undefined
});
}
async put<T>(endpoint: string, data?: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
body: data ? JSON.stringify(data) : undefined
});
}
async patch<T>(endpoint: string, data?: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'PATCH',
body: data ? JSON.stringify(data) : undefined,
body: data ? JSON.stringify(data) : undefined
});
}

View File

@@ -90,7 +90,7 @@ export const assetService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/assets/${id}`);
},
}
};
export const liabilityService = {
@@ -112,7 +112,7 @@ export const liabilityService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/liabilities/${id}`);
},
}
};
export const snapshotService = {
@@ -124,13 +124,7 @@ export const snapshotService = {
return apiClient.get<{snapshot: NetWorthSnapshot}>(`/networth/snapshots/${id}`);
},
async create(data: {
date: string;
totalAssets: number;
totalLiabilities: number;
netWorth: number;
notes?: string;
}): Promise<{snapshot: NetWorthSnapshot}> {
async create(data: {date: string; totalAssets: number; totalLiabilities: number; netWorth: number; notes?: string}): Promise<{snapshot: NetWorthSnapshot}> {
return apiClient.post<{snapshot: NetWorthSnapshot}>('/networth/snapshots', data);
},
@@ -140,5 +134,5 @@ export const snapshotService = {
async delete(id: string): Promise<void> {
return apiClient.delete<void>(`/networth/snapshots/${id}`);
},
}
};

View File

@@ -33,5 +33,5 @@ export const tokenStorage = {
clear(): void {
this.removeToken();
this.removeUser();
},
}
};

View File

@@ -9,33 +9,33 @@ const features = [
{
icon: TrendingUp,
title: 'Net Worth Tracking',
description: 'Monitor your assets and liabilities over time with beautiful charts and insights.',
description: 'Monitor your assets and liabilities over time with beautiful charts and insights.'
},
{
icon: CreditCard,
title: 'Debt Management',
description: 'Organize and track debt paydown across multiple accounts and categories.',
description: 'Organize and track debt paydown across multiple accounts and categories.'
},
{
icon: ArrowLeftRight,
title: 'Cashflow Analysis',
description: 'Understand your income and expenses to optimize your savings rate.',
description: 'Understand your income and expenses to optimize your savings rate.'
},
{
icon: FileText,
title: 'Invoicing',
description: 'Create professional invoices and track payments from your clients.',
description: 'Create professional invoices and track payments from your clients.'
},
{
icon: BarChart3,
title: 'Visual Reports',
description: 'Clean, minimal dashboards that put your data front and center.',
description: 'Clean, minimal dashboards that put your data front and center.'
},
{
icon: Shield,
title: 'Private & Secure',
description: 'Your financial data stays on your device. No cloud sync required.',
},
description: 'Your financial data stays on your device. No cloud sync required.'
}
];
export default function LandingPage() {
@@ -64,9 +64,7 @@ export default function LandingPage() {
{/* Hero */}
<section className="py-20 px-6">
<div className="max-w-3xl mx-auto text-center">
<h1 className="text-4xl font-semibold tracking-tight mb-4">
Take control of your finances
</h1>
<h1 className="text-4xl font-semibold tracking-tight mb-4">Take control of your finances</h1>
<p className="text-lg text-muted-foreground mb-8 max-w-xl mx-auto">
A clean, minimal tool to track your net worth, manage debt, monitor cashflow, and invoice clientsall in one place.
</p>
@@ -86,7 +84,7 @@ export default function LandingPage() {
<div className="max-w-5xl mx-auto">
<h2 className="text-xl font-semibold text-center mb-10">Everything you need</h2>
<div className="grid grid-cols-3 gap-4">
{features.map((feature) => (
{features.map(feature => (
<Card key={feature.title} className="card-elevated">
<CardContent className="p-5">
<feature.icon className="h-5 w-5 mb-3 text-muted-foreground" />
@@ -103,9 +101,7 @@ export default function LandingPage() {
<section className="py-16 px-6 border-t border-border">
<div className="max-w-xl mx-auto text-center">
<h2 className="text-xl font-semibold mb-3">Ready to build wealth?</h2>
<p className="text-muted-foreground mb-6">
Start tracking your finances today. It's free to get started.
</p>
<p className="text-muted-foreground mb-6">Start tracking your finances today. It's free to get started.</p>
<Button size="lg" onClick={() => setSignUpOpen(true)}>
Create your account
</Button>
@@ -116,11 +112,10 @@ export default function LandingPage() {
<section className="py-8 px-6 border-t border-border bg-muted/30">
<div className="max-w-3xl mx-auto">
<p className="text-xs text-muted-foreground text-center leading-relaxed">
<strong>Disclaimer:</strong> This application is for informational and personal tracking purposes only.
It does not constitute financial, investment, tax, or legal advice. The information provided should not
be relied upon for making financial decisions. Always consult with qualified professionals before making
any financial decisions. We make no guarantees about the accuracy or completeness of the data you enter
or the calculations performed. Use at your own risk. Past performance is not indicative of future results.
<strong>Disclaimer:</strong> This application is for informational and personal tracking purposes only. It does not constitute financial,
investment, tax, or legal advice. The information provided should not be relied upon for making financial decisions. Always consult with qualified
professionals before making any financial decisions. We make no guarantees about the accuracy or completeness of the data you enter or the
calculations performed. Use at your own risk. Past performance is not indicative of future results.
</p>
</div>
</section>
@@ -136,9 +131,22 @@ export default function LandingPage() {
</div>
</footer>
<LoginDialog open={loginOpen} onOpenChange={setLoginOpen} onSwitchToSignUp={() => { setLoginOpen(false); setSignUpOpen(true); }} />
<SignUpDialog open={signUpOpen} onOpenChange={setSignUpOpen} onSwitchToLogin={() => { setSignUpOpen(false); setLoginOpen(true); }} />
<LoginDialog
open={loginOpen}
onOpenChange={setLoginOpen}
onSwitchToSignUp={() => {
setLoginOpen(false);
setSignUpOpen(true);
}}
/>
<SignUpDialog
open={signUpOpen}
onOpenChange={setSignUpOpen}
onSwitchToLogin={() => {
setSignUpOpen(false);
setLoginOpen(true);
}}
/>
</div>
);
}

View File

@@ -1,5 +1,12 @@
import {createSlice, createAsyncThunk, type PayloadAction} from '@reduxjs/toolkit';
import {incomeService, expenseService, transactionService, type IncomeSource as ApiIncome, type Expense as ApiExpense, type Transaction as ApiTransaction} from '@/lib/api/cashflow.service';
import {
incomeService,
expenseService,
transactionService,
type IncomeSource as ApiIncome,
type Expense as ApiExpense,
type Transaction as ApiTransaction
} from '@/lib/api/cashflow.service';
export interface IncomeSource {
id: string;
@@ -66,7 +73,7 @@ const mapApiIncomeToIncome = (apiIncome: ApiIncome): IncomeSource => ({
category: 'Income',
nextDate: new Date().toISOString(),
isActive: true,
createdAt: apiIncome.createdAt || new Date().toISOString(),
createdAt: apiIncome.createdAt || new Date().toISOString()
});
const mapApiExpenseToExpense = (apiExpense: ApiExpense): Expense => ({
@@ -78,7 +85,7 @@ const mapApiExpenseToExpense = (apiExpense: ApiExpense): Expense => ({
nextDate: new Date().toISOString(),
isActive: true,
isEssential: apiExpense.isEssential || false,
createdAt: apiExpense.createdAt || new Date().toISOString(),
createdAt: apiExpense.createdAt || new Date().toISOString()
});
const mapApiTransactionToTransaction = (apiTransaction: ApiTransaction): Transaction => ({
@@ -88,7 +95,7 @@ const mapApiTransactionToTransaction = (apiTransaction: ApiTransaction): Transac
amount: apiTransaction.amount,
category: apiTransaction.category || 'Other',
date: apiTransaction.date,
note: apiTransaction.notes,
note: apiTransaction.notes
});
// Async thunks
@@ -210,7 +217,7 @@ const cashflowSlice = createSlice({
state.isLoading = false;
state.error = action.payload as string;
});
},
}
});
export const {

View File

@@ -8,7 +8,7 @@ import {
type CreateAssetRequest,
type UpdateAssetRequest,
type CreateLiabilityRequest,
type UpdateLiabilityRequest,
type UpdateLiabilityRequest
} from '@/lib/api/networth.service';
export interface Asset {
@@ -57,7 +57,7 @@ const mapApiAssetToAsset = (apiAsset: ApiAsset): Asset => ({
name: apiAsset.name,
type: apiAsset.type.toLowerCase() as Asset['type'],
value: apiAsset.value,
updatedAt: apiAsset.updatedAt || new Date().toISOString(),
updatedAt: apiAsset.updatedAt || new Date().toISOString()
});
const mapApiLiabilityToLiability = (apiLiability: ApiLiability): Liability => ({
@@ -65,7 +65,7 @@ const mapApiLiabilityToLiability = (apiLiability: ApiLiability): Liability => ({
name: apiLiability.name,
type: apiLiability.type.toLowerCase() as Liability['type'],
balance: apiLiability.currentBalance,
updatedAt: apiLiability.updatedAt || new Date().toISOString(),
updatedAt: apiLiability.updatedAt || new Date().toISOString()
});
// Async thunks for assets
@@ -191,7 +191,7 @@ const netWorthSlice = createSlice({
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
}
},
extraReducers: builder => {
// Fetch assets
@@ -267,7 +267,7 @@ const netWorthSlice = createSlice({
state.isLoading = false;
state.error = action.payload as string;
});
},
}
});
export const {setLoading, setError} = netWorthSlice.actions;