AIPS
diff --git a/front/src/components/RecentWhiteboardsItem.vue b/front/src/components/RecentWhiteboardsItem.vue
new file mode 100644
index 0000000..07b53ef
--- /dev/null
+++ b/front/src/components/RecentWhiteboardsItem.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
{{ whiteboard.title }}
+ {{ formatDate(whiteboard.createdAt) }}
+
+
+
+
+
diff --git a/front/src/components/RecentWhiteboardsList.vue b/front/src/components/RecentWhiteboardsList.vue
new file mode 100644
index 0000000..276d40c
--- /dev/null
+++ b/front/src/components/RecentWhiteboardsList.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
No recent whiteboards
+
+
+
diff --git a/front/src/components/RecentWhiteboardsPanel.vue b/front/src/components/RecentWhiteboardsPanel.vue
new file mode 100644
index 0000000..d253a8c
--- /dev/null
+++ b/front/src/components/RecentWhiteboardsPanel.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/front/src/components/WhiteboardHistoryItem.vue b/front/src/components/WhiteboardHistoryItem.vue
new file mode 100644
index 0000000..07b53ef
--- /dev/null
+++ b/front/src/components/WhiteboardHistoryItem.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
{{ whiteboard.title }}
+ {{ formatDate(whiteboard.createdAt) }}
+
+
+
+
+
diff --git a/front/src/components/WhiteboardHistoryList.vue b/front/src/components/WhiteboardHistoryList.vue
new file mode 100644
index 0000000..fb602ee
--- /dev/null
+++ b/front/src/components/WhiteboardHistoryList.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
You have not created a whiteboard yet
+
+
diff --git a/front/src/components/WhiteboardHistorySidebar.vue b/front/src/components/WhiteboardHistorySidebar.vue
new file mode 100644
index 0000000..71db867
--- /dev/null
+++ b/front/src/components/WhiteboardHistorySidebar.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front/src/enums/index.ts b/front/src/enums/index.ts
new file mode 100644
index 0000000..21b7edd
--- /dev/null
+++ b/front/src/enums/index.ts
@@ -0,0 +1,11 @@
+export enum WhiteboardJoinPolicy {
+ FreeToJoin,
+ RequestToJoin,
+ Private
+}
+
+export enum WhiteboardState {
+ Active,
+ Inactive,
+ Deleted
+}
diff --git a/front/src/main.ts b/front/src/main.ts
index a133b77..cacb205 100644
--- a/front/src/main.ts
+++ b/front/src/main.ts
@@ -1,4 +1,6 @@
import './assets/scss/main.scss'
+import 'bootstrap/dist/js/bootstrap.bundle.min.js'
+
import { createApp } from 'vue'
import { createPinia } from 'pinia'
diff --git a/front/src/services/whiteboardService.ts b/front/src/services/whiteboardService.ts
new file mode 100644
index 0000000..9a161a1
--- /dev/null
+++ b/front/src/services/whiteboardService.ts
@@ -0,0 +1,27 @@
+import type { Whiteboard } from "@/types";
+import { api } from './api'
+
+export const whiteboardService = {
+ async getWhiteboardHistory(): Promise
{
+ const raw = await api.get('/api/Whiteboard/history')
+ return raw.map(mapWhiteboard)
+ },
+
+ async getRecentWhiteboards(): Promise {
+ const raw = await api.get('/api/Whiteboard/recent')
+ return raw.map(mapWhiteboard)
+ }
+}
+
+function mapWhiteboard(raw: any): Whiteboard {
+ return {
+ id: raw.id,
+ ownerId: raw.ownerId,
+ title: raw.title,
+ createdAt: new Date(raw.createdAt),
+ deletedAt: raw.deletedAt ? new Date(raw.deletedAt) : undefined,
+ maxParticipants: raw.maxParticipants,
+ joinPolicy: raw.joinPolicy,
+ state: raw.state,
+ }
+}
diff --git a/front/src/stores/auth.ts b/front/src/stores/auth.ts
index 08e1dc7..1201c35 100644
--- a/front/src/stores/auth.ts
+++ b/front/src/stores/auth.ts
@@ -1,6 +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 { authService } from '@/services/authService'
const ACCESS_TOKEN = 'auth_token'
@@ -14,7 +15,6 @@ export const useAuthStore = defineStore('auth', () => {
const isAuthenticated = computed(() => !!user.value)
- // single-flight promise for refresh to avoid concurrent refresh requests
let refreshPromise: Promise | null = null
function setTokens(access: string | null, refresh: string | null) {
@@ -28,28 +28,23 @@ export const useAuthStore = defineStore('auth', () => {
if (refresh) {
localStorage.setItem(REFRESH_TOKEN, refresh)
} else if (refresh === null) {
- // explicit null means remove
localStorage.removeItem(REFRESH_TOKEN)
}
}
async function tryRefresh(): Promise {
- // if a refresh is already in progress, return that promise
if (refreshPromise) return refreshPromise
const refreshToken = localStorage.getItem(REFRESH_TOKEN)
if (!refreshToken) {
- // nothing to do
throw new Error('No refresh token')
}
refreshPromise = (async () => {
try {
const res = await authService.refreshLogin(refreshToken)
- // API expected to return { accessToken, refreshToken }
setTokens(res.accessToken ?? null, res.refreshToken ?? null)
} finally {
- // clear so subsequent calls can create a new promise
refreshPromise = null
}
})()
@@ -70,7 +65,6 @@ export const useAuthStore = defineStore('auth', () => {
isLoading.value = false
return
} catch (e) {
- // token might be expired; try refresh if we have refresh token
if (savedRefresh) {
try {
await tryRefresh()
@@ -78,14 +72,13 @@ export const useAuthStore = defineStore('auth', () => {
isLoading.value = false
return
} catch (err) {
- // refresh failed - fallthrough to clearing auth
console.warn('Token refresh failed during initialize', err)
}
}
}
}
- // not authenticated or refresh failed
+
setTokens(null, null)
user.value = null
isLoading.value = false
@@ -96,14 +89,14 @@ export const useAuthStore = defineStore('auth', () => {
error.value = null
try {
const res: AuthResponse = await authService.login(credentials)
- // expect AuthResponse to have accessToken and refreshToken
+
setTokens(res.accessToken ?? null, res.refreshToken ?? null)
try {
user.value = await authService.getMe()
} catch (e) {
console.error('Logged in but failed to fetch user profile', e)
- // keep tokens but clear user
+
user.value = null
}
} catch (e: any) {
@@ -124,7 +117,7 @@ export const useAuthStore = defineStore('auth', () => {
try {
user.value = await authService.getMe()
} catch (e) {
- // keep tokens but no profile
+
user.value = null
}
} catch (e: any) {
@@ -136,16 +129,17 @@ export const useAuthStore = defineStore('auth', () => {
}
async function logout(allDevices = false) {
+ const whiteboardStore = useWhiteboardStore()
isLoading.value = true
error.value = null
try {
if (allDevices) await authService.logoutAll()
else await authService.logout(localStorage.getItem(REFRESH_TOKEN)!)
} catch (e) {
- // ignore network errors on logout
console.warn('Logout request failed', e)
} finally {
setTokens(null, null)
+ whiteboardStore.clearWhiteboards()
user.value = null
isLoading.value = false
}
diff --git a/front/src/stores/whiteboards.ts b/front/src/stores/whiteboards.ts
new file mode 100644
index 0000000..46ed9d5
--- /dev/null
+++ b/front/src/stores/whiteboards.ts
@@ -0,0 +1,50 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { Whiteboard } from '@/types'
+import { whiteboardService } from '@/services/whiteboardService'
+
+export const useWhiteboardStore = defineStore('whiteboards', () => {
+ const ownedWhiteboards = ref([])
+ const recentWhiteboards = ref([])
+ const isLoading = ref(false)
+ const error = ref(null)
+
+ async function getWhiteboardHistory() {
+ isLoading.value = true
+ error.value = null
+ try {
+ ownedWhiteboards.value = await whiteboardService.getWhiteboardHistory()
+ } catch (err: any) {
+ error.value = err.message ?? 'Failed to load whiteboards'
+ } finally {
+ isLoading.value = false
+ }
+ }
+
+ async function getRecentWhiteboards() {
+ isLoading.value = true
+ error.value = null
+ try {
+ recentWhiteboards.value = await whiteboardService.getRecentWhiteboards()
+ } catch (err: any) {
+ error.value = err.message ?? 'Failed to load whiteboards'
+ } finally {
+ isLoading.value = false
+ }
+ }
+
+ function clearWhiteboards() {
+ ownedWhiteboards.value = []
+ recentWhiteboards.value = []
+ }
+
+ return {
+ ownedWhiteboards: ownedWhiteboards,
+ recentWhiteboards: recentWhiteboards,
+ isLoading,
+ error,
+ getWhiteboardHistory: getWhiteboardHistory,
+ getRecentWhiteboards: getRecentWhiteboards,
+ clearWhiteboards: clearWhiteboards
+ }
+})
diff --git a/front/src/types/index.ts b/front/src/types/index.ts
index 5cdb2ee..0fa8e21 100644
--- a/front/src/types/index.ts
+++ b/front/src/types/index.ts
@@ -1,3 +1,5 @@
+import {type WhiteboardJoinPolicy, WhiteboardState} from "@/enums";
+
export interface User {
username: string
email: string
@@ -18,3 +20,14 @@ export interface AuthResponse {
accessToken: string
refreshToken: string
}
+
+export interface Whiteboard {
+ id: string
+ ownerId: string
+ title: string
+ createdAt: Date
+ deletedAt?: Date
+ maxParticipants?: number
+ joinPolicy?: WhiteboardJoinPolicy
+ state: WhiteboardState
+}
diff --git a/front/src/views/HomeView.vue b/front/src/views/HomeView.vue
index bdd7d3e..98c0847 100644
--- a/front/src/views/HomeView.vue
+++ b/front/src/views/HomeView.vue
@@ -1,3 +1,26 @@
+
+
- Home
+
+
+
+
+