Added creating new whiteboards from home screen, refactoring

This commit is contained in:
Veljko Tosic
2026-02-18 20:24:27 +01:00
parent 7bae0d4dd6
commit cae724f2d6
10 changed files with 76 additions and 18 deletions

View File

@@ -2,11 +2,11 @@
import { onMounted, computed } from 'vue' import { onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useWhiteboardStore } from '@/stores/whiteboards' import { useWhiteboardsStore } from '@/stores/whiteboards'
import RecentWhiteboardsItem from './RecentWhiteboardsItem.vue' import RecentWhiteboardsItem from './RecentWhiteboardsItem.vue'
const router = useRouter() const router = useRouter()
const store = useWhiteboardStore() const store = useWhiteboardsStore()
onMounted(() => { onMounted(() => {
if (store.recentWhiteboards.length === 0) store.getRecentWhiteboards() if (store.recentWhiteboards.length === 0) store.getRecentWhiteboards()

View File

@@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, computed } from 'vue' import { onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useWhiteboardStore } from '@/stores/whiteboards' import { useWhiteboardsStore } from '@/stores/whiteboards'
import WhiteboardHistoryItem from './WhiteboardHistoryItem.vue' import WhiteboardHistoryItem from './WhiteboardHistoryItem.vue'
const router = useRouter() const router = useRouter()
const store = useWhiteboardStore() const store = useWhiteboardsStore()
onMounted(() => { onMounted(() => {
if (store.ownedWhiteboards.length === 0) store.getWhiteboardHistory() if (store.ownedWhiteboards.length === 0) store.getWhiteboardHistory()

View File

@@ -52,7 +52,10 @@ router.beforeEach((to) => {
} }
if (to.meta.requiresAuth && !auth.isAuthenticated) { if (to.meta.requiresAuth && !auth.isAuthenticated) {
return '/login' return {
name: 'login',
query: { redirect: to.fullPath }
}
} }
}) })

View File

@@ -1,6 +1,3 @@
// A small fetch-based HTTP client with automatic token attach and refresh-on-401.
// This avoids circular imports by reading/writing tokens directly from localStorage.
const ACCESS_TOKEN = 'auth_token' const ACCESS_TOKEN = 'auth_token'
const REFRESH_TOKEN = 'refresh_token' const REFRESH_TOKEN = 'refresh_token'
@@ -9,7 +6,6 @@ async function parseJsonOrThrow(res: Response) {
try { try {
return text ? JSON.parse(text) : undefined return text ? JSON.parse(text) : undefined
} catch (e) { } catch (e) {
// non-JSON response
return text return text
} }
} }

View File

@@ -1,5 +1,5 @@
import type { Whiteboard } from "@/types"; import type {Whiteboard} from "@/types";
import { api } from './api' import {api} from './api'
export const whiteboardService = { export const whiteboardService = {
async getWhiteboardHistory(): Promise<Whiteboard[]> { async getWhiteboardHistory(): Promise<Whiteboard[]> {
@@ -10,6 +10,14 @@ export const whiteboardService = {
async getRecentWhiteboards(): Promise<Whiteboard[]> { async getRecentWhiteboards(): Promise<Whiteboard[]> {
const raw = await api.get<any[]>('/api/Whiteboard/recent') const raw = await api.get<any[]>('/api/Whiteboard/recent')
return raw.map(mapWhiteboard) return raw.map(mapWhiteboard)
},
async createNewWhiteboard(title: string): Promise<string> {
return await api.post<string>('/api/Whiteboard', { title: title, maxParticipants: 10, joinPolicy: 0})
},
async getWhiteboardById(id: string): Promise<Whiteboard> {
return await api.get<any>(`/api/Whiteboard/${id}`).then(mapWhiteboard)
} }
} }

View File

@@ -1,7 +1,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { User, LoginCredentials, SignupCredentials, AuthResponse } from '@/types' import type { User, LoginCredentials, SignupCredentials, AuthResponse } from '@/types'
import { useWhiteboardStore } from '@/stores/whiteboards' import { useWhiteboardsStore } from '@/stores/whiteboards'
import { authService } from '@/services/authService' import { authService } from '@/services/authService'
const ACCESS_TOKEN = 'auth_token' const ACCESS_TOKEN = 'auth_token'
@@ -129,7 +129,7 @@ export const useAuthStore = defineStore('auth', () => {
} }
async function logout(allDevices = false) { async function logout(allDevices = false) {
const whiteboardStore = useWhiteboardStore() const whiteboardStore = useWhiteboardsStore()
isLoading.value = true isLoading.value = true
error.value = null error.value = null
try { try {

View File

@@ -3,7 +3,7 @@ import { ref } from 'vue'
import type { Whiteboard } from '@/types' import type { Whiteboard } from '@/types'
import { whiteboardService } from '@/services/whiteboardService' import { whiteboardService } from '@/services/whiteboardService'
export const useWhiteboardStore = defineStore('whiteboards', () => { export const useWhiteboardsStore = defineStore('whiteboards', () => {
const ownedWhiteboards = ref<Whiteboard[]>([]) const ownedWhiteboards = ref<Whiteboard[]>([])
const recentWhiteboards = ref<Whiteboard[]>([]) const recentWhiteboards = ref<Whiteboard[]>([])
const isLoading = ref(false) const isLoading = ref(false)
@@ -33,6 +33,25 @@ export const useWhiteboardStore = defineStore('whiteboards', () => {
} }
} }
async function createNewWhiteboard(title: string): Promise<string> {
let newWhiteboard: Whiteboard;
isLoading.value = true
error.value = null
try {
const newId = await whiteboardService.createNewWhiteboard(title)
newWhiteboard = await whiteboardService.getWhiteboardById(newId)
} catch (err: any) {
error.value = err.message ?? 'Failed to create whiteboard'
throw err
} finally {
isLoading.value = false
}
ownedWhiteboards.value.push(newWhiteboard)
return newWhiteboard.id;
}
function clearWhiteboards() { function clearWhiteboards() {
ownedWhiteboards.value = [] ownedWhiteboards.value = []
recentWhiteboards.value = [] recentWhiteboards.value = []
@@ -45,6 +64,7 @@ export const useWhiteboardStore = defineStore('whiteboards', () => {
error, error,
getWhiteboardHistory: getWhiteboardHistory, getWhiteboardHistory: getWhiteboardHistory,
getRecentWhiteboards: getRecentWhiteboards, getRecentWhiteboards: getRecentWhiteboards,
createNewWhiteboard: createNewWhiteboard,
clearWhiteboards: clearWhiteboards clearWhiteboards: clearWhiteboards
} }
}) })

View File

@@ -1,13 +1,38 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router'
import WhiteboardHistorySidebar from '@/components/WhiteboardHistorySidebar.vue' import WhiteboardHistorySidebar from '@/components/WhiteboardHistorySidebar.vue'
import RecentWhiteboardsPanel from '@/components/RecentWhiteboardsPanel.vue' import RecentWhiteboardsPanel from '@/components/RecentWhiteboardsPanel.vue'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useWhiteboardsStore } from "@/stores/whiteboards.ts";
const auth = useAuthStore() const auth = useAuthStore()
const whiteboards = useWhiteboardsStore()
const router = useRouter()
const joinCode = ref('') const joinCode = ref('')
const whiteboardTitle = ref('') const whiteboardTitle = ref('')
const showCreateModal = ref(false) const showCreateModal = ref(false)
async function handleCreateNewWhiteboard() {
if (!whiteboardTitle.value.trim()) {
alert('Please enter a title for the whiteboard.')
return
}
try {
const newWhiteboardId = await whiteboards.createNewWhiteboard(whiteboardTitle.value.trim())
showCreateModal.value = false
whiteboardTitle.value = ''
await router.push({ name: 'whiteboard', params: { id: newWhiteboardId } })
} catch (e) {
console.error('Failed to create new whiteboard', e)
}
}
</script> </script>
<template> <template>
@@ -80,7 +105,10 @@ const showCreateModal = ref(false)
</div> </div>
<div class="modal-footer border-secondary"> <div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary" @click="showCreateModal = false">Cancel</button> <button type="button" class="btn btn-secondary" @click="showCreateModal = false">Cancel</button>
<button type="button" class="btn btn-primary">Create</button> <button type="button" class="btn btn-primary" @click="handleCreateNewWhiteboard">
<span v-if="whiteboards.isLoading" class="spinner-border spinner-border-sm me-2"></span>
Create
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -26,10 +26,13 @@ async function handleLeave() {
</script> </script>
<template> <template>
<div v-if="store.isLoading" class="d-flex justify-content-center align-items-center vh-100"> <div v-if="store.isLoading" class="d-flex flex-column justify-content-center align-items-center vh-100">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
<p class="text-muted fs-5 text-center">
Please wait while your whiteboard is loading...
</p>
</div> </div>
<div v-else-if="store.error" class="d-flex justify-content-center align-items-center vh-100"> <div v-else-if="store.error" class="d-flex justify-content-center align-items-center vh-100">

View File

@@ -22,7 +22,7 @@ export default defineConfig({
target: 'http://localhost:5266', target: 'http://localhost:5266',
changeOrigin: true changeOrigin: true
}, },
'/hubs/': { '/hubs': {
target: 'http://localhost:5039', target: 'http://localhost:5039',
changeOrigin: true, changeOrigin: true,
ws: true ws: true