Merge pull request 'Client feature: Add Login view' (#2) from frontend/feature/login-view into feature/user-auth
Reviewed-on: https://git.tams.tech/scott/kalkutago/pulls/2
This commit is contained in:
commit
396ed28079
|
@ -6,7 +6,8 @@ const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/', component: TableView },
|
{ path: '/', component: TableView },
|
||||||
{ path: '/new-track', component: NewTrackView }
|
{ path: '/new-track', component: NewTrackView },
|
||||||
|
{ path: '/login', component: import('./views/Login.vue') }
|
||||||
// for other pages:
|
// for other pages:
|
||||||
// {path: '/', component: import('./views/TableView.vue')}
|
// {path: '/', component: import('./views/TableView.vue')}
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,9 +18,19 @@ function dateQuery(date: Date): URLSearchParams {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
export const state = reactive({
|
interface LoggedInUser {
|
||||||
tracks: new Array<Track>,
|
name: string
|
||||||
state: State.Unfetched,
|
}
|
||||||
|
|
||||||
|
class AppState {
|
||||||
|
tracks: Array<Track>
|
||||||
|
state: State
|
||||||
|
user?: LoggedInUser
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tracks = new Array<Track>
|
||||||
|
this.state = State.Unfetched
|
||||||
|
}
|
||||||
streamUpdatesFromServer() {
|
streamUpdatesFromServer() {
|
||||||
const source = new EventSource("/api/v1/updates")
|
const source = new EventSource("/api/v1/updates")
|
||||||
source.addEventListener("open", () => console.debug("opened event source"))
|
source.addEventListener("open", () => console.debug("opened event source"))
|
||||||
|
@ -69,17 +79,17 @@ export const state = reactive({
|
||||||
window.location = window.location
|
window.location = window.location
|
||||||
})
|
})
|
||||||
window.addEventListener('beforeunload', () => source.close())
|
window.addEventListener('beforeunload', () => source.close())
|
||||||
},
|
}
|
||||||
async repopulate() {
|
async repopulate() {
|
||||||
this.state = State.Fetching
|
this.state = State.Fetching
|
||||||
this.tracks = await Track.fetchAll()
|
this.tracks = await Track.fetchAll()
|
||||||
},
|
}
|
||||||
async populate() {
|
async populate() {
|
||||||
if (this.state != State.Unfetched) return
|
if (this.state != State.Unfetched) return
|
||||||
await this.repopulate()
|
await this.repopulate()
|
||||||
this.streamUpdatesFromServer()
|
this.streamUpdatesFromServer()
|
||||||
this.state = State.Fetched
|
this.state = State.Fetched
|
||||||
},
|
}
|
||||||
async taskCompleted(track: Track, date: Date): Promise<Tick> {
|
async taskCompleted(track: Track, date: Date): Promise<Tick> {
|
||||||
const query = dateQuery(date)
|
const query = dateQuery(date)
|
||||||
const response: Response = await fetch(`/api/v1/tracks/${track.id}/ticked?${query.toString()}`, { method: "PATCH" })
|
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}`)
|
throw new Error(`error setting tick for track ${track.id} ("${track.name}"): ${response.status} ${response.statusText}`)
|
||||||
}
|
}
|
||||||
return JSON.parse(body)
|
return JSON.parse(body)
|
||||||
},
|
}
|
||||||
async taskMarkedIncomplete(track: Track, date: Date) {
|
async taskMarkedIncomplete(track: Track, date: Date) {
|
||||||
const query = dateQuery(date)
|
const query = dateQuery(date)
|
||||||
const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks?${query.toString()}`, { method: 'DELETE' })
|
const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks?${query.toString()}`, { method: 'DELETE' })
|
||||||
if (!ok)
|
if (!ok)
|
||||||
error(`error deleting ticks for ${track.id}: ${statusText} (${status})`)
|
error(`error deleting ticks for ${track.id}: ${statusText} (${status})`)
|
||||||
},
|
}
|
||||||
async addTrack(track: Track): Promise<boolean> {
|
async addTrack(track: Track): Promise<boolean> {
|
||||||
const response = await fetch('/api/v1/tracks', {
|
const response = await fetch('/api/v1/tracks', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -105,9 +115,11 @@ export const state = reactive({
|
||||||
if (!response.ok)
|
if (!response.ok)
|
||||||
error(`error submitting track: ${track}: ${response.statusText} (${response.status})`)
|
error(`error submitting track: ${track}: ${response.statusText} (${response.status})`)
|
||||||
return response.ok
|
return response.ok
|
||||||
},
|
}
|
||||||
async removeTrack(trackID: number) {
|
async removeTrack(trackID: number) {
|
||||||
const response = await fetch(`/api/v1/tracks/${trackID}`, { method: "DELETE" })
|
const response = await fetch(`/api/v1/tracks/${trackID}`, { method: "DELETE" })
|
||||||
if (!response.ok) error(`error deleting track with ID ${trackID}: ${response.statusText} (${response.status})`)
|
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