added token storage in a store, and improved layout on mobile
This commit is contained in:
31
src/App.vue
31
src/App.vue
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
16
src/base.css
16
src/base.css
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user