diff --git a/package.json b/package.json index 83b1f02..d6ef8c2 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "license": "MIT", "scripts": { "dev": "vite dev", - "build": "vite build" + "build": "vite build", + "format": "prettier --write --tab-width 4 --use-tabs false \"src/**/*.{ts,vue,js,css}\"" }, "private": true, "dependencies": { diff --git a/src/app/App.vue b/src/app/App.vue index 2e50b56..ab1fbd4 100644 --- a/src/app/App.vue +++ b/src/app/App.vue @@ -3,17 +3,17 @@ import { computed, onMounted } from 'vue'; import { Layout, Menu, Button, Space, Typography } from 'ant-design-vue'; import type { MenuProps } from 'ant-design-vue'; import { - HomeOutlined, - InfoCircleOutlined, - RocketOutlined, - ReadOutlined, - TeamOutlined, - RobotOutlined, - BulbOutlined, - AppstoreOutlined, - DashboardOutlined, - LoginOutlined, - UserAddOutlined, + HomeOutlined, + InfoCircleOutlined, + RocketOutlined, + ReadOutlined, + TeamOutlined, + RobotOutlined, + BulbOutlined, + AppstoreOutlined, + DashboardOutlined, + LoginOutlined, + UserAddOutlined, } from '@ant-design/icons-vue'; import { useRoute, useRouter } from 'vue-router'; import { useAuthStore } from '../stores/authStore'; @@ -23,193 +23,193 @@ const route = useRoute(); const authStore = useAuthStore(); const navItems = [ - { - key: '/', - label: 'Home', - icon: HomeOutlined, - path: '/', - }, - { - key: '/about', - label: 'About', - icon: InfoCircleOutlined, - path: '/about', - }, - { - key: '/onboarding', - label: 'Onboarding', - icon: RocketOutlined, - path: '/onboarding', - }, - { - key: '/training', - label: 'Training', - icon: ReadOutlined, - path: '/training', - }, - { - key: '/roles', - label: 'Roles', - icon: TeamOutlined, - path: '/roles', - roles: ['manager', 'admin'], - }, - { - key: '/agents', - label: 'Agents', - icon: RobotOutlined, - path: '/agents', - roles: ['manager', 'admin'], - }, - { - key: '/assessments', - label: 'Assessments', - icon: BulbOutlined, - path: '/assessments', - }, - { - key: '/resources', - label: 'Resources', - icon: AppstoreOutlined, - path: '/resources', - }, - { - key: '/progress', - label: 'Progress', - icon: DashboardOutlined, - path: '/progress', - }, + { + key: '/', + label: 'Home', + icon: HomeOutlined, + path: '/', + }, + { + key: '/about', + label: 'About', + icon: InfoCircleOutlined, + path: '/about', + }, + { + key: '/onboarding', + label: 'Onboarding', + icon: RocketOutlined, + path: '/onboarding', + }, + { + key: '/training', + label: 'Training', + icon: ReadOutlined, + path: '/training', + }, + { + key: '/roles', + label: 'Roles', + icon: TeamOutlined, + path: '/roles', + roles: ['manager', 'admin'], + }, + { + key: '/agents', + label: 'Agents', + icon: RobotOutlined, + path: '/agents', + roles: ['manager', 'admin'], + }, + { + key: '/assessments', + label: 'Assessments', + icon: BulbOutlined, + path: '/assessments', + }, + { + key: '/resources', + label: 'Resources', + icon: AppstoreOutlined, + path: '/resources', + }, + { + key: '/progress', + label: 'Progress', + icon: DashboardOutlined, + path: '/progress', + }, ]; const visibleNavItems = computed(() => - navItems.filter((item) => - item.roles ? authStore.hasRole(item.roles) : true - ) + navItems.filter((item) => + item.roles ? authStore.hasRole(item.roles) : true + ) ); const selectedKeys = computed(() => { - const match = visibleNavItems.value.find((item) => - route.path.startsWith(item.key) - ); - return match ? [match.key] : []; + const match = visibleNavItems.value.find((item) => + route.path.startsWith(item.key) + ); + return match ? [match.key] : []; }); const onSelect: MenuProps['onSelect'] = ({ key }) => { - const item = visibleNavItems.value.find((n) => n.key === key); - if (item) router.push(item.path); + const item = visibleNavItems.value.find((n) => n.key === key); + if (item) router.push(item.path); }; const handleLogout = async () => { - await authStore.logout(); - router.push('/'); + await authStore.logout(); + router.push('/'); }; onMounted(() => { - authStore.fetchSession(); + authStore.fetchSession(); }); diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..6004d7d --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,67 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; + +class ApiClient { + private client: AxiosInstance; + + constructor() { + this.client = axios.create({ withCredentials: true }); + } + + private getCsrfToken(): string { + const match = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/); + return match ? decodeURIComponent(match[1]) : ''; + } + + private withCsrf(config?: AxiosRequestConfig): AxiosRequestConfig { + const token = this.getCsrfToken(); + const csrfHeader = token ? { 'X-CSRFToken': token } : {}; + return { + ...config, + headers: { + ...csrfHeader, + ...(config?.headers || {}), + }, + }; + } + + get( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.get(url, this.withCsrf(config)); + } + + post( + url: string, + data?: unknown, + config?: AxiosRequestConfig + ): Promise> { + return this.client.post(url, data, this.withCsrf(config)); + } + + put( + url: string, + data?: unknown, + config?: AxiosRequestConfig + ): Promise> { + return this.client.put(url, data, this.withCsrf(config)); + } + + patch( + url: string, + data?: unknown, + config?: AxiosRequestConfig + ): Promise> { + return this.client.patch(url, data, this.withCsrf(config)); + } + + delete( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.delete(url, this.withCsrf(config)); + } +} + +export const apiClient = new ApiClient(); +export { isAxiosError } from 'axios'; diff --git a/src/main.ts b/src/main.ts index 70e0fa8..59383ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,11 @@ import './styles.css'; +import 'ant-design-vue/dist/reset.css'; import router from './router'; import { createApp } from 'vue'; +import { createPinia } from 'pinia'; import App from './app/App.vue'; const app = createApp(App); +app.use(createPinia()); app.use(router); app.mount('#root'); diff --git a/src/router/index.ts b/src/router/index.ts index 06e4305..b77736f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,69 +1,110 @@ import { createRouter, createWebHistory } from 'vue-router'; +import { useAuthStore } from '../stores/authStore'; +import { message } from 'ant-design-vue'; const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: '/', - name: 'home', - component: () => import('../views/HomeView.vue'), - }, - { - path: '/about', - name: 'about', - component: () => import('../views/AboutView.vue'), - }, - { - path: '/login', - name: 'login', - component: () => import('../views/LoginView.vue'), - }, - { - path: '/register', - name: 'register', - component: () => import('../views/RegisterView.vue'), - }, - { - path: '/onboarding', - name: 'onboarding', - component: () => import('../views/OnboardingFlow.vue'), - }, - { - path: '/training/:moduleId?', - name: 'training', - component: () => import('../views/TrainingModule.vue'), - }, - { - path: '/agents', - name: 'agents', - component: () => import('../views/Agents.vue'), - }, - { - path: '/agents/:id', - name: 'agent-detail', - component: () => import('../views/AgentDetail.vue'), - }, - { - path: '/roles', - name: 'roles', - component: () => import('../views/RoleProfiles.vue'), - }, - { - path: '/progress', - name: 'progress', - component: () => import('../views/ProgressDashboard.vue'), - }, - { - path: '/assessments', - name: 'assessments', - component: () => import('../views/Assessments.vue'), - }, - { - path: '/resources', - name: 'resources', - component: () => import('../views/Resources.vue'), - }, - ], + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: () => import('../views/HomeView.vue'), + }, + { + path: '/about', + name: 'about', + component: () => import('../views/AboutView.vue'), + }, + { + path: '/login', + name: 'login', + component: () => import('../views/LoginView.vue'), + meta: { guestOnly: true }, + }, + { + path: '/register', + name: 'register', + component: () => import('../views/RegisterView.vue'), + meta: { guestOnly: true }, + }, + { + path: '/onboarding', + name: 'onboarding', + component: () => import('../views/OnboardingFlow.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/training/:moduleId?', + name: 'training', + component: () => import('../views/TrainingModule.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/agents', + name: 'agents', + component: () => import('../views/Agents.vue'), + meta: { requiresAuth: true, roles: ['manager', 'admin'] }, + }, + { + path: '/agents/:id', + name: 'agent-detail', + component: () => import('../views/AgentDetail.vue'), + meta: { requiresAuth: true, roles: ['manager', 'admin'] }, + }, + { + path: '/roles', + name: 'roles', + component: () => import('../views/RoleProfiles.vue'), + meta: { requiresAuth: true, roles: ['manager', 'admin'] }, + }, + { + path: '/progress', + name: 'progress', + component: () => import('../views/ProgressDashboard.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/assessments', + name: 'assessments', + component: () => import('../views/Assessments.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/resources', + name: 'resources', + component: () => import('../views/Resources.vue'), + meta: { requiresAuth: true }, + }, + ], }); export default router; + +router.beforeEach(async (to, _from, next) => { + const authStore = useAuthStore(); + + try { + await authStore.fetchSession(); + } catch (err) { + console.error('Failed to fetch session during navigation:', err); + } + + const isAuthed = authStore.isAuthenticated; + const role = authStore.user?.role; + + if (to.meta?.guestOnly && isAuthed) { + return next({ path: '/onboarding' }); + } + + if (to.meta?.requiresAuth && !isAuthed) { + return next({ path: '/login', query: { redirect: to.fullPath } }); + } + + const allowedRoles = (to.meta?.roles as string[] | undefined) || null; + if (allowedRoles && (!role || !allowedRoles.includes(role))) { + message.error('You do not have access to that page'); + return next({ path: '/' }); + } + + return next(); +}); diff --git a/src/stores/agentStore.ts b/src/stores/agentStore.ts new file mode 100644 index 0000000..dbd81fa --- /dev/null +++ b/src/stores/agentStore.ts @@ -0,0 +1,271 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; + +interface AgentEvent { + type: string; + content?: string | Record; + message?: string; + timestamp: Date; + event_type?: string; + error_message?: string; + execution_id?: string; + output_data?: Record; +} + +export const useAgentStore = defineStore('agent', () => { + const socket = ref(null); + const isConnected = ref(false); + const currentExecutionId = ref(null); + const events = ref([]); + const executionStatus = ref('idle'); + const agentId = ref(null); + const reconnectAttempts = ref(0); + const maxReconnectAttempts = 5; + + const connect = (agentIdParam: string) => { + console.log( + '[agentStore] connect() called with agent ID:', + agentIdParam + ); + + if (socket.value && isConnected.value) { + console.log( + '[agentStore] Already connected to agent:', + agentIdParam + ); + return; + } + + agentId.value = agentIdParam; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}/ws/agents/${agentIdParam}/`; + console.log('[agentStore] WebSocket URL:', wsUrl); + + socket.value = new WebSocket(wsUrl); + console.log('[agentStore] WebSocket object created'); + + socket.value.onopen = () => { + isConnected.value = true; + reconnectAttempts.value = 0; + console.log( + '[agentStore] SUCCESS - WebSocket connected to agent:', + agentIdParam + ); + console.log('[agentStore] isConnected is now:', isConnected.value); + }; + + socket.value.onmessage = (event) => { + console.log('[agentStore] Message received from WebSocket'); + try { + const data = JSON.parse(event.data); + console.log('[agentStore] Parsed message:', data); + handleMessage(data); + } catch (error) { + console.error( + '[agentStore] ERROR - Failed to parse WebSocket message:', + error + ); + console.error('[agentStore] Raw message:', event.data); + } + }; + + socket.value.onerror = (error) => { + console.error( + '[agentStore] ERROR - WebSocket error occurred:', + error + ); + isConnected.value = false; + }; + + socket.value.onclose = () => { + isConnected.value = false; + console.log( + '[agentStore] WebSocket closed for agent:', + agentIdParam + ); + attemptReconnect(agentIdParam); + }; + }; + + const attemptReconnect = (agentIdParam: string) => { + console.log('[agentStore] attemptReconnect() called'); + + if (reconnectAttempts.value < maxReconnectAttempts) { + reconnectAttempts.value++; + const delay = Math.min( + 1000 * Math.pow(2, reconnectAttempts.value), + 10000 + ); + console.log( + `[agentStore] Attempting to reconnect... (attempt ${reconnectAttempts.value}/${maxReconnectAttempts}, delay: ${delay}ms)` + ); + setTimeout(() => connect(agentIdParam), delay); + } else { + console.error( + '[agentStore] ERROR - Max reconnection attempts reached (${maxReconnectAttempts})' + ); + } + }; + + const handleMessage = (data: Record) => { + console.log( + '[agentStore] handleMessage() called with type:', + data.type + ); + console.log('[agentStore] Full message data:', data); + + if (data.type === 'connection') { + console.log('[agentStore] Connection message:', data.message); + } else if (data.type === 'execution_started') { + console.log('[agentStore] Execution started'); + currentExecutionId.value = data.execution_id as string; + executionStatus.value = 'running'; + events.value = []; + console.log( + '[agentStore] Status changed to: running, execution ID:', + currentExecutionId.value + ); + events.value.push({ + type: 'started', + message: data.message as string, + timestamp: new Date(), + }); + } else if (data.type === 'agent_event') { + console.log('[agentStore] Agent event received:', data.event_type); + events.value.push({ + type: data.event_type as string, + content: data.content as string | Record, + timestamp: new Date(data.timestamp as string), + }); + } else if (data.type === 'execution_completed') { + console.log('[agentStore] Execution completed'); + executionStatus.value = 'completed'; + events.value.push({ + type: 'completed', + content: data.output_data as Record, + message: data.message as string, + timestamp: new Date(), + }); + } else if (data.type === 'execution_error') { + console.log('[agentStore] Execution error:', data.error_message); + executionStatus.value = 'failed'; + events.value.push({ + type: 'error', + message: data.error_message as string, + timestamp: new Date(), + }); + } else if (data.type === 'execution_stopped') { + console.log('[agentStore] Execution stopped'); + executionStatus.value = 'stopped'; + events.value.push({ + type: 'stopped', + message: data.message as string, + timestamp: new Date(), + }); + } else if (data.type === 'error') { + console.log('[agentStore] Generic error:', data.message); + events.value.push({ + type: 'error', + message: data.message as string, + timestamp: new Date(), + }); + } else { + console.warn( + '[agentStore] WARNING - Unknown message type:', + data.type + ); + } + }; + + const startAgent = (inputData: Record = {}) => { + console.log('[agentStore] startAgent() called with data:', inputData); + + if (!socket.value) { + console.error('[agentStore] ERROR - WebSocket not initialized'); + return; + } + + if (!isConnected.value) { + console.error( + '[agentStore] ERROR - WebSocket not connected (isConnected:', + isConnected.value, + ')' + ); + return; + } + + try { + const message = { + action: 'start_agent', + input_data: inputData, + }; + console.log('[agentStore] Sending message:', message); + socket.value.send(JSON.stringify(message)); + console.log('[agentStore] SUCCESS - Message sent to WebSocket'); + } catch (error) { + console.error( + '[agentStore] ERROR - Failed to send WebSocket message:', + error + ); + } + }; + + const stopAgent = () => { + console.log('[agentStore] stopAgent() called'); + + if (!socket.value) { + console.error('[agentStore] ERROR - WebSocket not initialized'); + return; + } + + if (!isConnected.value) { + console.error('[agentStore] ERROR - WebSocket not connected'); + return; + } + + try { + const message = { + action: 'stop_agent', + execution_id: currentExecutionId.value, + }; + console.log('[agentStore] Sending message:', message); + socket.value.send(JSON.stringify(message)); + console.log( + '[agentStore] SUCCESS - Stop message sent to WebSocket' + ); + } catch (error) { + console.error( + '[agentStore] ERROR - Failed to send stop message:', + error + ); + } + }; + + const disconnect = () => { + console.log('[agentStore] disconnect() called'); + reconnectAttempts.value = maxReconnectAttempts; + + if (socket.value) { + console.log('[agentStore] Closing WebSocket connection'); + socket.value.close(); + console.log('[agentStore] WebSocket close initiated'); + } else { + console.warn('[agentStore] WARNING - No WebSocket to disconnect'); + } + }; + + const eventLog = computed(() => events.value); + + return { + socket, + isConnected, + currentExecutionId, + executionStatus, + agentId, + eventLog, + connect, + startAgent, + stopAgent, + disconnect, + }; +}); diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts new file mode 100644 index 0000000..e676a05 --- /dev/null +++ b/src/stores/authStore.ts @@ -0,0 +1,159 @@ +import { computed, ref } from 'vue'; +import { defineStore } from 'pinia'; +import { apiClient, isAxiosError } from '../lib/api'; + +export interface AuthUser { + id: number; + uuid: string; + email_address: string; + first_name: string; + last_name: string; + bio?: string; + timezone?: string; + avatar_url?: string; + role?: string; + date_of_birth?: string; + created_at?: string; + updated_at?: string; +} + +interface SessionResponse { + isAuthenticated: boolean; + isStaff: boolean; +} + +export const useAuthStore = defineStore('auth', () => { + const user = ref(null); + const loading = ref(false); + const initialized = ref(false); + const error = ref(null); + + const isAuthenticated = computed(() => Boolean(user.value)); + const hasRole = (roles: string[] = []) => { + if (!roles.length) return true; + return roles.includes(user.value?.role || ''); + }; + const displayName = computed(() => { + if (!user.value) return ''; + if (user.value.first_name || user.value.last_name) { + return `${user.value.first_name || ''} ${ + user.value.last_name || '' + }`.trim(); + } + return user.value.email_address; + }); + + const setUser = (value: AuthUser | null) => { + user.value = value; + initialized.value = true; + }; + + const fetchSession = async (force = false) => { + if (initialized.value && !force) return user.value; + loading.value = true; + error.value = null; + try { + const sessionRes = await apiClient.get( + '/api/user/session/' + ); + if (sessionRes.data?.isAuthenticated) { + const meRes = await apiClient.get('/api/user/me/'); + setUser(meRes.data); + } else { + setUser(null); + } + return user.value; + } catch (err) { + error.value = isAxiosError(err) + ? err.response?.data?.detail || err.message + : 'Unable to fetch session'; + setUser(null); + throw err; + } finally { + loading.value = false; + } + }; + + const login = async (emailAddress: string, password: string) => { + loading.value = true; + error.value = null; + try { + const res = await apiClient.post<{ + user: AuthUser; + message?: string; + }>('/api/user/login/', { email_address: emailAddress, password }); + setUser(res.data?.user ?? null); + return res.data; + } catch (err) { + error.value = isAxiosError(err) + ? err.response?.data?.error || + err.response?.data?.detail || + err.message + : 'Login failed'; + throw err; + } finally { + loading.value = false; + } + }; + + const register = async (payload: { + email_address: string; + password: string; + confirm_password?: string; + first_name: string; + last_name: string; + date_of_birth?: string; + role?: string; + }) => { + loading.value = true; + error.value = null; + try { + await apiClient.post('/api/user/signup/', { + ...payload, + confirm_password: payload.confirm_password || payload.password, + }); + await login(payload.email_address, payload.password); + } catch (err) { + error.value = isAxiosError(err) + ? err.response?.data?.detail || + err.response?.data?.error || + err.message + : 'Registration failed'; + throw err; + } finally { + loading.value = false; + } + }; + + const logout = async () => { + loading.value = true; + error.value = null; + try { + await apiClient.post('/api/user/logout/'); + } catch (err) { + error.value = isAxiosError(err) + ? err.response?.data?.detail || + err.response?.data?.error || + err.message + : 'Logout failed'; + throw err; + } finally { + setUser(null); + loading.value = false; + } + }; + + return { + user, + loading, + initialized, + error, + isAuthenticated, + hasRole, + displayName, + fetchSession, + login, + register, + logout, + }; +}); diff --git a/src/styles.css b/src/styles.css index 22b4e6a..19c75e2 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,41 +1,44 @@ html { - -webkit-text-size-adjust: 100%; - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - line-height: 1.5; - tab-size: 4; - scroll-behavior: smooth; + -webkit-text-size-adjust: 100%; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + line-height: 1.5; + tab-size: 4; + scroll-behavior: smooth; } body { - font-family: inherit; - line-height: inherit; - margin: 0; + font-family: inherit; + line-height: inherit; + margin: 0; + background: #0b1220; + color: #e5e7eb; } h1, h2, p, pre { - margin: 0; + margin: 0; } *, ::before, ::after { - box-sizing: border-box; - border-width: 0; - border-style: solid; - border-color: currentColor; + box-sizing: border-box; + border-width: 0; + border-style: solid; + border-color: currentColor; } h1, h2 { - font-size: inherit; - font-weight: inherit; + font-size: inherit; + font-weight: inherit; } a { - color: inherit; - text-decoration: inherit; + color: inherit; + text-decoration: inherit; } pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - 'Liberation Mono', 'Courier New', monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + 'Liberation Mono', 'Courier New', monospace; } diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue index 0e1c6ab..8281958 100644 --- a/src/views/AboutView.vue +++ b/src/views/AboutView.vue @@ -1,63 +1,134 @@ + + - diff --git a/src/views/AgentDetail.vue b/src/views/AgentDetail.vue index f18d977..f95c52c 100644 --- a/src/views/AgentDetail.vue +++ b/src/views/AgentDetail.vue @@ -1,68 +1,421 @@ diff --git a/src/views/Agents.vue b/src/views/Agents.vue index d679958..a9e62e8 100644 --- a/src/views/Agents.vue +++ b/src/views/Agents.vue @@ -1,61 +1,106 @@ diff --git a/src/views/Assessments.vue b/src/views/Assessments.vue index 39e4f07..de52c97 100644 --- a/src/views/Assessments.vue +++ b/src/views/Assessments.vue @@ -1,71 +1,60 @@ diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index f34457f..296c5a1 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,87 +1,329 @@ - diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index e71ecb3..5cc131f 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -1,53 +1,106 @@ diff --git a/src/views/OnboardingFlow.vue b/src/views/OnboardingFlow.vue index 14e1e5d..17ac7d1 100644 --- a/src/views/OnboardingFlow.vue +++ b/src/views/OnboardingFlow.vue @@ -1,5 +1,6 @@ diff --git a/src/views/ProgressDashboard.vue b/src/views/ProgressDashboard.vue index b971090..06d09ae 100644 --- a/src/views/ProgressDashboard.vue +++ b/src/views/ProgressDashboard.vue @@ -1,90 +1,79 @@ diff --git a/src/views/RegisterView.vue b/src/views/RegisterView.vue index d358242..553c71b 100644 --- a/src/views/RegisterView.vue +++ b/src/views/RegisterView.vue @@ -1,52 +1,172 @@ diff --git a/src/views/Resources.vue b/src/views/Resources.vue index c630fa2..e96591c 100644 --- a/src/views/Resources.vue +++ b/src/views/Resources.vue @@ -1,55 +1,49 @@ diff --git a/src/views/RoleProfiles.vue b/src/views/RoleProfiles.vue index 10d42e6..9cc8bff 100644 --- a/src/views/RoleProfiles.vue +++ b/src/views/RoleProfiles.vue @@ -1,66 +1,71 @@ diff --git a/src/views/TrainingModule.vue b/src/views/TrainingModule.vue index 0bd7722..b8910bc 100644 --- a/src/views/TrainingModule.vue +++ b/src/views/TrainingModule.vue @@ -1,94 +1,71 @@ diff --git a/src/vue-shims.d.ts b/src/vue-shims.d.ts index 798e8fc..b0ab4f2 100644 --- a/src/vue-shims.d.ts +++ b/src/vue-shims.d.ts @@ -1,5 +1,5 @@ declare module '*.vue' { - import { defineComponent } from 'vue'; - const component: ReturnType; - export default component; + import { defineComponent } from 'vue'; + const component: ReturnType; + export default component; }