@actual-app/core 26.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { NumberFormats } from './util';
|
|
2
|
+
|
|
3
|
+
export type Currency = {
|
|
4
|
+
code: string;
|
|
5
|
+
symbol: string;
|
|
6
|
+
name: string;
|
|
7
|
+
decimalPlaces: number;
|
|
8
|
+
numberFormat: NumberFormats;
|
|
9
|
+
symbolFirst: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// When adding a new currency with a higher decimal precision, make sure to update
|
|
13
|
+
// the MAX_SAFE_NUMBER in util.ts.
|
|
14
|
+
// When adding a currency, also update the translation map in
|
|
15
|
+
// packages/desktop-client/src/components/settings/Currency.tsx for the translation.
|
|
16
|
+
// Number formats and symbol placement based on CLDR (Common Locale Data Repository) /
|
|
17
|
+
// LDML (Locale Data Markup Language) locale conventions and Intl.NumberFormat standards
|
|
18
|
+
// References:
|
|
19
|
+
// https://www.localeplanet.com/icu/decimal-symbols.html
|
|
20
|
+
// https://www.localeplanet.com/api/auto/currencymap.html
|
|
21
|
+
// prettier-ignore
|
|
22
|
+
export const currencies: Currency[] = [
|
|
23
|
+
{ code: '', name: 'None', symbol: '', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
24
|
+
{ code: 'AED', name: 'UAE Dirham', symbol: 'د.إ', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: false },
|
|
25
|
+
{ code: 'ARS', name: 'Argentinian Peso', symbol: 'Arg$', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: true },
|
|
26
|
+
{ code: 'AUD', name: 'Australian Dollar', symbol: 'A$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
27
|
+
{ code: 'BRL', name: 'Brazilian Real', symbol: 'R$', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: true },
|
|
28
|
+
{ code: 'BYN', name: 'Belarusian Ruble', symbol: 'Br', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
29
|
+
{ code: 'CAD', name: 'Canadian Dollar', symbol: 'CA$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
30
|
+
{ code: 'CHF', name: 'Swiss Franc', symbol: 'Fr.', decimalPlaces: 2, numberFormat: 'apostrophe-dot', symbolFirst: true },
|
|
31
|
+
{ code: 'CNY', name: 'Yuan Renminbi', symbol: '¥', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
32
|
+
{ code: 'COP', name: 'Colombian Peso', symbol: 'Col$', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: true },
|
|
33
|
+
{ code: 'CRC', name: 'Costa Rican Colón', symbol: '₡', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: true },
|
|
34
|
+
{ code: 'CZK', name: 'Czech Koruna', symbol: 'Kč', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
35
|
+
{ code: 'DKK', name: 'Danish Krone', symbol: 'kr', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: false },
|
|
36
|
+
{ code: 'DOP', name: 'Dominican Peso', symbol: 'RD$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
37
|
+
{ code: 'EGP', name: 'Egyptian Pound', symbol: 'ج.م', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: false },
|
|
38
|
+
{ code: 'EUR', name: 'Euro', symbol: '€', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: false },
|
|
39
|
+
{ code: 'GBP', name: 'Pound Sterling', symbol: '£', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
40
|
+
{ code: 'GTQ', name: 'Guatemalan Quetzal', symbol: 'Q', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
41
|
+
{ code: 'HKD', name: 'Hong Kong Dollar', symbol: 'HK$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
42
|
+
{ code: 'HUF', name: 'Hungarian Forint', symbol: 'Ft', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
43
|
+
{ code: 'IDR', name: 'Indonesian Rupiah', symbol: 'Rp', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: true },
|
|
44
|
+
{ code: 'INR', name: 'Indian Rupee', symbol: '₹', decimalPlaces: 2, numberFormat: 'comma-dot-in', symbolFirst: true },
|
|
45
|
+
{ code: 'JMD', name: 'Jamaican Dollar', symbol: 'J$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
46
|
+
{ code: 'JPY', name: 'Japanese Yen', symbol: '¥', decimalPlaces: 0, numberFormat: 'comma-dot', symbolFirst: true },
|
|
47
|
+
{ code: 'KRW', name: 'South Korean Won', symbol: '₩', decimalPlaces: 0, numberFormat: 'comma-dot', symbolFirst: true },
|
|
48
|
+
{ code: 'LKR', name: 'Sri Lankan Rupee', symbol: 'Rs.', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
49
|
+
{ code: 'MDL', name: 'Moldovan Leu', symbol: 'L', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: false },
|
|
50
|
+
{ code: 'MYR', name: 'Malaysian Ringgit', symbol: 'RM', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
51
|
+
{ code: 'PHP', name: 'Philippine Peso', symbol: '₱', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
52
|
+
{ code: 'PLN', name: 'Polish Złoty', symbol: 'zł', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
53
|
+
{ code: 'QAR', name: 'Qatari Riyal', symbol: 'ر.ق', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: false },
|
|
54
|
+
{ code: 'RON', name: 'Romanian Leu', symbol: 'lei', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: false },
|
|
55
|
+
{ code: 'RSD', name: 'Serbian Dinar', symbol: 'дин', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: false },
|
|
56
|
+
{ code: 'RUB', name: 'Russian Ruble', symbol: '₽', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
57
|
+
{ code: 'SAR', name: 'Saudi Riyal', symbol: 'ر.س', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: false },
|
|
58
|
+
{ code: 'SEK', name: 'Swedish Krona', symbol: 'kr', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
59
|
+
{ code: 'SGD', name: 'Singapore Dollar', symbol: 'S$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
60
|
+
{ code: 'THB', name: 'Thai Baht', symbol: '฿', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
61
|
+
{ code: 'TRY', name: 'Turkish Lira', symbol: '₺', decimalPlaces: 2, numberFormat: 'dot-comma', symbolFirst: true },
|
|
62
|
+
{ code: 'TWD', name: 'New Taiwan Dollar', symbol: 'NT$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
63
|
+
{ code: 'UAH', name: 'Ukrainian Hryvnia', symbol: '₴', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
64
|
+
{ code: 'USD', name: 'US Dollar', symbol: '$', decimalPlaces: 2, numberFormat: 'comma-dot', symbolFirst: true },
|
|
65
|
+
{ code: 'UZS', name: 'Uzbek Soum', symbol: 'UZS', decimalPlaces: 2, numberFormat: 'space-comma', symbolFirst: false },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export function getCurrency(code: string): Currency {
|
|
69
|
+
return currencies.find(c => c.code === code) || currencies[0];
|
|
70
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { NewDashboardWidgetEntity } from '../types/models';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_DASHBOARD_STATE: NewDashboardWidgetEntity[] = [
|
|
4
|
+
// Top row: Key metrics at a glance
|
|
5
|
+
{
|
|
6
|
+
type: 'summary-card',
|
|
7
|
+
width: 3,
|
|
8
|
+
height: 2,
|
|
9
|
+
x: 0,
|
|
10
|
+
y: 0,
|
|
11
|
+
meta: {
|
|
12
|
+
name: 'Total Income (YTD)',
|
|
13
|
+
content: JSON.stringify({
|
|
14
|
+
type: 'sum',
|
|
15
|
+
fontSize: 20,
|
|
16
|
+
}),
|
|
17
|
+
timeFrame: {
|
|
18
|
+
start: '2024-01-01',
|
|
19
|
+
end: '2024-12-31',
|
|
20
|
+
mode: 'yearToDate',
|
|
21
|
+
},
|
|
22
|
+
conditions: [
|
|
23
|
+
{
|
|
24
|
+
field: 'amount',
|
|
25
|
+
op: 'gt',
|
|
26
|
+
value: 0,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
field: 'account',
|
|
30
|
+
op: 'onBudget',
|
|
31
|
+
value: '',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
field: 'transfer',
|
|
35
|
+
op: 'is',
|
|
36
|
+
value: false,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
conditionsOp: 'and',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'summary-card',
|
|
44
|
+
width: 3,
|
|
45
|
+
height: 2,
|
|
46
|
+
x: 3,
|
|
47
|
+
y: 0,
|
|
48
|
+
meta: {
|
|
49
|
+
name: 'Total Expenses (YTD)',
|
|
50
|
+
content: JSON.stringify({
|
|
51
|
+
type: 'sum',
|
|
52
|
+
fontSize: 20,
|
|
53
|
+
}),
|
|
54
|
+
timeFrame: {
|
|
55
|
+
start: '2024-01-01',
|
|
56
|
+
end: '2024-12-31',
|
|
57
|
+
mode: 'yearToDate',
|
|
58
|
+
},
|
|
59
|
+
conditions: [
|
|
60
|
+
{
|
|
61
|
+
field: 'amount',
|
|
62
|
+
op: 'lt',
|
|
63
|
+
value: 0,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
field: 'account',
|
|
67
|
+
op: 'onBudget',
|
|
68
|
+
value: '',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
field: 'transfer',
|
|
72
|
+
op: 'is',
|
|
73
|
+
value: false,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
conditionsOp: 'and',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'summary-card',
|
|
81
|
+
width: 3,
|
|
82
|
+
height: 2,
|
|
83
|
+
x: 6,
|
|
84
|
+
y: 0,
|
|
85
|
+
meta: {
|
|
86
|
+
name: 'Avg Per Month',
|
|
87
|
+
content: JSON.stringify({
|
|
88
|
+
type: 'avgPerMonth',
|
|
89
|
+
fontSize: 20,
|
|
90
|
+
}),
|
|
91
|
+
timeFrame: {
|
|
92
|
+
start: '2024-01-01',
|
|
93
|
+
end: '2024-12-31',
|
|
94
|
+
mode: 'yearToDate',
|
|
95
|
+
},
|
|
96
|
+
conditions: [
|
|
97
|
+
{
|
|
98
|
+
field: 'amount',
|
|
99
|
+
op: 'lt',
|
|
100
|
+
value: 0,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
field: 'account',
|
|
104
|
+
op: 'onBudget',
|
|
105
|
+
value: '',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
field: 'transfer',
|
|
109
|
+
op: 'is',
|
|
110
|
+
value: false,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
conditionsOp: 'and',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: 'summary-card',
|
|
118
|
+
width: 3,
|
|
119
|
+
height: 2,
|
|
120
|
+
x: 9,
|
|
121
|
+
y: 0,
|
|
122
|
+
meta: {
|
|
123
|
+
name: 'Avg Per Transaction',
|
|
124
|
+
content: JSON.stringify({
|
|
125
|
+
type: 'avgPerTransact',
|
|
126
|
+
fontSize: 20,
|
|
127
|
+
}),
|
|
128
|
+
timeFrame: {
|
|
129
|
+
start: '2024-01-01',
|
|
130
|
+
end: '2024-12-31',
|
|
131
|
+
mode: 'yearToDate',
|
|
132
|
+
},
|
|
133
|
+
conditions: [
|
|
134
|
+
{
|
|
135
|
+
field: 'amount',
|
|
136
|
+
op: 'lt',
|
|
137
|
+
value: 0,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
field: 'account',
|
|
141
|
+
op: 'onBudget',
|
|
142
|
+
value: '',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
field: 'transfer',
|
|
146
|
+
op: 'is',
|
|
147
|
+
value: false,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
conditionsOp: 'and',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
// Second row: Net worth and cash flow side by side
|
|
154
|
+
{
|
|
155
|
+
type: 'net-worth-card',
|
|
156
|
+
width: 6,
|
|
157
|
+
height: 2,
|
|
158
|
+
x: 0,
|
|
159
|
+
y: 2,
|
|
160
|
+
meta: null,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'cash-flow-card',
|
|
164
|
+
width: 6,
|
|
165
|
+
height: 2,
|
|
166
|
+
x: 6,
|
|
167
|
+
y: 2,
|
|
168
|
+
meta: null,
|
|
169
|
+
},
|
|
170
|
+
// Third row: Spending comparisons
|
|
171
|
+
{
|
|
172
|
+
type: 'spending-card',
|
|
173
|
+
width: 4,
|
|
174
|
+
height: 2,
|
|
175
|
+
x: 0,
|
|
176
|
+
y: 5,
|
|
177
|
+
meta: {
|
|
178
|
+
name: 'This Month',
|
|
179
|
+
mode: 'single-month',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'spending-card',
|
|
184
|
+
width: 4,
|
|
185
|
+
height: 2,
|
|
186
|
+
x: 4,
|
|
187
|
+
y: 5,
|
|
188
|
+
meta: {
|
|
189
|
+
name: 'Budget Overview',
|
|
190
|
+
mode: 'budget',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'spending-card',
|
|
195
|
+
width: 4,
|
|
196
|
+
height: 2,
|
|
197
|
+
x: 8,
|
|
198
|
+
y: 5,
|
|
199
|
+
meta: {
|
|
200
|
+
name: '3-Month Average',
|
|
201
|
+
mode: 'average',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
// Fourth row: Calendar and savings rate
|
|
205
|
+
{
|
|
206
|
+
type: 'calendar-card',
|
|
207
|
+
width: 8,
|
|
208
|
+
height: 4,
|
|
209
|
+
x: 0,
|
|
210
|
+
y: 8,
|
|
211
|
+
meta: {
|
|
212
|
+
name: 'Transaction Calendar',
|
|
213
|
+
timeFrame: {
|
|
214
|
+
start: '2024-01-01',
|
|
215
|
+
end: '2024-03-31',
|
|
216
|
+
mode: 'sliding-window',
|
|
217
|
+
},
|
|
218
|
+
conditions: [
|
|
219
|
+
{
|
|
220
|
+
field: 'transfer',
|
|
221
|
+
op: 'is',
|
|
222
|
+
value: false,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
conditionsOp: 'and',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'summary-card',
|
|
230
|
+
width: 4,
|
|
231
|
+
height: 2,
|
|
232
|
+
x: 8,
|
|
233
|
+
y: 8,
|
|
234
|
+
meta: {
|
|
235
|
+
name: 'Recent Net Worth Change',
|
|
236
|
+
content: JSON.stringify({
|
|
237
|
+
type: 'sum',
|
|
238
|
+
fontSize: 32,
|
|
239
|
+
}),
|
|
240
|
+
timeFrame: {
|
|
241
|
+
start: '2024-01-01',
|
|
242
|
+
end: '2024-03-31',
|
|
243
|
+
mode: 'sliding-window',
|
|
244
|
+
},
|
|
245
|
+
conditions: [],
|
|
246
|
+
conditionsOp: 'and',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
type: 'markdown-card',
|
|
251
|
+
width: 4,
|
|
252
|
+
height: 2,
|
|
253
|
+
x: 8,
|
|
254
|
+
y: 10,
|
|
255
|
+
meta: {
|
|
256
|
+
content:
|
|
257
|
+
'## Dashboard Tips\n\nYou can add new widgets or edit existing widgets by using the buttons at the top of the page. Choose a widget type and customize it to fit your needs.\n\n**Moving cards:** Drag any card by its header to reposition it.\n\n**Deleting cards:** Click the three-dot menu on any card and select "Remove".',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function isPreviewEnvironment() {
|
|
2
|
+
return String(process.env.REACT_APP_NETLIFY) === 'true';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function isDevelopmentEnvironment() {
|
|
6
|
+
return process.env.NODE_ENV === 'development';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isNonProductionEnvironment() {
|
|
10
|
+
return isPreviewEnvironment() || isDevelopmentEnvironment();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isElectron() {
|
|
14
|
+
if (navigator.userAgent.indexOf('Electron') >= 0) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { t } from 'i18next';
|
|
2
|
+
|
|
3
|
+
type ErrorWithMeta = {
|
|
4
|
+
reason: string;
|
|
5
|
+
meta?: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function getUploadError({ reason, meta }: ErrorWithMeta) {
|
|
9
|
+
switch (reason) {
|
|
10
|
+
case 'unauthorized':
|
|
11
|
+
return t('You are not logged in.');
|
|
12
|
+
case 'encrypt-failure':
|
|
13
|
+
if ((meta as { isMissingKey: boolean }).isMissingKey) {
|
|
14
|
+
return t(
|
|
15
|
+
'Encrypting your file failed because you are missing your encryption key. Create your key in the next step.',
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return t(
|
|
19
|
+
'Encrypting the file failed. You have the correct key so this is an internal bug. To fix this, generate a new key in the next step.',
|
|
20
|
+
);
|
|
21
|
+
case 'file-has-reset':
|
|
22
|
+
// Something really weird happened - during reset a sanity
|
|
23
|
+
// check on the server failed. The user just needs to
|
|
24
|
+
// restart the whole process.
|
|
25
|
+
return t(
|
|
26
|
+
'Something went wrong while resetting your file. Please try again.',
|
|
27
|
+
);
|
|
28
|
+
case 'file-has-new-key':
|
|
29
|
+
return t(
|
|
30
|
+
'Unable to encrypt your data because you are missing the key. Create the latest key in the next step.',
|
|
31
|
+
);
|
|
32
|
+
case 'network':
|
|
33
|
+
return t('Uploading the file failed. Check your network connection.');
|
|
34
|
+
default:
|
|
35
|
+
return t(
|
|
36
|
+
'An internal error occurred, sorry! Visit https://actualbudget.org/contact/ for support. (ref: {{reason}})',
|
|
37
|
+
{ reason },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getDownloadError({
|
|
43
|
+
reason,
|
|
44
|
+
meta,
|
|
45
|
+
fileName,
|
|
46
|
+
}: {
|
|
47
|
+
reason: string;
|
|
48
|
+
meta?: unknown;
|
|
49
|
+
fileName?: string;
|
|
50
|
+
}) {
|
|
51
|
+
switch (reason) {
|
|
52
|
+
case 'network':
|
|
53
|
+
case 'download-failure':
|
|
54
|
+
return t('Downloading the file failed. Check your network connection.');
|
|
55
|
+
case 'not-zip-file':
|
|
56
|
+
case 'invalid-zip-file':
|
|
57
|
+
case 'invalid-meta-file':
|
|
58
|
+
return t(
|
|
59
|
+
'Downloaded file is invalid, sorry! Visit https://actualbudget.org/contact/ for support.',
|
|
60
|
+
);
|
|
61
|
+
case 'decrypt-failure':
|
|
62
|
+
return (
|
|
63
|
+
'Unable to decrypt file ' +
|
|
64
|
+
(fileName || '(unknown)') +
|
|
65
|
+
'. To change your key, first ' +
|
|
66
|
+
'download this file with the proper password.'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
case 'out-of-sync-migrations':
|
|
70
|
+
return t(
|
|
71
|
+
'This budget cannot be loaded with this version of the app. Make sure the app is up-to-date.',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
case 'clock-drift':
|
|
75
|
+
return t(
|
|
76
|
+
'Failed to download the budget because your device time differs too much from the server. Please check your device time settings and ensure they are correct.',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
const info =
|
|
81
|
+
meta && typeof meta === 'object' && 'fileId' in meta && meta.fileId
|
|
82
|
+
? `, fileId: ${String(meta.fileId)}`
|
|
83
|
+
: '';
|
|
84
|
+
return t(
|
|
85
|
+
'Something went wrong trying to download that file, sorry! Visit https://actualbudget.org/contact/ for support. reason: {{reason}}{{info}}',
|
|
86
|
+
{ reason, info },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getCreateKeyError(error: ErrorWithMeta) {
|
|
92
|
+
return getUploadError(error);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getTestKeyError({ reason }: ErrorWithMeta) {
|
|
96
|
+
switch (reason) {
|
|
97
|
+
case 'network':
|
|
98
|
+
return t(
|
|
99
|
+
'Unable to connect to the server. We need to access the server to get some information about your keys.',
|
|
100
|
+
);
|
|
101
|
+
case 'old-key-style':
|
|
102
|
+
return t(
|
|
103
|
+
'This file is encrypted with an old unsupported key style. Recreate the key on a device where the file is available, or use an older version of Actual to download it.',
|
|
104
|
+
);
|
|
105
|
+
case 'decrypt-failure':
|
|
106
|
+
return t('Unable to decrypt file with this password. Please try again.');
|
|
107
|
+
default:
|
|
108
|
+
return t(
|
|
109
|
+
'Something went wrong trying to create a key, sorry! Visit https://actualbudget.org/contact/ for support.',
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getSyncError(error: string, id: string) {
|
|
115
|
+
if (error === 'out-of-sync-migrations' || error === 'out-of-sync-data') {
|
|
116
|
+
return t('This budget cannot be loaded with this version of the app.');
|
|
117
|
+
} else if (error === 'budget-not-found') {
|
|
118
|
+
return t(
|
|
119
|
+
'Budget "{{id}}" not found. Check the ID of your budget in the Advanced section of the settings page.',
|
|
120
|
+
{ id },
|
|
121
|
+
);
|
|
122
|
+
} else if (error === 'clock-drift') {
|
|
123
|
+
return t(
|
|
124
|
+
'Failed to sync because your device time differs too much from the server. Please check your device time settings and ensure they are correct.',
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
return t('We had an unknown problem opening "{{id}}".', { id });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getBankSyncError(error: { message?: string }) {
|
|
132
|
+
return error.message || t('We had an unknown problem syncing the account.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class LazyLoadFailedError extends Error {
|
|
136
|
+
type = 'app-init-failure';
|
|
137
|
+
meta = {};
|
|
138
|
+
|
|
139
|
+
constructor(name: string, cause: unknown) {
|
|
140
|
+
super(`Error: failed loading lazy-loaded module ${name}`);
|
|
141
|
+
this.meta = { name };
|
|
142
|
+
this.cause = cause;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getUserAccessErrors(reason: string) {
|
|
147
|
+
switch (reason) {
|
|
148
|
+
case 'unauthorized':
|
|
149
|
+
return t('You are not logged in.');
|
|
150
|
+
case 'token-expired':
|
|
151
|
+
return t('Login expired, please log in again.');
|
|
152
|
+
case 'user-cant-be-empty':
|
|
153
|
+
return t('Please select a user.');
|
|
154
|
+
case 'invalid-file-id':
|
|
155
|
+
return t('This file is invalid.');
|
|
156
|
+
case 'file-denied':
|
|
157
|
+
return t('You don`t have permissions over this file.');
|
|
158
|
+
case 'user-already-have-access':
|
|
159
|
+
return t('User already has access.');
|
|
160
|
+
default:
|
|
161
|
+
return t(
|
|
162
|
+
'An internal error occurred, sorry! Visit https://actualbudget.org/contact/ for support. (ref: {{reason}})',
|
|
163
|
+
{ reason },
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getSecretsError(error: string, reason: string) {
|
|
169
|
+
switch (reason) {
|
|
170
|
+
case 'unauthorized':
|
|
171
|
+
return t('You are not logged in.');
|
|
172
|
+
case 'not-admin':
|
|
173
|
+
return t('You have to be admin to set secrets');
|
|
174
|
+
default:
|
|
175
|
+
return error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getOpenIdErrors(reason: string) {
|
|
180
|
+
switch (reason) {
|
|
181
|
+
case 'unauthorized':
|
|
182
|
+
return t('You are not logged in.');
|
|
183
|
+
case 'configuration-error':
|
|
184
|
+
return t('This configuration is not valid. Please check it again.');
|
|
185
|
+
case 'unable-to-change-file-config-enabled':
|
|
186
|
+
return t(
|
|
187
|
+
'Unable to enable OpenID. Please update the config.json file in this case.',
|
|
188
|
+
);
|
|
189
|
+
default:
|
|
190
|
+
return t(
|
|
191
|
+
'An internal error occurred, sorry! Visit https://actualbudget.org/contact/ for support. (ref: {{reason}})',
|
|
192
|
+
{ reason },
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as localesNamespace from 'date-fns/locale';
|
|
2
|
+
|
|
3
|
+
// Spread into plain object to allow dynamic access without ESLint namespace warnings
|
|
4
|
+
const locales: Record<string, localesNamespace.Locale> = {
|
|
5
|
+
...localesNamespace,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function getLocale(language: string) {
|
|
9
|
+
if (!language || typeof language !== 'string') {
|
|
10
|
+
return locales.enUS;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let localeKey = language.replace('-', '');
|
|
14
|
+
|
|
15
|
+
if (localeKey in locales) {
|
|
16
|
+
return locales[localeKey];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//if language was not found with four letters, try with two
|
|
20
|
+
localeKey = language.replace('-', '').substring(0, 2);
|
|
21
|
+
|
|
22
|
+
if (localeKey in locales) {
|
|
23
|
+
return locales[localeKey];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return locales.enUS;
|
|
27
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { calculateDistance, formatDistance } from './location-utils';
|
|
4
|
+
|
|
5
|
+
describe('Location Utils', () => {
|
|
6
|
+
describe('calculateDistance', () => {
|
|
7
|
+
it('calculates distance between same location as 0', () => {
|
|
8
|
+
const pos = { latitude: 40.7128, longitude: -74.006 };
|
|
9
|
+
const distance = calculateDistance(pos, pos);
|
|
10
|
+
expect(distance).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('calculates distance between known coordinates accurately', () => {
|
|
14
|
+
// NYC to Philadelphia (approximately 129 km)
|
|
15
|
+
const nyc = { latitude: 40.7128, longitude: -74.006 };
|
|
16
|
+
const philly = { latitude: 39.9526, longitude: -75.1652 };
|
|
17
|
+
|
|
18
|
+
const distance = calculateDistance(nyc, philly);
|
|
19
|
+
// Should be approximately 129,000meters (allow 5% variance for rounding)
|
|
20
|
+
expect(distance).toBeGreaterThan(122000);
|
|
21
|
+
expect(distance).toBeLessThan(136000);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('calculates short distances accurately', () => {
|
|
25
|
+
// Two points very close together (about 100m apart in NYC)
|
|
26
|
+
const pos1 = { latitude: 40.7128, longitude: -74.006 };
|
|
27
|
+
const pos2 = { latitude: 40.7137, longitude: -74.0068 }; // ~100m north
|
|
28
|
+
|
|
29
|
+
const distance = calculateDistance(pos1, pos2);
|
|
30
|
+
// Should be approximately 100meters (allow reasonable variance for coord precision)
|
|
31
|
+
expect(distance).toBeGreaterThan(90);
|
|
32
|
+
expect(distance).toBeLessThan(130);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles cross-equator distances', () => {
|
|
36
|
+
const northPole = { latitude: 89.0, longitude: 0 };
|
|
37
|
+
const southPole = { latitude: -89.0, longitude: 0 };
|
|
38
|
+
|
|
39
|
+
const distance = calculateDistance(northPole, southPole);
|
|
40
|
+
// Should be very large (close to half Earth's circumference)
|
|
41
|
+
expect(distance).toBeGreaterThan(19000000); // > 19,000 km
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('handles cross-meridian distances', () => {
|
|
45
|
+
const nearAntimeridianEast = { latitude: 51.5074, longitude: 179 };
|
|
46
|
+
const nearAntimeridianWest = { latitude: 51.5074, longitude: -179 };
|
|
47
|
+
|
|
48
|
+
const distance = calculateDistance(
|
|
49
|
+
nearAntimeridianEast,
|
|
50
|
+
nearAntimeridianWest,
|
|
51
|
+
);
|
|
52
|
+
// Should be a reasonable distance, not the long way around
|
|
53
|
+
expect(distance).toBeGreaterThan(0);
|
|
54
|
+
expect(distance).toBeLessThan(1000000); // < 1000 km
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('formatDistance', () => {
|
|
59
|
+
it('formats feet/meters correctly', () => {
|
|
60
|
+
expect(formatDistance(0)).toBe('0ft | 0m');
|
|
61
|
+
expect(formatDistance(0.9)).toBe('3ft | 1m');
|
|
62
|
+
expect(formatDistance(50)).toBe('164ft | 50m');
|
|
63
|
+
expect(formatDistance(500)).toBe('1640ft | 500m');
|
|
64
|
+
expect(formatDistance(1000)).toBe('3281ft | 1000m');
|
|
65
|
+
expect(formatDistance(1500)).toBe('4921ft | 1500m');
|
|
66
|
+
expect(formatDistance(2500)).toBe('8202ft | 2500m');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions for location calculations and formatting.
|
|
3
|
+
* These functions have no side effects and can be easily tested.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const metersToFeet = 3.28084;
|
|
7
|
+
|
|
8
|
+
export type LocationCoordinates = {
|
|
9
|
+
latitude: number;
|
|
10
|
+
longitude: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calculate the distance between two geographic coordinates using the Haversine formula
|
|
15
|
+
* @param pos1 First position coordinates
|
|
16
|
+
* @param pos2 Second position coordinates
|
|
17
|
+
* @returns Distance in meters
|
|
18
|
+
*/
|
|
19
|
+
export function calculateDistance(
|
|
20
|
+
pos1: LocationCoordinates,
|
|
21
|
+
pos2: LocationCoordinates,
|
|
22
|
+
): number {
|
|
23
|
+
const R = 6371e3; // Earth's radius in meters
|
|
24
|
+
const phi1 = (pos1.latitude * Math.PI) / 180;
|
|
25
|
+
const phi2 = (pos2.latitude * Math.PI) / 180;
|
|
26
|
+
const deltaPhi = ((pos2.latitude - pos1.latitude) * Math.PI) / 180;
|
|
27
|
+
const deltaLambda = ((pos2.longitude - pos1.longitude) * Math.PI) / 180;
|
|
28
|
+
|
|
29
|
+
const a =
|
|
30
|
+
Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +
|
|
31
|
+
Math.cos(phi1) *
|
|
32
|
+
Math.cos(phi2) *
|
|
33
|
+
Math.sin(deltaLambda / 2) *
|
|
34
|
+
Math.sin(deltaLambda / 2);
|
|
35
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
36
|
+
|
|
37
|
+
return R * c; // Distance in meters
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format a distance in meters to a human-readable string
|
|
42
|
+
* @param meters Distance in meters
|
|
43
|
+
* @returns Formatted distance string
|
|
44
|
+
*/
|
|
45
|
+
export function formatDistance(meters: number): string {
|
|
46
|
+
const distanceImperial = `${Math.round(meters * metersToFeet)}ft`;
|
|
47
|
+
const distanceMetric = `${Math.round(meters)}m`;
|
|
48
|
+
return `${distanceImperial} | ${distanceMetric}`;
|
|
49
|
+
}
|