aboutsummaryrefslogtreecommitdiff
path: root/fe
diff options
context:
space:
mode:
authorDoog <157747121+doogongithub@users.noreply.github.com>2024-02-29 20:13:48 -0500
committerDoog <157747121+doogongithub@users.noreply.github.com>2024-02-29 20:13:48 -0500
commit9f9a33cbf55d38987a66b709284d2bb4ffea0fe9 (patch)
tree1e0539e708983ca05bb4e07d22b9ec10b95d2473 /fe
parente37c73e33a4aaf7fb8d25b5af03627f20bcda19f (diff)
modify api, build additional FE components, add types
Diffstat (limited to 'fe')
-rw-r--r--fe/src/lib/DataView.svelte67
-rw-r--r--fe/src/lib/LoginForm.svelte16
-rw-r--r--fe/src/lib/PreferencesForm.svelte45
-rw-r--r--fe/src/lib/Table.svelte19
-rw-r--r--fe/src/stores/auth.ts57
-rw-r--r--fe/src/types.ts14
6 files changed, 206 insertions, 12 deletions
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
index cd7b042..dc8acae 100644
--- a/fe/src/lib/DataView.svelte
+++ b/fe/src/lib/DataView.svelte
@@ -1,11 +1,24 @@
1<script lang='ts'> 1<script lang='ts'>
2import { onMount } from 'svelte'; 2import { onMount } from 'svelte';
3import { token } from '../stores/auth' 3import type { Preference } from '../types';
4import { token, user, preferences } from '../stores/auth'
4import Table from './Table.svelte'; 5import Table from './Table.svelte';
6import PreferencesForm from './PreferencesForm.svelte';
7
8const formatter = new Intl.DateTimeFormat(
9 'en',
10 {
11 year: 'numeric',
12 month: '2-digit',
13 day: '2-digit'
14 }
15);
5 16
6let json; 17let json;
7let showAddForm: boolean = false; 18let showAddForm: boolean = false;
8 19
20let statistic: Statistic = newStatistic();
21
9async function fetchData() { 22async function fetchData() {
10 const res = await fetch('http://localhost:8080/api/v1/stats/', { 23 const res = await fetch('http://localhost:8080/api/v1/stats/', {
11 method: "GET", 24 method: "GET",
@@ -40,7 +53,32 @@ function handleClick() {
40} 53}
41 54
42function handleAddDialogSubmit (e) { 55function handleAddDialogSubmit (e) {
43 console.log(e.keyCode) 56 console.log(statistic);
57 showAddForm = false;
58}
59
60function closeDialog () {
61 showAddForm = false;
62}
63
64function newStatistic (): Statistic {
65 let now = new Date(), month, day, year;
66
67 month = `${now.getMonth() + 1}`;
68 day = `${now.getDate()}`;
69 year = now.getFullYear();
70 if (month.length < 2)
71 month = '0' + month;
72 if (day.length < 2)
73 day = '0' + day;
74
75 const date = [year, month, day].join('-');
76
77 return {
78 user_id: $user.uuid,
79 date,
80 quantity: 1
81 }
44} 82}
45 83
46onMount(() => { 84onMount(() => {
@@ -50,10 +88,19 @@ onMount(() => {
50</script> 88</script>
51<div> 89<div>
52 <button on:click={submitStat}>Add Stat Test</button> 90 <button on:click={submitStat}>Add Stat Test</button>
91 <PreferencesForm />
53 <dialog open={showAddForm} on:submit={handleAddDialogSubmit}> 92 <dialog open={showAddForm} on:submit={handleAddDialogSubmit}>
93 <h2>Add Water</h2>
54 <form method="dialog"> 94 <form method="dialog">
55 <input name="date" type="date" /> 95 <div class='form input group'>
56 <input name="quantity" type="number" min="0" autocomplete="off"/> 96 <label for="date">Date:</label>
97 <input bind:value={statistic.date} id="date" name="date" type="date" />
98 </div>
99 <div class='form input group'>
100 <label for="quantity">Quantity:</label>
101 <input bind:value={statistic.quantity} id="quantity" name="quantity" type="number" min="0" autocomplete="off"/>
102 </div>
103 <button on:click={closeDialog}>Cancel</button>
57 <button type="submit">Submit</button> 104 <button type="submit">Submit</button>
58 </form> 105 </form>
59 </dialog> 106 </dialog>
@@ -65,3 +112,15 @@ onMount(() => {
65 {/await} 112 {/await}
66 <!-- <Chart /> --> 113 <!-- <Chart /> -->
67</div> 114</div>
115
116<style>
117dialog {
118 background: red;
119 box-shadow: 0 20px 5em 10px rgba(0,0,0,0.8);
120}
121dialog::backdrop {
122 padding: 20px;
123 box-shadow: 20px 20px rgba(0,0,0,0.8);
124 background-color: red;
125}
126</style>
diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte
index 22c0faf..499a457 100644
--- a/fe/src/lib/LoginForm.svelte
+++ b/fe/src/lib/LoginForm.svelte
@@ -1,8 +1,8 @@
1<script lang='ts'> 1<script lang='ts'>
2import { token } from '../stores/auth'; 2import { token, user, preferences } from '../stores/auth';
3import Card from './Card.svelte'; 3import Card from './Card.svelte';
4 4
5let user = { 5let credentials: CredentialObject = {
6 username: '', 6 username: '',
7 password: '' 7 password: ''
8} 8}
@@ -19,11 +19,11 @@ function prepareCredentials ({ username, password }: CredentialObject): string {
19} 19}
20 20
21async function onSubmit (e) { 21async function onSubmit (e) {
22 if (!user.username || !user.password) { 22 if (!credentials.username || !credentials.password) {
23 error = 'please enter your username and password'; 23 error = 'please enter your username and password';
24 return; 24 return;
25 } 25 }
26 const auth = prepareCredentials(user); 26 const auth = prepareCredentials(credentials);
27 27
28 const response = await fetch('http://localhost:8080/api/v1/auth', { 28 const response = await fetch('http://localhost:8080/api/v1/auth', {
29 method: 'POST', 29 method: 'POST',
@@ -38,7 +38,9 @@ async function onSubmit (e) {
38 } 38 }
39 39
40 if (response.ok) { 40 if (response.ok) {
41 const { token: apiToken } = await response.json(); 41 const { token: apiToken, user: userData, preferences: userPreferences } = await response.json();
42 user.setUser(userData);
43 preferences.set(userPreferences);
42 token.authenticate(apiToken); 44 token.authenticate(apiToken);
43 } 45 }
44 46
@@ -50,11 +52,11 @@ async function onSubmit (e) {
50 <form class="form" on:submit|preventDefault={onSubmit}> 52 <form class="form" on:submit|preventDefault={onSubmit}>
51 <div class='form input group'> 53 <div class='form input group'>
52 <label for="username">Username</label> 54 <label for="username">Username</label>
53 <input bind:value={user.username} id="username" name='username' type="text" autocomplete="username" /> 55 <input bind:value={credentials.username} id="username" name='username' type="text" autocomplete="username" />
54 </div> 56 </div>
55 <div class='form input group'> 57 <div class='form input group'>
56 <label for="password">Password</label> 58 <label for="password">Password</label>
57 <input bind:value={user.password} id="password" name='password' type="password" autocomplete="current-password"/> 59 <input bind:value={credentials.password} id="password" name='password' type="password" autocomplete="current-password"/>
58 </div> 60 </div>
59 {#if error} 61 {#if error}
60 <p class="error">{error}</p> 62 <p class="error">{error}</p>
diff --git a/fe/src/lib/PreferencesForm.svelte b/fe/src/lib/PreferencesForm.svelte
new file mode 100644
index 0000000..781866c
--- /dev/null
+++ b/fe/src/lib/PreferencesForm.svelte
@@ -0,0 +1,45 @@
1<script lang="ts">
2import { preferences } from '../stores/auth';
3import type { Size, Preference } from '../types';
4 export let open: boolean = true;
5
6 let preference: Preference = {
7 color: "#00FF00",
8 size: {
9 size: 8,
10 unit: 'oz'
11 }
12 }
13
14 preferences.subscribe((value) => {
15 preference = value;
16 });
17
18 function onPreferencesSave(): void {
19 preferences.set(preferences);
20 }
21</script>
22<dialog {open}>
23 <h2>User Preferences</h2>
24 <form method="dialog">
25 <div class="form input group">
26 <label>Color</label>
27 <input type="color" bind:value={preference.color}/>
28 </div>
29 <div class="form input group">
30 <label>Bottle Size</label>
31 <select 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 <button type="submit">Save</button>
38 </form>
39</dialog>
40<style>
41dialog {
42 background: white;
43 color: black;
44}
45</style>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
index 5572280..4b81800 100644
--- a/fe/src/lib/Table.svelte
+++ b/fe/src/lib/Table.svelte
@@ -30,6 +30,11 @@ function formatDatum([key, value]: any[]) {
30 const parsedDate = new Date(value); 30 const parsedDate = new Date(value);
31 return formatter.format(parsedDate); 31 return formatter.format(parsedDate);
32 } 32 }
33
34 if (key === 'user') {
35 return value['name'];
36 }
37
33 return value; 38 return value;
34} 39}
35 40
@@ -78,9 +83,23 @@ table {
78 padding: 16px; 83 padding: 16px;
79 margin: 8px; 84 margin: 8px;
80 border: solid 1px black; 85 border: solid 1px black;
86 border-collapse: collapse;
81} 87}
82 88
83th { 89th {
84 text-transform: capitalize; 90 text-transform: capitalize;
85} 91}
92
93thead tr {
94 background: rgba(0,0,23, 0.34);
95}
96
97tbody tr:nth-child(odd) {
98 background: rgba(0,0,23,0.14);
99}
100
101th, td {
102 padding: 1em;
103 border: 1px solid rgba(0,0,0, 1);
104}
86</style> 105</style>
diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts
index 7e70cda..10e6bd3 100644
--- a/fe/src/stores/auth.ts
+++ b/fe/src/stores/auth.ts
@@ -1,4 +1,5 @@
1import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; 1import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store';
2import type { Preference } from '../types';
2import { writable, derived } from 'svelte/store'; 3import { writable, derived } from 'svelte/store';
3 4
4type Nullable<T> = T | null; 5type Nullable<T> = T | null;
@@ -14,6 +15,18 @@ interface TokenStore {
14 unauthenticate: () => void 15 unauthenticate: () => void
15} 16}
16 17
18
19interface UserStore {
20 subscribe: (run: Subscriber<Nullable<User>>, invalidate: Invalidator<Nullable<User>>) => Unsubscriber,
21 setUser: (user: User) => void,
22 reset: () => void
23}
24
25interface PreferenceStore {
26 subscribe: (run: Subscriber<Nullable<Preference>>, invalidate: Invalidator<Nullable<Preference>>) => Unsubscriber,
27 set: (this: void, value: Nullable<Preference>) => void
28}
29
17function createTokenStore(): TokenStore { 30function createTokenStore(): TokenStore {
18 const storedToken = localStorage.getItem("token"); 31 const storedToken = localStorage.getItem("token");
19 const { subscribe, set } = writable<string | null>(storedToken); 32 const { subscribe, set } = writable<string | null>(storedToken);
@@ -43,6 +56,48 @@ function onTokenChange ($token: Nullable<string>): boolean {
43 return $token ? true : false; 56 return $token ? true : false;
44} 57}
45 58
59function createUserStore(): UserStore {
60 const user = localStorage.getItem('user');
61 const userObj: Nullable<User> = user ? JSON.parse(user) : null;
62 const { subscribe, set } = writable<User | null>(userObj);
63
64 const setUser = (user: User) => {
65 localStorage.setItem('user', JSON.stringify(user));
66 set(user);
67 }
68
69 const reset = () => {
70 localStorage.removeItem('user');
71 set(null);
72 }
73
74 return {
75 subscribe,
76 setUser,
77 reset
78 }
79}
80
81
82function createPreferenceStore(): PreferenceStore {
83 const preferences = localStorage.getItem('preferences');
84 const preferenceObj: Preference = preferences ? JSON.parse(preferences) : {
85 color: "#FF0000",
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}
99
46export const token = createTokenStore(); 100export const token = createTokenStore();
47export const authenticated = derived(token, onTokenChange); 101export const authenticated = derived(token, onTokenChange);
48export const user = writable<User | null>(null); 102export const user = createUserStore();
103export const preferences = createPreferenceStore();
diff --git a/fe/src/types.ts b/fe/src/types.ts
new file mode 100644
index 0000000..03d613d
--- /dev/null
+++ b/fe/src/types.ts
@@ -0,0 +1,14 @@
1export interface Size {
2 size: number;
3 unit: string;
4}
5
6export interface Preference {
7 color: string;
8 size: Size;
9}
10
11export interface User {
12 name: string;
13 uuid: string;
14}