aboutsummaryrefslogtreecommitdiff
path: root/fe/src
diff options
context:
space:
mode:
Diffstat (limited to 'fe/src')
-rw-r--r--fe/src/App.svelte167
-rw-r--r--fe/src/app.css111
-rw-r--r--fe/src/assets/svelte.svg1
-rw-r--r--fe/src/lib/Card.svelte22
-rw-r--r--fe/src/lib/Counter.svelte10
-rw-r--r--fe/src/lib/Table.svelte41
-rw-r--r--fe/src/lib/errors.ts7
-rw-r--r--fe/src/lib/utils.ts9
-rw-r--r--fe/src/main.ts8
-rw-r--r--fe/src/vite-env.d.ts2
10 files changed, 378 insertions, 0 deletions
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 @@
1<script lang="ts">
2 import {onMount} from 'svelte';
3 import svelteLogo from './assets/svelte.svg'
4 import viteLogo from '/vite.svg'
5 import Counter from './lib/Counter.svelte'
6 import Table from './lib/Table.svelte'
7 import Card from './lib/Card.svelte'
8 import { UnauthorizedError } from './lib/errors';
9
10 let data;
11 let error;
12
13 let user = {
14 username: '',
15 password: ''
16 }
17
18 interface CredentialObject {
19 username: string;
20 password: string;
21 }
22
23 function sleep(ms) {
24 return new Promise(resolve => setTimeout(resolve, ms));
25 }
26
27 async function getData() {
28 const res = await fetch('http://localhost:8080/api/v1/stats/');
29 if (res.ok) {
30 await sleep(3000);
31 return await res.json();
32 } else {
33 throw new Error('There was a problem with your request');
34 }
35 }
36
37 function handleClick () {
38 data = getData();
39 }
40
41 let authenticated: boolean = false;
42
43 function prepareCredentials ({ username, password }: CredentialObject): string {
44 return btoa(`${username}:${password}`);
45 }
46
47
48 async function onSubmit(e) {
49 if (!user.username || !user.password) {
50 error = 'please enter your username and password';
51 return;
52 }
53 const auth = prepareCredentials(user);
54
55 const response = await fetch('http://localhost:8080/api/v1/auth', {
56 method: 'POST',
57 headers: {
58 'Authorization': `Basic ${auth}`,
59 },
60 });
61
62 if (response.status === 401) {
63 error = "Your username or password is wrong";
64 return;
65 }
66
67 if (response.ok) {
68 const { token } = await response.json();
69 console.log(token);
70 localStorage.user = JSON.stringify(user);
71 localStorage.token = token;
72 authenticated = true;
73 }
74
75
76 error = null;
77 }
78
79 function logout() {
80 localStorage.removeItem("user");
81 localStorage.removeItem("token");
82 authenticated = false;
83 }
84
85
86 onMount(() => {
87 if (localStorage.token) {
88 authenticated = true;
89 }
90 });
91</script>
92
93<main>
94 {#if !authenticated}
95 <Card>
96 <form class="form" on:submit|preventDefault={onSubmit}>
97 <div class='form input group'>
98 <label for="username">Username</label>
99 <input bind:value={user.username} id="username" name='username' type="text" />
100 </div>
101 <div class='form input group'>
102 <label for="password">Password</label>
103 <input bind:value={user.password} id="password" name='password' type="password" />
104 </div>
105 {#if error}
106 <p class="error">{error}</p>
107 {/if}
108 <button type="submit">Log in</button>
109 </form>
110 </Card>
111 {:else}
112 <div>
113 <button on:click={logout}>Logout</button>
114 </div>
115 <div>
116 <a href="https://vitejs.dev" target="_blank" rel="noreferrer">
117 <img src={viteLogo} class="logo" alt="Vite Logo" />
118 </a>
119 <a href="https://svelte.dev" target="_blank" rel="noreferrer">
120 <img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
121 </a>
122 </div>
123
124 <button on:click={handleClick}>
125 Get Data
126 </button>
127
128 {#await data}
129 <p>...fetching</p>
130 {:then data}
131 {#if data}
132 <p>Status</p>
133 <p>{data.status}</p>
134 <Table />
135 <Table nofooter title="No Footer"/>
136 <Table noheader title="No Header"/>
137 {:else}
138 <p>No data yet</p>
139 {/if}
140 {:catch errror}
141 <p>{error.message}</p>
142 {/await}
143 {/if}
144</main>
145
146<style>
147 .logo {
148 height: 6em;
149 padding: 1.5em;
150 will-change: filter;
151 transition: filter 300ms;
152 }
153 .logo:hover {
154 filter: drop-shadow(0 0 2em #646cffaa);
155 }
156 .logo.svelte:hover {
157 filter: drop-shadow(0 0 2em #ff3e00aa);
158 }
159 .read-the-docs {
160 color: #888;
161 }
162
163 .error {
164 font-size: 0.75em;
165 color: red;
166 }
167</style>
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 @@
1:root {
2 font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 line-height: 1.5;
4 font-weight: 400;
5
6 color-scheme: light dark;
7 color: rgba(255, 255, 255, 0.87);
8 background-color: #242424;
9
10 font-synthesis: none;
11 text-rendering: optimizeLegibility;
12 -webkit-font-smoothing: antialiased;
13 -moz-osx-font-smoothing: grayscale;
14
15 --submit: #28a745;
16}
17
18a {
19 font-weight: 500;
20 color: #646cff;
21 text-decoration: inherit;
22}
23a:hover {
24 color: #535bf2;
25}
26
27body {
28 margin: 0;
29 display: flex;
30 place-items: center;
31 min-width: 320px;
32 min-height: 100vh;
33}
34
35h1 {
36 font-size: 3.2em;
37 line-height: 1.1;
38}
39
40.card {
41 padding: 2em;
42}
43
44#app {
45 max-width: 1280px;
46 margin: 0 auto;
47 padding: 2rem;
48}
49
50button {
51 border-radius: 8px;
52 border: 1px solid transparent;
53 padding: 0.6em 1.2em;
54 font-size: 1em;
55 font-weight: 500;
56 font-family: inherit;
57 background-color: #1a1a1a;
58 cursor: pointer;
59 transition: border-color 0.25s;
60}
61button:hover {
62 border-color: #646cff;
63}
64button:focus,
65button:focus-visible {
66 outline: 4px auto -webkit-focus-ring-color;
67}
68
69@media (prefers-color-scheme: light) {
70 :root {
71 color: #213547;
72 background-color: #ffffff;
73 }
74 a:hover {
75 color: #747bff;
76 }
77 button {
78 background-color: #f9f9f9;
79 }
80}
81
82@media (prefers-color-scheme: dark) {
83 :root {
84 color: #000;
85 }
86}
87
88.form {
89 display: flex;
90 flex-direction: column;
91}
92
93.form.input.group {
94 display: flex;
95 flex-direction: column;
96 margin-bottom: 1em;
97}
98
99.form.input.group label {
100 margin-bottom: .5em;
101}
102
103.form.input.group input {
104 padding: 1em;
105}
106
107.form button[type=submit] {
108 align-self: flex-end;
109 background: var(--submit);
110 color: #fff;
111}
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 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg> \ 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 @@
1<script lang="ts">
2export let title;
3</script>
4
5<div class="card">
6 {#if title}
7 <h2>{title}</h2>
8 {/if}
9 <slot />
10</div>
11
12<style>
13.card {
14 background: #fff;
15 width: 16rem;
16 display: flex;
17 flex-direction: column;
18 align-items: left;
19 border: solid 2px #00000066;
20 border-radius: 0.25em;
21}
22</style>
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 @@
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/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 @@
1<script lang="ts">
2 export let data;
3 export let nofooter: boolean = false;
4 export let noheader: boolean = false;
5 export let title: string;
6</script>
7<table>
8 {#if title}
9 <h2>{title}</h2>
10 {/if}
11 {#if !noheader}
12 <thead>
13 <tr>
14 <th>
15 Data Header
16 </th>
17 </tr>
18 </thead>
19 {/if}
20 <tbody>
21 <tr>
22 <td>Data</td>
23 </tr>
24 </tbody>
25 {#if !nofooter}
26 <slot name="footer">
27 <tfoot>
28 <tr>
29 <td>Table Footer</td>
30 </tr>
31 </tfoot>
32 </slot>
33 {/if}
34</table>
35<style>
36table {
37 padding: 16px;
38 margin: 8px;
39 border: solid 1px black;
40}
41</style>
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 @@
1export class UnauthorizedError extends Error {
2 constructor (message?: string , options?: ErrorOptions) {
3 super(message, options);
4 }
5}
6
7
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 @@
1export function processFormInput(form) {
2 const formData = new FormData(form);
3 const data = {};
4 for (let field of formData) {
5 const [key, value] = field;
6 data[key] = value;
7 }
8 return data;
9}
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 @@
1import './app.css'
2import App from './App.svelte'
3
4const app = new App({
5 target: document.getElementById('app'),
6})
7
8export 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 @@
1/// <reference types="svelte" />
2/// <reference types="vite/client" />