query.filter({ completed: false }), []),
- );
-
- let [filter, setFilter] = useState('');
-
- if (scheduleData == null) {
- return null;
- }
-
- let { schedules, statuses } = scheduleData;
-
- async function onSelect(scheduleId) {
- if (ids && ids.length > 0) {
- await send('transactions-batch-update', {
- updated: ids.map(id => ({ id, schedule: scheduleId })),
- });
- }
- actions.popModal();
- }
-
- return (
-
-
- Choose a schedule to link these transactions to:
-
-
-
-
-
-
- );
-}
diff --git a/packages/desktop-client/src/components/schedules/LinkSchedule.tsx b/packages/desktop-client/src/components/schedules/LinkSchedule.tsx
new file mode 100644
index 00000000000..094184313ee
--- /dev/null
+++ b/packages/desktop-client/src/components/schedules/LinkSchedule.tsx
@@ -0,0 +1,95 @@
+import React, { useCallback, useRef, useState } from 'react';
+
+import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
+import { send } from 'loot-core/src/platform/client/fetch';
+import { type Query } from 'loot-core/src/shared/query';
+
+import { type BoundActions } from '../../hooks/useActions';
+import { type CommonModalProps } from '../../types/modals';
+import Modal from '../common/Modal';
+import Search from '../common/Search';
+import Text from '../common/Text';
+import View from '../common/View';
+
+import { ROW_HEIGHT, SchedulesTable } from './SchedulesTable';
+
+export default function ScheduleLink({
+ modalProps,
+ actions,
+ transactionIds: ids,
+}: {
+ actions: BoundActions;
+ modalProps?: CommonModalProps;
+ transactionIds: string[];
+}) {
+ let [filter, setFilter] = useState('');
+
+ let scheduleData = useSchedules({
+ transform: useCallback((q: Query) => q.filter({ completed: false }), []),
+ });
+
+ let searchInput = useRef(null);
+ if (scheduleData == null) {
+ return null;
+ }
+
+ let { schedules, statuses } = scheduleData;
+
+ async function onSelect(scheduleId: string) {
+ if (ids?.length > 0) {
+ await send('transactions-batch-update', {
+ updated: ids.map(id => ({ id, schedule: scheduleId })),
+ });
+ }
+ actions.popModal();
+ }
+
+ return (
+
+
+
+ Choose the schedule{' '}
+ {ids?.length > 1
+ ? `these ${ids.length} transactions belong`
+ : `this transaction belongs`}{' '}
+ to:
+
+
+
+
+
+ {}}
+ onSelect={onSelect}
+ schedules={schedules}
+ statuses={statuses}
+ style={null}
+ tableStyle={{ marginInline: -20 }}
+ />
+
+
+ );
+}
diff --git a/packages/desktop-client/src/components/select/RecurringSchedulePicker.js b/packages/desktop-client/src/components/select/RecurringSchedulePicker.js
index adbb84dc3f6..a98b2ebe425 100644
--- a/packages/desktop-client/src/components/select/RecurringSchedulePicker.js
+++ b/packages/desktop-client/src/components/select/RecurringSchedulePicker.js
@@ -14,6 +14,7 @@ import Select from '../common/Select';
import Stack from '../common/Stack';
import Text from '../common/Text';
import View from '../common/View';
+import { Checkbox } from '../forms';
import { useTooltip, Tooltip } from '../tooltips';
import DateSelect from './DateSelect';
@@ -54,6 +55,8 @@ function parseConfig(config) {
interval: 1,
frequency: 'monthly',
patterns: [createMonthlyRecurrence(monthUtils.currentDay())],
+ skipWeekend: false,
+ weekendSolveMode: 'before',
}
);
}
@@ -131,6 +134,22 @@ function reducer(state, action) {
patterns: state.config.patterns.filter(p => p !== action.recurrence),
},
};
+ case 'set-skip-weekend':
+ return {
+ ...state,
+ config: {
+ ...state.config,
+ skipWeekend: action.skipWeekend,
+ },
+ };
+ case 'set-weekend-solve':
+ return {
+ ...state,
+ config: {
+ ...state.config,
+ weekendSolveMode: action.value,
+ },
+ };
default:
return state;
}
@@ -254,6 +273,9 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) {
config: parseConfig(currentConfig),
});
+ let skipWeekend = state.config.hasOwnProperty('skipWeekend')
+ ? state.config.skipWeekend
+ : false;
let dateFormat = useSelector(
state => state.prefs.local.dateFormat || 'MM/dd/yyyy',
);
@@ -346,6 +368,56 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) {
config.patterns.length > 0 && (
)}
+
+
+ {
+ dispatch({
+ type: 'set-skip-weekend',
+ skipWeekend: e.target.checked,
+ });
+ }}
+ />
+
+
+
CSSProperties;
privacyFilter?: ConditionalPrivacyFilterProps['privacyFilter'];
+ ['data-testid']?: string;
};
function CellValue({
@@ -31,6 +32,7 @@ function CellValue({
style,
getStyle,
privacyFilter,
+ 'data-testid': testId,
}: CellValueProps) {
let { fullSheetName } = useSheetName(binding);
let sheetValue = useSheetValue(binding);
@@ -53,7 +55,7 @@ function CellValue({
style,
getStyle && getStyle(sheetValue),
]}
- data-testid={fullSheetName}
+ data-testid={testId || fullSheetName}
data-cellname={fullSheetName}
>
{formatter ? formatter(sheetValue) : format(sheetValue, type)}
diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx
index e5d712e7b2d..9bbb2f2243d 100644
--- a/packages/desktop-client/src/components/table.tsx
+++ b/packages/desktop-client/src/components/table.tsx
@@ -506,6 +506,7 @@ type CellButtonProps = {
onSelect?: (e) => void;
onEdit?: () => void;
children: ReactNode;
+ className?: string;
};
export const CellButton = forwardRef(
(
@@ -518,6 +519,7 @@ export const CellButton = forwardRef(
onSelect,
onEdit,
children,
+ className,
},
ref,
) => {
@@ -537,7 +539,7 @@ export const CellButton = forwardRef(
return (
{
if (e.key === 'x' || e.key === ' ') {
@@ -607,6 +609,7 @@ type SelectCellProps = Omit, 'children'> & {
partial?: boolean;
onEdit?: () => void;
onSelect?: (e) => void;
+ buttonProps?: Partial;
};
export function SelectCell({
focused,
@@ -614,6 +617,7 @@ export function SelectCell({
style,
onSelect,
onEdit,
+ buttonProps = {},
...props
}: SelectCellProps) {
return (
@@ -652,6 +656,7 @@ export function SelectCell({
onEdit={onEdit}
onSelect={onSelect}
clickBehavior="none"
+ {...buttonProps}
>
{selected && }
diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js
index d5d3b66b6c2..9b766b97f98 100644
--- a/packages/desktop-client/src/components/transactions/MobileTransaction.js
+++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js
@@ -373,7 +373,7 @@ class TransactionEditInner extends PureComponent {
}}
>
{payeeId == null
? adding
@@ -473,6 +474,7 @@ class TransactionEditInner extends PureComponent {
this.onClick(transaction.id, 'payee')}
+ data-testid="payee-field"
/>
@@ -506,6 +508,7 @@ class TransactionEditInner extends PureComponent {
//
// }
onClick={() => this.onClick(transaction.id, 'category')}
+ data-testid="category-field"
/>
) : (
@@ -521,6 +524,7 @@ class TransactionEditInner extends PureComponent {
disabled={!adding}
value={account ? account.name : null}
onClick={() => this.onClick(transaction.id, 'account')}
+ data-testid="account-field"
/>
diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.js b/packages/desktop-client/src/components/transactions/TransactionsTable.js
index c068ab9fb1f..dcaa5b8cd72 100644
--- a/packages/desktop-client/src/components/transactions/TransactionsTable.js
+++ b/packages/desktop-client/src/components/transactions/TransactionsTable.js
@@ -53,7 +53,7 @@ import CheveronDown from '../../icons/v1/CheveronDown';
import ArrowsSynchronize from '../../icons/v2/ArrowsSynchronize';
import CalendarIcon from '../../icons/v2/Calendar';
import Hyperlink2 from '../../icons/v2/Hyperlink2';
-import { styles, theme } from '../../style';
+import { colors, styles, theme } from '../../style';
import AccountAutocomplete from '../autocomplete/AccountAutocomplete';
import CategoryAutocomplete from '../autocomplete/CategorySelect';
import PayeeAutocomplete from '../autocomplete/PayeeAutocomplete';
@@ -414,6 +414,17 @@ function StatusCell({
let isClearedField = status === 'cleared' || status == null;
let statusProps = getStatusProps(status);
+ let statusColor =
+ status === 'cleared'
+ ? colors.g5
+ : status === 'missed'
+ ? colors.r6
+ : status === 'due'
+ ? colors.y5
+ : selected
+ ? colors.b7
+ : colors.n7;
+
function onSelect() {
if (isClearedField) {
onUpdate('cleared', !(status === 'cleared'));
@@ -451,7 +462,7 @@ function StatusCell({
style: {
width: 13,
height: 13,
- color: statusProps.color,
+ color: statusColor,
marginTop: status === 'due' ? -1 : 0,
},
})}
@@ -806,7 +817,6 @@ const Transaction = memo(function Transaction(props) {
let backgroundFocus = focusedField === 'select';
let amountStyle = hideFraction ? { letterSpacing: -0.5 } : null;
- let statusProps = getStatusProps(notes);
let runningBalance = !isTemporaryId(id)
? balance
: balance + (_inverse ? -1 : 1) * amount;
@@ -822,9 +832,15 @@ const Transaction = memo(function Transaction(props) {
: theme.tableBackground,
},
{
- ':hover': {
+ ':hover': !(backgroundFocus || selected) && {
backgroundColor: theme.tableRowBackgroundHover,
},
+ '& .hover-visible': {
+ opacity: 0,
+ },
+ ':hover .hover-visible': {
+ opacity: 1,
+ },
},
highlighted || selected
? { color: theme.tableRowBackgroundHighlightText }
@@ -832,6 +848,7 @@ const Transaction = memo(function Transaction(props) {
style,
isPreview && {
color: theme.tableTextInactive,
+ backgroundColor: !selected ? '#fcfcfc' : undefined,
fontStyle: 'italic',
},
_unmatched && { opacity: 0.5 },
@@ -875,7 +892,10 @@ const Transaction = memo(function Transaction(props) {
) : (
{
dispatchSelected({ type: 'select', id: transaction.id, event: e });
@@ -1026,8 +1046,22 @@ const Transaction = memo(function Transaction(props) {
{() => (
unknown> =
? ReturnType
: ReturnType;
-type BoundActions = {
+export type BoundActions = {
[Key in keyof typeof actions]: (
...args: Parameters<(typeof actions)[Key]>
) => ActionReturnType<(typeof actions)[Key]>;
diff --git a/packages/desktop-client/src/style/themes/light.ts b/packages/desktop-client/src/style/themes/light.ts
index 19a86626739..af72c18fedb 100644
--- a/packages/desktop-client/src/style/themes/light.ts
+++ b/packages/desktop-client/src/style/themes/light.ts
@@ -29,7 +29,7 @@ export const tableTextSelected = colorPalette.navy700;
export const tableTextHover = colorPalette.navy900;
export const tableTextEditing = colorPalette.navy50;
export const tableTextEditingBackground = colorPalette.blue500;
-export const tableTextInactive = colorPalette.navy200;
+export const tableTextInactive = colorPalette.navy500;
export const tableHeaderText = colorPalette.navy600;
export const tableHeaderBackground = colorPalette.white;
export const tableBorder = colorPalette.navy100;
diff --git a/packages/desktop-client/src/types/modals.d.ts b/packages/desktop-client/src/types/modals.d.ts
new file mode 100644
index 00000000000..be9f5384821
--- /dev/null
+++ b/packages/desktop-client/src/types/modals.d.ts
@@ -0,0 +1,10 @@
+import { type PopModalAction } from 'loot-core/src/client/state-types/modals';
+
+export type CommonModalProps = {
+ onClose: () => PopModalAction;
+ onBack: () => PopModalAction;
+ showBack: boolean;
+ isCurrent: boolean;
+ isHidden: boolean;
+ stackIndex: number;
+};
diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx
index 61e2419237d..3603561f854 100644
--- a/packages/loot-core/src/client/data-hooks/schedules.tsx
+++ b/packages/loot-core/src/client/data-hooks/schedules.tsx
@@ -1,5 +1,6 @@
import React, { createContext, useEffect, useState, useContext } from 'react';
+import { type Query } from '../../shared/query';
import { getStatus, getHasTransactionsQuery } from '../../shared/schedules';
import q, { liveQuery } from '../query-helpers';
@@ -18,7 +19,7 @@ function loadStatuses(schedules, onData) {
});
}
-type UseSchedulesArgs = { transform?: (v: T) => T };
+type UseSchedulesArgs = { transform?: (q: Query) => Query };
export function useSchedules({ transform }: UseSchedulesArgs = {}) {
let [data, setData] = useState(null);
diff --git a/packages/loot-core/src/server/accounts/rules.ts b/packages/loot-core/src/server/accounts/rules.ts
index 87203e2c959..635f508e1f7 100644
--- a/packages/loot-core/src/server/accounts/rules.ts
+++ b/packages/loot-core/src/server/accounts/rules.ts
@@ -27,7 +27,13 @@ function parseRecurDate(desc) {
return {
type: 'recur',
- schedule: new RSchedule({ rrules: rules }),
+ schedule: new RSchedule({
+ rrules: rules,
+ data: {
+ skipWeekend: desc.skipWeekend,
+ weekendSolve: desc.weekendSolveMode,
+ },
+ }),
};
} catch (e) {
throw new RuleError('parse-recur-date', e.message);
diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts
index ef41374360b..fc45e1f8db1 100644
--- a/packages/loot-core/src/server/accounts/sync.ts
+++ b/packages/loot-core/src/server/accounts/sync.ts
@@ -752,29 +752,27 @@ export async function syncGoCardlessAccount(
'SELECT date FROM v_transactions WHERE account = ? ORDER BY date ASC LIMIT 1',
[id],
);
- const startingDate = db.fromDateRepr(startingTransaction.date);
- // assert(startingTransaction)
+ const startingDate = monthUtils.parseDate(
+ db.fromDateRepr(startingTransaction.date),
+ );
- // Get all transactions since the latest transaction, plus any 5
- // days before the latest transaction. This gives us a chance to
- // resolve any transactions that were entered manually.
- //
- // TODO: What this really should do is query the last imported_id
- // and since then
- let date = monthUtils.subDays(db.fromDateRepr(latestTransaction.date), 31);
+ const startDate = monthUtils.dayFromDate(
+ dateFns.max([
+ // Many GoCardless integrations do not support getting more than 90 days
+ // worth of data, so make that the earliest possible limit.
+ monthUtils.parseDate(monthUtils.subDays(monthUtils.currentDay(), 90)),
- // Never download transactions before the starting date. This was
- // when the account was added to the system.
- if (date < startingDate) {
- date = startingDate;
- }
+ // Never download transactions before the starting date.
+ startingDate,
+ ]),
+ );
let { transactions, accountBalance } = await downloadGoCardlessTransactions(
userId,
userKey,
acctId,
bankId,
- date,
+ startDate,
);
if (transactions.length === 0) {
@@ -789,8 +787,8 @@ export async function syncGoCardlessAccount(
return result;
});
} else {
- // Otherwise, download transaction for the past 30 days
- const startingDay = monthUtils.subDays(monthUtils.currentDay(), 30);
+ // Otherwise, download transaction for the past 90 days
+ const startingDay = monthUtils.subDays(monthUtils.currentDay(), 90);
const { transactions, startingBalance } =
await downloadGoCardlessTransactions(
@@ -798,7 +796,7 @@ export async function syncGoCardlessAccount(
userKey,
acctId,
bankId,
- dateFns.format(dateFns.parseISO(startingDay), 'yyyy-MM-dd'),
+ startingDay,
);
// We need to add a transaction that represents the starting
diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts
index 7939123c11e..88a2e12d457 100644
--- a/packages/loot-core/src/server/schedules/app.ts
+++ b/packages/loot-core/src/server/schedules/app.ts
@@ -84,6 +84,12 @@ export function getNextDate(dateCond, start = new Date()) {
if (dates.length > 0) {
let date = dates[0].date;
+ if (value.schedule.data.skipWeekend) {
+ date = getDateWithSkippedWeekend(
+ date,
+ value.schedule.data.weekendSolve,
+ );
+ }
return dayFromDate(date);
}
}
@@ -372,7 +378,12 @@ async function getUpcomingDates({ config, count }) {
return schedule
.occurrences({ start: d.startOfDay(new Date()), take: count })
.toArray()
- .map(date => dayFromDate(date.date));
+ .map(date =>
+ config.skipWeekend
+ ? getDateWithSkippedWeekend(date.date, config.weekendSolveMode)
+ : date.date,
+ )
+ .map(date => dayFromDate(date));
} catch (err) {
captureBreadcrumb(config);
throw err;
@@ -564,4 +575,17 @@ app.events.on('sync', ({ type, subtype }) => {
}
});
+function getDateWithSkippedWeekend(date, solveMode) {
+ if (d.isWeekend(date)) {
+ if (solveMode === 'after') {
+ return d.nextMonday(date);
+ } else if (solveMode === 'before') {
+ return d.previousFriday(date);
+ } else {
+ throw new Error('Unknown weekend solve mode, this should not happen!');
+ }
+ }
+ return date;
+}
+
export default app;
diff --git a/upcoming-release-notes/1484.md b/upcoming-release-notes/1484.md
new file mode 100644
index 00000000000..b864ff6e864
--- /dev/null
+++ b/upcoming-release-notes/1484.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [kyrias]
+---
+
+Fetch GoCardless transactions from the last 90 days or since first transaction.
diff --git a/upcoming-release-notes/1501.md b/upcoming-release-notes/1501.md
new file mode 100644
index 00000000000..f3c84192ea2
--- /dev/null
+++ b/upcoming-release-notes/1501.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [trevdor]
+---
+
+Fix collapsed schedules table in Link Schedule modal
diff --git a/upcoming-release-notes/1505.md b/upcoming-release-notes/1505.md
new file mode 100644
index 00000000000..4895b511bb5
--- /dev/null
+++ b/upcoming-release-notes/1505.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [pole95]
+---
+
+Allow schedules to skip weekends, and automatically reschedule to before or after the weekend.
diff --git a/upcoming-release-notes/1521.md b/upcoming-release-notes/1521.md
new file mode 100644
index 00000000000..e63435e87d7
--- /dev/null
+++ b/upcoming-release-notes/1521.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [MatissJanis]
+---
+
+Add e2e tests for mobile views
diff --git a/upcoming-release-notes/1533.md b/upcoming-release-notes/1533.md
new file mode 100644
index 00000000000..67302f9f949
--- /dev/null
+++ b/upcoming-release-notes/1533.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [MatissJanis]
+---
+
+Fix schedule colors in transaction table
diff --git a/upcoming-release-notes/1535.md b/upcoming-release-notes/1535.md
new file mode 100644
index 00000000000..78d3a1a5a2a
--- /dev/null
+++ b/upcoming-release-notes/1535.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [joel-jeremy]
+---
+
+Port App to functional component
diff --git a/upcoming-release-notes/1539.md b/upcoming-release-notes/1539.md
new file mode 100644
index 00000000000..c8d91b557a7
--- /dev/null
+++ b/upcoming-release-notes/1539.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [shall0pass]
+---
+
+Mobile: Don't show hidden categories
diff --git a/upcoming-release-notes/1540.md b/upcoming-release-notes/1540.md
new file mode 100644
index 00000000000..2f7d7c4ae65
--- /dev/null
+++ b/upcoming-release-notes/1540.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [shall0pass]
+---
+
+Mobile: Show the correct To Budget amount on Budget Summary
diff --git a/upcoming-release-notes/1541.md b/upcoming-release-notes/1541.md
new file mode 100644
index 00000000000..80345f4dc6c
--- /dev/null
+++ b/upcoming-release-notes/1541.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [MatissJanis]
+---
+
+Fix more darkmode regressions - transaction table, csv import modal
diff --git a/upcoming-release-notes/1545.md b/upcoming-release-notes/1545.md
new file mode 100644
index 00000000000..a49afd9d648
--- /dev/null
+++ b/upcoming-release-notes/1545.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [MatissJanis]
+---
+
+Mobile: add transaction creation button in the footer
diff --git a/upcoming-release-notes/1546.md b/upcoming-release-notes/1546.md
new file mode 100644
index 00000000000..3f72299a747
--- /dev/null
+++ b/upcoming-release-notes/1546.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [MatissJanis]
+---
+
+Mobile: hide sync button when sync is not active
diff --git a/upcoming-release-notes/1548.md b/upcoming-release-notes/1548.md
new file mode 100644
index 00000000000..0532eefec8a
--- /dev/null
+++ b/upcoming-release-notes/1548.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [MatissJanis]
+---
+
+Category spending: improving the visual style of the side-nav
diff --git a/upcoming-release-notes/1550.md b/upcoming-release-notes/1550.md
new file mode 100644
index 00000000000..24e5cb8d4c0
--- /dev/null
+++ b/upcoming-release-notes/1550.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [zigamacele]
+---
+
+Fixed expanding mobile header, aligned elements center
diff --git a/upcoming-release-notes/1551.md b/upcoming-release-notes/1551.md
new file mode 100644
index 00000000000..f0f8068ced3
--- /dev/null
+++ b/upcoming-release-notes/1551.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [trevdor]
+---
+
+Mobile: transaction entry screen should apply the same negative/positive logic to Amount whether or not it is focused for editing at the time Add Transaction is pressed.
diff --git a/upcoming-release-notes/1552.md b/upcoming-release-notes/1552.md
new file mode 100644
index 00000000000..7c0ffd41565
--- /dev/null
+++ b/upcoming-release-notes/1552.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [zigamacele]
+---
+
+Unified fatal error design
diff --git a/yarn.lock b/yarn.lock
index 8ab7e5539a5..d4250ebcbdd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -81,6 +81,7 @@ __metadata:
react-dnd: ^16.0.1
react-dnd-html5-backend: ^16.0.1
react-dom: 18.2.0
+ react-error-boundary: ^4.0.11
react-merge-refs: ^1.1.0
react-modal: 3.16.1
react-redux: 7.2.1
@@ -15466,6 +15467,17 @@ __metadata:
languageName: node
linkType: hard
+"react-error-boundary@npm:^4.0.11":
+ version: 4.0.11
+ resolution: "react-error-boundary@npm:4.0.11"
+ dependencies:
+ "@babel/runtime": ^7.12.5
+ peerDependencies:
+ react: ">=16.13.1"
+ checksum: b3c157fea4e8f78411e9aa0fbf5241f6907b66ede1cd8b7bb22faaeb0339ebeb3dc8e63bf90ef3f740bfa8fd994ca6edf975089cd371b664ad6c2735e7512d38
+ languageName: node
+ linkType: hard
+
"react-error-overlay@npm:6.0.9":
version: 6.0.9
resolution: "react-error-overlay@npm:6.0.9"