Compare commits
93 Commits
accounts-f
...
master-bef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee70e7f1f | ||
|
|
5f1fadb7cc | ||
|
|
c4ad24edde | ||
|
|
f5b9a5b23d | ||
|
|
15298703ac | ||
|
|
7096d00fc6 | ||
|
|
e8b4a750ed | ||
|
|
4a7b0e7365 | ||
|
|
f1da358186 | ||
|
|
a23a28522f | ||
|
|
6a5de96033 | ||
|
|
bd063423e5 | ||
|
|
bdf4dda3a8 | ||
|
|
0c1c8e6adb | ||
|
|
ed91fb1ef4 | ||
|
|
b14c77aed7 | ||
|
|
64df0e107c | ||
|
|
9b59333563 | ||
|
|
b09d800e40 | ||
|
|
d1c3b9bab1 | ||
|
|
663f830cc9 | ||
|
|
9dcd22962c | ||
|
|
f09f4af667 | ||
|
|
1f5e5d41a4 | ||
|
|
46f04f5d4c | ||
|
|
caaa801d24 | ||
|
|
5448a5c264 | ||
|
|
977657a0be | ||
|
|
2f8b839036 | ||
|
|
1cf64f87ab | ||
|
|
012cfd09ea | ||
|
|
39361e5b62 | ||
|
|
f0c81eebbf | ||
|
|
a84af23e7e | ||
|
|
1442662eb7 | ||
|
|
4850034e6f | ||
|
|
90dc050102 | ||
|
|
7791b7401e | ||
|
|
a97471557b | ||
|
|
dd2b0a8bd5 | ||
|
|
6cbf3e33e6 | ||
|
|
cdbf3e06c1 | ||
|
|
1f2c6541b8 | ||
|
|
e70dc4efb0 | ||
|
|
bbebf71378 | ||
|
|
f35c5a0ed9 | ||
|
|
9d63b23463 | ||
|
|
705985a8df | ||
|
|
eb31071043 | ||
|
|
91c4e3e067 | ||
|
|
7cb53502b8 | ||
|
|
87c26042b9 | ||
|
|
6070166f4e | ||
|
|
66619fa20d | ||
|
|
5e8a24f283 | ||
|
|
278e4ad74f | ||
|
|
c347653566 | ||
|
|
c4593f3be9 | ||
|
|
99724f611c | ||
|
|
f07ad1f8c6 | ||
|
|
4bfb64cdfc | ||
|
|
626e7973ac | ||
|
|
7ae6442296 | ||
|
|
774402503f | ||
|
|
cf360ad398 | ||
|
|
2a275b3821 | ||
|
|
87428a7b65 | ||
|
|
6655f51ccc | ||
|
|
ceeef91a45 | ||
|
|
b831d15eab | ||
|
|
26907d3b12 | ||
|
|
b9eaeafc1c | ||
|
|
b1627d7073 | ||
|
|
5fc3e2ea47 | ||
|
|
97b28ca375 | ||
|
|
aa529a2cf1 | ||
|
|
b92fa709eb | ||
|
|
5d91d29d77 | ||
|
|
61d41cc28a | ||
|
|
5921a35340 | ||
|
|
6573a52411 | ||
|
|
bec841932d | ||
|
|
629b001c01 | ||
|
|
a1be1d43f6 | ||
|
|
1c6697a7ee | ||
|
|
da13dfa570 | ||
|
|
6bcccaa943 | ||
|
|
5a34c06859 | ||
|
|
92c93b3f6e | ||
|
|
34ffc5c4b2 | ||
|
|
14b0cd7b1d | ||
|
|
daca767808 | ||
|
|
6111f94b51 |
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -12,7 +12,7 @@ body:
|
||||
id: intro-md
|
||||
attributes:
|
||||
value: |
|
||||
**IMPORTANT:** we use Github Issues only for BUG REPORTS and FEATURE REQUESTS. If you are looking for help/support - please reach out to the [community on Discord](https://discord.gg/pRYNYr4W5A). All non-bug and non-feature-request issues will be closed.
|
||||
**IMPORTANT:** we use GitHub Issues only for BUG REPORTS and FEATURE REQUESTS. If you are looking for help/support - please reach out to the [community on Discord](https://discord.gg/pRYNYr4W5A). All non-bug and non-feature-request issues will be closed.
|
||||
|
||||
**Bank-sync problems (SimpleFin / GoCardless)?** Reach out via the [community Discord](https://discord.gg/pRYNYr4W5A) first and open an issue only if the community deems the issue to be a legitimate bug in Actual.
|
||||
- type: checkboxes
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -6,3 +6,6 @@ contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/pRYNYr4W5A
|
||||
about: Need help with something? Having troubles setting up? Or perhaps issues using the API? Reach out to the community on Discord.
|
||||
- name: Translations
|
||||
url: https://hosted.weblate.org/projects/actualbudget/actual/
|
||||
about: Found a string that needs a better translation? Add your suggestion or upvote an existing one in Weblate.
|
||||
|
||||
26
.github/actions/bump-package-versions
vendored
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
version="${1#v}"
|
||||
|
||||
files_to_bump=(
|
||||
packages/api/package.json
|
||||
packages/desktop-client/package.json
|
||||
packages/desktop-electron/package.json
|
||||
)
|
||||
|
||||
for file in "${files_to_bump[@]}"; do
|
||||
if [ -z "$version" ]; then
|
||||
# version format: YY.MM.patch
|
||||
# logic: if before the 25th, bump patch, else set minor/major to next month
|
||||
version="$(jq -r .version "$file" | perl -e '($y,$m,$p)=split/\./,<>;$d=(localtime)[3];$d>25?($p=0,++$m,$m>12&&($m=1,++$y)):$p++;print"$y.$m.$p\n"')"
|
||||
if [ -z "$version" ]; then
|
||||
echo "Error: Failed to calculate new version" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Bumping $file to version $version"
|
||||
jq '.version = "'"$version"'"' "$file" > "$file.tmp"
|
||||
mv "$file.tmp" "$file"
|
||||
done
|
||||
25
.github/actions/setup/action.yml
vendored
@@ -1,5 +1,15 @@
|
||||
name: Setup
|
||||
|
||||
inputs:
|
||||
working-directory:
|
||||
description: 'Working directory to run in, default .'
|
||||
required: false
|
||||
default: '.'
|
||||
download-translations:
|
||||
description: 'Whether to download translations as part of setup, default true'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -15,9 +25,20 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: yarn-v1-${{ runner.os }}-${{ hashFiles('.nvmrc') }}-${{ hashFiles('**/yarn.lock') }}
|
||||
path: ${{ format('{0}/**/node_modules', inputs.working-directory) }}
|
||||
key: yarn-v1-${{ runner.os }}-${{ hashFiles(format('{0}/.nvmrc', inputs.working-directory)) }}-${{ hashFiles(format('{0}/**/yarn.lock', inputs.working-directory)) }}
|
||||
- name: Install
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: yarn --immutable
|
||||
shell: bash
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Download translations
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: actualbudget/translations
|
||||
path: ${{ inputs.working-directory }}/packages/desktop-client/locale
|
||||
if: ${{ inputs.download-translations == 'true' }}
|
||||
- name: Remove untranslated languages
|
||||
run: packages/desktop-client/bin/remove-untranslated-languages
|
||||
shell: bash
|
||||
if: ${{ inputs.download-translations == 'true' }}
|
||||
|
||||
35
.github/workflows/generate-release-pr.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Generate release PR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Commit or branch to release'
|
||||
required: true
|
||||
default: 'master'
|
||||
version:
|
||||
description: 'Version number for the release (optional)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
generate-release-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
- name: Bump package versions
|
||||
id: bump_package_versions
|
||||
shell: bash
|
||||
run: |
|
||||
.github/actions/bump-package-versions ${{ github.event.inputs.version }}
|
||||
echo "version=$(jq -r .version packages/desktop-client/package.json)" > $GITHUB_OUTPUT
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
|
||||
title: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
|
||||
body: 'Generated by [generate-release-pr.yml](../tree/master/.github/workflows/generate-release-pr.yml)'
|
||||
branch: 'release/v${{ steps.bump_package_versions.outputs.version }}'
|
||||
105
.github/workflows/i18n-string-extract-master.yml
vendored
@@ -1,36 +1,87 @@
|
||||
name: Extract and upload i18n strings
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
# 4am UTC
|
||||
- cron: "0 4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
extract-and-upload-i18n-strings:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'actualbudget/actual'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Configure i18n client
|
||||
run: |
|
||||
pip install wlc
|
||||
- name: Generate i18n strings
|
||||
run: yarn generate:i18n
|
||||
- name: Upload i18n strings
|
||||
run: |
|
||||
if [[ ! -f packages/desktop-client/locale/en.json ]]; then
|
||||
echo "File packages/desktop-client/locale/en.json not found. Ensure the file was generated correctly."
|
||||
exit 1
|
||||
fi
|
||||
wlc \
|
||||
--url https://hosted.weblate.org/api/ \
|
||||
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
|
||||
upload \
|
||||
--author-name "Actual Budget" \
|
||||
--author-email "dev@actualbudget.org" \
|
||||
--method add \
|
||||
--input packages/desktop-client/locale/en.json \
|
||||
actualbudget/actual/en
|
||||
echo "Translations uploaded"
|
||||
- name: Check out main repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: actual
|
||||
- name: Set up environment
|
||||
uses: ./actual/.github/actions/setup
|
||||
with:
|
||||
working-directory: actual
|
||||
download-translations: false # As we'll manually clone instead
|
||||
- name: Configure Git config
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
- name: Configure i18n client
|
||||
run: |
|
||||
pip install wlc
|
||||
|
||||
- name: Lock translations
|
||||
run: |
|
||||
wlc \
|
||||
--url https://hosted.weblate.org/api/ \
|
||||
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
|
||||
lock \
|
||||
actualbudget/actual
|
||||
|
||||
- name: Update VCS with latest translations
|
||||
run: |
|
||||
wlc \
|
||||
--url https://hosted.weblate.org/api/ \
|
||||
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
|
||||
push \
|
||||
actualbudget/actual
|
||||
- name: Check out updated translations
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.STRING_IMPORT_DEPLOY_KEY }}
|
||||
repository: actualbudget/translations
|
||||
path: translations
|
||||
- name: Generate i18n strings
|
||||
working-directory: actual
|
||||
run: |
|
||||
mkdir -p packages/desktop-client/locale/
|
||||
cp ../translations/en.json packages/desktop-client/locale/
|
||||
yarn generate:i18n
|
||||
if [[ ! -f packages/desktop-client/locale/en.json ]]; then
|
||||
echo "File packages/desktop-client/locale/en.json not found. Ensure the file was generated correctly."
|
||||
exit 1
|
||||
fi
|
||||
- name: Check in new i18n strings
|
||||
working-directory: translations
|
||||
run: |
|
||||
cp ../actual/packages/desktop-client/locale/en.json .
|
||||
git add .
|
||||
if git commit -m "Update source strings"; then
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
- name: Update Weblate with latest translations
|
||||
run: |
|
||||
wlc \
|
||||
--url https://hosted.weblate.org/api/ \
|
||||
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
|
||||
pull \
|
||||
actualbudget/actual
|
||||
|
||||
- name: Unlock translations
|
||||
if: always() # Clean up even on failure
|
||||
run: |
|
||||
wlc \
|
||||
--url https://hosted.weblate.org/api/ \
|
||||
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
|
||||
unlock \
|
||||
actualbudget/actual
|
||||
|
||||
4
.github/workflows/update-vrt.yml
vendored
@@ -79,6 +79,8 @@ jobs:
|
||||
with:
|
||||
name: patch
|
||||
- name: Apply patch and push
|
||||
env:
|
||||
BRANCH_NAME: ${{ steps.comment-branch.outputs.head_ref }}
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
@@ -89,7 +91,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "Update VRT"
|
||||
git push origin HEAD:${{ steps.comment-branch.outputs.head_ref }}
|
||||
git push origin HEAD:${BRANCH_NAME}
|
||||
- name: Add finished reaction
|
||||
uses: dkershner6/reaction-action@v2
|
||||
with:
|
||||
|
||||
@@ -2,6 +2,8 @@ compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableTransparentWorkspaces: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
|
||||
@@ -4,6 +4,15 @@ ROOT=`dirname $0`
|
||||
|
||||
cd "$ROOT/.."
|
||||
|
||||
echo "Updating translations..."
|
||||
if ! [ -d packages/desktop-client/locale ]; then
|
||||
git clone https://github.com/actualbudget/translations packages/desktop-client/locale
|
||||
fi
|
||||
pushd packages/desktop-client/locale > /dev/null
|
||||
git pull
|
||||
popd > /dev/null
|
||||
packages/desktop-client/bin/remove-untranslated-languages
|
||||
|
||||
yarn workspace loot-core build:browser
|
||||
yarn workspace @actual-app/web build:browser
|
||||
|
||||
|
||||
@@ -36,6 +36,16 @@ fi
|
||||
|
||||
yarn workspace loot-core build:node
|
||||
|
||||
# Get translations
|
||||
echo "Updating translations..."
|
||||
if ! [ -d packages/desktop-client/locale ]; then
|
||||
git clone https://github.com/actualbudget/translations packages/desktop-client/locale
|
||||
fi
|
||||
pushd packages/desktop-client/locale > /dev/null
|
||||
git pull
|
||||
popd > /dev/null
|
||||
packages/desktop-client/bin/remove-untranslated-languages
|
||||
|
||||
yarn workspace @actual-app/web build --mode=desktop # electron specific build
|
||||
|
||||
yarn workspace desktop-electron update-client
|
||||
|
||||
@@ -113,6 +113,7 @@ export default [
|
||||
'packages/loot-core/**/node_modules/*',
|
||||
'packages/loot-core/**/lib-dist/*',
|
||||
'packages/loot-core/**/proto/*',
|
||||
'packages/sync-server',
|
||||
'.yarn/*',
|
||||
'.github/*',
|
||||
],
|
||||
@@ -587,11 +588,7 @@ export default [
|
||||
'packages/desktop-client/**/*.{ts,tsx}',
|
||||
'packages/loot-core/src/client/**/*.{ts,tsx}',
|
||||
],
|
||||
|
||||
rules: {
|
||||
// enforce type over interface
|
||||
'@typescript-eslint/consistent-type-definitions': ['warn', 'type'],
|
||||
|
||||
// enforce import type
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'warn',
|
||||
@@ -628,11 +625,44 @@ export default [
|
||||
'no-restricted-imports': [
|
||||
'warn',
|
||||
{
|
||||
patterns: [
|
||||
paths: [
|
||||
{
|
||||
group: ['react-router-dom'],
|
||||
name: 'react-router-dom',
|
||||
importNames: ['useNavigate'],
|
||||
message: "Please use Actual's useNavigate() hook instead.",
|
||||
message:
|
||||
"Please import Actual's useNavigate() hook from `src/hooks` instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/desktop-client/**/*', 'packages/loot-core/**/*'],
|
||||
ignores: ['packages/desktop-client/src/redux/index.{ts,tsx}'],
|
||||
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'warn',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'react-redux',
|
||||
importNames: ['useDispatch'],
|
||||
message:
|
||||
"Please import Actual's useDispatch() hook from `src/redux` instead.",
|
||||
},
|
||||
{
|
||||
name: 'react-redux',
|
||||
importNames: ['useSelector'],
|
||||
message:
|
||||
"Please import Actual's useSelector() hook from `src/redux` instead.",
|
||||
},
|
||||
{
|
||||
name: 'react-redux',
|
||||
importNames: ['useStore'],
|
||||
message:
|
||||
"Please import Actual's useStore() hook from `src/redux` instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -708,6 +738,12 @@ export default [
|
||||
'import/no-default-export': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/api/index.ts'],
|
||||
rules: {
|
||||
'import/no-unresolved': 'off',
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
// TODO: fix the issues in these files
|
||||
@@ -715,7 +751,6 @@ export default [
|
||||
'packages/desktop-client/src/components/accounts/Account.jsx',
|
||||
'packages/desktop-client/src/components/accounts/MobileAccount.jsx',
|
||||
'packages/desktop-client/src/components/accounts/MobileAccounts.jsx',
|
||||
'packages/desktop-client/src/components/App.tsx',
|
||||
'packages/desktop-client/src/components/budget/BudgetCategories.jsx',
|
||||
'packages/desktop-client/src/components/budget/BudgetSummaries.tsx',
|
||||
'packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx',
|
||||
@@ -786,4 +821,15 @@ export default [
|
||||
'rulesdir/typography': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'packages/desktop-client/**/*.{ts,tsx}',
|
||||
'packages/loot-core/src/client/**/*.{ts,tsx}',
|
||||
],
|
||||
ignores: ['**/**/globals.d.ts'],
|
||||
rules: {
|
||||
// enforce type over interface
|
||||
'@typescript-eslint/consistent-type-definitions': ['warn', 'type'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
import type { InitConfig } from 'loot-core/server/main';
|
||||
|
||||
// @ts-ignore: bundle not available until we build it
|
||||
// eslint-disable-next-line import/extensions, import/no-unresolved
|
||||
// eslint-disable-next-line import/extensions
|
||||
import * as bundle from './app/bundle.api.js';
|
||||
import * as injected from './injected';
|
||||
import { validateNodeVersion } from './validateNodeVersion';
|
||||
|
||||
@@ -85,10 +85,21 @@ export function addTransactions(
|
||||
});
|
||||
}
|
||||
|
||||
export function importTransactions(accountId, transactions) {
|
||||
export interface ImportTransactionsOpts {
|
||||
defaultCleared?: boolean;
|
||||
}
|
||||
|
||||
export function importTransactions(
|
||||
accountId,
|
||||
transactions,
|
||||
opts: ImportTransactionsOpts = {
|
||||
defaultCleared: true,
|
||||
},
|
||||
) {
|
||||
return send('api/transactions-import', {
|
||||
accountId,
|
||||
transactions,
|
||||
opts,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/api",
|
||||
"version": "25.1.0",
|
||||
"version": "25.2.1",
|
||||
"license": "MIT",
|
||||
"description": "An API for Actual",
|
||||
"engines": {
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
// the latest Node 16.x release supports all of the features
|
||||
"target": "ES2021",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node10",
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"declarationDir": "@types",
|
||||
"paths": {
|
||||
"loot-core/src/*": ["./loot-core/*"],
|
||||
"loot-core/*": ["./@types/loot-core/*"],
|
||||
"loot-core/*": ["./@types/loot-core/*"]
|
||||
}
|
||||
},
|
||||
"include": ["."],
|
||||
|
||||
24
packages/component-library/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@actual-app/components",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.13.4",
|
||||
"react-aria-components": "^1.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.0",
|
||||
"react": "18.2.0"
|
||||
},
|
||||
"exports": {
|
||||
"./icons/*": "./src/icons/*.tsx",
|
||||
"./button": "./src/Button.tsx",
|
||||
"./styles": "./src/styles.ts",
|
||||
"./theme": "./src/theme.ts",
|
||||
"./tokens": "./src/tokens.ts",
|
||||
"./view": "./src/View.tsx"
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import { Button as ReactAriaButton } from 'react-aria-components';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { AnimatedLoading } from '../../icons/AnimatedLoading';
|
||||
import { styles, theme } from '../../style';
|
||||
|
||||
import { AnimatedLoading } from './icons/AnimatedLoading';
|
||||
import { styles } from './styles';
|
||||
import { theme } from './theme';
|
||||
import { View } from './View';
|
||||
|
||||
const backgroundColor: {
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type HTMLProps,
|
||||
type Ref,
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
import React, { forwardRef, type HTMLProps, type Ref } from 'react';
|
||||
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
import { type CSSProperties } from './styles';
|
||||
|
||||
type ViewProps = HTMLProps<HTMLDivElement> & {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
26
packages/component-library/src/icons/AnimatedLoading.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { type SVGProps } from 'react';
|
||||
|
||||
import { css, keyframes } from '@emotion/css';
|
||||
|
||||
import { SvgLoading } from './Loading';
|
||||
|
||||
const rotation = keyframes({
|
||||
'0%': { transform: 'rotate(-90deg)' },
|
||||
'100%': { transform: 'rotate(666deg)' },
|
||||
});
|
||||
|
||||
export function AnimatedLoading(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<span
|
||||
className={css({
|
||||
animationName: rotation,
|
||||
animationDuration: '1.6s',
|
||||
animationTimingFunction: 'cubic-bezier(0.17, 0.67, 0.83, 0.67)',
|
||||
animationIterationCount: 'infinite',
|
||||
lineHeight: 0,
|
||||
})}
|
||||
>
|
||||
<SvgLoading {...props} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
33
packages/component-library/src/icons/Loading.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { type SVGProps, useState } from 'react';
|
||||
|
||||
export const SvgLoading = (props: SVGProps<SVGSVGElement>) => {
|
||||
const { color = 'currentColor' } = props;
|
||||
const [gradientId] = useState('gradient-' + Math.random());
|
||||
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 38 38" style={{ ...props.style }}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
x1="8.042%"
|
||||
y1="0%"
|
||||
x2="65.682%"
|
||||
y2="23.865%"
|
||||
id={gradientId}
|
||||
>
|
||||
<stop stopColor={color} stopOpacity={0} offset="0%" />
|
||||
<stop stopColor={color} stopOpacity={0.631} offset="63.146%" />
|
||||
<stop stopColor={color} offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(1 2)" fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M36 18c0-9.94-8.06-18-18-18"
|
||||
stroke={'url(#' + gradientId + ')'}
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
/>
|
||||
<circle fill={color} cx={36} cy={18} r={1} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
151
packages/component-library/src/styles.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { keyframes } from '@emotion/css';
|
||||
|
||||
import { theme } from './theme';
|
||||
import { tokens } from './tokens';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type CSSProperties = Record<string, any>;
|
||||
|
||||
const MOBILE_MIN_HEIGHT = 40;
|
||||
|
||||
const shadowLarge = {
|
||||
boxShadow: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const styles: Record<string, any> = {
|
||||
incomeHeaderHeight: 70,
|
||||
cardShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
|
||||
monthRightPadding: 5,
|
||||
menuBorderRadius: 4,
|
||||
mobileMinHeight: MOBILE_MIN_HEIGHT,
|
||||
mobileMenuItem: {
|
||||
fontSize: 17,
|
||||
fontWeight: 400,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
height: MOBILE_MIN_HEIGHT,
|
||||
minHeight: MOBILE_MIN_HEIGHT,
|
||||
},
|
||||
mobileEditingPadding: 12,
|
||||
altMenuMaxHeight: 250,
|
||||
altMenuText: {
|
||||
fontSize: 13,
|
||||
},
|
||||
altMenuHeaderText: {
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
},
|
||||
veryLargeText: {
|
||||
fontSize: 30,
|
||||
fontWeight: 600,
|
||||
},
|
||||
largeText: {
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
mediumText: {
|
||||
fontSize: 15,
|
||||
fontWeight: 500,
|
||||
},
|
||||
smallText: {
|
||||
fontSize: 13,
|
||||
},
|
||||
verySmallText: {
|
||||
fontSize: 12,
|
||||
},
|
||||
tinyText: {
|
||||
fontSize: 10,
|
||||
},
|
||||
page: {
|
||||
flex: 1,
|
||||
'@media (max-height: 550px)': {
|
||||
minHeight: 700, // ensure we can scroll on small screens
|
||||
},
|
||||
paddingTop: 8, // height of the titlebar
|
||||
[`@media (min-width: ${tokens.breakpoint_small})`]: {
|
||||
paddingTop: 36,
|
||||
},
|
||||
},
|
||||
pageContent: {
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
[`@media (min-width: ${tokens.breakpoint_small})`]: {
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
},
|
||||
},
|
||||
settingsPageContent: {
|
||||
padding: 20,
|
||||
[`@media (min-width: ${tokens.breakpoint_small})`]: {
|
||||
padding: 'inherit',
|
||||
},
|
||||
},
|
||||
staticText: {
|
||||
cursor: 'default',
|
||||
userSelect: 'none',
|
||||
},
|
||||
shadow: {
|
||||
boxShadow: '0 2px 4px 0 rgba(0,0,0,0.1)',
|
||||
},
|
||||
shadowLarge,
|
||||
tnum: {
|
||||
// eslint-disable-next-line rulesdir/typography
|
||||
fontFeatureSettings: '"tnum"',
|
||||
},
|
||||
notFixed: { fontFeatureSettings: '' },
|
||||
text: {
|
||||
fontSize: 16,
|
||||
// lineHeight: 22.4 // TODO: This seems like trouble, but what's the right value?
|
||||
},
|
||||
delayedFadeIn: {
|
||||
animationName: keyframes({
|
||||
'0%': { opacity: 0 },
|
||||
'100%': { opacity: 1 },
|
||||
}),
|
||||
animationDuration: '1s',
|
||||
animationFillMode: 'both',
|
||||
animationDelay: '0.5s',
|
||||
},
|
||||
underlinedText: {
|
||||
borderBottom: `2px solid`,
|
||||
},
|
||||
noTapHighlight: {
|
||||
WebkitTapHighlightColor: 'transparent',
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
},
|
||||
lineClamp: (lines: number) => {
|
||||
return {
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: lines,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
wordBreak: 'break-word',
|
||||
};
|
||||
},
|
||||
tooltip: {
|
||||
padding: 5,
|
||||
...shadowLarge,
|
||||
borderWidth: 2,
|
||||
borderRadius: 4,
|
||||
borderStyle: 'solid',
|
||||
borderColor: theme.tooltipBorder,
|
||||
backgroundColor: theme.tooltipBackground,
|
||||
color: theme.tooltipText,
|
||||
overflow: 'auto',
|
||||
},
|
||||
popover: {
|
||||
border: 'none',
|
||||
backgroundColor: theme.menuBackground,
|
||||
color: theme.menuItemText,
|
||||
},
|
||||
// Dynamically set
|
||||
horizontalScrollbar: null as CSSProperties | null,
|
||||
lightScrollbar: null as CSSProperties | null,
|
||||
darkScrollbar: null as CSSProperties | null,
|
||||
scrollbarWidth: null as number | null,
|
||||
};
|
||||
203
packages/component-library/src/theme.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
export const theme = {
|
||||
pageBackground: 'var(--color-pageBackground)',
|
||||
pageBackgroundModalActive: 'var(--color-pageBackgroundModalActive)',
|
||||
pageBackgroundTopLeft: 'var(--color-pageBackgroundTopLeft)',
|
||||
pageBackgroundBottomRight: 'var(--color-pageBackgroundBottomRight)',
|
||||
pageBackgroundLineTop: 'var(--color-pageBackgroundLineTop)',
|
||||
pageBackgroundLineMid: 'var(--color-pageBackgroundLineMid)',
|
||||
pageBackgroundLineBottom: 'var(--color-pageBackgroundLineBottom)',
|
||||
pageText: 'var(--color-pageText)',
|
||||
pageTextLight: 'var(--color-pageTextLight)',
|
||||
pageTextSubdued: 'var(--color-pageTextSubdued)',
|
||||
pageTextDark: 'var(--color-pageTextDark)',
|
||||
pageTextPositive: 'var(--color-pageTextPositive)',
|
||||
pageTextLink: 'var(--color-pageTextLink)',
|
||||
pageTextLinkLight: 'var(--color-pageTextLinkLight)',
|
||||
cardBackground: 'var(--color-cardBackground)',
|
||||
cardBorder: 'var(--color-cardBorder)',
|
||||
cardShadow: 'var(--color-cardShadow)',
|
||||
tableBackground: 'var(--color-tableBackground)',
|
||||
tableRowBackgroundHover: 'var(--color-tableRowBackgroundHover)',
|
||||
tableText: 'var(--color-tableText)',
|
||||
tableTextLight: 'var(--color-tableTextLight)',
|
||||
tableTextSubdued: 'var(--color-tableTextSubdued)',
|
||||
tableTextSelected: 'var(--color-tableTextSelected)',
|
||||
tableTextHover: 'var(--color-tableTextHover)',
|
||||
tableTextInactive: 'var(--color-tableTextInactive)',
|
||||
tableHeaderText: 'var(--color-tableHeaderText)',
|
||||
tableHeaderBackground: 'var(--color-tableHeaderBackground)',
|
||||
tableBorder: 'var(--color-tableBorder)',
|
||||
tableBorderSelected: 'var(--color-tableBorderSelected)',
|
||||
tableBorderHover: 'var(--color-tableBorderHover)',
|
||||
tableBorderSeparator: 'var(--color-tableBorderSeparator)',
|
||||
tableRowBackgroundHighlight: 'var(--color-tableRowBackgroundHighlight)',
|
||||
tableRowBackgroundHighlightText:
|
||||
'var(--color-tableRowBackgroundHighlightText)',
|
||||
tableRowHeaderBackground: 'var(--color-tableRowHeaderBackground)',
|
||||
tableRowHeaderText: 'var(--color-tableRowHeaderText)',
|
||||
sidebarBackground: 'var(--color-sidebarBackground)',
|
||||
sidebarItemBackgroundPending: 'var(--color-sidebarItemBackgroundPending)',
|
||||
sidebarItemBackgroundPositive: 'var(--color-sidebarItemBackgroundPositive)',
|
||||
sidebarItemBackgroundFailed: 'var(--color-sidebarItemBackgroundFailed)',
|
||||
sidebarItemAccentSelected: 'var(--color-sidebarItemAccentSelected)',
|
||||
sidebarItemBackgroundHover: 'var(--color-sidebarItemBackgroundHover)',
|
||||
sidebarItemText: 'var(--color-sidebarItemText)',
|
||||
sidebarItemTextSelected: 'var(--color-sidebarItemTextSelected)',
|
||||
menuBackground: 'var(--color-menuBackground)',
|
||||
menuItemBackground: 'var(--color-menuItemBackground)',
|
||||
menuItemBackgroundHover: 'var(--color-menuItemBackgroundHover)',
|
||||
menuItemText: 'var(--color-menuItemText)',
|
||||
menuItemTextHover: 'var(--color-menuItemTextHover)',
|
||||
menuItemTextSelected: 'var(--color-menuItemTextSelected)',
|
||||
menuItemTextHeader: 'var(--color-menuItemTextHeader)',
|
||||
menuBorder: 'var(--color-menuBorder)',
|
||||
menuBorderHover: 'var(--color-menuBorderHover)',
|
||||
menuKeybindingText: 'var(--color-menuKeybindingText)',
|
||||
menuAutoCompleteBackground: 'var(--color-menuAutoCompleteBackground)',
|
||||
menuAutoCompleteBackgroundHover:
|
||||
'var(--color-menuAutoCompleteBackgroundHover)',
|
||||
menuAutoCompleteText: 'var(--color-menuAutoCompleteText)',
|
||||
menuAutoCompleteTextHover: 'var(--color-menuAutoCompleteTextHover)',
|
||||
menuAutoCompleteTextHeader: 'var(--color-menuAutoCompleteTextHeader)',
|
||||
menuAutoCompleteItemTextHover: 'var(--color-menuAutoCompleteItemTextHover)',
|
||||
menuAutoCompleteItemText: 'var(--color-menuAutoCompleteItemText)',
|
||||
modalBackground: 'var(--color-modalBackground)',
|
||||
modalBorder: 'var(--color-modalBorder)',
|
||||
mobileHeaderBackground: 'var(--color-mobileHeaderBackground)',
|
||||
mobileHeaderText: 'var(--color-mobileHeaderText)',
|
||||
mobileHeaderTextSubdued: 'var(--color-mobileHeaderTextSubdued)',
|
||||
mobileHeaderTextHover: 'var(--color-mobileHeaderTextHover)',
|
||||
mobilePageBackground: 'var(--color-mobilePageBackground)',
|
||||
mobileNavBackground: 'var(--color-mobileNavBackground)',
|
||||
mobileNavItem: 'var(--color-mobileNavItem)',
|
||||
mobileNavItemSelected: 'var(--color-mobileNavItemSelected)',
|
||||
mobileAccountShadow: 'var(--color-mobileAccountShadow)',
|
||||
mobileAccountText: 'var(--color-mobileAccountText)',
|
||||
mobileTransactionSelected: 'var(--color-mobileTransactionSelected)',
|
||||
mobileViewTheme: 'var(--color-mobileViewTheme)',
|
||||
mobileConfigServerViewTheme: 'var(--color-mobileConfigServerViewTheme)',
|
||||
markdownNormal: 'var(--color-markdownNormal)',
|
||||
markdownDark: 'var(--color-markdownDark)',
|
||||
markdownLight: 'var(--color-markdownLight)',
|
||||
buttonMenuText: 'var(--color-buttonMenuText)',
|
||||
buttonMenuTextHover: 'var(--color-buttonMenuTextHover)',
|
||||
buttonMenuBackground: 'var(--color-buttonMenuBackground)',
|
||||
buttonMenuBackgroundHover: 'var(--color-buttonMenuBackgroundHover)',
|
||||
buttonMenuBorder: 'var(--color-buttonMenuBorder)',
|
||||
buttonMenuSelectedText: 'var(--color-buttonMenuSelectedText)',
|
||||
buttonMenuSelectedTextHover: 'var(--color-buttonMenuSelectedTextHover)',
|
||||
buttonMenuSelectedBackground: 'var(--color-buttonMenuSelectedBackground)',
|
||||
buttonMenuSelectedBackgroundHover:
|
||||
'var(--color-buttonMenuSelectedBackgroundHover)',
|
||||
buttonMenuSelectedBorder: 'var(--color-buttonMenuSelectedBorder)',
|
||||
buttonPrimaryText: 'var(--color-buttonPrimaryText)',
|
||||
buttonPrimaryTextHover: 'var(--color-buttonPrimaryTextHover)',
|
||||
buttonPrimaryBackground: 'var(--color-buttonPrimaryBackground)',
|
||||
buttonPrimaryBackgroundHover: 'var(--color-buttonPrimaryBackgroundHover)',
|
||||
buttonPrimaryBorder: 'var(--color-buttonPrimaryBorder)',
|
||||
buttonPrimaryShadow: 'var(--color-buttonPrimaryShadow)',
|
||||
buttonPrimaryDisabledText: 'var(--color-buttonPrimaryDisabledText)',
|
||||
buttonPrimaryDisabledBackground:
|
||||
'var(--color-buttonPrimaryDisabledBackground)',
|
||||
buttonPrimaryDisabledBorder: 'var(--color-buttonPrimaryDisabledBorder)',
|
||||
buttonNormalText: 'var(--color-buttonNormalText)',
|
||||
buttonNormalTextHover: 'var(--color-buttonNormalTextHover)',
|
||||
buttonNormalBackground: 'var(--color-buttonNormalBackground)',
|
||||
buttonNormalBackgroundHover: 'var(--color-buttonNormalBackgroundHover)',
|
||||
buttonNormalBorder: 'var(--color-buttonNormalBorder)',
|
||||
buttonNormalShadow: 'var(--color-buttonNormalShadow)',
|
||||
buttonNormalSelectedText: 'var(--color-buttonNormalSelectedText)',
|
||||
buttonNormalSelectedBackground: 'var(--color-buttonNormalSelectedBackground)',
|
||||
buttonNormalDisabledText: 'var(--color-buttonNormalDisabledText)',
|
||||
buttonNormalDisabledBackground: 'var(--color-buttonNormalDisabledBackground)',
|
||||
buttonNormalDisabledBorder: 'var(--color-buttonNormalDisabledBorder)',
|
||||
buttonBareText: 'var(--color-buttonBareText)',
|
||||
buttonBareTextHover: 'var(--color-buttonBareTextHover)',
|
||||
buttonBareBackground: 'var(--color-buttonBareBackground)',
|
||||
buttonBareBackgroundHover: 'var(--color-buttonBareBackgroundHover)',
|
||||
buttonBareBackgroundActive: 'var(--color-buttonBareBackgroundActive)',
|
||||
buttonBareDisabledText: 'var(--color-buttonBareDisabledText)',
|
||||
buttonBareDisabledBackground: 'var(--color-buttonBareDisabledBackground)',
|
||||
calendarText: 'var(--color-calendarText)',
|
||||
calendarBackground: 'var(--color-calendarBackground)',
|
||||
calendarItemText: 'var(--color-calendarItemText)',
|
||||
calendarItemBackground: 'var(--color-calendarItemBackground)',
|
||||
calendarSelectedBackground: 'var(--color-calendarSelectedBackground)',
|
||||
noticeBackground: 'var(--color-noticeBackground)',
|
||||
noticeBackgroundLight: 'var(--color-noticeBackgroundLight)',
|
||||
noticeBackgroundDark: 'var(--color-noticeBackgroundDark)',
|
||||
noticeText: 'var(--color-noticeText)',
|
||||
noticeTextLight: 'var(--color-noticeTextLight)',
|
||||
noticeTextDark: 'var(--color-noticeTextDark)',
|
||||
noticeTextMenu: 'var(--color-noticeTextMenu)',
|
||||
noticeTextMenuHover: 'var(--color-noticeTextMenuHover)',
|
||||
noticeBorder: 'var(--color-noticeBorder)',
|
||||
warningBackground: 'var(--color-warningBackground)',
|
||||
warningText: 'var(--color-warningText)',
|
||||
warningTextLight: 'var(--color-warningTextLight)',
|
||||
warningTextDark: 'var(--color-warningTextDark)',
|
||||
warningBorder: 'var(--color-warningBorder)',
|
||||
errorBackground: 'var(--color-errorBackground)',
|
||||
errorText: 'var(--color-errorText)',
|
||||
errorTextDark: 'var(--color-errorTextDark)',
|
||||
errorTextDarker: 'var(--color-errorTextDarker)',
|
||||
errorTextMenu: 'var(--color-errorTextMenu)',
|
||||
errorBorder: 'var(--color-errorBorder)',
|
||||
upcomingBackground: 'var(--color-upcomingBackground)',
|
||||
upcomingText: 'var(--color-upcomingText)',
|
||||
upcomingBorder: 'var(--color-upcomingBorder)',
|
||||
formLabelText: 'var(--color-formLabelText)',
|
||||
formLabelBackground: 'var(--color-formLabelBackground)',
|
||||
formInputBackground: 'var(--color-formInputBackground)',
|
||||
formInputBackgroundSelected: 'var(--color-formInputBackgroundSelected)',
|
||||
formInputBackgroundSelection: 'var(--color-formInputBackgroundSelection)',
|
||||
formInputBorder: 'var(--color-formInputBorder)',
|
||||
formInputTextReadOnlySelection: 'var(--color-formInputTextReadOnlySelection)',
|
||||
formInputBorderSelected: 'var(--color-formInputBorderSelected)',
|
||||
formInputText: 'var(--color-formInputText)',
|
||||
formInputTextSelected: 'var(--color-formInputTextSelected)',
|
||||
formInputTextPlaceholder: 'var(--color-formInputTextPlaceholder)',
|
||||
formInputTextPlaceholderSelected:
|
||||
'var(--color-formInputTextPlaceholderSelected)',
|
||||
formInputTextSelection: 'var(--color-formInputTextSelection)',
|
||||
formInputShadowSelected: 'var(--color-formInputShadowSelected)',
|
||||
formInputTextHighlight: 'var(--color-formInputTextHighlight)',
|
||||
checkboxText: 'var(--color-checkboxText)',
|
||||
checkboxBackgroundSelected: 'var(--color-checkboxBackgroundSelected)',
|
||||
checkboxBorderSelected: 'var(--color-checkboxBorderSelected)',
|
||||
checkboxShadowSelected: 'var(--color-checkboxShadowSelected)',
|
||||
checkboxToggleBackground: 'var(--color-checkboxToggleBackground)',
|
||||
checkboxToggleBackgroundSelected:
|
||||
'var(--color-checkboxToggleBackgroundSelected)',
|
||||
checkboxToggleDisabled: 'var(--color-checkboxToggleDisabled)',
|
||||
pillBackground: 'var(--color-pillBackground)',
|
||||
pillBackgroundLight: 'var(--color-pillBackgroundLight)',
|
||||
pillText: 'var(--color-pillText)',
|
||||
pillTextHighlighted: 'var(--color-pillTextHighlighted)',
|
||||
pillBorder: 'var(--color-pillBorder)',
|
||||
pillBorderDark: 'var(--color-pillBorderDark)',
|
||||
pillBackgroundSelected: 'var(--color-pillBackgroundSelected)',
|
||||
pillTextSelected: 'var(--color-pillTextSelected)',
|
||||
pillBorderSelected: 'var(--color-pillBorderSelected)',
|
||||
pillTextSubdued: 'var(--color-pillTextSubdued)',
|
||||
reportsRed: 'var(--color-reportsRed)',
|
||||
reportsBlue: 'var(--color-reportsBlue)',
|
||||
reportsGreen: 'var(--color-reportsGreen)',
|
||||
reportsGray: 'var(--color-reportsGray)',
|
||||
reportsLabel: 'var(--color-reportsLabel)',
|
||||
reportsInnerLabel: 'var(--color-reportsInnerLabel)',
|
||||
noteTagBackground: 'var(--color-noteTagBackground)',
|
||||
noteTagBackgroundHover: 'var(--color-noteTagBackgroundHover)',
|
||||
noteTagText: 'var(--color-noteTagText)',
|
||||
budgetOtherMonth: 'var(--color-budgetOtherMonth)',
|
||||
budgetCurrentMonth: 'var(--color-budgetCurrentMonth)',
|
||||
budgetHeaderOtherMonth: 'var(--color-budgetHeaderOtherMonth)',
|
||||
budgetHeaderCurrentMonth: 'var(--color-budgetHeaderCurrentMonth)',
|
||||
floatingActionBarBackground: 'var(--color-floatingActionBarBackground)',
|
||||
floatingActionBarBorder: 'var(--color-floatingActionBarBorder)',
|
||||
floatingActionBarText: 'var(--color-floatingActionBarText)',
|
||||
tooltipText: 'var(--color-tooltipText)',
|
||||
tooltipBackground: 'var(--color-tooltipBackground)',
|
||||
tooltipBorder: 'var(--color-tooltipBorder)',
|
||||
calendarCellBackground: 'var(--color-calendarCellBackground)',
|
||||
};
|
||||
35
packages/component-library/src/tokens.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
enum BreakpointNames {
|
||||
small = 'small',
|
||||
medium = 'medium',
|
||||
wide = 'wide',
|
||||
}
|
||||
|
||||
type NumericBreakpoints = {
|
||||
[key in BreakpointNames]: number;
|
||||
};
|
||||
|
||||
export const breakpoints: NumericBreakpoints = {
|
||||
small: 512,
|
||||
medium: 730,
|
||||
wide: 1100,
|
||||
};
|
||||
|
||||
type BreakpointsPx = {
|
||||
[B in keyof NumericBreakpoints as `breakpoint_${B}`]: string;
|
||||
};
|
||||
|
||||
// Provide the same breakpoints in a form usable by CSS media queries
|
||||
// {
|
||||
// breakpoint_small: '512px',
|
||||
// breakpoint_medium: '740px',
|
||||
// breakpoint_wide: '1100px',
|
||||
// }
|
||||
export const tokens: BreakpointsPx = Object.entries(
|
||||
breakpoints,
|
||||
).reduce<BreakpointsPx>(
|
||||
(acc, [key, val]) => ({
|
||||
...acc,
|
||||
[`breakpoint_${key}`]: `${val}px`,
|
||||
}),
|
||||
{} as BreakpointsPx,
|
||||
);
|
||||
@@ -5,6 +5,7 @@
|
||||
// the latest Node 16.x release supports all of the features
|
||||
"target": "ES2021",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node10",
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
|
||||
53
packages/desktop-client/bin/remove-untranslated-languages
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Local path to the cloned translations repository
|
||||
const localRepoPath = './packages/desktop-client/locale';
|
||||
|
||||
// Compare JSON files and delete incomplete ones
|
||||
const processTranslations = () => {
|
||||
try {
|
||||
const files = fs.readdirSync(localRepoPath);
|
||||
const enJsonPath = path.join(localRepoPath, 'en.json');
|
||||
|
||||
if (!fs.existsSync(enJsonPath)) {
|
||||
throw new Error('en.json not found in the repository.');
|
||||
}
|
||||
|
||||
const enJson = JSON.parse(fs.readFileSync(enJsonPath, 'utf8'));
|
||||
const enKeysCount = Object.keys(enJson).length;
|
||||
|
||||
console.log(`en.json has ${enKeysCount} keys.`);
|
||||
|
||||
files.forEach((file) => {
|
||||
if (file === 'en.json' || path.extname(file) !== '.json') return;
|
||||
|
||||
if (file.startsWith('en-')) {
|
||||
console.log(`Keeping ${file} as it's an English language.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = path.join(localRepoPath, file);
|
||||
const jsonData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
const fileKeysCount = Object.keys(jsonData).length;
|
||||
|
||||
// Calculate the percentage of keys present compared to en.json
|
||||
const percentage = (fileKeysCount / enKeysCount) * 100;
|
||||
console.log(`${file} has ${fileKeysCount} keys (${percentage.toFixed(2)}%).`);
|
||||
|
||||
if (percentage < 50) {
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`Deleted ${file} due to insufficient keys.`);
|
||||
} else {
|
||||
console.log(`Keeping ${file}.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Processing completed.');
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
processTranslations();
|
||||
@@ -1,12 +1,13 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { type Page } from '@playwright/test';
|
||||
|
||||
import { expect, test } from './fixtures';
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { MobileNavigation } from './page-models/mobile-navigation';
|
||||
|
||||
test.describe('Mobile Accounts', () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let configurationPage;
|
||||
let page: Page;
|
||||
let navigation: MobileNavigation;
|
||||
let configurationPage: ConfigurationPage;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 55 KiB |
@@ -1,15 +1,17 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { type Page } from '@playwright/test';
|
||||
|
||||
import { expect, test } from './fixtures';
|
||||
import { type AccountPage } from './page-models/account-page';
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { Navigation } from './page-models/navigation';
|
||||
|
||||
test.describe('Accounts', () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let configurationPage;
|
||||
let accountPage;
|
||||
let page: Page;
|
||||
let navigation: Navigation;
|
||||
let configurationPage: ConfigurationPage;
|
||||
let accountPage: AccountPage;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,18 +1,89 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { type Page } from '@playwright/test';
|
||||
|
||||
import { amountToCurrency, currencyToAmount } from 'loot-core/shared/util';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
|
||||
import { expect, test } from './fixtures';
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { type MobileBudgetPage } from './page-models/mobile-budget-page';
|
||||
import { MobileNavigation } from './page-models/mobile-navigation';
|
||||
|
||||
const budgetTypes = ['Envelope', 'Tracking'];
|
||||
const copyLastMonthBudget = async (
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
) => {
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
await budgetMenuModal.copyLastMonthBudget();
|
||||
await budgetMenuModal.close();
|
||||
};
|
||||
|
||||
const setTo3MonthAverage = async (
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
) => {
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
await budgetMenuModal.setTo3MonthAverage();
|
||||
await budgetMenuModal.close();
|
||||
};
|
||||
|
||||
const setTo6MonthAverage = async (
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
) => {
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
await budgetMenuModal.setTo6MonthAverage();
|
||||
await budgetMenuModal.close();
|
||||
};
|
||||
|
||||
const setToYearlyAverage = async (
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
) => {
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
await budgetMenuModal.setToYearlyAverage();
|
||||
await budgetMenuModal.close();
|
||||
};
|
||||
|
||||
async function setBudgetAverage(
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
numberOfMonths: number,
|
||||
setBudgetAverageFn: (
|
||||
budgetPage: MobileBudgetPage,
|
||||
categoryName: string,
|
||||
numberOfMonths: number,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
let totalSpent = 0;
|
||||
|
||||
for (let i = 0; i < numberOfMonths; i++) {
|
||||
await budgetPage.goToPreviousMonth();
|
||||
const spentButton = await budgetPage.getButtonForSpent(categoryName);
|
||||
const spent = await spentButton.textContent();
|
||||
totalSpent += currencyToAmount(spent) ?? 0;
|
||||
}
|
||||
|
||||
// Calculate average amount
|
||||
const averageSpent = totalSpent / numberOfMonths;
|
||||
|
||||
// Go back to the current month
|
||||
for (let i = 0; i < numberOfMonths; i++) {
|
||||
await budgetPage.goToNextMonth();
|
||||
}
|
||||
|
||||
await setBudgetAverageFn(budgetPage, categoryName, numberOfMonths);
|
||||
|
||||
return averageSpent;
|
||||
}
|
||||
|
||||
const budgetTypes = ['Envelope', 'Tracking'] as const;
|
||||
|
||||
budgetTypes.forEach(budgetType => {
|
||||
test.describe(`Mobile Budget [${budgetType}]`, () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let configurationPage;
|
||||
let previousGlobalIsTesting;
|
||||
let page: Page;
|
||||
let navigation: MobileNavigation;
|
||||
let configurationPage: ConfigurationPage;
|
||||
let previousGlobalIsTesting: boolean;
|
||||
|
||||
test.beforeAll(() => {
|
||||
// TODO: Hack, properly mock the currentMonth function
|
||||
@@ -37,11 +108,8 @@ budgetTypes.forEach(budgetType => {
|
||||
await page.goto('/');
|
||||
await configurationPage.createTestFile();
|
||||
|
||||
if (budgetType === 'Tracking') {
|
||||
// Set budget type to tracking
|
||||
const settingsPage = await navigation.goToSettingsPage();
|
||||
await settingsPage.useBudgetType('tracking');
|
||||
}
|
||||
const settingsPage = await navigation.goToSettingsPage();
|
||||
await settingsPage.useBudgetType(budgetType);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
@@ -50,7 +118,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('loads the budget page with budgeted amounts', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await expect(budgetPage.categoryNames).toHaveText([
|
||||
'Food',
|
||||
@@ -77,7 +144,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the Actual logo in the page header opens the budget page menu', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openBudgetPageMenu();
|
||||
|
||||
@@ -89,7 +155,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test("checks that clicking the left arrow in the page header shows the previous month's budget", async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
const displayMonth = monthUtils.format(
|
||||
@@ -111,7 +176,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the month in the page header opens the month menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
|
||||
@@ -130,7 +194,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test("checks that clicking the right arrow in the page header shows the next month's budget", async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
const displayMonth = monthUtils.format(
|
||||
@@ -154,7 +217,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the category group name opens the category group menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryGroupName = await budgetPage.getCategoryGroupNameForRow(0);
|
||||
await budgetPage.openCategoryGroupMenu(categoryGroupName);
|
||||
@@ -169,16 +231,11 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the category name opens the category menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openCategoryMenu(categoryName);
|
||||
const categoryMenuModal = await budgetPage.openCategoryMenu(categoryName);
|
||||
|
||||
const categoryMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(categoryMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(categoryMenuModal.heading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
@@ -186,32 +243,108 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the budgeted cell opens the budget menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openBudgetMenu(categoryName);
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
|
||||
const budgetMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(budgetMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(budgetMenuModal.heading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test('updates the budgeted amount', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
|
||||
// Set to 100.00
|
||||
await budgetPage.setBudget(categoryName, 10000);
|
||||
const budgetAmount = 123;
|
||||
|
||||
// Set to 123.00
|
||||
await budgetMenuModal.setBudgetAmount(`${budgetAmount}00`);
|
||||
|
||||
const budgetedButton =
|
||||
await budgetPage.getButtonForBudgeted(categoryName);
|
||||
|
||||
await expect(budgetedButton).toHaveText('100.00');
|
||||
await expect(budgetedButton).toHaveText(amountToCurrency(budgetAmount));
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test(`copies last month's budget`, async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(3);
|
||||
const budgetedButton =
|
||||
await budgetPage.getButtonForBudgeted(categoryName);
|
||||
|
||||
await budgetPage.goToPreviousMonth();
|
||||
|
||||
const lastMonthBudget = await budgetedButton.textContent();
|
||||
|
||||
await budgetPage.goToNextMonth();
|
||||
|
||||
await copyLastMonthBudget(budgetPage, categoryName);
|
||||
|
||||
await expect(budgetedButton).toHaveText(lastMonthBudget);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
(
|
||||
[
|
||||
[3, setTo3MonthAverage],
|
||||
[6, setTo6MonthAverage],
|
||||
[12, setToYearlyAverage],
|
||||
] as const
|
||||
).forEach(([numberOfMonths, setBudgetAverageFn]) => {
|
||||
test(`set budget to ${numberOfMonths} month average`, async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(3);
|
||||
|
||||
const averageSpent = await setBudgetAverage(
|
||||
budgetPage,
|
||||
categoryName,
|
||||
numberOfMonths,
|
||||
setBudgetAverageFn,
|
||||
);
|
||||
|
||||
const budgetedButton =
|
||||
await budgetPage.getButtonForBudgeted(categoryName);
|
||||
|
||||
await expect(budgetedButton).toHaveText(
|
||||
amountToCurrency(Math.abs(averageSpent)),
|
||||
);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
});
|
||||
|
||||
test(`applies budget template`, async () => {
|
||||
const settingsPage = await navigation.goToSettingsPage();
|
||||
await settingsPage.enableExperimentalFeature('Goal templates');
|
||||
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(1);
|
||||
|
||||
const amountToTemplate = 123;
|
||||
|
||||
const categoryMenuModal = await budgetPage.openCategoryMenu(categoryName);
|
||||
const editNotesModal = await categoryMenuModal.editNotes();
|
||||
const templateNotes = `#template ${amountToTemplate}`;
|
||||
await editNotesModal.updateNotes(templateNotes);
|
||||
await editNotesModal.close();
|
||||
|
||||
const budgetedButton =
|
||||
await budgetPage.getButtonForBudgeted(categoryName);
|
||||
|
||||
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
||||
await budgetMenuModal.applyBudgetTemplate();
|
||||
await budgetMenuModal.close();
|
||||
|
||||
await expect(budgetedButton).toHaveText(
|
||||
amountToCurrency(amountToTemplate),
|
||||
);
|
||||
const notification = page.getByRole('alert').first();
|
||||
await expect(notification).toContainText(templateNotes);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
@@ -219,7 +352,6 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking spent cell redirects to the category transactions page', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
const accountPage = await budgetPage.openSpentPage(categoryName);
|
||||
@@ -233,31 +365,24 @@ budgetTypes.forEach(budgetType => {
|
||||
|
||||
test('checks that clicking the balance cell opens the balance menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openBalanceMenu(categoryName);
|
||||
const balanceMenuModal = await budgetPage.openBalanceMenu(categoryName);
|
||||
|
||||
const balanceMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(balanceMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(balanceMenuModal.heading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
if (budgetType === 'Envelope') {
|
||||
test('checks that clicking the To Budget/Overbudgeted amount opens the budget summary menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openEnvelopeBudgetSummaryMenu();
|
||||
const envelopeBudgetSummaryModal =
|
||||
await budgetPage.openEnvelopeBudgetSummary();
|
||||
|
||||
const summaryModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(summaryModalHeading).toHaveText('Budget Summary');
|
||||
await expect(envelopeBudgetSummaryModal.heading).toHaveText(
|
||||
'Budget Summary',
|
||||
);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
}
|
||||
@@ -265,15 +390,13 @@ budgetTypes.forEach(budgetType => {
|
||||
if (budgetType === 'Tracking') {
|
||||
test('checks that clicking the Saved/Projected Savings/Overspent amount opens the budget summary menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openTrackingBudgetSummaryMenu();
|
||||
const trackingBudgetSummaryModal =
|
||||
await budgetPage.openTrackingBudgetSummary();
|
||||
|
||||
const summaryModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(summaryModalHeading).toHaveText('Budget Summary');
|
||||
await expect(trackingBudgetSummaryModal.heading).toHaveText(
|
||||
'Budget Summary',
|
||||
);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
}
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |