Skip to content

Commit

Permalink
refactor: move locale funcs to repo
Browse files Browse the repository at this point in the history
Signed-off-by: rare-magma <[email protected]>
  • Loading branch information
rare-magma committed Sep 29, 2024
1 parent 36fdb9c commit 9cebd95
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 36 deletions.
3 changes: 3 additions & 0 deletions src/guitos/domain/optionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ export interface OptionsRepository {
saveCurrencyCode(newCode: string): Promise<boolean>;
getLocale(): Promise<string>;
saveLocale(newLocale: string): Promise<boolean>;
getUserLang(): string;
getCountryCode(locale: string): string;
getDefaultCurrencyCode(): string;
}
14 changes: 14 additions & 0 deletions src/guitos/hooks/useDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,26 @@ export function useDB() {
.then((c) => {
if (c) {
handleCurrency(c as string);
setIntlConfig({
locale: optionsRepository.getUserLang(),
currency: c as string,
});
}
})
.catch((e) => {
handleError(e);
});
}

const saveCurrencyOption = useCallback(
(currencyCode: string) => {
optionsRepository.saveCurrencyCode(currencyCode).catch((e) => {
handleError(e);
});
},
[handleError],
);

function searchBudgets() {
let options: SearchOption[] = [];

Expand Down Expand Up @@ -457,6 +470,7 @@ export function useDB() {
selectBudgetsWithFilter,
saveBudget,
loadCurrencyOption,
saveCurrencyOption,
loadBudget,
loadFromDb,
options,
Expand Down
51 changes: 45 additions & 6 deletions src/guitos/infrastructure/localForageOptionsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import localforage from "localforage";
import { CURRENCY_CODE, LOCALE } from "../domain/options";
import type { OptionsRepository } from "../domain/optionsRepository";
import { optionsDB } from "./localForageDb";
import { currenciesMap } from "../../lists/currenciesMap";

export class localForageOptionsRepository implements OptionsRepository {
private readonly optionsDB;

constructor() {
this.optionsDB = localforage.createInstance({
name: "guitos",
storeName: "options",
});
}

async getCurrencyCode(): Promise<string> {
try {
const code = await optionsDB.getItem<string>(CURRENCY_CODE);
if (!code) throw new Error();
const code = await this.optionsDB.getItem<string>(CURRENCY_CODE);
if (!code) {
return this.getDefaultCurrencyCode();
}
return code;
} catch (e) {
throw new Error((e as Error).message);
Expand All @@ -15,7 +27,7 @@ export class localForageOptionsRepository implements OptionsRepository {

async saveCurrencyCode(newCode: string): Promise<boolean> {
try {
await optionsDB.setItem<string>(CURRENCY_CODE, newCode);
await this.optionsDB.setItem<string>(CURRENCY_CODE, newCode);
return true;
} catch {
return false;
Expand All @@ -24,7 +36,7 @@ export class localForageOptionsRepository implements OptionsRepository {

async getLocale(): Promise<string> {
try {
const locale = await optionsDB.getItem<string>(LOCALE);
const locale = await this.optionsDB.getItem<string>(LOCALE);
if (!locale) throw new Error();
return locale;
} catch (e) {
Expand All @@ -34,10 +46,37 @@ export class localForageOptionsRepository implements OptionsRepository {

async saveLocale(newLocale: string): Promise<boolean> {
try {
await optionsDB.setItem<string>(LOCALE, newLocale);
await this.optionsDB.setItem<string>(LOCALE, newLocale);
return true;
} catch {
return false;
}
}

getUserLang(): string {
return navigator.language;
}

getCountryCode(locale: string): string {
return locale.split("-").length >= 2
? locale.split("-")[1].toUpperCase()
: locale.toUpperCase();
}

getDefaultCurrencyCode(): string {
const country = this.getCountryCode(this.getUserLang());
return this.getCurrencyCodeFromCountry(country);
}

getCurrencyCodeFromCountry(country: string): string {
const countryIsInMap =
currenciesMap[country as keyof typeof currenciesMap] !== undefined;

if (countryIsInMap) {
return currenciesMap[
country as keyof typeof currenciesMap
] as unknown as string;
}
return "USD";
}
}
128 changes: 128 additions & 0 deletions src/guitos/sections/NavBar/NavBarSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useRef } from "react";
import {
Button,
InputGroup,
Nav,
OverlayTrigger,
Popover,
Stack,
Tooltip,
} from "react-bootstrap";
import { Typeahead } from "react-bootstrap-typeahead";
import type { Option } from "react-bootstrap-typeahead/types/types";
import { useHotkeys } from "react-hotkeys-hook";
import { BsGear } from "react-icons/bs";
import { useConfig } from "../../context/ConfigContext";
import { currenciesList } from "../../../lists/currenciesList";
import { useDB } from "../../hooks/useDB";

interface NavBarSettingsProps {
expanded: boolean;
}

export function NavBarSettings({ expanded }: NavBarSettingsProps) {
const { currency, handleCurrency } = useConfig();
const { saveCurrencyOption } = useDB();
const settingsButtonRef = useRef<HTMLButtonElement>(null);
const versionRef = useRef<HTMLAnchorElement>(null);

useHotkeys("t", (e) => !e.repeat && settingsButtonRef.current?.click(), {
preventDefault: true,
});

return (
<Nav className={expanded ? "m-4 flex-grow-1 h-25" : "m-2"}>
<OverlayTrigger
trigger="click"
key="nav-settings-overlay"
placement="bottom"
rootClose={true}
overlay={
<Popover id={"nav-popover-settings-button"}>
<Popover.Body>
<Stack gap={3}>
<Stack className="align-self-center" direction="horizontal">
<InputGroup
size="sm"
className="mb-1"
key={"export-button-group"}
>
<Typeahead
id="currency-option-list"
maxResults={currenciesList.length}
maxHeight="30vh"
paginate={false}
inputProps={{
"aria-label": "select display currency",
}}
onChange={(c: Option[]) => {
if (currenciesList.includes(c[0] as string)) {
handleCurrency(c[0] as string);
saveCurrencyOption(c[0] as string);
}
}}
className="w-100 fixed-width-font"
style={
expanded
? {
maxWidth: "10ch",
minWidth: "10ch",
}
: {
maxWidth: "7ch",
minWidth: "7ch",
}
}
options={currenciesList.sort((a, b) =>
a.localeCompare(b),
)}
placeholder={currency}
/>
</InputGroup>
</Stack>
<OverlayTrigger
delay={250}
placement="bottom"
overlay={
<Tooltip
id={"tooltip-guitos-version"}
style={{ position: "fixed" }}
>
guitos version
</Tooltip>
}
>
<a
className="version align-self-center"
aria-label="open guitos changelog"
href="https://github.com/rare-magma/guitos/blob/main/CHANGELOG.md"
target="_blank"
ref={versionRef}
rel="noreferrer"
>
{/* biome-ignore lint/correctness/noUndeclaredVariables: <explanation> */}
v{APP_VERSION}
</a>
</OverlayTrigger>
</Stack>
</Popover.Body>
</Popover>
}
>
<Button
className="w-100 h-100"
aria-label="budget settings"
variant="outline-info"
ref={settingsButtonRef}
onClick={() => {
setTimeout(() => {
versionRef.current?.focus();
}, 0);
}}
>
{<BsGear size={expanded ? 50 : 0} aria-hidden={true} />}
</Button>
</OverlayTrigger>
</Nav>
);
}
12 changes: 7 additions & 5 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Big from "big.js";
import { expect, test } from "vitest";
import type { FilteredItem } from "./components/ChartsPage/ChartsPage";
import type { FilteredItem } from "./guitos/sections/ChartsPage/ChartsPage";
import type { Budget } from "./guitos/domain/budget";
import { BudgetMother } from "./guitos/domain/budget.mother";
import type { ItemOperation } from "./guitos/domain/calculationHistoryItem";
Expand All @@ -11,8 +11,6 @@ import { firefoxLocalesList } from "./lists/firefoxLocalesList";
import {
calc,
createBudgetNameList,
getCountryCode,
getCurrencyCode,
getLabelKey,
getLabelKeyFilteredItem,
getNestedProperty,
Expand All @@ -22,6 +20,9 @@ import {
parseLocaleNumber,
roundBig,
} from "./utils";
import { localForageOptionsRepository } from "./guitos/infrastructure/localForageOptionsRepository";

const optionsRepository = new localForageOptionsRepository();

test("round", () => {
expect(roundBig(Big(123.123123123), 5)).eq(123.12312);
Expand Down Expand Up @@ -76,8 +77,9 @@ test("intlFormat", () => {
test("intlFormat browser locale list", () => {
for (const list of [firefoxLocalesList, chromeLocalesList]) {
for (const locale of list) {
const countryCode = getCountryCode(locale);
const currencyCode = getCurrencyCode(countryCode);
const countryCode = optionsRepository.getCountryCode(locale);
const currencyCode =
optionsRepository.getCurrencyCodeFromCountry(countryCode);
expect(intlFormat(1, currencyCode)).toBeTruthy();
}
}
Expand Down
26 changes: 1 addition & 25 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,6 @@ import type { FilteredItem } from "./guitos/sections/ChartsPage/ChartsPage";
import type { SearchOption } from "./guitos/sections/NavBar/NavBar";
import type { Budget } from "./guitos/domain/budget";
import type { ItemOperation } from "./guitos/domain/calculationHistoryItem";
import { currenciesMap } from "./lists/currenciesMap";

export const userLang = navigator.language;

export function getCountryCode(locale: string): string {
return locale.split("-").length >= 2
? locale.split("-")[1].toUpperCase()
: locale.toUpperCase();
}

export function getCurrencyCode(country: string): string {
const countryIsInMap =
currenciesMap[country as keyof typeof currenciesMap] !== undefined;

if (countryIsInMap) {
return currenciesMap[
country as keyof typeof currenciesMap
] as unknown as string;
}
return "USD";
}

export const countryCode = getCountryCode(userLang);
export const initialCurrencyCode = getCurrencyCode(countryCode);

export function roundBig(number: Big, precision: number): number {
return Big(number).round(precision, 1).toNumber();
Expand Down Expand Up @@ -71,7 +47,7 @@ export function calc(
}

export function intlFormat(amount: number, currencyCode: string) {
return new Intl.NumberFormat(userLang, {
return new Intl.NumberFormat(navigator.language, {
style: "currency",
currency: currencyCode,
currencyDisplay: "symbol",
Expand Down

0 comments on commit 9cebd95

Please sign in to comment.