Skip to content
Merged
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
3 changes: 1 addition & 2 deletions frontend/src/app/settings/settings-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ export default function SettingsContent() {
</div>
</div>

<div className="flex gap-2">
{(["light", "dark", "system"] as const).map((t) => (
<div className="flex gap-2"> {(["light", "dark", "system"] as const).map((t) => (
<button
key={t}
onClick={() => handleThemeChange(t)}
Expand Down
25 changes: 4 additions & 21 deletions frontend/src/hooks/useSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,12 @@ describe('useSettings and formatAmountWithPreference', () => {
expect(result.current.isHydrated).toBe(true);
});

expect(result.current.theme).toBe(DEFAULT_SETTINGS.theme);
expect(result.current.displayCurrency).toBe(DEFAULT_SETTINGS.displayCurrency);
expect(result.current.amountFormat).toBe(DEFAULT_SETTINGS.amountFormat);
expect(result.current.decimalPlaces).toBe(DEFAULT_SETTINGS.decimalPlaces);
});

it('hydrates settings from pre-seeded localStorage values', async () => {
localStorage.setItem(STORAGE_KEYS.theme, 'light');
localStorage.setItem(STORAGE_KEYS.displayCurrency, 'XLM');
localStorage.setItem(STORAGE_KEYS.amountFormat, 'compact');
localStorage.setItem(STORAGE_KEYS.decimalPlaces, '4');
Expand All @@ -63,26 +61,11 @@ describe('useSettings and formatAmountWithPreference', () => {
expect(result.current.isHydrated).toBe(true);
});

expect(result.current.theme).toBe('light');
expect(result.current.displayCurrency).toBe('XLM');
expect(result.current.amountFormat).toBe('compact');
expect(result.current.decimalPlaces).toBe(4);
});

it('updates state and localStorage when setTheme is called', async () => {
const { result } = renderHook(() => useSettings());

await waitFor(() => expect(result.current.isHydrated).toBe(true));

act(() => {
result.current.setTheme('light');
});

expect(result.current.theme).toBe('light');
expect(localStorage.getItem(STORAGE_KEYS.theme)).toBe('light');
expect(document.documentElement.classList.contains('dark')).toBe(false);
});

it('updates state and localStorage when setDecimalPlaces is called', async () => {
const { result } = renderHook(() => useSettings());

Expand Down Expand Up @@ -146,17 +129,17 @@ describe('useSettings and formatAmountWithPreference', () => {

act(() => {
// Simulate other tab changing local storage
localStorage.setItem(STORAGE_KEYS.theme, 'light');
localStorage.setItem(STORAGE_KEYS.displayCurrency, 'EUR');

// Dispatch storage event
const event = new StorageEvent('storage', {
key: STORAGE_KEYS.theme,
newValue: 'light'
key: STORAGE_KEYS.displayCurrency,
newValue: 'EUR'
});
window.dispatchEvent(event);
});

expect(result.current.theme).toBe('light');
expect(result.current.displayCurrency).toBe('EUR');
});
});

Expand Down
46 changes: 5 additions & 41 deletions frontend/src/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@

import { useState, useEffect, useCallback } from "react";

export type Theme = "light" | "dark" | "system";
export type DisplayCurrency = "USD" | "EUR" | "GBP" | "XLM" | "USDC";
export type AmountFormat = "full" | "compact";
export type DecimalPlaces = 2 | 4 | 7;

interface Settings {
theme: Theme;
displayCurrency: DisplayCurrency;
amountFormat: AmountFormat;
decimalPlaces: DecimalPlaces;
}

const DEFAULT_SETTINGS: Settings = {
theme: "dark",
displayCurrency: "USD",
amountFormat: "full",
decimalPlaces: 7,
};

const STORAGE_KEYS = {
theme: "flowfi-theme",
displayCurrency: "flowfi-currency",
amountFormat: "flowfi-amount-format",
decimalPlaces: "flowfi-decimal-places",
Expand All @@ -38,7 +34,6 @@ function notifyListeners() {

function loadSettingsFromStorage(): Settings {
if (typeof window === "undefined") return { ...DEFAULT_SETTINGS };
const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) as Theme | null;
const savedCurrency = localStorage.getItem(
STORAGE_KEYS.displayCurrency
) as DisplayCurrency | null;
Expand All @@ -48,7 +43,6 @@ function loadSettingsFromStorage(): Settings {
const savedDecimals = localStorage.getItem(STORAGE_KEYS.decimalPlaces);

return {
theme: savedTheme || DEFAULT_SETTINGS.theme,
displayCurrency: savedCurrency || DEFAULT_SETTINGS.displayCurrency,
amountFormat: savedFormat || DEFAULT_SETTINGS.amountFormat,
decimalPlaces: savedDecimals
Expand Down Expand Up @@ -102,20 +96,6 @@ export function useSettings() {
};
}, []);

const setTheme = useCallback((theme: Theme) => {
sharedSettings = { ...sharedSettings, theme };
localStorage.setItem(STORAGE_KEYS.theme, theme);
notifyListeners();

// Apply theme immediately
if (theme === "system") {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.classList.toggle("dark", prefersDark);
} else {
document.documentElement.classList.toggle("dark", theme === "dark");
}
}, []);

const setDisplayCurrency = useCallback((currency: DisplayCurrency) => {
sharedSettings = { ...sharedSettings, displayCurrency: currency };
localStorage.setItem(STORAGE_KEYS.displayCurrency, currency);
Expand All @@ -137,7 +117,6 @@ export function useSettings() {
return {
...settings,
isHydrated,
setTheme,
setDisplayCurrency,
setAmountFormat,
setDecimalPlaces,
Expand All @@ -151,25 +130,6 @@ export function getDecimalPlaces(): DecimalPlaces {
return saved ? (parseInt(saved, 10) as DecimalPlaces) : 7;
}

// Helper function to get theme synchronously
export function getStoredTheme(): Theme {
if (typeof window === "undefined") return "dark";
return (localStorage.getItem(STORAGE_KEYS.theme) as Theme) || "dark";
}

// Apply theme immediately (useful for initial page load)
export function applyStoredTheme(): void {
if (typeof window === "undefined") return;

const theme = getStoredTheme();
if (theme === "system") {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.classList.toggle("dark", prefersDark);
} else {
document.documentElement.classList.toggle("dark", theme === "dark");
}
}

// Format amount based on user's decimal places preference
export function formatAmountWithPreference(
amount: string | number | bigint,
Expand Down Expand Up @@ -228,10 +188,14 @@ export default useSettings;
* Usage examples:
*
* In React components:
* const { theme, decimalPlaces, setTheme } = useSettings();
* const { decimalPlaces, setDecimalPlaces } = useSettings();
*
* For non-React code (utils, formatters):
* const decimals = getDecimalPlaces(); // 2, 4, or 7
* const formatted = formatAmountWithPreference(rawAmount, 7);
* const format = getAmountFormat(); // 'full' or 'compact'
*
* For theme management, use next-themes' useTheme() hook instead:
* import { useTheme } from 'next-themes';
* const { theme, setTheme } = useTheme();
*/
Loading