From 3eafb413a48cde60dea8a7355ee621c6acca952f Mon Sep 17 00:00:00 2001 From: Doog <157747121+doogongithub@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:07:27 -0500 Subject: first commit --- fe/src/App.svelte | 167 ++++++++++++++++++++++++++++++++++++++++++++++ fe/src/app.css | 111 ++++++++++++++++++++++++++++++ fe/src/assets/svelte.svg | 1 + fe/src/lib/Card.svelte | 22 ++++++ fe/src/lib/Counter.svelte | 10 +++ fe/src/lib/Table.svelte | 41 ++++++++++++ fe/src/lib/errors.ts | 7 ++ fe/src/lib/utils.ts | 9 +++ fe/src/main.ts | 8 +++ fe/src/vite-env.d.ts | 2 + 10 files changed, 378 insertions(+) create mode 100644 fe/src/App.svelte create mode 100644 fe/src/app.css create mode 100644 fe/src/assets/svelte.svg create mode 100644 fe/src/lib/Card.svelte create mode 100644 fe/src/lib/Counter.svelte create mode 100644 fe/src/lib/Table.svelte create mode 100644 fe/src/lib/errors.ts create mode 100644 fe/src/lib/utils.ts create mode 100644 fe/src/main.ts create mode 100644 fe/src/vite-env.d.ts (limited to 'fe/src') diff --git a/fe/src/App.svelte b/fe/src/App.svelte new file mode 100644 index 0000000..cc4e594 --- /dev/null +++ b/fe/src/App.svelte @@ -0,0 +1,167 @@ + + +
+ {#if !authenticated} + +
+
+ + +
+
+ + +
+ {#if error} +

{error}

+ {/if} + +
+
+ {:else} +
+ +
+
+ + + + + + +
+ + + + {#await data} +

...fetching

+ {:then data} + {#if data} +

Status

+

{data.status}

+ +
+
+ {:else} +

No data yet

+ {/if} + {:catch errror} +

{error.message}

+ {/await} + {/if} + + + diff --git a/fe/src/app.css b/fe/src/app.css new file mode 100644 index 0000000..4768cf6 --- /dev/null +++ b/fe/src/app.css @@ -0,0 +1,111 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + --submit: #28a745; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} + +@media (prefers-color-scheme: dark) { + :root { + color: #000; + } +} + +.form { + display: flex; + flex-direction: column; +} + +.form.input.group { + display: flex; + flex-direction: column; + margin-bottom: 1em; +} + +.form.input.group label { + margin-bottom: .5em; +} + +.form.input.group input { + padding: 1em; +} + +.form button[type=submit] { + align-self: flex-end; + background: var(--submit); + color: #fff; +} diff --git a/fe/src/assets/svelte.svg b/fe/src/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/fe/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte new file mode 100644 index 0000000..feb5bcc --- /dev/null +++ b/fe/src/lib/Card.svelte @@ -0,0 +1,22 @@ + + +
+ {#if title} +

{title}

+ {/if} + +
+ + diff --git a/fe/src/lib/Counter.svelte b/fe/src/lib/Counter.svelte new file mode 100644 index 0000000..979b4df --- /dev/null +++ b/fe/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte new file mode 100644 index 0000000..2df9f8c --- /dev/null +++ b/fe/src/lib/Table.svelte @@ -0,0 +1,41 @@ + +
+ {#if title} +

{title}

+ {/if} + {#if !noheader} + + + + + + {/if} + + + + + + {#if !nofooter} + + + + + + + + {/if} +
+ Data Header +
Data
Table Footer
+ diff --git a/fe/src/lib/errors.ts b/fe/src/lib/errors.ts new file mode 100644 index 0000000..0663d63 --- /dev/null +++ b/fe/src/lib/errors.ts @@ -0,0 +1,7 @@ +export class UnauthorizedError extends Error { + constructor (message?: string , options?: ErrorOptions) { + super(message, options); + } +} + + diff --git a/fe/src/lib/utils.ts b/fe/src/lib/utils.ts new file mode 100644 index 0000000..c5501ae --- /dev/null +++ b/fe/src/lib/utils.ts @@ -0,0 +1,9 @@ +export function processFormInput(form) { + const formData = new FormData(form); + const data = {}; + for (let field of formData) { + const [key, value] = field; + data[key] = value; + } + return data; +} diff --git a/fe/src/main.ts b/fe/src/main.ts new file mode 100644 index 0000000..8a909a1 --- /dev/null +++ b/fe/src/main.ts @@ -0,0 +1,8 @@ +import './app.css' +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app'), +}) + +export default app diff --git a/fe/src/vite-env.d.ts b/fe/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/fe/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// -- cgit v1.1 From e37c73e33a4aaf7fb8d25b5af03627f20bcda19f Mon Sep 17 00:00:00 2001 From: Doog <157747121+doogongithub@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:08:35 -0500 Subject: add gitignore --- fe/src/App.svelte | 147 +++----------------------------------------- fe/src/app.css | 2 +- fe/src/lib/DataView.svelte | 67 ++++++++++++++++++++ fe/src/lib/Layout.svelte | 57 +++++++++++++++++ fe/src/lib/LoginForm.svelte | 64 +++++++++++++++++++ fe/src/lib/Table.svelte | 61 +++++++++++++++--- fe/src/stores/auth.ts | 48 +++++++++++++++ 7 files changed, 300 insertions(+), 146 deletions(-) create mode 100644 fe/src/lib/DataView.svelte create mode 100644 fe/src/lib/Layout.svelte create mode 100644 fe/src/lib/LoginForm.svelte create mode 100644 fe/src/stores/auth.ts (limited to 'fe/src') diff --git a/fe/src/App.svelte b/fe/src/App.svelte index cc4e594..8811c52 100644 --- a/fe/src/App.svelte +++ b/fe/src/App.svelte @@ -1,146 +1,19 @@
- {#if !authenticated} - -
-
- - -
-
- - -
- {#if error} -

{error}

- {/if} - -
-
+ + {#if !$authenticated} + {:else} -
- -
- - - - - {#await data} -

...fetching

- {:then data} - {#if data} -

Status

-

{data.status}

- -
-
- {:else} -

No data yet

- {/if} - {:catch errror} -

{error.message}

- {/await} + {/if} + diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte new file mode 100644 index 0000000..22c0faf --- /dev/null +++ b/fe/src/lib/LoginForm.svelte @@ -0,0 +1,64 @@ + + + +
+
+ + +
+
+ + +
+ {#if error} +

{error}

+ {/if} + + +
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte index 2df9f8c..5572280 100644 --- a/fe/src/lib/Table.svelte +++ b/fe/src/lib/Table.svelte @@ -1,8 +1,38 @@
{#if title} @@ -11,16 +41,27 @@ {#if !noheader} - + {#each getDataKeys(data) as header} + + {/each} {/if} + {#if data} + {#each data as row} - + {#each getRow(row) as datum} + + + {/each} + {/each} + {:else} + + There is not data. + + {/if} {#if !nofooter} @@ -38,4 +79,8 @@ table { margin: 8px; border: solid 1px black; } + +th { + text-transform: capitalize; +} diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts new file mode 100644 index 0000000..7e70cda --- /dev/null +++ b/fe/src/stores/auth.ts @@ -0,0 +1,48 @@ +import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; +import { writable, derived } from 'svelte/store'; + +type Nullable = T | null; + +interface User { + uuid: string; + username: string; +} + +interface TokenStore { + subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, + authenticate: (newToken: string) => void, + unauthenticate: () => void +} + +function createTokenStore(): TokenStore { + const storedToken = localStorage.getItem("token"); + const { subscribe, set } = writable(storedToken); + + function authenticate(newToken: string): void { + try { + localStorage.setItem("token", newToken); + set(newToken); + } catch (e) { + console.error('error', e); + } + } + + function unauthenticate(): void { + localStorage.removeItem("token"); + set(null); + } + + return { + subscribe, + authenticate, + unauthenticate + }; +} + +function onTokenChange ($token: Nullable): boolean { + return $token ? true : false; +} + +export const token = createTokenStore(); +export const authenticated = derived(token, onTokenChange); +export const user = writable(null); -- cgit v1.1 From 9f9a33cbf55d38987a66b709284d2bb4ffea0fe9 Mon Sep 17 00:00:00 2001 From: Doog <157747121+doogongithub@users.noreply.github.com> Date: Thu, 29 Feb 2024 20:13:48 -0500 Subject: modify api, build additional FE components, add types --- fe/src/lib/DataView.svelte | 67 ++++++++++++++++++++++++++++++++++++--- fe/src/lib/LoginForm.svelte | 16 ++++++---- fe/src/lib/PreferencesForm.svelte | 45 ++++++++++++++++++++++++++ fe/src/lib/Table.svelte | 19 +++++++++++ fe/src/stores/auth.ts | 57 ++++++++++++++++++++++++++++++++- fe/src/types.ts | 14 ++++++++ 6 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 fe/src/lib/PreferencesForm.svelte create mode 100644 fe/src/types.ts (limited to 'fe/src') 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 @@
+ +

Add Water

- - +
+ + +
+
+ + +
+
@@ -65,3 +112,15 @@ onMount(() => { {/await}
+ + 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 @@ + +

User Preferences

+
+
+ + +
+
+ + +
+ + +
+ 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[]) { const parsedDate = new Date(value); return formatter.format(parsedDate); } + + if (key === 'user') { + return value['name']; + } + return value; } @@ -78,9 +83,23 @@ table { padding: 16px; margin: 8px; border: solid 1px black; + border-collapse: collapse; } th { text-transform: capitalize; } + +thead tr { + background: rgba(0,0,23, 0.34); +} + +tbody tr:nth-child(odd) { + background: rgba(0,0,23,0.14); +} + +th, td { + padding: 1em; + border: 1px solid rgba(0,0,0, 1); +} 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 @@ import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; +import type { Preference } from '../types'; import { writable, derived } from 'svelte/store'; type Nullable = T | null; @@ -14,6 +15,18 @@ interface TokenStore { unauthenticate: () => void } + +interface UserStore { + subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, + setUser: (user: User) => void, + reset: () => void +} + +interface PreferenceStore { + subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, + set: (this: void, value: Nullable) => void +} + function createTokenStore(): TokenStore { const storedToken = localStorage.getItem("token"); const { subscribe, set } = writable(storedToken); @@ -43,6 +56,48 @@ function onTokenChange ($token: Nullable): boolean { return $token ? true : false; } +function createUserStore(): UserStore { + const user = localStorage.getItem('user'); + const userObj: Nullable = user ? JSON.parse(user) : null; + const { subscribe, set } = writable(userObj); + + const setUser = (user: User) => { + localStorage.setItem('user', JSON.stringify(user)); + set(user); + } + + const reset = () => { + localStorage.removeItem('user'); + set(null); + } + + return { + subscribe, + setUser, + reset + } +} + + +function createPreferenceStore(): PreferenceStore { + const preferences = localStorage.getItem('preferences'); + const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { + color: "#FF0000", + size: { + size: 16, + unit: 'oz' + } + }; + + const { subscribe, set } = writable>(preferenceObj); + + return { + subscribe, + set + } +} + export const token = createTokenStore(); export const authenticated = derived(token, onTokenChange); -export const user = writable(null); +export const user = createUserStore(); +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 @@ +export interface Size { + size: number; + unit: string; +} + +export interface Preference { + color: string; + size: Size; +} + +export interface User { + name: string; + uuid: string; +} -- cgit v1.1 From afeffe31bd7d0f8333627a972e1d32e64a325b5b Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 1 Mar 2024 18:17:42 -0500 Subject: reformat fe --- fe/src/App.svelte | 42 ++------- fe/src/lib/Card.svelte | 14 +-- fe/src/lib/Counter.svelte | 10 --- fe/src/lib/DataView.svelte | 177 +++++++++++++++++++------------------- fe/src/lib/Layout.svelte | 77 +++++++++-------- fe/src/lib/LoginForm.svelte | 105 +++++++++++++--------- fe/src/lib/PreferencesForm.svelte | 32 ++++--- fe/src/lib/Table.svelte | 138 ++++++++++++++--------------- fe/src/lib/errors.ts | 6 +- fe/src/lib/utils.ts | 16 ++-- fe/src/main.ts | 2 +- fe/src/stores/auth.ts | 132 ++++++++++++++-------------- fe/src/types.ts | 18 ++-- 13 files changed, 384 insertions(+), 385 deletions(-) delete mode 100644 fe/src/lib/Counter.svelte (limited to 'fe/src') 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 @@
- + {#if !$authenticated} - + {:else} - - {/if} - + + {/if} +
- - 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 @@
- {#if title} -

{title}

- {/if} - + {#if title} +

{title}

+ {/if} +
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 @@ - - - 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 @@ - +
- - - -

Add Water

-
-
- - -
-
- - -
- - - -
- - {#await json then data} -
- Data Header - {header}
Data{formatDatum(datum)}
- {:catch error} -

{error}

- {/await} - + + +

Add Water

+
+
+ + +
+
+ + +
+ + + +
+ + {#await json then data} +
+ {:catch error} +

{error}

+ {/await} + 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 @@
- {#if $authenticated} + {#if $authenticated} - {/if} -
- -
+ + {/if} +
+ +
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 @@ - -
-
- - -
-
- - -
- {#if error} -

{error}

- {/if} - - +
+
+ + +
+
+ + +
+ {#if error} +

{error}

+ {/if} + +
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 @@ - +

User Preferences

- - + +
- - + +
@@ -42,4 +43,9 @@ dialog { background: white; color: black; } + +input[type="color"] { + width: 100%; + height: 100%; +} 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 @@ +
- {#if title} + {#if title}

{title}

- {/if} - {#if !noheader} + {/if} + {#if !noheader && data} - - {#each getDataKeys(data) as header} - - {/each} - + + {#each getDataKeys(data) as header} + + {/each} + - {/if} - - {#if data} - {#each data as row} + {/if} + + {#if data} + {#each data as row} - {#each getRow(row) as datum} - + {#each getRow(row) as datum} - {/each} + {/each} - {/each} - {:else} - - There is not data. - - {/if} - - {#if !nofooter} + {/each} + {:else} + There is not data. + {/if} + + {#if !nofooter} - + - + - + - {/if} + {/if}
{header}
{header}
{formatDatum(datum)}
Table FooterTable Footer
+ 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 @@ export class UnauthorizedError extends Error { - constructor (message?: string , options?: ErrorOptions) { - super(message, options); - } + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + } } 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 @@ -export function processFormInput(form) { - const formData = new FormData(form); - const data = {}; - for (let field of formData) { - const [key, value] = field; - data[key] = value; - } - return data; +export function processFormInput(form: HTMLFormElement) { + const formData = new FormData(form); + const data: Record = {}; + for (let field of formData) { + const [key, value] = field; + data[key] = value; + } + return data; } 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' import App from './App.svelte' const app = new App({ - target: document.getElementById('app'), + target: document.getElementById('app') as HTMLElement, }) export 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'; type Nullable = T | null; interface User { - uuid: string; - username: string; + uuid: string; + username: string; } interface TokenStore { - subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, - authenticate: (newToken: string) => void, - unauthenticate: () => void + subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, + authenticate: (newToken: string) => void, + unauthenticate: () => void } interface UserStore { - subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, - setUser: (user: User) => void, - reset: () => void + subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, + setUser: (user: User) => void, + reset: () => void } interface PreferenceStore { - subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, - set: (this: void, value: Nullable) => void + subscribe: (run: Subscriber, invalidate?: Invalidator) => Unsubscriber, + set: (this: void, value: Preference) => void } function createTokenStore(): TokenStore { - const storedToken = localStorage.getItem("token"); - const { subscribe, set } = writable(storedToken); - - function authenticate(newToken: string): void { - try { - localStorage.setItem("token", newToken); - set(newToken); - } catch (e) { - console.error('error', e); - } + const storedToken = localStorage.getItem("token"); + const { subscribe, set } = writable(storedToken); + + function authenticate(newToken: string): void { + try { + localStorage.setItem("token", newToken); + set(newToken); + } catch (e) { + console.error('error', e); } - - function unauthenticate(): void { - localStorage.removeItem("token"); - set(null); - } - - return { - subscribe, - authenticate, - unauthenticate - }; + } + + function unauthenticate(): void { + localStorage.removeItem("token"); + set(null); + } + + return { + subscribe, + authenticate, + unauthenticate + }; } -function onTokenChange ($token: Nullable): boolean { - return $token ? true : false; +function onTokenChange($token: Nullable): boolean { + return $token ? true : false; } function createUserStore(): UserStore { - const user = localStorage.getItem('user'); - const userObj: Nullable = user ? JSON.parse(user) : null; - const { subscribe, set } = writable(userObj); - - const setUser = (user: User) => { - localStorage.setItem('user', JSON.stringify(user)); - set(user); - } + const user = localStorage.getItem('user'); + const userObj: Nullable = user ? JSON.parse(user) : null; + const { subscribe, set } = writable(userObj); + + const setUser = (user: User) => { + localStorage.setItem('user', JSON.stringify(user)); + set(user); + } + + const reset = () => { + localStorage.removeItem('user'); + set(null); + } + + return { + subscribe, + setUser, + reset + } +} - const reset = () => { - localStorage.removeItem('user'); - set(null); - } - return { - subscribe, - setUser, - reset +function createPreferenceStore(): PreferenceStore { + const preferences = localStorage.getItem('preferences'); + const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { + color: "#FF0000", + size: { + size: 16, + unit: 'oz' } -} + }; + const { subscribe, set } = writable(preferenceObj); -function createPreferenceStore(): PreferenceStore { - const preferences = localStorage.getItem('preferences'); - const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { - color: "#FF0000", - size: { - size: 16, - unit: 'oz' - } - }; - - const { subscribe, set } = writable>(preferenceObj); - - return { - subscribe, - set - } + return { + subscribe, + set + } } export 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 @@ export interface Size { - size: number; - unit: string; + size: number; + unit: string; } export interface Preference { - color: string; - size: Size; + color: string; + size: Size; } export interface User { - name: string; - uuid: string; + name: string; + uuid: string; } + +export interface Statistic { + user_id: string; + date: string; + quantity: number; +} \ No newline at end of file -- cgit v1.1 From 74ec025991f6acde6383e448974738e857758ebb Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 1 Mar 2024 18:50:51 -0500 Subject: Add dependencies, refine dataview --- fe/src/lib/DataView.svelte | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'fe/src') diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 1458c9a..00ee21a 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -1,12 +1,16 @@ @@ -102,6 +126,7 @@ + {#await json then data} {:catch error} -- cgit v1.1 From d8b0f1335078d53d95a4212b1a4d4b0b28016702 Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 1 Mar 2024 20:12:21 -0500 Subject: feat(DataView): Add functionality to add water statistic This commit adds functionality to add water statistics to the DataView component. It includes the following changes: - Remove unused imports and variables - Move the 'handleClick' function logic to a new 'AddForm' component - Create the 'AddForm' component which displays a dialog with input fields for date and quantity and allows the user to add a new water statistic - Dispatch events on form submit and dialog close in the 'AddForm' component - Call the 'fetchData' function on successful submission of a new statistic - Update chart data to display sample data New component: - AddForm.svelte: A form component to add a new water statistic Note: This commit message exceeds the 50-character limit in the subject line, but adheres to the other specified requirements. --- fe/src/lib/DataView.svelte | 206 +++++++++++++++------------------------- fe/src/lib/forms/AddForm.svelte | 74 +++++++++++++++ 2 files changed, 148 insertions(+), 132 deletions(-) create mode 100644 fe/src/lib/forms/AddForm.svelte (limited to 'fe/src') diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 00ee21a..7f368c6 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -1,148 +1,90 @@
- - -

Add Water

-
-
- - -
-
- - -
- - - -
- - - {#await json then data} -
- {:catch error} -

{error}

- {/await} - + + + + {#await json then data} +
+ {:catch error} +

{error}

+ {/await} + diff --git a/fe/src/lib/forms/AddForm.svelte b/fe/src/lib/forms/AddForm.svelte new file mode 100644 index 0000000..f22e5f4 --- /dev/null +++ b/fe/src/lib/forms/AddForm.svelte @@ -0,0 +1,74 @@ + + + +

Add Water

+
+
+ + +
+
+ + +
+ + + +
\ No newline at end of file -- cgit v1.1 From 326f186d67017f87e631a1fbcdf3f184cbc42d7d Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 1 Mar 2024 20:26:42 -0500 Subject: feat: Add last seven days labels to chart In the `DataView.svelte` component, the last seven days are now included as labels in the chart. This allows users to easily visualize data for the past week. The `getLastSevenDays` function generates an array of string values representing the dates in ISO format. This array is assigned to the `lastSevenDays` variable, which is then used as the labels in the chart's dataset. --- fe/src/lib/DataView.svelte | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'fe/src') diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 7f368c6..5182a85 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -12,6 +12,8 @@ let canvasRef: HTMLCanvasElement; let chart: any; + let lastSevenDays: string[]; + async function fetchData() { const res = await fetch("http://localhost:8080/api/v1/stats/", { method: "GET", @@ -26,6 +28,16 @@ } } + function getLastSevenDays() { + const result = []; + for (let i = 0; i < 7; i++) { + let d = new Date(); + d.setDate(d.getDate() - i); + result.push(d.toISOString().substring(0, 10)); + } + return result; + } + function handleClick() { open = true; } @@ -41,14 +53,21 @@ onMount(() => { fetchData(); + lastSevenDays = getLastSevenDays(); chart = new Chart(canvasRef, { type: "bar", data: { - labels: ["one", "two"], + labels: lastSevenDays, datasets: [ { - label: "Water", - data: [1, 2], + label: "Zach", + data: [1, 2, 8, 2, 5, 5, 1], + backgroundColor: "rgba(255, 192, 192, 0.2)", + borderColor: "rgba(75, 192, 192, 1)", + borderWidth: 1 + }, { + label: "Parker", + data: [6, 1, 1, 4, 3, 5, 1], backgroundColor: "rgba(75, 192, 192, 0.2)", borderColor: "rgba(75, 192, 192, 1)", borderWidth: 1 @@ -66,8 +85,8 @@
- + {#await json then data}
{:catch error} -- cgit v1.1 From cf2113e77edabf8e3a632c7b76c769752039ba88 Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Sat, 2 Mar 2024 16:52:55 -0500 Subject: feat: Add API logging Add logging to the API to keep track of specific requests and headers. Also added CORS middleware to handle OPTIONS requests. --- The commit adds logging functionality to the API and includes a middleware function to handle CORS OPTIONS requests. This will allow us to track specific requests and headers received by the API. [API/main.go](/api/main.go): - Added import for the 'log' package - Added logging statements to print the request headers and "_I am here_" message - Removed unnecessary newlines and comments [fe/src/app.css](/fe/src/app.css): - Added a new style for button hover effects [fe/src/lib/Card.svelte](/fe/src/lib/Card.svelte): - Added a new `height` prop to the Card component [fe/src/lib/Column.svelte](/fe/src/lib/Column.svelte): - Added a new CSS class for column layout - Set the width and gap using CSS variables [fe/src/lib/DataView.svelte](/fe/src/lib/DataView.svelte): - Updated the 'fetchData' function to also fetch 'totals' and 'userStats' data - Added canvas references and chart variables for bar and line charts - Added a new 'getLastSevenDays' function to calculate the labels for the charts - Updated the 'onMount' function to initialize the bar and line charts using the canvas references and data - Updated the 'onDestroy' function to destroy the bar and line charts - Added a new 'addFormOpen' store and imported it - Added a new 'onClick' handler for the Add button to open the AddForm modal - Updated the layout and added Card components to display the bar and line charts and the JSON data - Added a new 'fetchTotals' function to fetch data for the 'totals' section - Added a new 'fetchStatsForUser' function to fetch data for the 'userStats' section [fe/src/lib/Layout.svelte](/fe/src/lib/Layout.svelte): - Added a new 'preferenceFormOpen' variable and initialized it to 'false' - Added a new 'showPreferencesDialog' function to set 'preferenceFormOpen' to 'true' - Added a new 'closePreferenceDialog' function to set 'preferenceFormOpen' to 'false' - Added a new 'showAddDialog' function to open the AddForm modal --- fe/src/app.css | 101 +++++++++++++------------- fe/src/lib/Card.svelte | 30 ++++---- fe/src/lib/Column.svelte | 13 ++++ fe/src/lib/DataView.svelte | 129 +++++++++++++++++++++++++++------- fe/src/lib/Layout.svelte | 116 ++++++++++++++++-------------- fe/src/lib/Table.svelte | 172 ++++++++++++++++++++++++--------------------- fe/src/stores/forms.ts | 6 ++ 7 files changed, 345 insertions(+), 222 deletions(-) create mode 100644 fe/src/lib/Column.svelte create mode 100644 fe/src/stores/forms.ts (limited to 'fe/src') diff --git a/fe/src/app.css b/fe/src/app.css index 0d5fa90..de19b52 100644 --- a/fe/src/app.css +++ b/fe/src/app.css @@ -1,82 +1,87 @@ :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; - --submit: #28a745; + --submit: #28a745; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } + a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + font-size: 3.2em; + line-height: 1.1; } .card { - padding: 2em; + padding: 2em; } #app { - flex-grow: 2; - max-width: 1280px; - margin: 0 auto; + flex-grow: 2; + max-width: 1280px; + margin: 0 auto; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; } + button:hover { - border-color: #646cff; + border-color: #646cff; } + button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } } @media (prefers-color-scheme: dark) { @@ -97,7 +102,7 @@ button:focus-visible { } .form.input.group label { - margin-bottom: .5em; + margin-bottom: .5em; } .form.input.group input { diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte index 0835940..d7cd900 100644 --- a/fe/src/lib/Card.svelte +++ b/fe/src/lib/Card.svelte @@ -1,22 +1,24 @@
- {#if title} -

{title}

- {/if} - + {#if title} +

{title}

+ {/if} +
diff --git a/fe/src/lib/Column.svelte b/fe/src/lib/Column.svelte new file mode 100644 index 0000000..f036073 --- /dev/null +++ b/fe/src/lib/Column.svelte @@ -0,0 +1,13 @@ +
+ +
+ + \ No newline at end of file diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 5182a85..2b1b8b9 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -1,16 +1,21 @@ -
- - - - {#await json then data} -
- {:catch error} -

{error}

- {/await} - - + + + + + + + + + + + + + {#await json then data} +
+ {:catch error} +

{error}

+ {/await} + + + + {#await totals then data} + {JSON.stringify(data)} + {:catch error} +

{error}

+ {/await} +
+ + + diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte index 3a66e0d..d1cd7da 100644 --- a/fe/src/lib/Table.svelte +++ b/fe/src/lib/Table.svelte @@ -1,105 +1,113 @@
- {#if title} -

{title}

- {/if} - {#if !noheader && data} - - - {#each getDataKeys(data) as header} - - {/each} - - - {/if} - - {#if data} - {#each data as row} + {#if title} +

{title}

+ {/if} + {#if !noheader && data} + - {#each getRow(row) as datum} - - {/each} + {#each getDataKeys(data) as header} + + {/each} - {/each} + + {/if} + + {#if data} + {#each limitedData as row} + + {#each getRow(row) as datum} + + {/each} + + {/each} {:else} - There is not data. + There is not data. + {/if} + + {#if !nofooter} + + + + + + + {/if} - - {#if !nofooter} - - - - - - - - {/if}
{header}
{formatDatum(datum)}{header}
{formatDatum(datum)}
Table Footer
Table Footer
diff --git a/fe/src/stores/forms.ts b/fe/src/stores/forms.ts new file mode 100644 index 0000000..daf9181 --- /dev/null +++ b/fe/src/stores/forms.ts @@ -0,0 +1,6 @@ +import type { Writable } from "svelte/store"; +import { writable } from "svelte/store"; + + +export const preferencesFormOpen: Writable = writable(false); +export const addFormOpen: Writable = writable(false); \ No newline at end of file -- cgit v1.1 From 5fa57845052655883120ba4d19a85d8756fb8d8c Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Wed, 6 Mar 2024 21:53:07 -0500 Subject: [FEAT] Refactor API main file and models This commit refactors the `main.go` file in the API directory, as well as the related models in the `models.go` file. The changes include: - Reordering imports and removing unnecessary imports - Fixing error messages to be more descriptive - Handling database connections more efficiently with deferred closures - Handling errors and returning appropriate error responses - Adding proper JSON bindings for POST requests - Adding new views in the database scripts for aggregated statistics and daily user statistics No changes were made to imports and requires. --- fe/src/lib/DataView.svelte | 130 +++++++++++++++++++++------------------- fe/src/lib/forms/AddForm.svelte | 13 ++-- 2 files changed, 77 insertions(+), 66 deletions(-) (limited to 'fe/src') diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 2b1b8b9..7d62a43 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -9,8 +9,6 @@ import AddForm from "./forms/AddForm.svelte"; let json: Promise; - let totals: Promise; - let userStats: Promise; let barCanvasRef: HTMLCanvasElement; let lineCanvasRef: HTMLCanvasElement; @@ -18,6 +16,10 @@ let lineChart: any; let lastSevenDays: string[]; + let lastSevenDaysData: number[]; + + let userTotalsLabels: string[]; + let userTotalsData: number[]; async function fetchData() { const res = await fetch("http://localhost:8080/api/v1/stats/", { @@ -33,24 +35,27 @@ } } - async function fetchTotals() { + async function fetchDailyUserStatistics() { const res = await fetch("http://localhost:8080/api/v1/stats/totals/", { - method: 'GET', - mode: 'no-cors', + method: "GET", headers: { Authorization: `Bearer ${$token}` } }); if (res.ok) { - totals = res.json(); + const json = await res.json(); + let labels = json.map(d => d.name); + let data = json.map(d => d.total); + return [labels, data]; } else { throw new Error("There was a problem with your request"); } + } - async function fetchStatsForUser() { - const res = await fetch("http://localhost:8080/api/v1/stats/user/1aa668f3-7527-4a67-9c24-fdf307542eeb", { + async function fetchWeeklyTotals() { + const res = await fetch("http://localhost:8080/api/v1/stats/weekly/", { method: "GET", headers: { Authorization: `Bearer ${$token}` @@ -58,22 +63,15 @@ }); if (res.ok) { - userStats = res.json(); + const json = await res.json(); + let labels = json.map(d => d.date); + let data = json.map(d => d.total); + return [labels, data]; } else { throw new Error("There was a problem with your request"); } } - function getLastSevenDays() { - const result = []; - for (let i = 0; i < 7; i++) { - let d = new Date(); - d.setDate(d.getDate() - i); - result.push(d.toISOString().substring(0, 10)); - } - return result; - } - function closeDialog() { addFormOpen.set(false); } @@ -81,61 +79,79 @@ function onStatisticAdd() { closeDialog(); fetchData(); + fetchWeeklyTotals().then(updateWeeklyTotalsChart).catch(err => console.error(err)); + fetchDailyUserStatistics().then(updateDailyUserTotalsChart).catch(err => console.error(err)); } - onMount(() => { - fetchData(); -// fetchTotals(); - fetchStatsForUser(); - lastSevenDays = getLastSevenDays(); - barChart = new Chart(barCanvasRef, { - type: "bar", + function setupWeeklyTotalsChart(result) { + [lastSevenDays, lastSevenDaysData] = result; + lineChart = new Chart(lineCanvasRef, { + type: "line", data: { labels: lastSevenDays, datasets: [ { - label: "Zach", - data: [1, 2, 8, 2, 5, 5, 1], - backgroundColor: "rgba(255, 192, 192, 0.2)", - borderColor: "rgba(75, 192, 192, 1)", - borderWidth: 1 - }, { - label: "Parker", - data: [6, 1, 1, 4, 3, 5, 1], - backgroundColor: "rgba(75, 192, 192, 0.2)", - borderColor: "rgba(75, 192, 192, 1)", - borderWidth: 1 + label: "Totals", + data: lastSevenDaysData, + backgroundColor: "rgba(255, 192, 192, 0.2)" } ] }, options: { - responsive: true + responsive: true, + plugins: { + legend: { + display: false + } + } } }); - lineChart = new Chart(lineCanvasRef, { - type: "line", + } + + function setupDailyUserTotalsChart(result) { + [userTotalsLabels, userTotalsData] = result; + + barChart = new Chart(barCanvasRef, { + type: "bar", data: { - labels: lastSevenDays, + labels: userTotalsLabels, datasets: [ { - label: "Zach", - data: [1, 2, 8, 2, 5, 5, 1], - backgroundColor: "rgba(255, 192, 192, 0.2)", - borderColor: "rgba(75, 192, 192, 1)", - borderWidth: 1 - }, { - label: "Parker", - data: [6, 1, 1, 4, 3, 5, 1], - backgroundColor: "rgba(75, 192, 192, 0.2)", - borderColor: "rgba(75, 192, 192, 1)", - borderWidth: 1 + data: userTotalsData, + backgroundColor: [ + "#330000", + "rgba(100, 200, 192, 0.2)" + ] } ] }, options: { - responsive: true + responsive: true, + plugins: { + legend: { + display: false + } + } } }); + } + + function updateWeeklyTotalsChart(result) { + [,lastSevenDaysData] = result; + lineChart.data.datasets[0].data = lastSevenDaysData; + lineChart.update(); + } + + function updateDailyUserTotalsChart(result) { + [,userTotalsData] = result; + barChart.data.datasets[0].data = userTotalsData; + barChart.update(); + } + + onMount(() => { + fetchData(); + fetchWeeklyTotals().then(setupWeeklyTotalsChart); + fetchDailyUserStatistics().then(setupDailyUserTotalsChart); }); onDestroy(() => { @@ -164,14 +180,6 @@

{error}

{/await} - - - {#await totals then data} - {JSON.stringify(data)} - {:catch error} -

{error}

- {/await} -
diff --git a/fe/src/lib/forms/AddForm.svelte b/fe/src/lib/forms/AddForm.svelte index f22e5f4..4520b1b 100644 --- a/fe/src/lib/forms/AddForm.svelte +++ b/fe/src/lib/forms/AddForm.svelte @@ -34,20 +34,23 @@ dispatch("close"); } - async function handleSubmitStat() { - const response = await fetch("http://localhost:8080/api/v1/stats/", { + async function handleSubmitStat() + { + const { date, quantity } = statistic; + await fetch("http://localhost:8080/api/v1/stats/", { method: "POST", headers: { Authorization: `Bearer ${$token}` }, body: JSON.stringify({ - date: new Date(), - user_id: 1, - quantity: 3 + date: new Date(date), + user_id: 2, + quantity }) }); dispatch("submit"); } + -- cgit v1.1 From 8fab2d03bce82e4dee798ebffb1e93c557f62a4b Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Thu, 7 Mar 2024 23:16:22 -0500 Subject: feat: Update authentication route and add comments to exported members - The authentication route in the API has been updated to use a new router setup function. - Comments have been added to all exported members of the `auth.go` module in the internal controllers package. --- fe/src/http.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++ fe/src/lib/DataView.svelte | 53 +++++++++++++++++++++++++++++++++++----- fe/src/lib/Table.svelte | 13 +++++----- 3 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 fe/src/http.ts (limited to 'fe/src') diff --git a/fe/src/http.ts b/fe/src/http.ts new file mode 100644 index 0000000..cc5a906 --- /dev/null +++ b/fe/src/http.ts @@ -0,0 +1,60 @@ +export default class HttpClient { + private static instance: HttpClient; + baseURL: string; + + private constructor(baseURL: string) { + this.baseURL = baseURL; + } + + private getURL(endpoint: string): URL { + return new URL(endpoint, this.baseURL) + } + + public static getInstance(): HttpClient { + if (!HttpClient.instance) { + const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? 'http://localhost:8080/api/v1'; + HttpClient.instance = new HttpClient(baseUrl); + } + + return HttpClient.instance; + } + + async get({ endpoint }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); + return response.json(); + } + + async post({ endpoint }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + }); + return response.json(); + } + + async patch({ endpoint, authenticated, headers }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + if (authenticated) { + + } + const response: Response = await fetch(url) + } + + async delete({ endpoint, authenticated }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + if (authenticated) { } + const response = await fetch() + } +} + +interface IHttpParameters { + endpoint: string; + authenticated: boolean; + headers: Headers +} diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 7d62a43..0a6b81b 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -1,5 +1,6 @@ - - + + - + diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte index d1cd7da..621157e 100644 --- a/fe/src/lib/Table.svelte +++ b/fe/src/lib/Table.svelte @@ -5,6 +5,10 @@ export let omit: string[] = ["id"]; export let title: string | undefined = undefined; + export let sortBy: string = 'date'; + + type SortComparator = (a, b) => number + function getDataKeys(data: any[]): string[] { if (!data || data.length === 0) return []; return Object.keys(data[0]) @@ -16,11 +20,8 @@ return Object.entries(row).filter((r) => !omit.includes(r[0])); } - - let limitedData: Array = []; - - if (data && (data as any[]).length > 0) { - limitedData = (data as any[]).slice(0, 4); + function sort(arr: Array>, fn: SortComparator = (a , b) => new Date(b[sortBy]) - new Date(a[sortBy])) { + return arr.sort(fn) } const formatter = new Intl.DateTimeFormat("en", { @@ -62,7 +63,7 @@ {/if} {#if data} - {#each limitedData as row} + {#each sort(data) as row} {#each getRow(row) as datum} {formatDatum(datum)} -- cgit v1.1 From 9cae9c1d2a0b4f7fa72f3075541b9ffafe1a7275 Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 15 Mar 2024 18:49:43 -0400 Subject: Add routes for preference, clean up and add types --- fe/src/app.css | 4 + fe/src/http.ts | 96 ++++++++++++++++-------- fe/src/lib/Card.svelte | 1 - fe/src/lib/Chart.svelte | 63 ++++++++++++++++ fe/src/lib/DataView.svelte | 40 ++++------ fe/src/lib/Layout.svelte | 8 +- fe/src/lib/LoginForm.svelte | 2 +- fe/src/lib/PreferencesForm.svelte | 145 ++++++++++++++++++++++++++---------- fe/src/lib/errors.ts | 6 +- fe/src/lib/utils.ts | 2 +- fe/src/stores/auth.ts | 153 +++++++++++++++++--------------------- fe/src/types.ts | 45 +++++++++-- 12 files changed, 370 insertions(+), 195 deletions(-) create mode 100644 fe/src/lib/Chart.svelte (limited to 'fe/src') diff --git a/fe/src/app.css b/fe/src/app.css index de19b52..c24c713 100644 --- a/fe/src/app.css +++ b/fe/src/app.css @@ -109,6 +109,10 @@ button:focus-visible { padding: 1em; } +.form.input.group input[type=color] { + padding: 0; +} + .form button[type=submit] { align-self: flex-end; background: var(--submit); diff --git a/fe/src/http.ts b/fe/src/http.ts index cc5a906..3b2a4f0 100644 --- a/fe/src/http.ts +++ b/fe/src/http.ts @@ -1,60 +1,92 @@ -export default class HttpClient { - private static instance: HttpClient; +let instance; +const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? "http://localhost:8080/api/v1"; + +class HttpClient { baseURL: string; - - private constructor(baseURL: string) { + commonHeaders: Headers; + + constructor(baseURL: string) { this.baseURL = baseURL; + this.commonHeaders = new Headers({ + "Content-Type": "application/json" + }) + if (instance) { + throw new Error("New instance cannot be created!"); + } + + instance = this; } - + private getURL(endpoint: string): URL { - return new URL(endpoint, this.baseURL) + return new URL(endpoint, this.baseURL); } - public static getInstance(): HttpClient { - if (!HttpClient.instance) { - const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? 'http://localhost:8080/api/v1'; - HttpClient.instance = new HttpClient(baseUrl); - } + private token(): string | null { + return localStorage.getItem('token'); + } - return HttpClient.instance; + private async makeRequest(request: Request): Promise { + return fetch(request) } - async get({ endpoint }: IHttpParameters): Promise { - const url = this.getURL(endpoint); - const response = await fetch(url, { - method: 'GET', - headers: headers, + async get({ endpoint, headers }: IHttpParameters): Promise { + const url: URL = this.getURL(endpoint); + headers = Object.assign(headers, this.commonHeaders); + const request: Request = new Request(url, { + method: "GET", + headers }); - return response.json(); + + return this.makeRequest(request); } - async post({ endpoint }: IHttpParameters): Promise { + async post({ endpoint, authenticated, body, headers }: IHttpParameters): Promise { const url = this.getURL(endpoint); - const response = await fetch(url, { - method: 'POST', + + if (authenticated) { + const token: string | null = this.token(); + headers.append('Authorization', `Bearer ${token}`); + } + + const request: Request = new Request(url, { + method: "POST", body: JSON.stringify(body), - headers: headers, - }); - return response.json(); + headers + }) + + return this.makeRequest(request); } - + async patch({ endpoint, authenticated, headers }: IHttpParameters): Promise { const url = this.getURL(endpoint); if (authenticated) { - + } - const response: Response = await fetch(url) + const response: Response = await fetch(url, { + method: "PATCH", + headers + }); } - - async delete({ endpoint, authenticated }: IHttpParameters): Promise { + + async delete({ endpoint, authenticated, headers }: IHttpParameters): Promise { const url = this.getURL(endpoint); - if (authenticated) { } - const response = await fetch() + if (authenticated) { + + } + const response: Response = await fetch(url, { + method: "DELETE", + headers + }) } } interface IHttpParameters { endpoint: string; + body: Record; authenticated: boolean; - headers: Headers + headers: Headers; } + +let http: Readonly = Object.freeze(new HttpClient(baseUrl)); + +export default http; diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte index d7cd900..cd1e02c 100644 --- a/fe/src/lib/Card.svelte +++ b/fe/src/lib/Card.svelte @@ -1,6 +1,5 @@
diff --git a/fe/src/lib/Chart.svelte b/fe/src/lib/Chart.svelte new file mode 100644 index 0000000..b19d932 --- /dev/null +++ b/fe/src/lib/Chart.svelte @@ -0,0 +1,63 @@ + + + \ No newline at end of file diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 0a6b81b..5e81a5a 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -1,10 +1,11 @@ + -

User Preferences

-
-
- - -
-
- - -
- -
+

User Preferences

+
+
+ + +
+
+ + +
+ + +
+ diff --git a/fe/src/lib/errors.ts b/fe/src/lib/errors.ts index d44bec5..81f7145 100644 --- a/fe/src/lib/errors.ts +++ b/fe/src/lib/errors.ts @@ -1,7 +1,5 @@ export class UnauthorizedError extends Error { - constructor(message?: string, options?: ErrorOptions) { - super(message, options); + constructor(message?: string) { + super(message); } } - - diff --git a/fe/src/lib/utils.ts b/fe/src/lib/utils.ts index 22d4e9a..e78556c 100644 --- a/fe/src/lib/utils.ts +++ b/fe/src/lib/utils.ts @@ -1,5 +1,5 @@ export function processFormInput(form: HTMLFormElement) { - const formData = new FormData(form); + const formData: FormData = new FormData(form); const data: Record = {}; for (let field of formData) { const [key, value] = field; diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts index 0efc80b..63f027e 100644 --- a/fe/src/stores/auth.ts +++ b/fe/src/stores/auth.ts @@ -1,100 +1,87 @@ -import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; -import type { Preference } from '../types'; -import { writable, derived } from 'svelte/store'; +import type { Preference, TokenStore, Nullable, UserStore, User, PreferenceStore } from "../types"; +import { writable, derived } from "svelte/store"; -type Nullable = T | null; - -interface User { - uuid: string; - username: string; -} - -interface TokenStore { - subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, - authenticate: (newToken: string) => void, - unauthenticate: () => void -} - - -interface UserStore { - subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, - setUser: (user: User) => void, - reset: () => void -} - -interface PreferenceStore { - subscribe: (run: Subscriber, invalidate?: Invalidator) => Unsubscriber, - set: (this: void, value: Preference) => void -} function createTokenStore(): TokenStore { - const storedToken = localStorage.getItem("token"); - const { subscribe, set } = writable(storedToken); - - function authenticate(newToken: string): void { - try { - localStorage.setItem("token", newToken); - set(newToken); - } catch (e) { - console.error('error', e); + const storedToken = localStorage.getItem("token"); + const { subscribe, set } = writable(storedToken); + + function authenticate(newToken: string): void { + try { + localStorage.setItem("token", newToken); + set(newToken); + } catch (e) { + console.error("error", e); + } + } + + function unauthenticate(): void { + localStorage.removeItem("token"); + set(null); } - } - - function unauthenticate(): void { - localStorage.removeItem("token"); - set(null); - } - - return { - subscribe, - authenticate, - unauthenticate - }; + + return { + subscribe, + authenticate, + unauthenticate + }; } function onTokenChange($token: Nullable): boolean { - return $token ? true : false; + return $token ? true : false; } function createUserStore(): UserStore { - const user = localStorage.getItem('user'); - const userObj: Nullable = user ? JSON.parse(user) : null; - const { subscribe, set } = writable(userObj); - - const setUser = (user: User) => { - localStorage.setItem('user', JSON.stringify(user)); - set(user); - } - - const reset = () => { - localStorage.removeItem('user'); - set(null); - } - - return { - subscribe, - setUser, - reset - } + const user = localStorage.getItem("user"); + const userObj: Nullable = user ? JSON.parse(user) : null; + const { subscribe, set } = writable(userObj); + + const setUser = (user: User) => { + localStorage.setItem("user", JSON.stringify(user)); + set(user); + }; + + const reset = () => { + localStorage.removeItem("user"); + set(null); + }; + + return { + subscribe, + setUser, + reset + }; } function createPreferenceStore(): PreferenceStore { - const preferences = localStorage.getItem('preferences'); - const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { - color: "#FF0000", - size: { - size: 16, - unit: 'oz' - } - }; - - const { subscribe, set } = writable(preferenceObj); - - return { - subscribe, - set - } + const preferences = localStorage.getItem("preferences"); + const preferenceObj: Preference = preferences ? JSON.parse(preferences) : { + id: 0, + color: "#FF0000", + size_id: 0, + user_id: 0 + }; + + const { subscribe, set, update } = writable>(preferenceObj); + + const setPreference = (preference: Preference) => { + localStorage.setItem("preference", JSON.stringify(preference)); + set(preference); + }; + + const reset = () => { + localStorage.removeItem("preference"); + set(null); + }; + + return { + set, + subscribe, + reset, + update, + setPreference, + }; } export const token = createTokenStore(); diff --git a/fe/src/types.ts b/fe/src/types.ts index 526e7eb..c8f2f00 100644 --- a/fe/src/types.ts +++ b/fe/src/types.ts @@ -1,14 +1,19 @@ -export interface Size { - size: number; - unit: string; -} +import type { Invalidator, Subscriber, Unsubscriber, Updater } from "svelte/store"; export interface Preference { + id: number; color: string; - size: Size; + size_id: number; + user_id: number; +} + +export interface Size { + size: number; + unit: string; } export interface User { + id: number; name: string; uuid: string; } @@ -17,4 +22,32 @@ export interface Statistic { user_id: string; date: string; quantity: number; -} \ No newline at end of file +} + +export type Nullable = T | null; + +export interface User { + uuid: string; + username: string; +} + +export interface TokenStore { + subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, + authenticate: (newToken: string) => void, + unauthenticate: () => void +} + + +export interface UserStore { + subscribe: (run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber, + setUser: (user: User) => void, + reset: () => void +} + +export interface PreferenceStore { + set: (this: void, value: Preference) => void; + subscribe: (this: void, run: Subscriber>, invalidate?: Invalidator>) => Unsubscriber; + reset: () => void; + update: (this: void, updater: Updater>) => void; + setPreference: (user: Preference) => void; +} -- cgit v1.1 From c4e5776f9e174fe6bf91721649c0541a9fb310ae Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 15 Mar 2024 21:41:12 -0400 Subject: add env samples, move files --- fe/src/errors.ts | 5 +++++ fe/src/lib/errors.ts | 5 ----- fe/src/lib/utils.ts | 9 --------- fe/src/utils.ts | 9 +++++++++ 4 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 fe/src/errors.ts delete mode 100644 fe/src/lib/errors.ts delete mode 100644 fe/src/lib/utils.ts create mode 100644 fe/src/utils.ts (limited to 'fe/src') diff --git a/fe/src/errors.ts b/fe/src/errors.ts new file mode 100644 index 0000000..81f7145 --- /dev/null +++ b/fe/src/errors.ts @@ -0,0 +1,5 @@ +export class UnauthorizedError extends Error { + constructor(message?: string) { + super(message); + } +} diff --git a/fe/src/lib/errors.ts b/fe/src/lib/errors.ts deleted file mode 100644 index 81f7145..0000000 --- a/fe/src/lib/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class UnauthorizedError extends Error { - constructor(message?: string) { - super(message); - } -} diff --git a/fe/src/lib/utils.ts b/fe/src/lib/utils.ts deleted file mode 100644 index e78556c..0000000 --- a/fe/src/lib/utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function processFormInput(form: HTMLFormElement) { - const formData: FormData = new FormData(form); - const data: Record = {}; - for (let field of formData) { - const [key, value] = field; - data[key] = value; - } - return data; -} diff --git a/fe/src/utils.ts b/fe/src/utils.ts new file mode 100644 index 0000000..e78556c --- /dev/null +++ b/fe/src/utils.ts @@ -0,0 +1,9 @@ +export function processFormInput(form: HTMLFormElement) { + const formData: FormData = new FormData(form); + const data: Record = {}; + for (let field of formData) { + const [key, value] = field; + data[key] = value; + } + return data; +} -- cgit v1.1 From fd1332a3df191577e91c6d846a8b5db1747099fd Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Fri, 15 Mar 2024 22:00:10 -0400 Subject: cleanup --- fe/src/lib/DataView.svelte | 7 ++++--- fe/src/lib/LoginForm.svelte | 3 ++- fe/src/lib/PreferencesForm.svelte | 15 ++++++++------- fe/src/lib/forms/AddForm.svelte | 3 ++- fe/src/utils.ts | 5 +++++ 5 files changed, 21 insertions(+), 12 deletions(-) (limited to 'fe/src') diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 5e81a5a..ffc2fe8 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte @@ -9,6 +9,7 @@ import Card from "./Card.svelte"; import Column from "./Column.svelte"; import AddForm from "./forms/AddForm.svelte"; + import { apiURL } from "../utils"; let json: Promise; @@ -24,7 +25,7 @@ let userTotalsData: number[]; async function fetchData() { - const res = await fetch("http://localhost:8080/api/v1/stats/", { + const res = await fetch(apiURL("stats"), { method: "GET", headers: { Authorization: `Bearer ${$token}` @@ -38,7 +39,7 @@ } async function fetchDailyUserStatistics() { - const res = await fetch("http://localhost:8080/api/v1/stats/daily/", { + const res = await fetch(apiURL("stats/daily"), { method: "GET", headers: { Authorization: `Bearer ${$token}` @@ -57,7 +58,7 @@ } async function fetchWeeklyTotals() { - const res = await fetch("http://localhost:8080/api/v1/stats/weekly/", { + const res = await fetch(apiURL("stats/weekly"), { method: "GET", headers: { Authorization: `Bearer ${$token}` diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte index 8c3c288..cf5febf 100644 --- a/fe/src/lib/LoginForm.svelte +++ b/fe/src/lib/LoginForm.svelte @@ -1,6 +1,7 @@