Security fix for path-traversal attack (#48)
Additionally: - Make expense and budget modal "reset" once closed, saved, or deleted. - Make manifest icons dark - Budgets in small screens should be full-screen - Minor code cleanup Fixes #48
This commit is contained in:
@@ -9,26 +9,33 @@ interface BudgetModalProps {
|
||||
onClickSave: (newBudgetName: string, newBudgetMonth: string, newBudgetValue: number) => Promise<void>;
|
||||
onClickDelete: () => Promise<void>;
|
||||
onClose: () => void;
|
||||
shouldResetForm: boolean;
|
||||
}
|
||||
|
||||
export default function BudgetModal(
|
||||
{ isOpen, budget, onClickSave, onClickDelete, onClose }: BudgetModalProps,
|
||||
{ isOpen, budget, onClickSave, onClickDelete, onClose, shouldResetForm }: BudgetModalProps,
|
||||
) {
|
||||
const newBudgetName = useSignal<string>(budget?.name ?? '');
|
||||
const newBudgetMonth = useSignal<string>(budget?.month ?? new Date().toISOString().substring(0, 10));
|
||||
const newBudgetValue = useSignal<number>(budget?.value ?? 100);
|
||||
|
||||
const resetForm = () => {
|
||||
newBudgetName.value = '';
|
||||
newBudgetMonth.value = new Date().toISOString().substring(0, 10);
|
||||
newBudgetValue.value = 100;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (budget) {
|
||||
newBudgetName.value = budget.name;
|
||||
newBudgetMonth.value = `${budget.month}-15`;
|
||||
newBudgetValue.value = budget.value;
|
||||
} else {
|
||||
newBudgetName.value = '';
|
||||
newBudgetMonth.value = new Date().toISOString().substring(0, 10);
|
||||
newBudgetValue.value = 100;
|
||||
}
|
||||
}, [budget]);
|
||||
|
||||
if (shouldResetForm) {
|
||||
resetForm();
|
||||
}
|
||||
}, [budget, shouldResetForm]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -21,10 +21,11 @@ interface ExpenseModalProps {
|
||||
) => Promise<void>;
|
||||
onClickDelete: () => Promise<void>;
|
||||
onClose: () => void;
|
||||
shouldResetForm: boolean;
|
||||
}
|
||||
|
||||
export default function ExpenseModal(
|
||||
{ isOpen, expense, budgets, onClickSave, onClickDelete, onClose }: ExpenseModalProps,
|
||||
{ isOpen, expense, budgets, onClickSave, onClickDelete, onClose, shouldResetForm }: ExpenseModalProps,
|
||||
) {
|
||||
const newExpenseCost = useSignal<number | ''>(expense?.cost ?? '');
|
||||
const newExpenseDescription = useSignal<string>(expense?.description ?? '');
|
||||
@@ -34,6 +35,14 @@ export default function ExpenseModal(
|
||||
const suggestions = useSignal<string[]>([]);
|
||||
const showSuggestions = useSignal<boolean>(false);
|
||||
|
||||
const resetForm = () => {
|
||||
newExpenseCost.value = '';
|
||||
newExpenseDescription.value = '';
|
||||
newExpenseBudget.value = 'Misc';
|
||||
newExpenseDate.value = '';
|
||||
newExpenseIsRecurring.value = false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (expense) {
|
||||
newExpenseCost.value = expense.cost;
|
||||
@@ -42,15 +51,12 @@ export default function ExpenseModal(
|
||||
newExpenseDate.value = expense.date;
|
||||
newExpenseIsRecurring.value = expense.is_recurring;
|
||||
showSuggestions.value = false;
|
||||
} else {
|
||||
newExpenseCost.value = '';
|
||||
newExpenseDescription.value = '';
|
||||
newExpenseBudget.value = 'Misc';
|
||||
newExpenseDate.value = '';
|
||||
newExpenseIsRecurring.value = false;
|
||||
showSuggestions.value = false;
|
||||
}
|
||||
}, [expense]);
|
||||
|
||||
if (shouldResetForm) {
|
||||
resetForm();
|
||||
}
|
||||
}, [expense, shouldResetForm]);
|
||||
|
||||
const sortedBudgetNames = budgets.map((budget) => budget.name).sort();
|
||||
|
||||
@@ -225,14 +231,15 @@ export default function ExpenseModal(
|
||||
: null}
|
||||
<button
|
||||
class='px-5 py-2 bg-slate-600 hover:bg-slate-500 text-white cursor-pointer rounded-md mr-2'
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
onClickSave(
|
||||
newExpenseCost.value as number,
|
||||
newExpenseDescription.value,
|
||||
newExpenseBudget.value,
|
||||
newExpenseDate.value,
|
||||
newExpenseIsRecurring.value,
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
{expense ? 'Update' : 'Create'}
|
||||
</button>
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function ListBudgets(
|
||||
return (
|
||||
<div
|
||||
onClick={() => budget.id === 'total' ? swapView('chart') : onClickEditBudget(budget.id)}
|
||||
class='flex max-w-sm gap-y-4 gap-x-4 rounded shadow-md bg-slate-700 relative cursor-pointer py-4 px-6 hover:opacity-80'
|
||||
class='flex w-full md:w-auto max-w-sm gap-y-4 gap-x-4 rounded shadow-md bg-slate-700 relative cursor-pointer py-4 px-6 hover:opacity-80'
|
||||
>
|
||||
<article class='order-first tracking-tight flex flex-col text-base mr-4'>
|
||||
<span class='font-bold text-lg' title='Amount used from budgeted amount'>
|
||||
|
||||
@@ -59,6 +59,8 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
const editingExpense = useSignal<Expense | null>(null);
|
||||
const isBudgetModalOpen = useSignal<boolean>(false);
|
||||
const editingBudget = useSignal<Budget | null>(null);
|
||||
const shouldResetExpenseModal = useSignal<boolean>(false);
|
||||
const shouldResetBudgetModal = useSignal<boolean>(false);
|
||||
const searchTimeout = useSignal<ReturnType<typeof setTimeout>>(0);
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' });
|
||||
@@ -197,6 +199,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
return;
|
||||
}
|
||||
|
||||
shouldResetExpenseModal.value = false;
|
||||
editingExpense.value = null;
|
||||
isExpenseModalOpen.value = true;
|
||||
}
|
||||
@@ -209,6 +212,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
return;
|
||||
}
|
||||
|
||||
shouldResetBudgetModal.value = false;
|
||||
editingBudget.value = null;
|
||||
isBudgetModalOpen.value = true;
|
||||
}
|
||||
@@ -221,6 +225,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
return;
|
||||
}
|
||||
|
||||
shouldResetExpenseModal.value = false;
|
||||
editingExpense.value = expenses.value.find((expense) => expense.id === expenseId)!;
|
||||
isExpenseModalOpen.value = true;
|
||||
}
|
||||
@@ -238,6 +243,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
return;
|
||||
}
|
||||
|
||||
shouldResetBudgetModal.value = false;
|
||||
editingBudget.value = budgets.value.find((budget) => budget.id === budgetId)!;
|
||||
isBudgetModalOpen.value = true;
|
||||
}
|
||||
@@ -292,6 +298,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
|
||||
isExpenseModalOpen.value = false;
|
||||
editingExpense.value = null;
|
||||
shouldResetExpenseModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -326,6 +333,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
budgets.value = [...result.newBudgets];
|
||||
|
||||
isExpenseModalOpen.value = false;
|
||||
shouldResetExpenseModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -379,6 +387,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
|
||||
isBudgetModalOpen.value = false;
|
||||
editingBudget.value = null;
|
||||
shouldResetBudgetModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -410,6 +419,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
budgets.value = [...result.newBudgets];
|
||||
|
||||
isBudgetModalOpen.value = false;
|
||||
shouldResetBudgetModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -460,6 +470,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
|
||||
isExpenseModalOpen.value = false;
|
||||
editingExpense.value = null;
|
||||
shouldResetExpenseModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -508,6 +519,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
|
||||
isBudgetModalOpen.value = false;
|
||||
editingBudget.value = null;
|
||||
shouldResetBudgetModal.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(error);
|
||||
@@ -519,11 +531,13 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
function onCloseExpense() {
|
||||
isExpenseModalOpen.value = false;
|
||||
editingExpense.value = null;
|
||||
shouldResetExpenseModal.value = true;
|
||||
}
|
||||
|
||||
function onCloseBudget() {
|
||||
isBudgetModalOpen.value = false;
|
||||
editingBudget.value = null;
|
||||
shouldResetBudgetModal.value = true;
|
||||
}
|
||||
|
||||
function toggleNewOptionsDropdown() {
|
||||
@@ -780,6 +794,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
onClickSave={onClickSaveExpense}
|
||||
onClickDelete={onClickDeleteExpense}
|
||||
onClose={onCloseExpense}
|
||||
shouldResetForm={shouldResetExpenseModal.value}
|
||||
/>
|
||||
|
||||
<BudgetModal
|
||||
@@ -788,6 +803,7 @@ export default function MainExpenses({ initialBudgets, initialExpenses, initialM
|
||||
onClickSave={onClickSaveBudget}
|
||||
onClickDelete={onClickDeleteBudget}
|
||||
onClose={onCloseBudget}
|
||||
shouldResetForm={shouldResetBudgetModal.value}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user