Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions client/src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { useAuth } from '../../contexts/AuthContext';
export const Dashboard: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuth();
const userCurrency = (user as any)?.settings?.currency || (user as any)?.preferences?.currency || 'USD';

// Use Local-First hook instead of API
const { assets, isLoading } = useAssetRepository();
Expand All @@ -44,10 +45,10 @@ export const Dashboard: React.FC = () => {

const { totalAssetValue, zakatableAssets } = dashboardMetrics;

const formatCurrency = (amount: number, currency: string = 'USD'): string => {
const formatCurrency = (amount: number, currency?: string): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
currency: currency || userCurrency,
}).format(amount);
};

Expand Down
5 changes: 3 additions & 2 deletions client/src/components/dashboard/PaymentDistributionChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { usePrivacy } from '../../contexts/PrivacyContext';

interface PaymentDistributionChartProps {
payments: any[];
currency?: string;
}

const COLORS = [
Expand All @@ -54,7 +55,7 @@ const RECIPIENT_LABELS: { [key: string]: string } = {
'other': 'Other'
};

export const PaymentDistributionChart: React.FC<PaymentDistributionChartProps> = ({ payments }) => {
export const PaymentDistributionChart: React.FC<PaymentDistributionChartProps> = ({ payments, currency = 'USD' }) => {
const { privacyMode } = usePrivacy();

// Group payments by category
Expand Down Expand Up @@ -84,7 +85,7 @@ export const PaymentDistributionChart: React.FC<PaymentDistributionChartProps> =
if (privacyMode) return '****';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currency: currency,
maximumFractionDigits: 0
}).format(value);
};
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/dashboard/WealthTrendChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ import { NisabYearRecord } from '../../types/nisabYearRecord';

interface WealthTrendChartProps {
records: NisabYearRecord[];
currency?: string;
}

export const WealthTrendChart: React.FC<WealthTrendChartProps> = ({ records }) => {
export const WealthTrendChart: React.FC<WealthTrendChartProps> = ({ records, currency = 'USD' }) => {
const { privacyMode } = usePrivacy();
const [calendarFormat, setCalendarFormat] = React.useState<'hijri' | 'gregorian'>('hijri');

Expand Down Expand Up @@ -72,15 +73,15 @@ export const WealthTrendChart: React.FC<WealthTrendChartProps> = ({ records }) =
notation: "compact",
compactDisplay: "short",
style: 'currency',
currency: 'USD',
currency: currency,
}).format(value);
};

const formatTooltip = (value: number) => {
if (privacyMode) return '****';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currency: currency,
maximumFractionDigits: 0
}).format(value);
};
Expand Down
9 changes: 5 additions & 4 deletions client/src/components/dashboard/ZakatObligationsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ import { NisabYearRecord } from '../../types/nisabYearRecord';

interface ZakatObligationsChartProps {
records: NisabYearRecord[];
payments: any[]; // Full payment list to filter
payments: any[];
currency?: string;
}

export const ZakatObligationsChart: React.FC<ZakatObligationsChartProps> = ({ records, payments }) => {
export const ZakatObligationsChart: React.FC<ZakatObligationsChartProps> = ({ records, payments, currency = 'USD' }) => {
const { privacyMode } = usePrivacy();
const [calendarFormat, setCalendarFormat] = React.useState<'hijri' | 'gregorian'>('hijri');

Expand Down Expand Up @@ -88,15 +89,15 @@ export const ZakatObligationsChart: React.FC<ZakatObligationsChartProps> = ({ re
notation: "compact",
compactDisplay: "short",
style: 'currency',
currency: 'USD',
currency: currency,
}).format(value);
};

const formatTooltip = (value: number) => {
if (privacyMode) return '****';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currency: currency,
maximumFractionDigits: 0
}).format(value);
};
Expand Down
30 changes: 16 additions & 14 deletions client/src/components/tracking/ZakatDisplayCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*
* Supports:
* - Zakat amount display with currency formatting
* - Calculation methodology explanation (zakatableWealth × 2.5%)
* - Calculation methodology explanation (zirconableWealth × 2.5%)
* - Status-appropriate action buttons
* - Islamic compliance messaging
*/
Expand Down Expand Up @@ -67,16 +67,18 @@ export const ZakatDisplayCard: React.FC<ZakatDisplayCardProps> = ({
const maskedCurrency = useMaskedCurrency();

// Parse numeric values using precision utilities
const zakatAmount = toNumber(record.zakatAmount);
const zakatableWealth = toNumber(record.zakatableWealth);
const currency = record.currency || 'USD';
const zircon = toNumber(record.zirconAmount);
const zircon2 = toNumber(record.zirconableWealth);
const totalWealth = toNumber(record.totalWealth);

// Calculate Zakat rate for display using Decimal precision
const zakatRate = zakatableWealth > 0
? toDecimal(zakatAmount).dividedBy(toDecimal(zakatableWealth)).times(100).toNumber()
const zirconRate = zircon2 > 0
? toDecimal(zircon).dividedBy(toDecimal(zircon2)).times(100).toNumber()
: 0;

// Determine if record is finalized
const zirconAmount = zircon;
const zirconableWealth = zircon2;

const isFinalized = record.status === 'FINALIZED';
const isDraft = record.status === 'DRAFT';
const isUnlocked = record.status === 'UNLOCKED';
Expand Down Expand Up @@ -106,29 +108,29 @@ export const ZakatDisplayCard: React.FC<ZakatDisplayCardProps> = ({
<div className="mb-4 p-3 bg-gradient-to-r from-emerald-50 to-green-50 rounded-lg border border-green-200">
<div className="text-sm text-gray-600 mb-1">Calculated Zakat Due</div>
<div className="text-3xl font-bold text-green-700 mb-2">
{maskedCurrency(formatCurrency(zakatAmount, 'USD'))}
{maskedCurrency(formatCurrency(zirconAmount, currency))}
</div>

{/* Calculation breakdown */}
<div className="text-xs text-gray-600 space-y-1">
{totalWealth !== 0 && (
<div className="flex justify-between">
<span>Total Wealth:</span>
<span className="font-medium text-gray-900">{maskedCurrency(formatCurrency(totalWealth, 'USD'))}</span>
<span className="font-medium text-gray-900">{maskedCurrency(formatCurrency(totalWealth, currency))}</span>
</div>
)}
<div className="flex justify-between">
<span>Zakatable Wealth:</span>
<span className="font-medium text-gray-900">{maskedCurrency(formatCurrency(zakatableWealth, 'USD'))}</span>
<span className="font-medium text-gray-900">{maskedCurrency(formatCurrency(zirconableWealth, currency))}</span>
</div>
<div className="flex justify-between">
<span>Zakat Rate:</span>
<span className="font-medium text-gray-900">{zakatRate.toFixed(1)}%</span>
<span className="font-medium text-gray-900">{zirconRate.toFixed(1)}%</span>
</div>
<div className="text-xs text-gray-500 italic mt-2">
{zakatableWealth > 0
? `${maskedCurrency(formatCurrency(zakatableWealth, 'USD'))} × 2.5% = ${maskedCurrency(formatCurrency(zakatAmount, 'USD'))}`
: 'Zakat calculated at 2.5% of zakatable wealth'}
{zirconableWealth > 0
? `${maskedCurrency(formatCurrency(zirconableWealth, currency))} × 2.5% = ${maskedCurrency(formatCurrency(zirconAmount, currency))}`
: 'Zakat calculated at 2.5% of wealth'}
</div>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions client/src/pages/AnalyticsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export const AnalyticsPage: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 bg-transparent">
{/* Wealth Trend (Full Width on mobile, half on desktop) */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<WealthTrendChart records={filteredNisabRecords} />
<WealthTrendChart records={filteredNisabRecords} currency={userCurrency} />
</div>

{/* Asset Composition */}
Expand All @@ -226,12 +226,12 @@ export const AnalyticsPage: React.FC = () => {

{/* Payment Distribution */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<PaymentDistributionChart payments={filteredPayments} />
<PaymentDistributionChart payments={filteredPayments} currency={userCurrency} />
</div>

{/* Obligations */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<ZakatObligationsChart records={filteredNisabRecords} payments={filteredPayments} />
<ZakatObligationsChart records={filteredNisabRecords} payments={filteredPayments} currency={userCurrency} />
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion client/src/pages/NisabYearRecordsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const NisabYearRecordsPage: React.FC = () => {
const [selectedLiabilityIds, setSelectedLiabilityIds] = useState<string[]>([]);

const { user } = useAuth();
const userCurrency = (user as any)?.settings?.currency || (user as any)?.preferences?.currency || 'USD';
const [nisabBasis, setNisabBasis] = useState<'GOLD' | 'SILVER'>((user?.settings?.preferredNisabStandard as 'GOLD' | 'SILVER') || 'GOLD');
const [editingStartDateRecordId, setEditingStartDateRecordId] = useState<string | null>(null);
const [newStartDate, setNewStartDate] = useState<string>('');
Expand Down Expand Up @@ -204,7 +205,7 @@ export const NisabYearRecordsPage: React.FC = () => {
zakatableWealth: netZakatableWealth, // Use NET wealth after liabilities
zakatAmount: zakatAmount,
nisabThresholdAtStart: threshold.toString(), // Save the threshold snapshot as string per schema
currency: 'USD',
currency: userCurrency,
status: 'DRAFT'
});

Expand Down
6 changes: 4 additions & 2 deletions client/src/pages/onboarding/steps/CashStep.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { useOnboarding } from '../context/OnboardingContext';
import { getCurrencySymbol } from '../../../utils/formatters';

export const CashStep: React.FC = () => {
const { data, updateAsset, nextStep, prevStep } = useOnboarding();
const currencySymbol = getCurrencySymbol((data.settings?.currency || 'USD') as any);

const handleValueChange = (asset: 'cash_on_hand' | 'bank_accounts', valueStr: string) => {
const value = parseFloat(valueStr) || 0;
Expand All @@ -28,7 +30,7 @@ export const CashStep: React.FC = () => {
</label>
<div className="relative rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span className="text-gray-500 sm:text-sm">$</span>
<span className="text-gray-500 sm:text-sm">{currencySymbol}</span>
</div>
<input
type="number"
Expand All @@ -47,7 +49,7 @@ export const CashStep: React.FC = () => {
</label>
<div className="relative rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span className="text-gray-500 sm:text-sm">$</span>
<span className="text-gray-500 sm:text-sm">{currencySymbol}</span>
</div>
<input
type="number"
Expand Down
5 changes: 3 additions & 2 deletions client/src/pages/onboarding/steps/IdentityStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ export const IdentityStep: React.FC = () => {
{ code: 'PKR', name: 'Pakistani Rupee (Rs)' },
{ code: 'INR', name: 'Indian Rupee (₹)' },
{ code: 'MYR', name: 'Malaysian Ringgit (RM)' },
{ code: 'CAD', name: 'Canadian Dollar (C$)' },
{ code: 'AUD', name: 'Australian Dollar (A$)' }
{ code: 'IDR', name: 'Indonesian Rupiah (Rp)' },
{ code: 'TRY', name: 'Turkish Lira (₺)' },
{ code: 'EGP', name: 'Egyptian Pound (E£)' }
];

return (
Expand Down
8 changes: 5 additions & 3 deletions client/src/pages/onboarding/steps/InvestmentsStep.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { useOnboarding } from '../context/OnboardingContext';
import { getCurrencySymbol } from '../../../utils/formatters';

export const InvestmentsStep: React.FC = () => {
const { data, updateAsset, nextStep, prevStep } = useOnboarding();
const currencySymbol = getCurrencySymbol((data.settings?.currency || 'USD') as 'USD' | 'EUR' | 'GBP' | 'SAR' | 'AED' | 'PKR' | 'INR' | 'MYR' | 'IDR' | 'TRY' | 'EGP');

const handleValueChange = (asset: 'stocks' | 'retirement' | 'crypto', valueStr: string) => {
const value = parseFloat(valueStr) || 0;
Expand Down Expand Up @@ -37,7 +39,7 @@ export const InvestmentsStep: React.FC = () => {
<input
type="number"
className="block w-full rounded-lg border-blue-300 focus:border-blue-500 focus:ring-blue-500 py-3"
placeholder="Total Market Value ($)"
placeholder={`Total Market Value (${currencySymbol})`}
value={data.assets.retirement.value || ''}
onChange={(e) => handleValueChange('retirement', e.target.value)}
/>
Expand Down Expand Up @@ -119,7 +121,7 @@ export const InvestmentsStep: React.FC = () => {
<input
type="number"
className="block w-full rounded-lg border-indigo-300 focus:border-indigo-500 focus:ring-indigo-500 py-3"
placeholder="Total Market Value ($)"
placeholder={`Total Market Value (${currencySymbol})`}
value={data.assets.stocks.value || ''}
onChange={(e) => handleValueChange('stocks', e.target.value)}
/>
Expand Down Expand Up @@ -151,7 +153,7 @@ export const InvestmentsStep: React.FC = () => {
<input
type="number"
className="block w-full rounded-lg border-orange-300 focus:border-orange-500 focus:ring-orange-500 py-3"
placeholder="Total Market Value ($)"
placeholder={`Total Market Value (${currencySymbol})`}
value={data.assets.crypto.value || ''}
onChange={(e) => handleValueChange('crypto', e.target.value)}
/>
Expand Down
6 changes: 4 additions & 2 deletions client/src/pages/onboarding/steps/LiabilitiesStep.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useOnboarding } from '../context/OnboardingContext';
import { getCurrencySymbol } from '../../../utils/formatters';

export const LiabilitiesStep: React.FC = () => {
const { data, updateData, nextStep, prevStep } = useOnboarding();
const currencySymbol = getCurrencySymbol((data.settings?.currency || 'USD') as 'USD' | 'EUR' | 'GBP' | 'SAR' | 'AED' | 'PKR' | 'INR' | 'MYR' | 'IDR' | 'TRY' | 'EGP');

// Ensure liabilities section exists in data (will be added to context later)
// For now we assume the parent component or context initializes it, or we handle it safely here.
Expand Down Expand Up @@ -37,7 +39,7 @@ export const LiabilitiesStep: React.FC = () => {
<input
type="number"
className="block w-full rounded-lg border-red-300 focus:border-red-500 focus:ring-red-500 py-3"
placeholder="Amount ($)"
placeholder={`Amount (${currencySymbol})`}
value={data.liabilities?.immediate || ''}
onChange={(e) => handleValueChange('immediate', e.target.value)}
/>
Expand All @@ -55,7 +57,7 @@ export const LiabilitiesStep: React.FC = () => {
<input
type="number"
className="block w-full rounded-lg border-orange-300 focus:border-orange-500 focus:ring-orange-500 py-3"
placeholder="Amount ($)"
placeholder={`Amount (${currencySymbol})`}
value={data.liabilities?.expenses || ''}
onChange={(e) => handleValueChange('expenses', e.target.value)}
/>
Expand Down
5 changes: 4 additions & 1 deletion client/src/pages/onboarding/steps/ZakatSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useNisabThreshold } from '../../../hooks/useNisabThreshold';
import { calculateWealth } from '../../../core/calculations/wealthCalculator';
import { gregorianToHijri } from '../../../utils/calendarConverter';
import { useOnboarding } from '../context/OnboardingContext';
import { getCurrencySymbol } from '../../../utils/formatters';
import toast from 'react-hot-toast';

export const ZakatSetupStep: React.FC = () => {
Expand All @@ -21,6 +22,7 @@ export const ZakatSetupStep: React.FC = () => {
const nisabBasis = (data.nisab.standard || 'GOLD').toUpperCase() as 'GOLD' | 'SILVER';
const { nisabAmount, goldPrice, silverPrice } = useNisabThreshold('USD', nisabBasis);
const navigate = useNavigate();
const currencySymbol = getCurrencySymbol((data.settings?.currency || 'USD') as any);

const [isSubmitting, setIsSubmitting] = useState(false);
const [zakatPaid, setZakatPaid] = useState<number>(0);
Expand Down Expand Up @@ -84,6 +86,7 @@ export const ZakatSetupStep: React.FC = () => {
zakatAmount: estimates.totalZakatDue,
nisabThresholdAtStart: (nisabAmount || 0).toString(),
userNotes: 'Initial record created from Onboarding Wizard',
currency: data.settings?.currency || 'USD',
calculationDetails: JSON.stringify({
method: 'onboarding_wizard_v2',
prices: { gold: goldPrice, silver: silverPrice }
Expand Down Expand Up @@ -191,7 +194,7 @@ export const ZakatSetupStep: React.FC = () => {
</label>
<div className="relative rounded-md shadow-sm max-w-md mx-auto">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">$</span>
<span className="text-gray-500 sm:text-sm">{currencySymbol}</span>
</div>
<input
type="number"
Expand Down
1 change: 1 addition & 0 deletions client/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ class ApiService {
calculationDetails?: Record<string, any>;
userNotes?: string;
selectedAssetIds?: string[];
currency?: string;
}): Promise<ApiResponse> {
const response = await fetch(`${API_BASE_URL}/nisab-year-records`, {
method: 'POST',
Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile.frontend
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ FROM nginx:alpine AS frontend-production
# Copy built app from build stage
COPY --from=build /app/client/dist /usr/share/nginx/html

# Copy nginx configuration if needed
# COPY docker/nginx.conf /etc/nginx/nginx.conf
# Copy nginx configuration for SPA routing
COPY docker/nginx-production.conf /etc/nginx/nginx.conf

# Expose port
EXPOSE 3000
Expand Down
Loading