forked from TWS/kalkutago
Compare commits
2 commits
e25301655b
...
396ed28079
Author | SHA1 | Date | |
---|---|---|---|
scott | 396ed28079 | ||
D. Scott Boggs | a390f79a75 |
|
@ -6,7 +6,8 @@ const router = createRouter({
|
|||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: TableView },
|
||||
{ path: '/new-track', component: NewTrackView }
|
||||
{ path: '/new-track', component: NewTrackView },
|
||||
{ path: '/login', component: import('./views/Login.vue') }
|
||||
// for other pages:
|
||||
// {path: '/', component: import('./views/TableView.vue')}
|
||||
]
|
||||
|
|
|
@ -18,9 +18,19 @@ function dateQuery(date: Date): URLSearchParams {
|
|||
return query
|
||||
}
|
||||
|
||||
export const state = reactive({
|
||||
tracks: new Array<Track>,
|
||||
state: State.Unfetched,
|
||||
interface LoggedInUser {
|
||||
name: string
|
||||
}
|
||||
|
||||
class AppState {
|
||||
tracks: Array<Track>
|
||||
state: State
|
||||
user?: LoggedInUser
|
||||
|
||||
constructor() {
|
||||
this.tracks = new Array<Track>
|
||||
this.state = State.Unfetched
|
||||
}
|
||||
streamUpdatesFromServer() {
|
||||
const source = new EventSource("/api/v1/updates")
|
||||
source.addEventListener("open", () => console.debug("opened event source"))
|
||||
|
@ -69,17 +79,17 @@ export const state = reactive({
|
|||
window.location = window.location
|
||||
})
|
||||
window.addEventListener('beforeunload', () => source.close())
|
||||
},
|
||||
}
|
||||
async repopulate() {
|
||||
this.state = State.Fetching
|
||||
this.tracks = await Track.fetchAll()
|
||||
},
|
||||
}
|
||||
async populate() {
|
||||
if (this.state != State.Unfetched) return
|
||||
await this.repopulate()
|
||||
this.streamUpdatesFromServer()
|
||||
this.state = State.Fetched
|
||||
},
|
||||
}
|
||||
async taskCompleted(track: Track, date: Date): Promise<Tick> {
|
||||
const query = dateQuery(date)
|
||||
const response: Response = await fetch(`/api/v1/tracks/${track.id}/ticked?${query.toString()}`, { method: "PATCH" })
|
||||
|
@ -89,13 +99,13 @@ export const state = reactive({
|
|||
throw new Error(`error setting tick for track ${track.id} ("${track.name}"): ${response.status} ${response.statusText}`)
|
||||
}
|
||||
return JSON.parse(body)
|
||||
},
|
||||
}
|
||||
async taskMarkedIncomplete(track: Track, date: Date) {
|
||||
const query = dateQuery(date)
|
||||
const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks?${query.toString()}`, { method: 'DELETE' })
|
||||
if (!ok)
|
||||
error(`error deleting ticks for ${track.id}: ${statusText} (${status})`)
|
||||
},
|
||||
}
|
||||
async addTrack(track: Track): Promise<boolean> {
|
||||
const response = await fetch('/api/v1/tracks', {
|
||||
method: "POST",
|
||||
|
@ -105,9 +115,11 @@ export const state = reactive({
|
|||
if (!response.ok)
|
||||
error(`error submitting track: ${track}: ${response.statusText} (${response.status})`)
|
||||
return response.ok
|
||||
},
|
||||
}
|
||||
async removeTrack(trackID: number) {
|
||||
const response = await fetch(`/api/v1/tracks/${trackID}`, { method: "DELETE" })
|
||||
if (!response.ok) error(`error deleting track with ID ${trackID}: ${response.statusText} (${response.status})`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const state = reactive(new AppState)
|
||||
|
|
69
client/src/views/Login.vue
Normal file
69
client/src/views/Login.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { state } from '../state';
|
||||
|
||||
const name = ref("")
|
||||
const password = ref("")
|
||||
|
||||
async function signUp() {
|
||||
const $name = name.value
|
||||
const result = await fetch("/api/v1/auth", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: $name, password: password.value })
|
||||
})
|
||||
if (result.ok) {
|
||||
state.user = { name: $name }
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const $name = name.value
|
||||
const result = await fetch("/api/v1/auth", {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ name: $name, password: password.value })
|
||||
})
|
||||
if (result.ok) {
|
||||
state.user = { name: $name }
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="modal is-active">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<div class="modal-card-title">Log in or Sign up</div>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label for="username">Name</label>
|
||||
<div class="control">
|
||||
<input type="text" name="username" class="input" v-model="name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="password" class="label">Password</label>
|
||||
<div class="control">
|
||||
<input type="password" name="password" class="input" v-model="password" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="submit button is-success" @click="login">Log in</button>
|
||||
<button class="submit button is-info" @click="signUp">Sign Up</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-card-foot {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.button.submit {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue