session-wrap-dashboard 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.env.example +6 -0
  2. package/coverage/base.css +224 -0
  3. package/coverage/block-navigation.js +87 -0
  4. package/coverage/coverage-final.json +6 -0
  5. package/coverage/favicon.png +0 -0
  6. package/coverage/index.html +146 -0
  7. package/coverage/prettify.css +1 -0
  8. package/coverage/prettify.js +2 -0
  9. package/coverage/sort-arrow-sprite.png +0 -0
  10. package/coverage/sorter.js +210 -0
  11. package/coverage/src/auth.ts.html +307 -0
  12. package/coverage/src/components/Header.tsx.html +262 -0
  13. package/coverage/src/components/Sidebar.tsx.html +262 -0
  14. package/coverage/src/components/index.html +131 -0
  15. package/coverage/src/hooks/index.html +131 -0
  16. package/coverage/src/hooks/useAuth.ts.html +292 -0
  17. package/coverage/src/hooks/useWorkspace.ts.html +475 -0
  18. package/coverage/src/index.html +116 -0
  19. package/index.html +13 -0
  20. package/package.json +49 -0
  21. package/playwright.config.ts +38 -0
  22. package/postcss.config.js +6 -0
  23. package/src/App.tsx +67 -0
  24. package/src/api.ts +130 -0
  25. package/src/auth.ts +74 -0
  26. package/src/components/AgentLeaderboard.tsx +84 -0
  27. package/src/components/AnalyticsDashboard.tsx +223 -0
  28. package/src/components/Header.tsx +62 -0
  29. package/src/components/IntegrationManager.tsx +292 -0
  30. package/src/components/RoleManager.tsx +230 -0
  31. package/src/components/Sidebar.tsx +62 -0
  32. package/src/components/TrendChart.tsx +92 -0
  33. package/src/components/WorkspaceSelector.tsx +157 -0
  34. package/src/components/__tests__/Header.test.tsx +64 -0
  35. package/src/components/__tests__/Sidebar.test.tsx +39 -0
  36. package/src/components/index.ts +8 -0
  37. package/src/hooks/__tests__/useAuth.test.ts +88 -0
  38. package/src/hooks/__tests__/useWorkspace.test.ts +101 -0
  39. package/src/hooks/index.ts +4 -0
  40. package/src/hooks/useAnalytics.ts +76 -0
  41. package/src/hooks/useApi.ts +62 -0
  42. package/src/hooks/useAuth.ts +69 -0
  43. package/src/hooks/useWorkspace.ts +130 -0
  44. package/src/index.css +57 -0
  45. package/src/main.tsx +13 -0
  46. package/src/pages/DashboardPage.tsx +13 -0
  47. package/src/pages/HomePage.tsx +156 -0
  48. package/src/pages/IntegrationsPage.tsx +9 -0
  49. package/src/pages/RolesPage.tsx +9 -0
  50. package/src/pages/SettingsPage.tsx +118 -0
  51. package/src/pages/WorkspacesPage.tsx +9 -0
  52. package/src/pages/index.ts +6 -0
  53. package/src/test/setup.ts +31 -0
  54. package/src/test/utils.tsx +15 -0
  55. package/src/types.ts +132 -0
  56. package/tailwind.config.js +11 -0
  57. package/tests/e2e/auth.spec.ts +42 -0
  58. package/tests/e2e/dashboard.spec.ts +52 -0
  59. package/tests/e2e/integrations.spec.ts +91 -0
  60. package/tests/e2e/workspaces.spec.ts +78 -0
  61. package/tsconfig.json +26 -0
  62. package/tsconfig.node.json +10 -0
  63. package/vite.config.ts +26 -0
  64. package/vitest.config.ts +31 -0
@@ -0,0 +1,69 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import * as authService from '../auth'
3
+ import { userAPI } from '../api'
4
+ import * as types from '../types'
5
+
6
+ interface UseAuthReturn {
7
+ user: types.User | null
8
+ isAuthenticated: boolean
9
+ isLoading: boolean
10
+ login: (claudeToken: string) => Promise<void>
11
+ logout: () => void
12
+ }
13
+
14
+ export const useAuth = (): UseAuthReturn => {
15
+ const [user, setUser] = useState<types.User | null>(null)
16
+ const [isAuthenticated, setIsAuthenticated] = useState(false)
17
+ const [isLoading, setIsLoading] = useState(true)
18
+
19
+ // Initialize auth state
20
+ useEffect(() => {
21
+ const initAuth = async () => {
22
+ const authState = await authService.initAuth()
23
+ setIsAuthenticated(authState.isAuthenticated)
24
+
25
+ if (authState.isAuthenticated) {
26
+ try {
27
+ const response = await userAPI.getProfile()
28
+ setUser(response.data.user)
29
+ } catch (error) {
30
+ console.error('Failed to fetch user profile:', error)
31
+ authService.logout()
32
+ setIsAuthenticated(false)
33
+ }
34
+ }
35
+
36
+ setIsLoading(false)
37
+ }
38
+
39
+ initAuth()
40
+ }, [])
41
+
42
+ const login = useCallback(async (claudeToken: string) => {
43
+ setIsLoading(true)
44
+ try {
45
+ const response = await authService.login(claudeToken)
46
+ setUser(response.user)
47
+ setIsAuthenticated(true)
48
+ } catch (error) {
49
+ console.error('Login failed:', error)
50
+ throw error
51
+ } finally {
52
+ setIsLoading(false)
53
+ }
54
+ }, [])
55
+
56
+ const logout = useCallback(() => {
57
+ authService.logout()
58
+ setUser(null)
59
+ setIsAuthenticated(false)
60
+ }, [])
61
+
62
+ return {
63
+ user,
64
+ isAuthenticated,
65
+ isLoading,
66
+ login,
67
+ logout
68
+ }
69
+ }
@@ -0,0 +1,130 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import { workspaceAPI, rbacAPI } from '../api'
3
+ import * as types from '../types'
4
+
5
+ interface UseWorkspaceReturn {
6
+ workspaces: types.Workspace[]
7
+ currentWorkspace: types.Workspace | null
8
+ members: types.WorkspaceMember[]
9
+ roles: types.Role[]
10
+ isLoading: boolean
11
+ error: string | null
12
+ setCurrentWorkspace: (workspace: types.Workspace | null) => void
13
+ createWorkspace: (name: string, isPublic: boolean) => Promise<void>
14
+ addMember: (userId: string, roleName: string) => Promise<void>
15
+ removeMember: (userId: string) => Promise<void>
16
+ updateMember: (userId: string, roleName: string) => Promise<void>
17
+ }
18
+
19
+ export const useWorkspace = (): UseWorkspaceReturn => {
20
+ const [workspaces, setWorkspaces] = useState<types.Workspace[]>([])
21
+ const [currentWorkspace, setCurrentWorkspace] = useState<types.Workspace | null>(null)
22
+ const [members, setMembers] = useState<types.WorkspaceMember[]>([])
23
+ const [roles, setRoles] = useState<types.Role[]>([])
24
+ const [isLoading, setIsLoading] = useState(true)
25
+ const [error, setError] = useState<string | null>(null)
26
+
27
+ // Fetch workspaces and roles
28
+ useEffect(() => {
29
+ const loadData = async () => {
30
+ try {
31
+ setIsLoading(true)
32
+ const [workspacesRes, rolesRes] = await Promise.all([
33
+ workspaceAPI.list(),
34
+ rbacAPI.getRoles()
35
+ ])
36
+ setWorkspaces(workspacesRes.data)
37
+ setRoles(rolesRes.data)
38
+
39
+ // Set first workspace as current
40
+ if (workspacesRes.data.length > 0) {
41
+ setCurrentWorkspace(workspacesRes.data[0])
42
+ }
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err.message : 'Failed to load workspaces')
45
+ } finally {
46
+ setIsLoading(false)
47
+ }
48
+ }
49
+
50
+ loadData()
51
+ }, [])
52
+
53
+ // Fetch members when workspace changes
54
+ useEffect(() => {
55
+ const loadMembers = async () => {
56
+ if (!currentWorkspace) {
57
+ setMembers([])
58
+ return
59
+ }
60
+
61
+ try {
62
+ const res = await workspaceAPI.getMembers(currentWorkspace.id)
63
+ setMembers(res.data)
64
+ } catch (err) {
65
+ console.error('Failed to load members:', err)
66
+ }
67
+ }
68
+
69
+ loadMembers()
70
+ }, [currentWorkspace])
71
+
72
+ const createWorkspace = useCallback(async (name: string, isPublic: boolean) => {
73
+ try {
74
+ const res = await workspaceAPI.create(name, isPublic)
75
+ setWorkspaces([...workspaces, res.data])
76
+ } catch (err) {
77
+ throw err
78
+ }
79
+ }, [workspaces])
80
+
81
+ const addMember = useCallback(async (userId: string, roleName: string) => {
82
+ if (!currentWorkspace) throw new Error('No workspace selected')
83
+
84
+ try {
85
+ await workspaceAPI.addMember(currentWorkspace.id, userId, roleName)
86
+ // Refresh members
87
+ const res = await workspaceAPI.getMembers(currentWorkspace.id)
88
+ setMembers(res.data)
89
+ } catch (err) {
90
+ throw err
91
+ }
92
+ }, [currentWorkspace])
93
+
94
+ const removeMember = useCallback(async (userId: string) => {
95
+ if (!currentWorkspace) throw new Error('No workspace selected')
96
+
97
+ try {
98
+ await workspaceAPI.removeMember(currentWorkspace.id, userId)
99
+ setMembers(members.filter(m => m.id !== userId))
100
+ } catch (err) {
101
+ throw err
102
+ }
103
+ }, [currentWorkspace, members])
104
+
105
+ const updateMember = useCallback(async (userId: string, roleName: string) => {
106
+ if (!currentWorkspace) throw new Error('No workspace selected')
107
+
108
+ try {
109
+ await workspaceAPI.updateMember(currentWorkspace.id, userId, roleName)
110
+ const res = await workspaceAPI.getMembers(currentWorkspace.id)
111
+ setMembers(res.data)
112
+ } catch (err) {
113
+ throw err
114
+ }
115
+ }, [currentWorkspace])
116
+
117
+ return {
118
+ workspaces,
119
+ currentWorkspace,
120
+ members,
121
+ roles,
122
+ isLoading,
123
+ error,
124
+ setCurrentWorkspace,
125
+ createWorkspace,
126
+ addMember,
127
+ removeMember,
128
+ updateMember
129
+ }
130
+ }
package/src/index.css ADDED
@@ -0,0 +1,57 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Global Styles */
6
+ * {
7
+ @apply transition-colors duration-200;
8
+ }
9
+
10
+ body {
11
+ @apply bg-slate-50 text-slate-900;
12
+ }
13
+
14
+ /* Utility Classes */
15
+ .btn {
16
+ @apply px-4 py-2 rounded font-medium cursor-pointer transition-colors;
17
+ }
18
+
19
+ .btn-primary {
20
+ @apply bg-blue-600 text-white hover:bg-blue-700;
21
+ }
22
+
23
+ .btn-secondary {
24
+ @apply bg-slate-200 text-slate-900 hover:bg-slate-300;
25
+ }
26
+
27
+ .btn-sm {
28
+ @apply px-3 py-1 text-sm;
29
+ }
30
+
31
+ .card {
32
+ @apply bg-white rounded-lg border border-slate-200 p-4 shadow-sm;
33
+ }
34
+
35
+ .input {
36
+ @apply px-3 py-2 border border-slate-300 rounded font-medium focus:outline-none focus:border-blue-500;
37
+ }
38
+
39
+ .badge {
40
+ @apply inline-block px-2 py-1 text-sm font-medium rounded;
41
+ }
42
+
43
+ .badge-success {
44
+ @apply bg-green-100 text-green-800;
45
+ }
46
+
47
+ .badge-warning {
48
+ @apply bg-yellow-100 text-yellow-800;
49
+ }
50
+
51
+ .badge-danger {
52
+ @apply bg-red-100 text-red-800;
53
+ }
54
+
55
+ .loading-spinner {
56
+ @apply inline-block w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin;
57
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router-dom'
4
+ import App from './App.tsx'
5
+ import './index.css'
6
+
7
+ ReactDOM.createRoot(document.getElementById('root')!).render(
8
+ <React.StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </React.StrictMode>,
13
+ )
@@ -0,0 +1,13 @@
1
+ import { useSearchParams } from 'react-router-dom'
2
+ import { AnalyticsDashboard } from '../components'
3
+
4
+ export const DashboardPage = () => {
5
+ const [searchParams] = useSearchParams()
6
+ const workspaceId = searchParams.get('workspace')
7
+
8
+ return (
9
+ <div className="p-6 max-w-7xl mx-auto">
10
+ <AnalyticsDashboard workspaceId={workspaceId} />
11
+ </div>
12
+ )
13
+ }
@@ -0,0 +1,156 @@
1
+ import { useNavigate } from 'react-router-dom'
2
+ import { useWorkspace } from '../hooks'
3
+ import { BarChart3, Users, Zap, ArrowRight } from 'lucide-react'
4
+
5
+ export const HomePage = () => {
6
+ const navigate = useNavigate()
7
+ const { currentWorkspace } = useWorkspace()
8
+
9
+ const features = [
10
+ {
11
+ icon: BarChart3,
12
+ title: 'Analytics Dashboard',
13
+ description: 'Track task completion, decision quality, and agent performance in real-time.',
14
+ color: 'blue',
15
+ href: '/dashboard'
16
+ },
17
+ {
18
+ icon: Users,
19
+ title: 'Workspace Management',
20
+ description: 'Manage team members and control access with role-based permissions.',
21
+ color: 'green',
22
+ href: '/workspaces'
23
+ },
24
+ {
25
+ icon: Zap,
26
+ title: 'Integrations',
27
+ description: 'Connect with Slack, GitHub, and Jira to enhance your workflow.',
28
+ color: 'yellow',
29
+ href: '/integrations'
30
+ }
31
+ ]
32
+
33
+ const colorClasses = {
34
+ blue: 'bg-blue-50 text-blue-600 border-blue-200',
35
+ green: 'bg-green-50 text-green-600 border-green-200',
36
+ yellow: 'bg-yellow-50 text-yellow-600 border-yellow-200'
37
+ }
38
+
39
+ return (
40
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
41
+ {/* Hero Section */}
42
+ <div className="p-6 max-w-6xl mx-auto">
43
+ <div className="mb-12">
44
+ <h1 className="text-4xl font-bold text-slate-900 mb-4">
45
+ Welcome to Session Wrap
46
+ </h1>
47
+ <p className="text-xl text-slate-600 max-w-2xl">
48
+ Empower your team with real-time analytics, collaboration tools, and seamless integrations.
49
+ </p>
50
+ </div>
51
+
52
+ {/* Quick Stats */}
53
+ {currentWorkspace && (
54
+ <div className="grid grid-cols-3 gap-4 mb-12">
55
+ <div className="card">
56
+ <p className="text-sm text-slate-600">Current Workspace</p>
57
+ <p className="text-2xl font-bold text-slate-900 mt-2">{currentWorkspace.name}</p>
58
+ <p className="text-xs text-slate-500 mt-2">
59
+ {currentWorkspace.is_public ? '🌍 Public' : '🔒 Private'}
60
+ </p>
61
+ </div>
62
+ <div className="card">
63
+ <p className="text-sm text-slate-600">Member Access</p>
64
+ <p className="text-2xl font-bold text-slate-900 mt-2">
65
+ {currentWorkspace.roles?.length || 0}
66
+ </p>
67
+ <p className="text-xs text-slate-500 mt-2">roles configured</p>
68
+ </div>
69
+ <div className="card">
70
+ <p className="text-sm text-slate-600">Features</p>
71
+ <p className="text-2xl font-bold text-slate-900 mt-2">6+</p>
72
+ <p className="text-xs text-slate-500 mt-2">available modules</p>
73
+ </div>
74
+ </div>
75
+ )}
76
+
77
+ {/* Features Grid */}
78
+ <div className="mb-12">
79
+ <h2 className="text-2xl font-bold text-slate-900 mb-6">Core Features</h2>
80
+ <div className="grid grid-cols-3 gap-6">
81
+ {features.map(({ icon: Icon, title, description, color, href }) => (
82
+ <button
83
+ key={href}
84
+ onClick={() => navigate(href)}
85
+ className={`card border-2 transition-all hover:shadow-lg cursor-pointer ${
86
+ colorClasses[color as keyof typeof colorClasses]
87
+ }`}
88
+ >
89
+ <div className="flex items-start justify-between mb-3">
90
+ <Icon size={32} />
91
+ <ArrowRight size={18} className="opacity-0 group-hover:opacity-100" />
92
+ </div>
93
+ <h3 className="text-lg font-semibold text-slate-900 text-left mb-2">
94
+ {title}
95
+ </h3>
96
+ <p className="text-sm text-slate-600 text-left">
97
+ {description}
98
+ </p>
99
+ </button>
100
+ ))}
101
+ </div>
102
+ </div>
103
+
104
+ {/* Quick Links */}
105
+ <div className="bg-white rounded-lg border border-slate-200 p-6">
106
+ <h2 className="text-lg font-semibold text-slate-900 mb-4">Quick Actions</h2>
107
+ <div className="grid grid-cols-2 gap-4">
108
+ <button
109
+ onClick={() => navigate('/workspaces')}
110
+ className="btn btn-primary text-left"
111
+ >
112
+ 🏢 Create Workspace
113
+ </button>
114
+ <button
115
+ onClick={() => navigate('/roles')}
116
+ className="btn btn-secondary text-left"
117
+ >
118
+ 👥 Manage Members
119
+ </button>
120
+ <button
121
+ onClick={() => navigate('/integrations')}
122
+ className="btn btn-secondary text-left"
123
+ >
124
+ ⚡ Setup Integrations
125
+ </button>
126
+ <button
127
+ onClick={() => navigate('/settings')}
128
+ className="btn btn-secondary text-left"
129
+ >
130
+ ⚙️ Adjust Settings
131
+ </button>
132
+ </div>
133
+ </div>
134
+
135
+ {/* Getting Started Tips */}
136
+ <div className="mt-12 bg-blue-50 border border-blue-200 rounded-lg p-6">
137
+ <h3 className="text-lg font-semibold text-blue-900 mb-3">Getting Started</h3>
138
+ <ul className="space-y-2 text-sm text-blue-800">
139
+ <li>
140
+ <span className="font-semibold">1. Create a Workspace</span> — Start by creating a workspace for your team
141
+ </li>
142
+ <li>
143
+ <span className="font-semibold">2. Add Members</span> — Invite team members and assign roles
144
+ </li>
145
+ <li>
146
+ <span className="font-semibold">3. Setup Integrations</span> — Connect with Slack, GitHub, or Jira
147
+ </li>
148
+ <li>
149
+ <span className="font-semibold">4. Monitor Analytics</span> — Track progress and performance
150
+ </li>
151
+ </ul>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ )
156
+ }
@@ -0,0 +1,9 @@
1
+ import { IntegrationManager } from '../components'
2
+
3
+ export const IntegrationsPage = () => {
4
+ return (
5
+ <div className="p-6 max-w-6xl mx-auto">
6
+ <IntegrationManager />
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,9 @@
1
+ import { RoleManager } from '../components'
2
+
3
+ export const RolesPage = () => {
4
+ return (
5
+ <div className="p-6 max-w-6xl mx-auto">
6
+ <RoleManager />
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,118 @@
1
+ import { useAuth } from '../hooks'
2
+ import { Save } from 'lucide-react'
3
+
4
+ export const SettingsPage = () => {
5
+ const { user } = useAuth()
6
+
7
+ return (
8
+ <div className="p-6 max-w-4xl mx-auto">
9
+ <div className="mb-8">
10
+ <h1 className="text-2xl font-bold text-slate-900">Settings</h1>
11
+ <p className="text-slate-600 mt-2">Manage your preferences and account settings</p>
12
+ </div>
13
+
14
+ {/* User Profile Section */}
15
+ <div className="card mb-6">
16
+ <h2 className="text-lg font-semibold mb-4">Account</h2>
17
+ <div className="space-y-4">
18
+ <div>
19
+ <label className="block text-sm font-medium text-slate-700 mb-2">GitHub Login</label>
20
+ <input
21
+ type="text"
22
+ value={user?.github_login || ''}
23
+ disabled
24
+ className="input w-full bg-slate-100"
25
+ />
26
+ </div>
27
+
28
+ {user?.email && (
29
+ <div>
30
+ <label className="block text-sm font-medium text-slate-700 mb-2">Email</label>
31
+ <input
32
+ type="email"
33
+ value={user.email}
34
+ disabled
35
+ className="input w-full bg-slate-100"
36
+ />
37
+ </div>
38
+ )}
39
+
40
+ {user?.subscription && (
41
+ <div className="p-4 bg-green-50 border border-green-200 rounded-lg">
42
+ <p className="text-sm font-medium text-green-800">
43
+ ✓ Subscription Active
44
+ </p>
45
+ <p className="text-xs text-green-700 mt-1">
46
+ Expires: {new Date(user.subscription.expires_at).toLocaleDateString()}
47
+ </p>
48
+ </div>
49
+ )}
50
+ </div>
51
+ </div>
52
+
53
+ {/* Preferences Section */}
54
+ <div className="card mb-6">
55
+ <h2 className="text-lg font-semibold mb-4">Preferences</h2>
56
+ <div className="space-y-4">
57
+ <div>
58
+ <label className="block text-sm font-medium text-slate-700 mb-2">
59
+ <input type="checkbox" defaultChecked className="mr-2" />
60
+ Email notifications
61
+ </label>
62
+ <p className="text-xs text-slate-600 ml-6">
63
+ Receive email updates for important workspace events
64
+ </p>
65
+ </div>
66
+
67
+ <div>
68
+ <label className="block text-sm font-medium text-slate-700 mb-2">
69
+ <input type="checkbox" defaultChecked className="mr-2" />
70
+ Analytics summaries
71
+ </label>
72
+ <p className="text-xs text-slate-600 ml-6">
73
+ Weekly analytics digest to your email
74
+ </p>
75
+ </div>
76
+
77
+ <div>
78
+ <label className="block text-sm font-medium text-slate-700 mb-2">
79
+ <input type="checkbox" className="mr-2" />
80
+ Slack notifications
81
+ </label>
82
+ <p className="text-xs text-slate-600 ml-6">
83
+ Send real-time alerts to Slack (requires Slack integration)
84
+ </p>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ {/* Theme Section */}
90
+ <div className="card mb-6">
91
+ <h2 className="text-lg font-semibold mb-4">Theme</h2>
92
+ <div className="space-y-3">
93
+ <label className="flex items-center gap-3">
94
+ <input type="radio" name="theme" defaultChecked />
95
+ <span className="text-sm">Light (default)</span>
96
+ </label>
97
+ <label className="flex items-center gap-3">
98
+ <input type="radio" name="theme" />
99
+ <span className="text-sm">Dark</span>
100
+ </label>
101
+ <label className="flex items-center gap-3">
102
+ <input type="radio" name="theme" />
103
+ <span className="text-sm">System</span>
104
+ </label>
105
+ </div>
106
+ </div>
107
+
108
+ {/* Action Buttons */}
109
+ <div className="flex gap-3">
110
+ <button className="btn btn-primary flex items-center gap-2">
111
+ <Save size={18} />
112
+ Save Settings
113
+ </button>
114
+ <button className="btn btn-secondary">Cancel</button>
115
+ </div>
116
+ </div>
117
+ )
118
+ }
@@ -0,0 +1,9 @@
1
+ import { WorkspaceSelector } from '../components'
2
+
3
+ export const WorkspacesPage = () => {
4
+ return (
5
+ <div className="p-6 max-w-6xl mx-auto">
6
+ <WorkspaceSelector />
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,6 @@
1
+ export { HomePage } from './HomePage'
2
+ export { DashboardPage } from './DashboardPage'
3
+ export { WorkspacesPage } from './WorkspacesPage'
4
+ export { RolesPage } from './RolesPage'
5
+ export { IntegrationsPage } from './IntegrationsPage'
6
+ export { SettingsPage } from './SettingsPage'
@@ -0,0 +1,31 @@
1
+ import '@testing-library/jest-dom'
2
+ import { vi } from 'vitest'
3
+
4
+ // Mock import.meta.env
5
+ Object.defineProperty(import.meta, 'env', {
6
+ value: {
7
+ VITE_API_URL: 'http://localhost:3000',
8
+ VITE_JWT_STORAGE_KEY: 'auth_token',
9
+ VITE_ENV: 'test'
10
+ }
11
+ })
12
+
13
+ // Mock localStorage
14
+ const localStorageMock = {
15
+ getItem: vi.fn(),
16
+ setItem: vi.fn(),
17
+ removeItem: vi.fn(),
18
+ clear: vi.fn()
19
+ }
20
+ global.localStorage = localStorageMock as any
21
+
22
+ // Mock window.location.href
23
+ delete (window as any).location
24
+ window.location = { href: '' } as any
25
+
26
+ // Suppress console errors in tests
27
+ global.console = {
28
+ ...console,
29
+ error: vi.fn(),
30
+ warn: vi.fn()
31
+ }
@@ -0,0 +1,15 @@
1
+ import { ReactElement } from 'react'
2
+ import { render, RenderOptions } from '@testing-library/react'
3
+ import { BrowserRouter } from 'react-router-dom'
4
+
5
+ const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
6
+ return <BrowserRouter>{children}</BrowserRouter>
7
+ }
8
+
9
+ const customRender = (
10
+ ui: ReactElement,
11
+ options?: Omit<RenderOptions, 'wrapper'>
12
+ ) => render(ui, { wrapper: AllTheProviders, ...options })
13
+
14
+ export * from '@testing-library/react'
15
+ export { customRender as render }