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 { useRouter } from 'vue-router'
import { useWhiteboardStore } from '@/stores/whiteboards'
import { useWhiteboardsStore } from '@/stores/whiteboards'
import RecentWhiteboardsItem from './RecentWhiteboardsItem.vue'
const router = useRouter()
const store = useWhiteboardStore()
const store = useWhiteboardsStore()
onMounted(() => {
if (store.recentWhiteboards.length === 0) store.getRecentWhiteboards()

View File

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

View File

@@ -52,7 +52,10 @@ router.beforeEach((to) => {
}
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 REFRESH_TOKEN = 'refresh_token'
@@ -9,7 +6,6 @@ async function parseJsonOrThrow(res: Response) {
try {
return text ? JSON.parse(text) : undefined
} catch (e) {
// non-JSON response
return text
}
}

View File

@@ -1,5 +1,5 @@
import type { Whiteboard } from "@/types";
import { api } from './api'
import type {Whiteboard} from "@/types";
import {api} from './api'
export const whiteboardService = {
async getWhiteboardHistory(): Promise<Whiteboard[]> {
@@ -10,6 +10,14 @@ export const whiteboardService = {
async getRecentWhiteboards(): Promise<Whiteboard[]> {
const raw = await api.get<any[]>('/api/Whiteboard/recent')
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 { defineStore } from 'pinia'
import type { User, LoginCredentials, SignupCredentials, AuthResponse } from '@/types'
import { useWhiteboardStore } from '@/stores/whiteboards'
import { useWhiteboardsStore } from '@/stores/whiteboards'
import { authService } from '@/services/authService'
const ACCESS_TOKEN = 'auth_token'
@@ -129,7 +129,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function logout(allDevices = false) {
const whiteboardStore = useWhiteboardStore()
const whiteboardStore = useWhiteboardsStore()
isLoading.value = true
error.value = null
try {

View File

@@ -3,7 +3,7 @@ import { ref } from 'vue'
import type { Whiteboard } from '@/types'
import { whiteboardService } from '@/services/whiteboardService'
export const useWhiteboardStore = defineStore('whiteboards', () => {
export const useWhiteboardsStore = defineStore('whiteboards', () => {
const ownedWhiteboards = ref<Whiteboard[]>([])
const recentWhiteboards = ref<Whiteboard[]>([])
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() {
ownedWhiteboards.value = []
recentWhiteboards.value = []
@@ -45,6 +64,7 @@ export const useWhiteboardStore = defineStore('whiteboards', () => {
error,
getWhiteboardHistory: getWhiteboardHistory,
getRecentWhiteboards: getRecentWhiteboards,
createNewWhiteboard: createNewWhiteboard,
clearWhiteboards: clearWhiteboards
}
})

View File

@@ -1,13 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import WhiteboardHistorySidebar from '@/components/WhiteboardHistorySidebar.vue'
import RecentWhiteboardsPanel from '@/components/RecentWhiteboardsPanel.vue'
import { useAuthStore } from '@/stores/auth'
import { useWhiteboardsStore } from "@/stores/whiteboards.ts";
const auth = useAuthStore()
const whiteboards = useWhiteboardsStore()
const router = useRouter()
const joinCode = ref('')
const whiteboardTitle = ref('')
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>
<template>
@@ -80,7 +105,10 @@ const showCreateModal = ref(false)
</div>
<div class="modal-footer border-secondary">
<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>

View File

@@ -26,10 +26,13 @@ async function handleLeave() {
</script>
<template>
<div v-if="store.isLoading" class="d-flex justify-content-center align-items-center vh-100">
<div class="spinner-border text-primary" role="status">
<div v-if="store.isLoading" class="d-flex flex-column justify-content-center align-items-center vh-100">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="text-muted fs-5 text-center">
Please wait while your whiteboard is loading...
</p>
</div>
<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',
changeOrigin: true
},
'/hubs/': {
'/hubs': {
target: 'http://localhost:5039',
changeOrigin: true,
ws: true