added token storage in a store, and improved layout on mobile

This commit is contained in:
2025-12-15 16:24:30 +01:00
parent d6a26c0d09
commit 0714088d4b
17 changed files with 170 additions and 154 deletions

View File

@@ -1,10 +1,25 @@
<script setup lang="ts">
import { useAuthStore } from "./stores/auth";
const auth = useAuthStore();
</script>
<template>
<!-- You can later replace this with a real layout -->
<router-view />
<div id="page">
<router-view />
</div>
</template>
<style scoped>
#page {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
padding: 2rem;
box-sizing: border-box;
background: var(--background);
}
@media screen and (max-width: 720px) {
#page {
padding: 0;
padding-top: 20px;
padding-bottom: 40px;
}
}
</style>

View File

@@ -1,12 +1,12 @@
import { useAuthStore } from '../stores/auth'
import { initAuth } from '../stores/auth.ts'
const BASE_URL = 'http://localhost:8080'
const BASE_URL = 'http://192.168.1.183:8081'
export async function apiFetch<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const auth = useAuthStore()
const auth = await initAuth()
const res = await fetch(`${BASE_URL}${path}`, {
...options,

View File

@@ -1,4 +1,3 @@
/* ---- CSS reset (minimal, modern) ---- */
*,
*::before,
*::after {
@@ -16,19 +15,17 @@ body,
height: 100%;
}
/* ---- Theme variables ---- */
:root {
--bg: #0f1115;
--panel: #171923;
--bg: #0f1116;
--panel: #171922;
--text: #e6e6eb;
--muted: #9aa0a6;
--accent: #f27aa2;
--accent-hover: #ff91b4;
--border: #2a2f3a;
--muted: #9aa0aa;
--accent: #f27aa3;
--accent-hover: #ff91b3;
--border: #2a2f3b;
--radius: 8px;
}
/* ---- Base ---- */
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
@@ -37,7 +34,6 @@ body {
line-height: 1.5;
}
/* ---- Inputs & buttons ---- */
input, textarea {
background: var(--panel);
border: 1px solid var(--border);

View File

@@ -5,8 +5,9 @@ defineProps<{ messages: Message[] }>()
<template>
<ul>
<li v-for="(m, i) in messages" :key="i">
<strong>{{ m.sender }}:</strong> {{ m.content }}
<li v-for="(m, i) in messages" :key="i" class="message">
<div class="sender">{{ m.sender }}</div>
<div class="message-content">{{ m.content }}</div>
</li>
</ul>
</template>
@@ -16,16 +17,22 @@ ul {
padding: 0;
margin: 0;
list-style: none;
word-wrap: break-word;
}
li {
white-space: pre-wrap;
.message {
margin-bottom: 1rem;
}
.sender {
font-weight: bold;
margin-bottom: 0.25rem;
}
.message-content {
display: inline-block;
padding-left: 1rem;
white-space: pre-wrap;
word-wrap: break-word;
max-width: 100%;
word-break: break-word;
display: block;
}
</style>

View File

@@ -1,11 +1,9 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
import './base.css'
createApp(App)
.use(createPinia())
.use(router)
.mount('#app')

View File

@@ -50,9 +50,8 @@ function scrollToBottom() {
.chat-page {
display: flex;
flex-direction: column;
height: 100%;
max-width: 720px;
margin: 0 auto;
height: 95%;
width: 90%;
padding: 15px;
border: 1px solid var(--border);
border-radius: var(--radius);

View File

@@ -1,18 +1,22 @@
<script setup lang="ts">
import { ref } from "vue";
import { useAuthStore } from "../stores/auth";
import { login } from '../stores/auth.ts'
import { useRouter } from "vue-router";
const email = ref("");
const username = ref("");
const password = ref("");
const errorMessage = ref("");
const auth = useAuthStore();
const router = useRouter();
async function submit() {
await auth.login(email.value, username.value, password.value);
router.push("/");
errorMessage.value = "";
try {
await login(email.value, "", password.value);
router.push("/");
} catch (err: any) {
errorMessage.value = err?.message || "An unknown error occurred";
}
}
</script>
@@ -25,6 +29,8 @@ async function submit() {
<input v-model="password" type="password" placeholder="password" />
<button type="submit">Login</button>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</form>
</div>
</template>
@@ -55,4 +61,11 @@ async function submit() {
margin-bottom: 0.5rem;
text-align: center;
}
.error-message {
color: red;
font-size: 0.9rem;
text-align: center;
margin-top: 0.5rem;
}
</style>

View File

@@ -17,14 +17,14 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAuthStore } from '../stores/auth'
import { initAuth } from '../stores/auth.ts'
import { fetchRooms } from '../api/rooms'
import type { Room } from '../types/api'
const auth = useAuthStore()
const rooms = ref<Room[]>([])
onMounted(async () => {
const auth = await initAuth()
rooms.value = await fetchRooms(auth.uuid!)
})
</script>

View File

@@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { initAuth } from '../stores/auth.ts'
import LoginPage from '../pages/LoginPage.vue'
import RoomsPage from '../pages/RoomsPage.vue'
@@ -14,8 +14,8 @@ const router = createRouter({
],
})
router.beforeEach((to) => {
const auth = useAuthStore()
router.beforeEach(async (to) => {
const auth = await initAuth()
if (!auth.isAuthenticated && to.path !== '/login') {
return '/login'
}

View File

@@ -1,32 +1,41 @@
import { defineStore } from 'pinia'
import { load, Store } from '@tauri-apps/plugin-store'
import type { LoginResponse } from '../types/api'
import { apiFetch } from '../api/client'
export const useAuthStore = defineStore('auth', {
state: () => ({
token: null as string | null,
uuid: null as string | null,
}),
let store: Store | null = null
getters: {
isAuthenticated: (s) => !!s.token,
},
async function getStore() {
if (!store) {
store = await load('store.json', { autoSave: false })
}
return store
}
actions: {
async login(email: string, username: string, password: string) {
const res = await apiFetch<LoginResponse>('/login', {
method: 'POST',
body: JSON.stringify({ email, username, password }),
})
export async function initAuth() {
const s = await getStore()
const token = await s.get<string>('token')
const uuid = await s.get<string>('uuid')
return { token: token || null, uuid: uuid || null, isAuthenticated: !!token }
}
this.token = res.token
this.uuid = res.uuid
},
export async function login(email: string, username: string, password: string) {
const res: LoginResponse = await apiFetch('/login', {
method: 'POST',
body: JSON.stringify({ email, username, password }),
})
logout() {
this.token = null
this.uuid = null
},
},
})
const s = await getStore()
await s.set('token', res.token)
await s.set('uuid', res.uuid)
await s.save()
return { token: res.token, uuid: res.uuid, isAuthenticated: true }
}
export async function logout() {
const s = await getStore()
await s.delete('token')
await s.delete('uuid')
await s.save()
return { token: null, uuid: null, isAuthenticated: false }
}