aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Berwaldt <zberwaldt@tutamail.com>2024-03-01 18:17:42 -0500
committerZach Berwaldt <zberwaldt@tutamail.com>2024-03-01 18:17:42 -0500
commitafeffe31bd7d0f8333627a972e1d32e64a325b5b (patch)
tree6efd6548755e49e379a0a390c96efcf9f7a65eeb
parent9f9a33cbf55d38987a66b709284d2bb4ffea0fe9 (diff)
reformat fe
-rw-r--r--.gitignore2
-rw-r--r--db/water.sqlite3bin36864 -> 0 bytes
-rw-r--r--fe/.prettierrc4
-rw-r--r--fe/src/App.svelte42
-rw-r--r--fe/src/lib/Card.svelte14
-rw-r--r--fe/src/lib/Counter.svelte10
-rw-r--r--fe/src/lib/DataView.svelte177
-rw-r--r--fe/src/lib/Layout.svelte77
-rw-r--r--fe/src/lib/LoginForm.svelte105
-rw-r--r--fe/src/lib/PreferencesForm.svelte32
-rw-r--r--fe/src/lib/Table.svelte138
-rw-r--r--fe/src/lib/errors.ts6
-rw-r--r--fe/src/lib/utils.ts16
-rw-r--r--fe/src/main.ts2
-rw-r--r--fe/src/stores/auth.ts132
-rw-r--r--fe/src/types.ts18
16 files changed, 389 insertions, 386 deletions
diff --git a/.gitignore b/.gitignore
index 4e424ad..8a5cdb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,4 +46,4 @@ node_modules/
46.env.production.local 46.env.production.local
47.env.local 47.env.local
48 48
49 49*.sqlite3
diff --git a/db/water.sqlite3 b/db/water.sqlite3
deleted file mode 100644
index 716c5a4..0000000
--- a/db/water.sqlite3
+++ /dev/null
Binary files differ
diff --git a/fe/.prettierrc b/fe/.prettierrc
new file mode 100644
index 0000000..222861c
--- /dev/null
+++ b/fe/.prettierrc
@@ -0,0 +1,4 @@
1{
2 "tabWidth": 2,
3 "useTabs": false
4}
diff --git a/fe/src/App.svelte b/fe/src/App.svelte
index 8811c52..25d53dc 100644
--- a/fe/src/App.svelte
+++ b/fe/src/App.svelte
@@ -1,40 +1,16 @@
1<script lang="ts"> 1<script lang="ts">
2 import { onMount, onDestroy } from 'svelte'; 2 import Layout from "./lib/Layout.svelte";
3 import Layout from './lib/Layout.svelte' 3 import LoginForm from "./lib/LoginForm.svelte";
4 import LoginForm from './lib/LoginForm.svelte'; 4 import DataView from "./lib/DataView.svelte";
5 import DataView from './lib/DataView.svelte'; 5 import { authenticated } from "./stores/auth";
6 import { authenticated } from './stores/auth';
7</script> 6</script>
8 7
9<main> 8<main>
10 <Layout> 9 <Layout>
11 {#if !$authenticated} 10 {#if !$authenticated}
12 <LoginForm /> 11 <LoginForm />
13 {:else} 12 {:else}
14 <DataView /> 13 <DataView />
15 {/if} 14 {/if}
16 </Layout> 15 </Layout>
17</main> 16</main>
18
19<style>
20 .logo {
21 height: 6em;
22 padding: 1.5em;
23 will-change: filter;
24 transition: filter 300ms;
25 }
26 .logo:hover {
27 filter: drop-shadow(0 0 2em #646cffaa);
28 }
29 .logo.svelte:hover {
30 filter: drop-shadow(0 0 2em #ff3e00aa);
31 }
32 .read-the-docs {
33 color: #888;
34 }
35
36 .error {
37 font-size: 0.75em;
38 color: red;
39 }
40</style>
diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte
index feb5bcc..0835940 100644
--- a/fe/src/lib/Card.svelte
+++ b/fe/src/lib/Card.svelte
@@ -1,16 +1,16 @@
1<script lang="ts"> 1<script lang="ts">
2export let title; 2 export let title = "";
3</script> 3</script>
4 4
5<div class="card"> 5<div class="card">
6 {#if title} 6 {#if title}
7 <h2>{title}</h2> 7 <h2>{title}</h2>
8 {/if} 8 {/if}
9 <slot /> 9 <slot />
10</div> 10</div>
11 11
12<style> 12<style>
13.card { 13 .card {
14 background: #fff; 14 background: #fff;
15 width: 16rem; 15 width: 16rem;
16 display: flex; 16 display: flex;
@@ -18,5 +18,5 @@ export let title;
18 align-items: left; 18 align-items: left;
19 border: solid 2px #00000066; 19 border: solid 2px #00000066;
20 border-radius: 0.25em; 20 border-radius: 0.25em;
21} 21 }
22</style> 22</style>
diff --git a/fe/src/lib/Counter.svelte b/fe/src/lib/Counter.svelte
deleted file mode 100644
index 979b4df..0000000
--- a/fe/src/lib/Counter.svelte
+++ /dev/null
@@ -1,10 +0,0 @@
1<script lang="ts">
2 let count: number = 0
3 const increment = () => {
4 count += 1
5 }
6</script>
7
8<button on:click={increment}>
9 count is {count}
10</button>
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
index dc8acae..1458c9a 100644
--- a/fe/src/lib/DataView.svelte
+++ b/fe/src/lib/DataView.svelte
@@ -1,126 +1,123 @@
1<script lang='ts'> 1<script lang="ts">
2import { onMount } from 'svelte'; 2 import { onMount } from "svelte";
3import type { Preference } from '../types'; 3 import type { Statistic } from "../types";
4import { token, user, preferences } from '../stores/auth' 4 import { token, user } from "../stores/auth";
5import Table from './Table.svelte'; 5 import Table from "./Table.svelte";
6import PreferencesForm from './PreferencesForm.svelte';
7 6
8const formatter = new Intl.DateTimeFormat( 7 let json: Promise<any>;
9 'en', 8 let showAddForm: boolean = false;
10 {
11 year: 'numeric',
12 month: '2-digit',
13 day: '2-digit'
14 }
15);
16
17let json;
18let showAddForm: boolean = false;
19 9
20let statistic: Statistic = newStatistic(); 10 let statistic: Statistic = newStatistic();
21 11
22async function fetchData() { 12 async function fetchData() {
23 const res = await fetch('http://localhost:8080/api/v1/stats/', { 13 const res = await fetch("http://localhost:8080/api/v1/stats/", {
24 method: "GET", 14 method: "GET",
25 headers: { 15 headers: {
26 'Authorization': `Bearer ${$token}` 16 Authorization: `Bearer ${$token}`,
27 } 17 },
28 }); 18 });
29 if (res.ok) { 19 if (res.ok) {
30 json = res.json(); 20 json = res.json();
31 } else { 21 } else {
32 throw new Error('There was a problem with your request'); 22 throw new Error("There was a problem with your request");
33 } 23 }
34} 24 }
35 25
36async function submitStat() { 26 async function submitStat() {
37 const response = await fetch('http://localhost:8080/api/v1/stats/', { 27 const response = await fetch("http://localhost:8080/api/v1/stats/", {
38 method: "POST", 28 method: "POST",
39 headers: { 29 headers: {
40 'Authorization': `Bearer ${$token}` 30 Authorization: `Bearer ${$token}`,
41 }, 31 },
42 body: JSON.stringify({ 32 body: JSON.stringify({
43 date: new Date, 33 date: new Date(),
44 user_id: 1, 34 user_id: 1,
45 quantity: 3 35 quantity: 3,
46 }) 36 }),
47 }); 37 });
48 fetchData(); 38 fetchData();
49} 39 }
50 40
51function handleClick() { 41 function handleClick() {
52 showAddForm = true; 42 showAddForm = true;
53} 43 }
54 44
55function handleAddDialogSubmit (e) { 45 function handleAddDialogSubmit(e: Event) {
56 console.log(statistic); 46 console.log(statistic);
57 showAddForm = false; 47 showAddForm = false;
58} 48 }
59 49
60function closeDialog () { 50 function closeDialog() {
61 showAddForm = false; 51 showAddForm = false;
62} 52 }
63 53
64function newStatistic (): Statistic { 54 function newStatistic(): Statistic {
65 let now = new Date(), month, day, year; 55 let now = new Date(),
56 month,
57 day,
58 year;
66 59
67 month = `${now.getMonth() + 1}`; 60 month = `${now.getMonth() + 1}`;
68 day = `${now.getDate()}`; 61 day = `${now.getDate()}`;
69 year = now.getFullYear(); 62 year = now.getFullYear();
70 if (month.length < 2) 63 if (month.length < 2) month = "0" + month;
71 month = '0' + month; 64 if (day.length < 2) day = "0" + day;
72 if (day.length < 2)
73 day = '0' + day;
74 65
75 const date = [year, month, day].join('-'); 66 const date = [year, month, day].join("-");
76 67
77 return { 68 return {
78 user_id: $user.uuid, 69 user_id: $user!.uuid,
79 date, 70 date,
80 quantity: 1 71 quantity: 1,
81 } 72 };
82} 73 }
83 74
84onMount(() => { 75 onMount(() => {
85 fetchData(); 76 fetchData();
86}); 77 });
87
88</script> 78</script>
79
89<div> 80<div>
90 <button on:click={submitStat}>Add Stat Test</button> 81 <button on:click={submitStat}>Add Stat Test</button>
91 <PreferencesForm /> 82 <dialog open={showAddForm} on:submit={handleAddDialogSubmit}>
92 <dialog open={showAddForm} on:submit={handleAddDialogSubmit}> 83 <h2>Add Water</h2>
93 <h2>Add Water</h2> 84 <form method="dialog">
94 <form method="dialog"> 85 <div class="form input group">
95 <div class='form input group'> 86 <label for="date">Date:</label>
96 <label for="date">Date:</label> 87 <input bind:value={statistic.date} id="date" name="date" type="date" />
97 <input bind:value={statistic.date} id="date" name="date" type="date" /> 88 </div>
98 </div> 89 <div class="form input group">
99 <div class='form input group'> 90 <label for="quantity">Quantity:</label>
100 <label for="quantity">Quantity:</label> 91 <input
101 <input bind:value={statistic.quantity} id="quantity" name="quantity" type="number" min="0" autocomplete="off"/> 92 bind:value={statistic.quantity}
102 </div> 93 id="quantity"
103 <button on:click={closeDialog}>Cancel</button> 94 name="quantity"
104 <button type="submit">Submit</button> 95 type="number"
105 </form> 96 min="0"
106 </dialog> 97 autocomplete="off"
107 <button on:click={handleClick}>Add</button> 98 />
108 {#await json then data} 99 </div>
109 <Table {data} nofooter /> 100 <button on:click={closeDialog}>Cancel</button>
110 {:catch error} 101 <button type="submit">Submit</button>
111 <p>{error}</p> 102 </form>
112 {/await} 103 </dialog>
113 <!-- <Chart /> --> 104 <button on:click={handleClick}>Add</button>
105 {#await json then data}
106 <Table {data} nofooter />
107 {:catch error}
108 <p>{error}</p>
109 {/await}
110 <!-- <Chart /> -->
114</div> 111</div>
115 112
116<style> 113<style>
117dialog { 114 dialog {
118 background: red; 115 background: red;
119 box-shadow: 0 20px 5em 10px rgba(0,0,0,0.8); 116 box-shadow: 0 20px 5em 10px rgba(0, 0, 0, 0.8);
120} 117 }
121dialog::backdrop { 118 dialog::backdrop {
122 padding: 20px; 119 padding: 20px;
123 box-shadow: 20px 20px rgba(0,0,0,0.8); 120 box-shadow: 20px 20px rgba(0, 0, 0, 0.8);
124 background-color: red; 121 background-color: red;
125} 122 }
126</style> 123</style>
diff --git a/fe/src/lib/Layout.svelte b/fe/src/lib/Layout.svelte
index f349632..94ce84d 100644
--- a/fe/src/lib/Layout.svelte
+++ b/fe/src/lib/Layout.svelte
@@ -1,57 +1,62 @@
1<script> 1<script>
2import { authenticated, token } from '../stores/auth'; 2 import { authenticated, token } from "../stores/auth";
3 3 import PreferencesForm from "./PreferencesForm.svelte";
4const logout = () => token.unauthenticate(); 4 const logout = () => token.unauthenticate();
5 5 let open = false;
6function showSettingsDialog() { 6
7 console.log('show settings'); 7 function showSettingsDialog() {
8} 8 open = true;
9 9 }
10
11 function closeDialog() {
12 open = false;
13 }
10</script> 14</script>
11 15
12<div class="layout"> 16<div class="layout">
13 {#if $authenticated} 17 {#if $authenticated}
14 <nav> 18 <nav>
15 <div> 19 <div>
16 <h1>Water</h1> 20 <h1>Water</h1>
17 </div> 21 </div>
18 <div> 22 <div>
19 <button on:click={showSettingsDialog}>Settings</button> 23 <button on:click={showSettingsDialog}>Settings</button>
20 <button on:click={logout}>Logout</button> 24 <button on:click={logout}>Logout</button>
21 </div> 25 </div>
22 </nav> 26 </nav>
23 {/if} 27 <PreferencesForm {open} on:close={closeDialog} />
24 <div id="content"> 28 {/if}
25 <slot /> 29 <div id="content">
26 </div> 30 <slot />
31 </div>
27</div> 32</div>
28 33
29<style> 34<style>
30.layout { 35 .layout {
31 height: 100vh; 36 height: 100vh;
32} 37 }
33nav { 38 nav {
34 display: flex; 39 display: flex;
35 flex-direction: row; 40 flex-direction: row;
36 align-items: center; 41 align-items: center;
37 justify-content: space-between; 42 justify-content: space-between;
38 height: 64px; 43 height: 64px;
39 padding: 0 2em; 44 padding: 0 2em;
40} 45 }
41 46
42nav div { 47 nav div {
43 width: fit-content; 48 width: fit-content;
44} 49 }
45 50
46nav div h1 { 51 nav div h1 {
47 font-size: 1.75em; 52 font-size: 1.75em;
48} 53 }
49 54
50#content { 55 #content {
51 display: flex; 56 display: flex;
52 flex-direction: column; 57 flex-direction: column;
53 justify-content: center; 58 justify-content: center;
54 align-items: center; 59 align-items: center;
55 padding: 3em 0; 60 padding: 3em 0;
56} 61 }
57</style> 62</style>
diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte
index 499a457..bf6d9ad 100644
--- a/fe/src/lib/LoginForm.svelte
+++ b/fe/src/lib/LoginForm.svelte
@@ -1,66 +1,85 @@
1<script lang='ts'> 1<script lang="ts">
2import { token, user, preferences } from '../stores/auth'; 2 import { token, user, preferences } from "../stores/auth";
3import Card from './Card.svelte'; 3 import Card from "./Card.svelte";
4 4
5let credentials: CredentialObject = { 5 let credentials: CredentialObject = {
6 username: '', 6 username: "",
7 password: '' 7 password: "",
8} 8 };
9 9
10let error; 10 let error: string | null = null;
11 11
12interface CredentialObject { 12 interface CredentialObject {
13 username: string; 13 username: string;
14 password: string; 14 password: string;
15} 15 }
16 16
17function prepareCredentials ({ username, password }: CredentialObject): string { 17 function prepareCredentials({
18 username,
19 password,
20 }: CredentialObject): string {
18 return btoa(`${username}:${password}`); 21 return btoa(`${username}:${password}`);
19} 22 }
20 23
21async function onSubmit (e) { 24 async function onSubmit(e: Event) {
22 if (!credentials.username || !credentials.password) { 25 if (!credentials.username || !credentials.password) {
23 error = 'please enter your username and password'; 26 error = "please enter your username and password";
24 return; 27 return;
25 } 28 }
26 const auth = prepareCredentials(credentials); 29 const auth = prepareCredentials(credentials);
27 30
28 const response = await fetch('http://localhost:8080/api/v1/auth', { 31 const response = await fetch("http://localhost:8080/api/v1/auth", {
29 method: 'POST', 32 method: "POST",
30 headers: { 33 headers: {
31 'Authorization': `Basic ${auth}`, 34 Authorization: `Basic ${auth}`,
32 }, 35 },
33 }); 36 });
34 37
35 if (response.status === 401) { 38 if (response.status === 401) {
36 error = "Your username or password is wrong"; 39 error = "Your username or password is wrong";
37 return; 40 return;
38 } 41 }
39 42
40 if (response.ok) { 43 if (response.ok) {
41 const { token: apiToken, user: userData, preferences: userPreferences } = await response.json(); 44 const {
42 user.setUser(userData); 45 token: apiToken,
43 preferences.set(userPreferences); 46 user: userData,
44 token.authenticate(apiToken); 47 preferences: userPreferences,
48 } = await response.json();
49 user.setUser(userData);
50 preferences.set(userPreferences);
51 token.authenticate(apiToken);
45 } 52 }
46 53
47 error = null; 54 error = null;
48} 55 }
49</script> 56</script>
50 57
51<Card> 58<Card>
52 <form class="form" on:submit|preventDefault={onSubmit}> 59 <form class="form" on:submit|preventDefault={onSubmit}>
53 <div class='form input group'> 60 <div class="form input group">
54 <label for="username">Username</label> 61 <label for="username">Username</label>
55 <input bind:value={credentials.username} id="username" name='username' type="text" autocomplete="username" /> 62 <input
56 </div> 63 bind:value={credentials.username}
57 <div class='form input group'> 64 id="username"
58 <label for="password">Password</label> 65 name="username"
59 <input bind:value={credentials.password} id="password" name='password' type="password" autocomplete="current-password"/> 66 type="text"
60 </div> 67 autocomplete="username"
61 {#if error} 68 />
62 <p class="error">{error}</p> 69 </div>
63 {/if} 70 <div class="form input group">
64 <button type="submit">Log in</button> 71 <label for="password">Password</label>
65 </form> 72 <input
73 bind:value={credentials.password}
74 id="password"
75 name="password"
76 type="password"
77 autocomplete="current-password"
78 />
79 </div>
80 {#if error}
81 <p class="error">{error}</p>
82 {/if}
83 <button type="submit">Log in</button>
84 </form>
66</Card> 85</Card>
diff --git a/fe/src/lib/PreferencesForm.svelte b/fe/src/lib/PreferencesForm.svelte
index 781866c..95e04c1 100644
--- a/fe/src/lib/PreferencesForm.svelte
+++ b/fe/src/lib/PreferencesForm.svelte
@@ -1,7 +1,8 @@
1<script lang="ts"> 1<script lang="ts">
2import { preferences } from '../stores/auth'; 2 import { preferences } from '../stores/auth';
3import type { Size, Preference } from '../types'; 3 import type { Size, Preference } from '../types';
4 export let open: boolean = true; 4 import { createEventDispatcher } from 'svelte';
5 export let open: boolean;
5 6
6 let preference: Preference = { 7 let preference: Preference = {
7 color: "#00FF00", 8 color: "#00FF00",
@@ -10,29 +11,29 @@ import type { Size, Preference } from '../types';
10 unit: 'oz' 11 unit: 'oz'
11 } 12 }
12 } 13 }
14
15 const dispatch = createEventDispatcher();
13 16
14 preferences.subscribe((value) => { 17 preferences.subscribe((value) => {
15 preference = value; 18 preference = value;
16 }); 19 });
17 20
18 function onPreferencesSave(): void { 21 function onPreferencesSave(): void {
19 preferences.set(preferences); 22 preferences.set(preference);
23 dispatch('close')
20 } 24 }
25
21</script> 26</script>
22<dialog {open}> 27<dialog {open} on:submit|preventDefault={onPreferencesSave}>
23 <h2>User Preferences</h2> 28 <h2>User Preferences</h2>
24 <form method="dialog"> 29 <form method="dialog">
25 <div class="form input group"> 30 <div class="form input group">
26 <label>Color</label> 31 <label for="color">Color</label>
27 <input type="color" bind:value={preference.color}/> 32 <input id="color" name="color" type="color" bind:value={preference.color}/>
28 </div> 33 </div>
29 <div class="form input group"> 34 <div class="form input group">
30 <label>Bottle Size</label> 35 <label for="size">Bottle Size</label>
31 <select bind:value={preference.size.size}> 36 <input id="size" name="size" type="number" min="8" max="48" step="8" bind:value={preference.size.size}/>
32 {#each [8,16,24,32,40,48] as size}
33 <option>{ size }</option>
34 {/each}
35 </select>
36 </div> 37 </div>
37 <button type="submit">Save</button> 38 <button type="submit">Save</button>
38 </form> 39 </form>
@@ -42,4 +43,9 @@ dialog {
42 background: white; 43 background: white;
43 color: black; 44 color: black;
44} 45}
46
47input[type="color"] {
48 width: 100%;
49 height: 100%;
50}
45</style> 51</style>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
index 4b81800..3a66e0d 100644
--- a/fe/src/lib/Table.svelte
+++ b/fe/src/lib/Table.svelte
@@ -1,105 +1,105 @@
1<script lang="ts"> 1<script lang="ts">
2import {afterUpdate} from 'svelte'; 2 export let data: Array<any> | undefined = undefined;
3export let data: Array<any> | undefined = undefined; 3 export let nofooter: boolean = false;
4export let nofooter: boolean = false; 4 export let noheader: boolean = false;
5export let noheader: boolean = false; 5 export let omit: string[] = ["id"];
6export let omit: string[] = ['id']; 6 export let title: string | undefined = undefined;
7export let title: string | undefined = undefined;
8 7
9function getDataKeys(data: any[]): string[] { 8 function getDataKeys(data: any[]): string[] {
10 if (!data || data.length === 0) return []; 9 if (!data || data.length === 0) return [];
11 return Object.keys(data[0]).map(k => k.split('_').join(' ')).filter(k => !omit.includes(k)); 10 return Object.keys(data[0])
12} 11 .map((k) => k.split("_").join(" "))
12 .filter((k) => !omit.includes(k));
13 }
13 14
14function getRow(row: Record<string, any>): Array<any> { 15 function getRow(row: Record<string, any>): Array<any> {
15 return Object.entries(row).filter(r => !omit.includes(r[0])); 16 return Object.entries(row).filter((r) => !omit.includes(r[0]));
16} 17 }
17 18
18const formatter = new Intl.DateTimeFormat('en', { 19 const formatter = new Intl.DateTimeFormat("en", {
19 year: 'numeric', 20 year: "numeric",
20 month: 'numeric', 21 month: "numeric",
21 day: 'numeric', 22 day: "numeric",
22 hour: 'numeric', 23 hour: "numeric",
23 minute: '2-digit', 24 minute: "2-digit",
24 second: '2-digit', 25 second: "2-digit",
25 timeZone: "America/New_York" 26 timeZone: "America/New_York",
26}); 27 });
27 28
28function formatDatum([key, value]: any[]) { 29 function formatDatum([key, value]: any[]) {
29 if (key === 'date') { 30 if (key === "date") {
30 const parsedDate = new Date(value); 31 const parsedDate = new Date(value);
31 return formatter.format(parsedDate); 32 return formatter.format(parsedDate);
32 } 33 }
33 34
34 if (key === 'user') { 35 if (key === "user") {
35 return value['name']; 36 return value["name"];
36 } 37 }
37 38
38 return value; 39 return value;
39} 40 }
40
41</script> 41</script>
42
42<table> 43<table>
43 {#if title} 44 {#if title}
44 <h2>{title}</h2> 45 <h2>{title}</h2>
45 {/if} 46 {/if}
46 {#if !noheader} 47 {#if !noheader && data}
47 <thead> 48 <thead>
48 <tr> 49 <tr>
49 {#each getDataKeys(data) as header} 50 {#each getDataKeys(data) as header}
50 <th>{header}</th> 51 <th>{header}</th>
51 {/each} 52 {/each}
52 </tr> 53 </tr>
53 </thead> 54 </thead>
54 {/if} 55 {/if}
55 <tbody> 56 <tbody>
56 {#if data} 57 {#if data}
57 {#each data as row} 58 {#each data as row}
58 <tr> 59 <tr>
59 {#each getRow(row) as datum} 60 {#each getRow(row) as datum}
60
61 <td>{formatDatum(datum)}</td> 61 <td>{formatDatum(datum)}</td>
62 {/each} 62 {/each}
63 </tr> 63 </tr>
64 {/each} 64 {/each}
65 {:else} 65 {:else}
66 <tr> 66 <tr> There is not data. </tr>
67 There is not data. 67 {/if}
68 </tr> 68 </tbody>
69 {/if} 69 {#if !nofooter}
70 </tbody>
71 {#if !nofooter}
72 <slot name="footer"> 70 <slot name="footer">
73 <tfoot> 71 <tfoot>
74 <tr> 72 <tr>
75 <td>Table Footer</td> 73 <td>Table Footer</td>
76 </tr> 74 </tr>
77 </tfoot> 75 </tfoot>
78 </slot> 76 </slot>
79 {/if} 77 {/if}
80</table> 78</table>
79
81<style> 80<style>
82table { 81 table {
83 padding: 16px; 82 padding: 16px;
84 margin: 8px; 83 margin: 8px;
85 border: solid 1px black; 84 border: solid 1px black;
86 border-collapse: collapse; 85 border-collapse: collapse;
87} 86 }
88 87
89th { 88 th {
90 text-transform: capitalize; 89 text-transform: capitalize;
91} 90 }
92 91
93thead tr { 92 thead tr {
94 background: rgba(0,0,23, 0.34); 93 background: rgba(0, 0, 23, 0.34);
95} 94 }
96 95
97tbody tr:nth-child(odd) { 96 tbody tr:nth-child(odd) {
98 background: rgba(0,0,23,0.14); 97 background: rgba(0, 0, 23, 0.14);
99} 98 }
100 99
101th, td { 100 th,
101 td {
102 padding: 1em; 102 padding: 1em;
103 border: 1px solid rgba(0,0,0, 1); 103 border: 1px solid rgba(0, 0, 0, 1);
104} 104 }
105</style> 105</style>
diff --git a/fe/src/lib/errors.ts b/fe/src/lib/errors.ts
index 0663d63..d44bec5 100644
--- a/fe/src/lib/errors.ts
+++ b/fe/src/lib/errors.ts
@@ -1,7 +1,7 @@
1export class UnauthorizedError extends Error { 1export class UnauthorizedError extends Error {
2 constructor (message?: string , options?: ErrorOptions) { 2 constructor(message?: string, options?: ErrorOptions) {
3 super(message, options); 3 super(message, options);
4 } 4 }
5} 5}
6 6
7 7
diff --git a/fe/src/lib/utils.ts b/fe/src/lib/utils.ts
index c5501ae..22d4e9a 100644
--- a/fe/src/lib/utils.ts
+++ b/fe/src/lib/utils.ts
@@ -1,9 +1,9 @@
1export function processFormInput(form) { 1export function processFormInput(form: HTMLFormElement) {
2 const formData = new FormData(form); 2 const formData = new FormData(form);
3 const data = {}; 3 const data: Record<string, any> = {};
4 for (let field of formData) { 4 for (let field of formData) {
5 const [key, value] = field; 5 const [key, value] = field;
6 data[key] = value; 6 data[key] = value;
7 } 7 }
8 return data; 8 return data;
9} 9}
diff --git a/fe/src/main.ts b/fe/src/main.ts
index 8a909a1..ff866d0 100644
--- a/fe/src/main.ts
+++ b/fe/src/main.ts
@@ -2,7 +2,7 @@ import './app.css'
2import App from './App.svelte' 2import App from './App.svelte'
3 3
4const app = new App({ 4const app = new App({
5 target: document.getElementById('app'), 5 target: document.getElementById('app') as HTMLElement,
6}) 6})
7 7
8export default app 8export default app
diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts
index 10e6bd3..0efc80b 100644
--- a/fe/src/stores/auth.ts
+++ b/fe/src/stores/auth.ts
@@ -5,96 +5,96 @@ import { writable, derived } from 'svelte/store';
5type Nullable<T> = T | null; 5type Nullable<T> = T | null;
6 6
7interface User { 7interface User {
8 uuid: string; 8 uuid: string;
9 username: string; 9 username: string;
10} 10}
11 11
12interface TokenStore { 12interface TokenStore {
13 subscribe: (run: Subscriber<Nullable<string>>, invalidate: Invalidator<Nullable<string>>) => Unsubscriber, 13 subscribe: (run: Subscriber<Nullable<string>>, invalidate?: Invalidator<Nullable<string>>) => Unsubscriber,
14 authenticate: (newToken: string) => void, 14 authenticate: (newToken: string) => void,
15 unauthenticate: () => void 15 unauthenticate: () => void
16} 16}
17 17
18 18
19interface UserStore { 19interface UserStore {
20 subscribe: (run: Subscriber<Nullable<User>>, invalidate: Invalidator<Nullable<User>>) => Unsubscriber, 20 subscribe: (run: Subscriber<Nullable<User>>, invalidate?: Invalidator<Nullable<User>>) => Unsubscriber,
21 setUser: (user: User) => void, 21 setUser: (user: User) => void,
22 reset: () => void 22 reset: () => void
23} 23}
24 24
25interface PreferenceStore { 25interface PreferenceStore {
26 subscribe: (run: Subscriber<Nullable<Preference>>, invalidate: Invalidator<Nullable<Preference>>) => Unsubscriber, 26 subscribe: (run: Subscriber<Preference>, invalidate?: Invalidator<Preference>) => Unsubscriber,
27 set: (this: void, value: Nullable<Preference>) => void 27 set: (this: void, value: Preference) => void
28} 28}
29 29
30function createTokenStore(): TokenStore { 30function createTokenStore(): TokenStore {
31 const storedToken = localStorage.getItem("token"); 31 const storedToken = localStorage.getItem("token");
32 const { subscribe, set } = writable<string | null>(storedToken); 32 const { subscribe, set } = writable<string | null>(storedToken);
33 33
34 function authenticate(newToken: string): void { 34 function authenticate(newToken: string): void {
35 try { 35 try {
36 localStorage.setItem("token", newToken); 36 localStorage.setItem("token", newToken);
37 set(newToken); 37 set(newToken);
38 } catch (e) { 38 } catch (e) {
39 console.error('error', e); 39 console.error('error', e);
40 }
41 } 40 }
42 41 }
43 function unauthenticate(): void { 42
44 localStorage.removeItem("token"); 43 function unauthenticate(): void {
45 set(null); 44 localStorage.removeItem("token");
46 } 45 set(null);
47 46 }
48 return { 47
49 subscribe, 48 return {
50 authenticate, 49 subscribe,
51 unauthenticate 50 authenticate,
52 }; 51 unauthenticate
52 };
53} 53}
54 54
55function onTokenChange ($token: Nullable<string>): boolean { 55function onTokenChange($token: Nullable<string>): boolean {
56 return $token ? true : false; 56 return $token ? true : false;
57} 57}
58 58
59function createUserStore(): UserStore { 59function createUserStore(): UserStore {
60 const user = localStorage.getItem('user'); 60 const user = localStorage.getItem('user');
61 const userObj: Nullable<User> = user ? JSON.parse(user) : null; 61 const userObj: Nullable<User> = user ? JSON.parse(user) : null;
62 const { subscribe, set } = writable<User | null>(userObj); 62 const { subscribe, set } = writable<User | null>(userObj);
63 63
64 const setUser = (user: User) => { 64 const setUser = (user: User) => {
65 localStorage.setItem('user', JSON.stringify(user)); 65 localStorage.setItem('user', JSON.stringify(user));
66 set(user); 66 set(user);
67 } 67 }
68
69 const reset = () => {
70 localStorage.removeItem('user');
71 set(null);
72 }
73
74 return {
75 subscribe,
76 setUser,
77 reset
78 }
79}
68 80
69 const reset = () => {
70 localStorage.removeItem('user');
71 set(null);
72 }
73 81
74 return { 82function createPreferenceStore(): PreferenceStore {
75 subscribe, 83 const preferences = localStorage.getItem('preferences');
76 setUser, 84 const preferenceObj: Preference = preferences ? JSON.parse(preferences) : {
77 reset 85 color: "#FF0000",
86 size: {
87 size: 16,
88 unit: 'oz'
78 } 89 }
79} 90 };
80 91
92 const { subscribe, set } = writable<Preference>(preferenceObj);
81 93
82function createPreferenceStore(): PreferenceStore { 94 return {
83 const preferences = localStorage.getItem('preferences'); 95 subscribe,
84 const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { 96 set
85 color: "#FF0000", 97 }
86 size: {
87 size: 16,
88 unit: 'oz'
89 }
90 };
91
92 const { subscribe, set } = writable<Nullable<Preference>>(preferenceObj);
93
94 return {
95 subscribe,
96 set
97 }
98} 98}
99 99
100export const token = createTokenStore(); 100export const token = createTokenStore();
diff --git a/fe/src/types.ts b/fe/src/types.ts
index 03d613d..526e7eb 100644
--- a/fe/src/types.ts
+++ b/fe/src/types.ts
@@ -1,14 +1,20 @@
1export interface Size { 1export interface Size {
2 size: number; 2 size: number;
3 unit: string; 3 unit: string;
4} 4}
5 5
6export interface Preference { 6export interface Preference {
7 color: string; 7 color: string;
8 size: Size; 8 size: Size;
9} 9}
10 10
11export interface User { 11export interface User {
12 name: string; 12 name: string;
13 uuid: string; 13 uuid: string;
14} 14}
15
16export interface Statistic {
17 user_id: string;
18 date: string;
19 quantity: number;
20} \ No newline at end of file