Skip to content
Snippets Groups Projects
Unverified Commit f329fe21 authored by Jed Fox's avatar Jed Fox Committed by GitHub
Browse files

Add clear typings to the modals (#1359)

parent ba2de7ec
No related branches found
No related tags found
No related merge requests found
import React from 'react';
import { connect } from 'react-redux';
import { useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from 'loot-core/src/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import useFeatureFlag from '../hooks/useFeatureFlag';
import { useActions } from '../hooks/useActions';
import useSyncServerStatus from '../hooks/useSyncServerStatus';
import BudgetSummary from './modals/BudgetSummary';
......@@ -27,21 +24,21 @@ import NordigenInitialise from './modals/NordigenInitialise';
import PlaidExternalMsg from './modals/PlaidExternalMsg';
import SelectLinkedAccounts from './modals/SelectLinkedAccounts';
function Modals({
modalStack,
isHidden,
accounts,
categoryGroups,
categories,
budgetId,
actions,
}) {
const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
export default function Modals() {
const modalStack = useSelector(state => state.modals.modalStack);
const isHidden = useSelector(state => state.modals.isHidden);
const accounts = useSelector(state => state.queries.accounts);
const categoryGroups = useSelector(state => state.queries.categories.grouped);
const categories = useSelector(state => state.queries.categories.list);
const budgetId = useSelector(
state => state.prefs.local && state.prefs.local.id,
);
const actions = useActions();
const syncServerStatus = useSyncServerStatus();
return modalStack
.map(({ name, options = {} }, idx) => {
.map(({ name, options }, idx) => {
const modalProps = {
onClose: actions.popModal,
onBack: actions.popModal,
......@@ -61,7 +58,6 @@ function Modals({
return (
<CreateAccount
modalProps={modalProps}
actions={actions}
syncServerStatus={syncServerStatus}
/>
);
......@@ -91,7 +87,6 @@ function Modals({
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
localAccounts={accounts.filter(acct => acct.closed === 0)}
upgradingAccountId={options.upgradingAccountId}
actions={actions}
/>
);
......@@ -100,9 +95,14 @@ function Modals({
return (
<ConfirmCategoryDelete
modalProps={modalProps}
actions={actions}
category={categories.find(c => c.id === options.category)}
group={categoryGroups.find(g => g.id === options.group)}
category={
'category' in options &&
categories.find(c => c.id === options.category)
}
group={
'group' in options &&
categoryGroups.find(g => g.id === options.group)
}
categoryGroups={categoryGroups}
onDelete={options.onDelete}
/>
......@@ -115,6 +115,7 @@ function Modals({
budgetId={budgetId}
modalProps={modalProps}
actions={actions}
backupDisabled={false}
/>
);
......@@ -148,7 +149,6 @@ function Modals({
return (
<PlaidExternalMsg
modalProps={modalProps}
actions={actions}
onMoveExternal={options.onMoveExternal}
onClose={() => {
options.onClose?.();
......@@ -170,7 +170,6 @@ function Modals({
return (
<NordigenExternalMsg
modalProps={modalProps}
actions={actions}
onMoveExternal={options.onMoveExternal}
onClose={() => {
options.onClose?.();
......@@ -217,8 +216,6 @@ function Modals({
key={name}
modalProps={modalProps}
month={options.month}
actions={actions}
isGoalTemplatesEnabled={isGoalTemplatesEnabled}
/>
);
......@@ -231,15 +228,3 @@ function Modals({
<React.Fragment key={modalStack[idx].name}>{modal}</React.Fragment>
));
}
export default connect(
state => ({
modalStack: state.modals.modalStack,
isHidden: state.modals.isHidden,
accounts: state.queries.accounts,
categoryGroups: state.queries.categories.grouped,
categories: state.queries.categories.list,
budgetId: state.prefs.local && state.prefs.local.id,
}),
dispatch => ({ actions: bindActionCreators(actions, dispatch) }),
)(Modals);
......@@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
import * as actions from 'loot-core/src/client/actions';
// https://react-redux.js.org/api/hooks#recipe-useactions
// eslint-disable-next-line import/no-unused-modules
export function useActions() {
const dispatch = useDispatch();
return useMemo(() => {
......
......@@ -80,6 +80,7 @@ export function loadBudget(
);
if (showBackups) {
// @ts-expect-error manager modals are not yet typed
dispatch(pushModal('load-backup', { budgetId: id }));
}
} else {
......
import * as constants from '../constants';
import type { Modal } from '../state-types/modals';
import type { ActionResult } from './types';
export function pushModal(name: string, options: unknown): ActionResult {
return { type: constants.PUSH_MODAL, name, options };
export function pushModal<M extends Modal>(
name: M['name'],
options: M['options'],
): ActionResult {
// @ts-expect-error TS is unable to determine that `name` and `options` match
let modal: M = { name, options };
return { type: constants.PUSH_MODAL, modal };
}
export function replaceModal(name: string, options: unknown): ActionResult {
return { type: constants.REPLACE_MODAL, name, options };
export function replaceModal<M extends Modal>(
name: M['name'],
options: M['options'],
): ActionResult {
// @ts-expect-error TS is unable to determine that `name` and `options` match
let modal: M = { name, options };
return { type: constants.REPLACE_MODAL, modal };
}
export function popModal(): ActionResult {
......
......@@ -12,15 +12,12 @@ function update(state = initialState, action: Action): ModalsState {
case constants.PUSH_MODAL:
return {
...state,
modalStack: [
...state.modalStack,
{ name: action.name, options: action.options },
],
modalStack: [...state.modalStack, action.modal],
};
case constants.REPLACE_MODAL:
return {
...state,
modalStack: [{ name: action.name, options: action.options }],
modalStack: [action.modal],
};
case constants.POP_MODAL:
return { ...state, modalStack: state.modalStack.slice(0, -1) };
......
import type { AccountEntity } from '../../types/models';
import type { RuleEntity } from '../../types/models/rule';
import type * as constants from '../constants';
// TODO: type this more throughly
type Modal = {
name: string;
options: unknown;
[K in keyof FinanceModals]: {
name: K;
options: FinanceModals[K];
};
}[keyof FinanceModals];
// There is a separate (overlapping!) set of modals for the management app. Fun!
type FinanceModals = {
'import-transactions': {
accountId: string;
filename: string;
onImported: (didChange: boolean) => void;
};
'add-account': null;
'add-local-account': null;
'close-account': {
account: AccountEntity;
balance: number;
canDelete: boolean;
};
'select-linked-accounts': {
accounts: unknown[];
requisitionId: string;
upgradingAccountId: string;
};
'configure-linked-accounts': never;
'confirm-category-delete': { onDelete: () => void } & (
| { category: string }
| { group: string }
);
'load-backup': null;
'manage-rules': { payeeId: string } | null;
'edit-rule': {
rule: RuleEntity;
onSave: (rule: RuleEntity) => void;
};
'merge-unused-payees': {
payeeIds: string[];
targetPayeeId: string;
};
'plaid-external-msg': {
onMoveExternal: () => Promise<void>;
onClose?: () => void;
onSuccess: (data: unknown) => Promise<void>;
};
'nordigen-init': {
onSuccess: () => void;
};
'nordigen-external-msg': {
onMoveExternal: (arg: {
institutionId: string;
}) => Promise<{ error: string } | { data: unknown }>;
onClose?: () => void;
onSuccess: (data: unknown) => Promise<void>;
};
'create-encryption-key': { recreate: boolean } | null;
'fix-encryption-key': {
hasExistingKey: boolean;
cloudFileId: string;
onSuccess?: () => void;
};
'edit-field': {
name: string;
onSubmit: (name: string, value: string) => void;
};
'budget-summary': {
month: string;
};
};
export type PushModalAction = {
type: typeof constants.PUSH_MODAL;
name: string;
options: unknown;
modal: Modal;
};
export type ReplaceModalAction = {
type: typeof constants.REPLACE_MODAL;
name: string;
options: unknown;
modal: Modal;
};
export type PopModalAction = {
......
---
category: Maintenance
authors: [j-f1]
---
Port the modal infrastructure to TypeScript
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment