diff options
author | Doog <157747121+doogongithub@users.noreply.github.com> | 2024-02-29 20:13:48 -0500 |
---|---|---|
committer | Doog <157747121+doogongithub@users.noreply.github.com> | 2024-02-29 20:13:48 -0500 |
commit | 9f9a33cbf55d38987a66b709284d2bb4ffea0fe9 (patch) | |
tree | 1e0539e708983ca05bb4e07d22b9ec10b95d2473 /fe | |
parent | e37c73e33a4aaf7fb8d25b5af03627f20bcda19f (diff) |
modify api, build additional FE components, add types
Diffstat (limited to 'fe')
-rw-r--r-- | fe/src/lib/DataView.svelte | 67 | ||||
-rw-r--r-- | fe/src/lib/LoginForm.svelte | 16 | ||||
-rw-r--r-- | fe/src/lib/PreferencesForm.svelte | 45 | ||||
-rw-r--r-- | fe/src/lib/Table.svelte | 19 | ||||
-rw-r--r-- | fe/src/stores/auth.ts | 57 | ||||
-rw-r--r-- | fe/src/types.ts | 14 |
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'> |
2 | import { onMount } from 'svelte'; | 2 | import { onMount } from 'svelte'; |
3 | import { token } from '../stores/auth' | 3 | import type { Preference } from '../types'; |
4 | import { token, user, preferences } from '../stores/auth' | ||
4 | import Table from './Table.svelte'; | 5 | import Table from './Table.svelte'; |
6 | import PreferencesForm from './PreferencesForm.svelte'; | ||
7 | |||
8 | const formatter = new Intl.DateTimeFormat( | ||
9 | 'en', | ||
10 | { | ||
11 | year: 'numeric', | ||
12 | month: '2-digit', | ||
13 | day: '2-digit' | ||
14 | } | ||
15 | ); | ||
5 | 16 | ||
6 | let json; | 17 | let json; |
7 | let showAddForm: boolean = false; | 18 | let showAddForm: boolean = false; |
8 | 19 | ||
20 | let statistic: Statistic = newStatistic(); | ||
21 | |||
9 | async function fetchData() { | 22 | async 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 | ||
42 | function handleAddDialogSubmit (e) { | 55 | function handleAddDialogSubmit (e) { |
43 | console.log(e.keyCode) | 56 | console.log(statistic); |
57 | showAddForm = false; | ||
58 | } | ||
59 | |||
60 | function closeDialog () { | ||
61 | showAddForm = false; | ||
62 | } | ||
63 | |||
64 | function 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 | ||
46 | onMount(() => { | 84 | onMount(() => { |
@@ -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> | ||
117 | dialog { | ||
118 | background: red; | ||
119 | box-shadow: 0 20px 5em 10px rgba(0,0,0,0.8); | ||
120 | } | ||
121 | dialog::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'> |
2 | import { token } from '../stores/auth'; | 2 | import { token, user, preferences } from '../stores/auth'; |
3 | import Card from './Card.svelte'; | 3 | import Card from './Card.svelte'; |
4 | 4 | ||
5 | let user = { | 5 | let 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 | ||
21 | async function onSubmit (e) { | 21 | async 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"> | ||
2 | import { preferences } from '../stores/auth'; | ||
3 | import 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> | ||
41 | dialog { | ||
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 | ||
83 | th { | 89 | th { |
84 | text-transform: capitalize; | 90 | text-transform: capitalize; |
85 | } | 91 | } |
92 | |||
93 | thead tr { | ||
94 | background: rgba(0,0,23, 0.34); | ||
95 | } | ||
96 | |||
97 | tbody tr:nth-child(odd) { | ||
98 | background: rgba(0,0,23,0.14); | ||
99 | } | ||
100 | |||
101 | th, 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 @@ | |||
1 | import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; | 1 | import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; |
2 | import type { Preference } from '../types'; | ||
2 | import { writable, derived } from 'svelte/store'; | 3 | import { writable, derived } from 'svelte/store'; |
3 | 4 | ||
4 | type Nullable<T> = T | null; | 5 | type Nullable<T> = T | null; |
@@ -14,6 +15,18 @@ interface TokenStore { | |||
14 | unauthenticate: () => void | 15 | unauthenticate: () => void |
15 | } | 16 | } |
16 | 17 | ||
18 | |||
19 | interface UserStore { | ||
20 | subscribe: (run: Subscriber<Nullable<User>>, invalidate: Invalidator<Nullable<User>>) => Unsubscriber, | ||
21 | setUser: (user: User) => void, | ||
22 | reset: () => void | ||
23 | } | ||
24 | |||
25 | interface PreferenceStore { | ||
26 | subscribe: (run: Subscriber<Nullable<Preference>>, invalidate: Invalidator<Nullable<Preference>>) => Unsubscriber, | ||
27 | set: (this: void, value: Nullable<Preference>) => void | ||
28 | } | ||
29 | |||
17 | function createTokenStore(): TokenStore { | 30 | function 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 | ||
59 | function 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 | |||
82 | function 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 | |||
46 | export const token = createTokenStore(); | 100 | export const token = createTokenStore(); |
47 | export const authenticated = derived(token, onTokenChange); | 101 | export const authenticated = derived(token, onTokenChange); |
48 | export const user = writable<User | null>(null); | 102 | export const user = createUserStore(); |
103 | export 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 @@ | |||
1 | export interface Size { | ||
2 | size: number; | ||
3 | unit: string; | ||
4 | } | ||
5 | |||
6 | export interface Preference { | ||
7 | color: string; | ||
8 | size: Size; | ||
9 | } | ||
10 | |||
11 | export interface User { | ||
12 | name: string; | ||
13 | uuid: string; | ||
14 | } | ||