Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support adding expenses #8

Merged
merged 9 commits into from
Dec 26, 2023
1 change: 1 addition & 0 deletions src/components/MonthlyOverview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
{:else}
<p>No expenses found.</p>
{/if}
<a href="/expense">Add Expense</a>

<h2>Income</h2>
{#if data.income.length}
Expand Down
17 changes: 17 additions & 0 deletions src/components/inputs/AmountInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script>
export let amount = '';
</script>

<div>
<label for="amount">Amount</label>
<input
id="amount"
type="text"
name="amount"
inputmode="numeric"
pattern={'^(\\s{1,})?\\$?\\d{1,3}(,?\\d{3})*(\\.\\d{1,2})?(\\s{1,})?$'}
required
aria-required="true"
value={amount}
/>
</div>
47 changes: 47 additions & 0 deletions src/components/inputs/CategoryInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script>
export let categories = [];
export let selectedCategory;
</script>

<fieldset>
<legend>Category</legend>
<div>
<div>
<input
id="category__new"
type="radio"
name="category"
value="NEW_CATEGORY"
required
bind:group={selectedCategory}
/>
<label for="category__new">New Category</label>
</div>
{#each categories as { category }, index}
<div>
<input
id="category__{index}"
type="radio"
name="category"
value={category}
required
bind:group={selectedCategory}
/>
<label for="category__{index}">{category}</label>
</div>
{/each}
</div>
</fieldset>
{#if selectedCategory === 'NEW_CATEGORY'}
<div>
<label for="new-category">New Category</label>
<input
id="new-category"
type="text"
name="new-category"
required
aria-required="true"
autocapitalize="on"
/>
</div>
{/if}
52 changes: 52 additions & 0 deletions src/components/inputs/DateInputs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script>
export let date = new Date();

const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
</script>

<fieldset>
<legend>Date</legend>
<div>
<div>
<label for="year">Year</label>
<input
id="year"
type="text"
name="year"
inputmode="numeric"
pattern="^[1-2][0-9][0-9][0-9]$"
required
aria-required="true"
value={year}
/>
</div>
<div>
<label for="month">Month</label>
<input
id="month"
type="text"
name="month"
inputmode="numeric"
pattern="^(0?[1-9]|1[012])$"
required
aria-required="true"
value={month}
/>
</div>
<div>
<label for="day">Day</label>
<input
id="day"
type="text"
name="day"
inputmode="numeric"
pattern="^(0?[1-9]|[12]\d|3[01])$"
required
aria-required="true"
value={day}
/>
</div>
</div>
</fieldset>
8 changes: 8 additions & 0 deletions src/components/inputs/DescriptionInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
export let description = '';
</script>

<div>
<label for="description">Description (optional)</label>
<input id="description" type="text" name="description" value={description} autocapitalize="on" />
</div>
9 changes: 9 additions & 0 deletions src/lib/format-inputs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const formatAmount = (value) => Number(value.toString().replace(/[^0-9|.]/g, ''));

export const formatDate = (year, month, day) => {
const numericYear = Number(year);
const numericMonth = Number(month) - 1;
const numericDay = Number(day);

return new Date(numericYear, numericMonth, numericDay);
};
48 changes: 48 additions & 0 deletions src/routes/expense/+page.server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { fail, redirect } from '@sveltejs/kit';
import { formatAmount, formatDate } from '$lib/format-inputs.js';

export const load = async ({ locals: { supabase } }) => {
try {
const { data: categories } = await supabase
.from('expense_categories')
.select('category')
.order('category');

return {
categories,
};
} catch (error) {
return fail(500, { message: 'Server error. Try again later.', success: false });
}
};

export const actions = {
default: async ({ request, locals: { supabase } }) => {
const formData = await request.formData();
const amount = formatAmount(formData.get('amount'));

const selectedCategory = formData.get('category');
const newCategory = formData.get('new-category');
const category = newCategory ?? selectedCategory;

const description = formData.get('description') || category;

const year = formData.get('year');
const month = formData.get('month');
const day = formData.get('day');
const date = formatDate(year, month, day);

const {
data: { user },
} = await supabase.auth.getUser();
const { error } = await supabase
.from('expenses')
.insert({ user_id: user.id, date, category, description, amount });

if (error) {
return fail(500, { message: 'Server error. Try again later.', success: false });
}

throw redirect(303, '/overview');
},
};
20 changes: 20 additions & 0 deletions src/routes/expense/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script>
import AmountInput from '../../components/inputs/AmountInput.svelte';
import CategoryInput from '../../components/inputs/CategoryInput.svelte';
import DateInputs from '../../components/inputs/DateInputs.svelte';
import DescriptionInput from '../../components/inputs/DescriptionInput.svelte';

export let data;
</script>

<h1>Add Expense</h1>

<form method="POST">
<AmountInput />
<CategoryInput categories={data.categories} />
<DescriptionInput />
<DateInputs />
<div>
<button type="submit">Save Expense</button>
</div>
</form>