From afee0ee9dd78d440e88f9e69f5e628757462f547 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 26 Oct 2026 20:33:44 +1100 Subject: [PATCH 1/4] feat: allow calculations in expense amounts --- public/locales/en/common.json | 1 + src/components/AddExpense/AddExpensePage.tsx | 25 ++- src/components/ui/currency-input.tsx | 53 +++++- src/tests/expression.test.ts | 41 +++++ src/tests/number.test.ts | 15 ++ src/utils/expression.ts | 162 +++++++++++++++++++ src/utils/numbers.ts | 5 + 7 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 src/tests/expression.test.ts create mode 100644 src/utils/expression.ts diff --git a/public/locales/en/common.json b/public/locales/en/common.json index ee0cab553..660e3ea95 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -145,6 +145,7 @@ "group_name_update_failed": "Error in updating group name", "import_failed": "Error importing file", "invalid_currency_code": "Invalid currency code {code}", + "invalid_expression": "Invalid expression", "invalid_split": "Adjust who owes this expense before saving.", "language_change_failed": "Error changing language", "less_than": "File should be less than {size}MB", diff --git a/src/components/AddExpense/AddExpensePage.tsx b/src/components/AddExpense/AddExpensePage.tsx index af7c7e20d..3271e1e07 100644 --- a/src/components/AddExpense/AddExpensePage.tsx +++ b/src/components/AddExpense/AddExpensePage.tsx @@ -25,7 +25,8 @@ import { UploadFile } from './UploadFile'; import { UserInput } from './UserInput'; import { CurrencyInput } from '../ui/currency-input'; import { CurrencyConversion } from '../Friend/CurrencyConversion'; -import { currencyConversion } from '~/utils/numbers'; +import { BigMath, currencyConversion } from '~/utils/numbers'; +import { isExpression, safeEvaluateExpression } from '~/utils/expression'; import { CurrencyConversionIcon } from '../ui/categoryIcons'; import { useSession } from 'next-auth/react'; import { isStringQueryParam } from '~/utils/queryParams'; @@ -111,6 +112,22 @@ export const AddOrEditExpensePage: React.FC<{ return; } + let finalAmount = amount; + if (isExpression(amtStr)) { + const evaluated = safeEvaluateExpression(amtStr); + if (null === evaluated) { + toast.error(t('errors.invalid_expression')); + return; + } + finalAmount = BigMath.abs( + getCurrencyHelpersCached(currency).expressionResultToBigInt(evaluated), + ); + } + + if (0n === finalAmount) { + return; + } + setMultipleTransactions([]); setIsTransactionLoading(false); @@ -122,7 +139,7 @@ export const AddOrEditExpensePage: React.FC<{ { name: description, currency, - amount: amount * sign, + amount: finalAmount * sign, groupId: group?.id ?? null, splitType, participants: participants.map((p) => ({ @@ -190,10 +207,12 @@ export const AddOrEditExpensePage: React.FC<{ } } }, [ + t, description, currency, isNegative, amount, + amtStr, participants, category, expenseDate, @@ -212,6 +231,7 @@ export const AddOrEditExpensePage: React.FC<{ setSingleTransaction, t, update, + getCurrencyHelpersCached, ]); const handleDescriptionChange = useCallback( @@ -315,6 +335,7 @@ export const AddOrEditExpensePage: React.FC<{ placeholder={t('expense_details.add_expense_details.amount_placeholder')} currency={currency} strValue={amtStr} + allowExpressions allowNegative hideSymbol onValueChange={onUpdateAmount} diff --git a/src/components/ui/currency-input.tsx b/src/components/ui/currency-input.tsx index 4e0ac4fc1..6f873192d 100644 --- a/src/components/ui/currency-input.tsx +++ b/src/components/ui/currency-input.tsx @@ -2,32 +2,75 @@ import React from 'react'; import { Input, InputProps } from './input'; import { cn } from '~/lib/utils'; import { useTranslationWithUtils } from '~/hooks/useTranslationWithUtils'; +import { isExpression, safeEvaluateExpression, sanitizeExpressionInput } from '~/utils/expression'; const CurrencyInput: React.FC< Omit & { currency: string; strValue: string; onValueChange: (v: { strValue?: string; bigIntValue?: bigint }) => void; + allowExpressions?: boolean; allowNegative?: boolean; hideSymbol?: boolean; } -> = ({ className, currency, allowNegative, strValue, onValueChange, hideSymbol, ...props }) => { +> = ({ + className, + currency, + allowExpressions = false, + allowNegative, + strValue, + onValueChange, + hideSymbol, + ...props +}) => { const { getCurrencyHelpersCached } = useTranslationWithUtils(undefined); - const { format, parseToCleanString, toSafeBigInt, sanitizeInput } = - getCurrencyHelpersCached(currency); + const { + format, + parseToCleanString, + toSafeBigInt, + toUIString, + expressionResultToBigInt, + sanitizeInput, + } = getCurrencyHelpersCached(currency); return ( onValueChange({ strValue: parseToCleanString(strValue, allowNegative) })} + onFocus={() => { + if (!allowExpressions || !isExpression(strValue)) { + onValueChange({ strValue: parseToCleanString(strValue, allowNegative) }); + } + }} onBlur={() => { + if (allowExpressions && isExpression(strValue)) { + const evaluated = safeEvaluateExpression(strValue); + if (null === evaluated) { + return; + } + + const bigIntValue = expressionResultToBigInt(evaluated); + return onValueChange({ + strValue: toUIString(bigIntValue, allowNegative, hideSymbol), + bigIntValue, + }); + } + const formattedValue = format(strValue, { signed: allowNegative, hideSymbol }); return onValueChange({ strValue: formattedValue }); }} onChange={(e) => { const rawValue = e.target.value; + if (allowExpressions && isExpression(rawValue)) { + const strValue = sanitizeExpressionInput(rawValue); + const evaluated = safeEvaluateExpression(strValue); + return onValueChange({ + strValue, + bigIntValue: null === evaluated ? 0n : expressionResultToBigInt(evaluated), + }); + } + const strValue = sanitizeInput(rawValue, allowNegative, true); const bigIntValue = toSafeBigInt(strValue, allowNegative); onValueChange({ strValue, bigIntValue }); diff --git a/src/tests/expression.test.ts b/src/tests/expression.test.ts new file mode 100644 index 000000000..c0d68a6a1 --- /dev/null +++ b/src/tests/expression.test.ts @@ -0,0 +1,41 @@ +import { isExpression, safeEvaluateExpression, sanitizeExpressionInput } from '../utils/expression'; + +describe('isExpression', () => { + it.each([ + ['123.45', false], + ['-123.45', false], + ['-', false], + ['30.5+10.75-12-1.86', true], + ['10*5', true], + ['10/4', true], + ['(2+3)*4', true], + ])('returns %p for %p', (input, expected) => { + expect(isExpression(input)).toBe(expected); + }); +}); + +describe('sanitizeExpressionInput', () => { + it('keeps arithmetic and decimal characters only', () => { + expect(sanitizeExpressionInput('$30.5 + 10,75 xyz')).toBe('30.5+10,75'); + }); +}); + +describe('safeEvaluateExpression', () => { + it.each([ + ['30.5+10.75-12-1.86', { numerator: 2739n, denominator: 100n }], + ['2+3*4', { numerator: 14n, denominator: 1n }], + ['(2+3)*4', { numerator: 20n, denominator: 1n }], + ['10/4', { numerator: 5n, denominator: 2n }], + ['2++3', { numerator: 5n, denominator: 1n }], + ['0.1+0.2', { numerator: 3n, denominator: 10n }], + ['1,5+2', { numerator: 7n, denominator: 2n }], + ['9007199254740992+1', { numerator: 9007199254740993n, denominator: 1n }], + ['-10+5', { numerator: -5n, denominator: 1n }], + ])('evaluates %p exactly', (input, expected) => { + expect(safeEvaluateExpression(input)).toEqual(expected); + }); + + it.each(['', 'abc', '30.5+', '()', '(2+3', '30/0', '1.2.3'])('rejects %p', (input) => { + expect(safeEvaluateExpression(input)).toBeNull(); + }); +}); diff --git a/src/tests/number.test.ts b/src/tests/number.test.ts index 15c14456a..3341b48a9 100644 --- a/src/tests/number.test.ts +++ b/src/tests/number.test.ts @@ -2,6 +2,21 @@ import { currencyConversion, getCurrencyHelpers } from '../utils/numbers'; describe('getCurrencyHelpers', () => { + describe('expressionResultToBigInt', () => { + it('converts exact expression results to minor currency units', () => { + const { expressionResultToBigInt } = getCurrencyHelpers({ currency: 'USD' }); + + expect(expressionResultToBigInt({ numerator: 2739n, denominator: 100n })).toBe(2739n); + expect(expressionResultToBigInt({ numerator: 1n, denominator: 3n })).toBe(33n); + }); + + it('rounds results using the currency precision', () => { + const { expressionResultToBigInt } = getCurrencyHelpers({ currency: 'JPY' }); + + expect(expressionResultToBigInt({ numerator: 7n, denominator: 2n })).toBe(4n); + }); + }); + describe('toUIString', () => { describe('en-US locale', () => { const locale = 'en-US'; diff --git a/src/utils/expression.ts b/src/utils/expression.ts new file mode 100644 index 000000000..8ae44bfc5 --- /dev/null +++ b/src/utils/expression.ts @@ -0,0 +1,162 @@ +export interface ExpressionResult { + numerator: bigint; + denominator: bigint; +} + +export const isExpression = (input: string): boolean => + /[+*/()]/.test(input) || (input.length > 1 && input.slice(1).includes('-')); + +export const sanitizeExpressionInput = (input: string): string => + input.replace(/[^0-9.,+\-*/()]/g, ''); + +const abs = (value: bigint) => (0n > value ? -value : value); + +const gcd = (a: bigint, b: bigint): bigint => (0n === b ? a : gcd(b, a % b)); + +const reduce = ({ numerator, denominator }: ExpressionResult): ExpressionResult => { + if (0n > denominator) { + numerator = -numerator; + denominator = -denominator; + } + + if (0n === numerator) { + return { numerator: 0n, denominator: 1n }; + } + + const divisor = gcd(abs(numerator), denominator); + return { + numerator: numerator / divisor, + denominator: denominator / divisor, + }; +}; + +const add = (left: ExpressionResult, right: ExpressionResult): ExpressionResult => + reduce({ + numerator: left.numerator * right.denominator + right.numerator * left.denominator, + denominator: left.denominator * right.denominator, + }); + +const multiply = (left: ExpressionResult, right: ExpressionResult): ExpressionResult => + reduce({ + numerator: left.numerator * right.numerator, + denominator: left.denominator * right.denominator, + }); + +const divide = (left: ExpressionResult, right: ExpressionResult): ExpressionResult | null => { + if (0n === right.numerator) { + return null; + } + + return reduce({ + numerator: left.numerator * right.denominator, + denominator: left.denominator * right.numerator, + }); +}; + +const negate = (value: ExpressionResult): ExpressionResult => ({ + numerator: -value.numerator, + denominator: value.denominator, +}); + +/** Evaluates an arithmetic expression exactly, returning null for invalid input or division by zero. */ +export const safeEvaluateExpression = (input: string): ExpressionResult | null => { + const normalized = input.replace(/\s/g, '').replaceAll(',', '.'); + if ('' === normalized) { + return null; + } + + let pos = 0; + + const peek = (): string => (pos < normalized.length ? normalized[pos]! : ''); + const consume = (): string => normalized[pos++]!; + + const parseExpression = (): ExpressionResult | null => { + let left = parseTerm(); + if (null === left) { + return null; + } + + while ('+' === peek() || '-' === peek()) { + const op = consume(); + const right = parseTerm(); + if (null === right) { + return null; + } + left = '+' === op ? add(left, right) : add(left, negate(right)); + } + return left; + }; + + const parseTerm = (): ExpressionResult | null => { + let left = parseFactor(); + if (null === left) { + return null; + } + + while ('*' === peek() || '/' === peek()) { + const op = consume(); + const right = parseFactor(); + if (null === right) { + return null; + } + if ('*' === op) { + left = multiply(left, right); + } else { + left = divide(left, right); + if (null === left) { + return null; + } + } + } + return left; + }; + + const parseFactor = (): ExpressionResult | null => { + if ('+' === peek()) { + consume(); + return parseFactor(); + } + if ('-' === peek()) { + consume(); + const factor = parseFactor(); + return null === factor ? null : negate(factor); + } + return parsePrimary(); + }; + + const parsePrimary = (): ExpressionResult | null => { + if ('(' === peek()) { + consume(); + const result = parseExpression(); + if (null === result || ')' !== peek()) { + return null; + } + consume(); + return result; + } + return parseNumber(); + }; + + const parseNumber = (): ExpressionResult | null => { + let numStr = ''; + while (pos < normalized.length && /[0-9.]/.test(peek())) { + numStr += consume(); + } + if ('' === numStr || '.' === numStr) { + return null; + } + + const [integerPart = '', fractionPart = ''] = numStr.split('.'); + if (numStr.split('.').length > 2) { + return null; + } + + return reduce({ + numerator: BigInt(`${integerPart || '0'}${fractionPart}`), + denominator: 10n ** BigInt(fractionPart.length), + }); + }; + + const result = parseExpression(); + return null === result || pos !== normalized.length ? null : result; +}; diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index b39912e58..4d5bb141f 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -1,4 +1,5 @@ import { CURRENCIES, type CurrencyCode, isCurrencyCode } from '~/lib/currency'; +import type { ExpressionResult } from '~/utils/expression'; export const getCurrencyHelpers = ({ locale = 'en-US', @@ -37,6 +38,9 @@ export const getCurrencyHelpers = ({ return parseToBigIntBeforeSubmit(stringNumber); }; + const expressionResultToBigInt = ({ numerator, denominator }: ExpressionResult): bigint => + BigMath.roundDiv(numerator * decimalMultiplierN, denominator); + /* Parse sanitized string to number before submit */ const parseToBigIntBeforeSubmit = (stringNumber: string | number | bigint): bigint => { if (typeof stringNumber === 'number') { @@ -237,6 +241,7 @@ export const getCurrencyHelpers = ({ formatter, sanitizeInput, toSafeBigInt, + expressionResultToBigInt, }; }; From 9d17f8b2705b1adc54a85743cf9272d9370a39ab Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 26 Oct 2026 21:15:04 +1100 Subject: [PATCH 2/4] fix: preserve expressions and currency precision --- src/components/AddExpense/AddExpensePage.tsx | 1 - src/components/ui/currency-input.tsx | 15 ++----- src/tests/expression.test.ts | 10 ++++- src/utils/expression.ts | 43 +++++++++++++++++++- src/utils/numbers.ts | 1 + 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/components/AddExpense/AddExpensePage.tsx b/src/components/AddExpense/AddExpensePage.tsx index 3271e1e07..714b02646 100644 --- a/src/components/AddExpense/AddExpensePage.tsx +++ b/src/components/AddExpense/AddExpensePage.tsx @@ -207,7 +207,6 @@ export const AddOrEditExpensePage: React.FC<{ } } }, [ - t, description, currency, isNegative, diff --git a/src/components/ui/currency-input.tsx b/src/components/ui/currency-input.tsx index 6f873192d..e2e36391d 100644 --- a/src/components/ui/currency-input.tsx +++ b/src/components/ui/currency-input.tsx @@ -25,10 +25,10 @@ const CurrencyInput: React.FC< }) => { const { getCurrencyHelpersCached } = useTranslationWithUtils(undefined); const { + decimalDigits, format, parseToCleanString, toSafeBigInt, - toUIString, expressionResultToBigInt, sanitizeInput, } = getCurrencyHelpersCached(currency); @@ -45,16 +45,7 @@ const CurrencyInput: React.FC< }} onBlur={() => { if (allowExpressions && isExpression(strValue)) { - const evaluated = safeEvaluateExpression(strValue); - if (null === evaluated) { - return; - } - - const bigIntValue = expressionResultToBigInt(evaluated); - return onValueChange({ - strValue: toUIString(bigIntValue, allowNegative, hideSymbol), - bigIntValue, - }); + return; } const formattedValue = format(strValue, { signed: allowNegative, hideSymbol }); @@ -63,7 +54,7 @@ const CurrencyInput: React.FC< onChange={(e) => { const rawValue = e.target.value; if (allowExpressions && isExpression(rawValue)) { - const strValue = sanitizeExpressionInput(rawValue); + const strValue = sanitizeExpressionInput(rawValue, decimalDigits); const evaluated = safeEvaluateExpression(strValue); return onValueChange({ strValue, diff --git a/src/tests/expression.test.ts b/src/tests/expression.test.ts index c0d68a6a1..9be3d146e 100644 --- a/src/tests/expression.test.ts +++ b/src/tests/expression.test.ts @@ -16,7 +16,15 @@ describe('isExpression', () => { describe('sanitizeExpressionInput', () => { it('keeps arithmetic and decimal characters only', () => { - expect(sanitizeExpressionInput('$30.5 + 10,75 xyz')).toBe('30.5+10,75'); + expect(sanitizeExpressionInput('$30.5 + 10,75 xyz', 2)).toBe('30.5+10,75'); + }); + + it('limits each operand to the currency precision', () => { + expect(sanitizeExpressionInput('1.234+3.456/0.789', 2)).toBe('1.23+3.45/0.78'); + }); + + it('drops fractional digits for zero-decimal currencies', () => { + expect(sanitizeExpressionInput('12.34+5.6', 0)).toBe('12+5'); }); }); diff --git a/src/utils/expression.ts b/src/utils/expression.ts index 8ae44bfc5..dd47d1ad6 100644 --- a/src/utils/expression.ts +++ b/src/utils/expression.ts @@ -6,8 +6,47 @@ export interface ExpressionResult { export const isExpression = (input: string): boolean => /[+*/()]/.test(input) || (input.length > 1 && input.slice(1).includes('-')); -export const sanitizeExpressionInput = (input: string): string => - input.replace(/[^0-9.,+\-*/()]/g, ''); +export const sanitizeExpressionInput = (input: string, decimalDigits: number): string => { + let cleaned = ''; + let hasDecimalSeparator = false; + let fractionalDigits = 0; + let ignoreFraction = false; + + input.split('').forEach((character) => { + if (/[0-9]/.test(character)) { + if (ignoreFraction) { + return; + } + if (hasDecimalSeparator) { + if (fractionalDigits >= decimalDigits) { + return; + } + fractionalDigits += 1; + } + cleaned += character; + return; + } + + if ('.' === character || ',' === character) { + if (0 === decimalDigits) { + ignoreFraction = true; + } else if (!hasDecimalSeparator) { + cleaned += character; + hasDecimalSeparator = true; + } + return; + } + + if (['+', '-', '*', '/', '(', ')'].includes(character)) { + cleaned += character; + hasDecimalSeparator = false; + fractionalDigits = 0; + ignoreFraction = false; + } + }); + + return cleaned; +}; const abs = (value: bigint) => (0n > value ? -value : value); diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index 4d5bb141f..3a685858a 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -235,6 +235,7 @@ export const getCurrencyHelpers = ({ }; return { + decimalDigits, parseToCleanString, toUIString, format, From d9aedc6bb6a0e842e45c3347dbcd06a50a7127e5 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 26 Oct 2026 21:33:43 +1100 Subject: [PATCH 3/4] fix: normalize expression decimals to locale --- src/components/ui/currency-input.tsx | 9 ++++++++- src/tests/expression.test.ts | 8 +++++++- src/utils/expression.ts | 11 ++++++++--- src/utils/numbers.ts | 2 ++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/ui/currency-input.tsx b/src/components/ui/currency-input.tsx index e2e36391d..b7ba9c300 100644 --- a/src/components/ui/currency-input.tsx +++ b/src/components/ui/currency-input.tsx @@ -26,6 +26,8 @@ const CurrencyInput: React.FC< const { getCurrencyHelpersCached } = useTranslationWithUtils(undefined); const { decimalDigits, + decimalSeparator, + alternativeDecimalSeparator, format, parseToCleanString, toSafeBigInt, @@ -54,7 +56,12 @@ const CurrencyInput: React.FC< onChange={(e) => { const rawValue = e.target.value; if (allowExpressions && isExpression(rawValue)) { - const strValue = sanitizeExpressionInput(rawValue, decimalDigits); + const strValue = sanitizeExpressionInput( + rawValue, + decimalDigits, + decimalSeparator, + alternativeDecimalSeparator, + ); const evaluated = safeEvaluateExpression(strValue); return onValueChange({ strValue, diff --git a/src/tests/expression.test.ts b/src/tests/expression.test.ts index 9be3d146e..5d50e154d 100644 --- a/src/tests/expression.test.ts +++ b/src/tests/expression.test.ts @@ -16,13 +16,18 @@ describe('isExpression', () => { describe('sanitizeExpressionInput', () => { it('keeps arithmetic and decimal characters only', () => { - expect(sanitizeExpressionInput('$30.5 + 10,75 xyz', 2)).toBe('30.5+10,75'); + expect(sanitizeExpressionInput('$30.5 + 10,75 xyz', 2)).toBe('30.5+10.75'); }); it('limits each operand to the currency precision', () => { expect(sanitizeExpressionInput('1.234+3.456/0.789', 2)).toBe('1.23+3.45/0.78'); }); + it('normalizes alternate decimal marks to the locale separator', () => { + expect(sanitizeExpressionInput('12,5+3.75', 2, '.', ',')).toBe('12.5+3.75'); + expect(sanitizeExpressionInput('12,5+3.75', 2, ',', '.')).toBe('12,5+3,75'); + }); + it('drops fractional digits for zero-decimal currencies', () => { expect(sanitizeExpressionInput('12.34+5.6', 0)).toBe('12+5'); }); @@ -37,6 +42,7 @@ describe('safeEvaluateExpression', () => { ['2++3', { numerator: 5n, denominator: 1n }], ['0.1+0.2', { numerator: 3n, denominator: 10n }], ['1,5+2', { numerator: 7n, denominator: 2n }], + ['12,5+3,75', { numerator: 65n, denominator: 4n }], ['9007199254740992+1', { numerator: 9007199254740993n, denominator: 1n }], ['-10+5', { numerator: -5n, denominator: 1n }], ])('evaluates %p exactly', (input, expected) => { diff --git a/src/utils/expression.ts b/src/utils/expression.ts index dd47d1ad6..b15c83d33 100644 --- a/src/utils/expression.ts +++ b/src/utils/expression.ts @@ -6,7 +6,12 @@ export interface ExpressionResult { export const isExpression = (input: string): boolean => /[+*/()]/.test(input) || (input.length > 1 && input.slice(1).includes('-')); -export const sanitizeExpressionInput = (input: string, decimalDigits: number): string => { +export const sanitizeExpressionInput = ( + input: string, + decimalDigits: number, + decimalSeparator = '.', + alternativeDecimalSeparator = decimalSeparator === '.' ? ',' : '.', +): string => { let cleaned = ''; let hasDecimalSeparator = false; let fractionalDigits = 0; @@ -27,11 +32,11 @@ export const sanitizeExpressionInput = (input: string, decimalDigits: number): s return; } - if ('.' === character || ',' === character) { + if (character === decimalSeparator || character === alternativeDecimalSeparator) { if (0 === decimalDigits) { ignoreFraction = true; } else if (!hasDecimalSeparator) { - cleaned += character; + cleaned += decimalSeparator; hasDecimalSeparator = true; } return; diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index 3a685858a..a295b4000 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -236,6 +236,8 @@ export const getCurrencyHelpers = ({ return { decimalDigits, + decimalSeparator, + alternativeDecimalSeparator, parseToCleanString, toUIString, format, From 85df5f376a7c464af7144303e1fb422e373fbab8 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 26 Oct 2026 22:00:33 +1100 Subject: [PATCH 4/4] fix: remove invalid expression toast --- public/locales/en/common.json | 1 - src/components/AddExpense/AddExpensePage.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 660e3ea95..ee0cab553 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -145,7 +145,6 @@ "group_name_update_failed": "Error in updating group name", "import_failed": "Error importing file", "invalid_currency_code": "Invalid currency code {code}", - "invalid_expression": "Invalid expression", "invalid_split": "Adjust who owes this expense before saving.", "language_change_failed": "Error changing language", "less_than": "File should be less than {size}MB", diff --git a/src/components/AddExpense/AddExpensePage.tsx b/src/components/AddExpense/AddExpensePage.tsx index 714b02646..4b0947260 100644 --- a/src/components/AddExpense/AddExpensePage.tsx +++ b/src/components/AddExpense/AddExpensePage.tsx @@ -116,7 +116,6 @@ export const AddOrEditExpensePage: React.FC<{ if (isExpression(amtStr)) { const evaluated = safeEvaluateExpression(amtStr); if (null === evaluated) { - toast.error(t('errors.invalid_expression')); return; } finalAmount = BigMath.abs(