Skip to content

Commit 922994d

Browse files
committed
feat: implement global settings page with role-based access, add not found page, and enhance user data fetching
1 parent 59e2c60 commit 922994d

File tree

9 files changed

+144
-16
lines changed

9 files changed

+144
-16
lines changed

services/frontend/src/components/AppSidebar.vue

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, onMounted, defineProps } from 'vue' // Added defineProps
2+
import { ref, onMounted, defineProps, computed } from 'vue' // Added defineProps
33
import { useRouter } from 'vue-router'
44
import { useI18n } from 'vue-i18n'
55
import { getEnv } from '@/utils/env' // Import getEnv
@@ -30,14 +30,16 @@ import {
3030
} from '@/components/ui/dropdown-menu'
3131
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
3232
import { TeamService, type Team } from '@/services/teamService'
33+
import { UserService, type User } from '@/services/userService'
3334
import {
3435
Server,
3536
Settings,
3637
Key,
3738
ChevronDown,
38-
User,
39+
User as UserIcon,
3940
LogOut,
40-
Users
41+
Users,
42+
FileSliders
4143
} from 'lucide-vue-next'
4244
4345
// Define props, including variant
@@ -51,10 +53,16 @@ const router = useRouter()
5153
const { t } = useI18n()
5254
5355
// User data
56+
const currentUser = ref<User | null>(null)
5457
const userEmail = ref('')
5558
const userName = ref('')
5659
const userLoading = ref(true)
5760
61+
// Role checking
62+
const isGlobalAdmin = computed(() => {
63+
return currentUser.value?.role_id === 'global_admin'
64+
})
65+
5866
// Teams data
5967
const teams = ref<Team[]>([])
6068
const selectedTeam = ref<Team | null>(null)
@@ -80,21 +88,24 @@ const navigationItems = [
8088
},
8189
]
8290
83-
// Fetch user data logic (remains the same)
91+
// Fetch user data logic using UserService
8492
const fetchUserData = async () => {
8593
try {
86-
const apiUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL') // Use getEnv with the correct key
87-
if (!apiUrl) {
88-
throw new Error('API URL not configured. Make sure VITE_DEPLOYSTACK_BACKEND_URL is set.')
94+
const user = await UserService.getCurrentUser()
95+
if (user) {
96+
currentUser.value = user
97+
userEmail.value = user.email
98+
userName.value = user.username || ''
99+
} else {
100+
// User not logged in, redirect to login
101+
router.push('/login')
89102
}
90-
const response = await fetch(`${apiUrl}/api/users/me`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, credentials: 'include' })
91-
if (!response.ok) {
92-
if (response.status === 401) { router.push('/login'); return }
93-
throw new Error(`Failed to fetch user data: ${response.status}`)
94-
}
95-
const data = await response.json()
96-
if (data.success && data.data) { userEmail.value = data.data.email; userName.value = data.data.username; }
97-
} catch (error) { console.error('Error fetching user data:', error) } finally { userLoading.value = false }
103+
} catch (error) {
104+
console.error('Error fetching user data:', error)
105+
currentUser.value = null
106+
} finally {
107+
userLoading.value = false
108+
}
98109
}
99110
100111
// Fetch teams logic (remains the same)
@@ -197,6 +208,26 @@ onMounted(() => {
197208
</SidebarMenu>
198209
</SidebarGroupContent>
199210
</SidebarGroup>
211+
212+
<!-- Admin Area section - only visible to global_admin -->
213+
<SidebarGroup v-if="isGlobalAdmin">
214+
<SidebarGroupLabel>{{ t('sidebar.adminArea.title') }}</SidebarGroupLabel>
215+
<SidebarGroupContent>
216+
<SidebarMenu>
217+
<SidebarMenuItem>
218+
<SidebarMenuButton
219+
@click="navigateTo('/global-settings')"
220+
:is-active="router.currentRoute.value.path === '/global-settings'"
221+
class="w-full justify-start"
222+
:aria-current="router.currentRoute.value.path === '/global-settings' ? 'page' : undefined"
223+
>
224+
<FileSliders class="mr-2 h-4 w-4 shrink-0" />
225+
<span>{{ t('sidebar.adminArea.globalSettings') }}</span>
226+
</SidebarMenuButton>
227+
</SidebarMenuItem>
228+
</SidebarMenu>
229+
</SidebarGroupContent>
230+
</SidebarGroup>
200231
</SidebarContent>
201232

202233
<SidebarFooter>
@@ -224,7 +255,7 @@ onMounted(() => {
224255
align="start"
225256
>
226257
<DropdownMenuItem @click="goToAccount" class="gap-2">
227-
<User class="size-4" />
258+
<UserIcon class="size-4" />
228259
{{ t('sidebar.user.account') }}
229260
</DropdownMenuItem>
230261
<DropdownMenuSeparator />

services/frontend/src/i18n/locales/en/dashboard.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export default {
77
provider: 'Provider',
88
credentials: 'Credentials',
99
},
10+
adminArea: {
11+
title: 'Admin Area',
12+
globalSettings: 'Global Settings',
13+
},
1014
teams: {
1115
selectTeam: 'Select Team',
1216
noTeams: 'No teams available',
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
globalSettings: {
3+
title: 'Global Settings',
4+
description: 'Manage global application settings',
5+
},
6+
}

services/frontend/src/i18n/locales/en/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import commonMessages from './common'
33
import authMessages from './auth'
44
import setupMessages from './setup'
55
import dashboardMessages from './dashboard'
6+
import globalSettingsMessages from './globalSettings'
7+
import notFoundMessages from './notFound'
68

79
export default {
810
...commonMessages,
911
...authMessages,
1012
...setupMessages,
1113
...dashboardMessages,
14+
...globalSettingsMessages,
15+
...notFoundMessages,
1216
// If there are any top-level keys directly under 'en', they can be added here.
1317
// For example, if you had a global 'appName': 'My Application'
1418
// appName: 'DeployStack Application',
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
notFound: {
3+
title: '404',
4+
description: "Looks like you've ventured into the unknown digital realm.",
5+
returnButton: 'Return to website',
6+
},
7+
}

services/frontend/src/router/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ const routes = [
6767
component: () => import('../views/Credentials.vue'),
6868
meta: { requiresSetup: true },
6969
},
70+
{
71+
path: '/global-settings',
72+
name: 'GlobalSettings',
73+
component: () => import('../views/GlobalSettings.vue'),
74+
meta: {
75+
requiresSetup: true,
76+
requiresRole: 'global_admin'
77+
},
78+
},
79+
{
80+
path: '/:pathMatch(.*)*',
81+
name: 'NotFound',
82+
component: () => import('../views/NotFound.vue'),
83+
},
7084
]
7185

7286
const router = createRouter({
@@ -112,6 +126,15 @@ router.beforeEach(async (to, from, next) => {
112126
}
113127
}
114128

129+
// Check role requirements
130+
if (to.meta.requiresRole) {
131+
const currentUser = await UserService.getCurrentUser()
132+
if (!currentUser || currentUser.role_id !== to.meta.requiresRole) {
133+
next({ name: 'NotFound' })
134+
return
135+
}
136+
}
137+
115138
next()
116139
})
117140

services/frontend/src/services/userService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { getEnv } from '@/utils/env';
33
export interface User {
44
id: string;
55
email: string;
6+
username?: string;
7+
role_id: string;
8+
first_name?: string;
9+
last_name?: string;
610
// Add other user properties as needed
711
}
812

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<div class="container mx-auto p-6">
3+
<h1 class="text-3xl font-bold mb-4">
4+
{{ t('globalSettings.title') }}
5+
</h1>
6+
<p class="text-gray-600">
7+
{{ t('globalSettings.description') }}
8+
</p>
9+
</div>
10+
</template>
11+
12+
<script setup lang="ts">
13+
import { useI18n } from 'vue-i18n'
14+
15+
const { t } = useI18n()
16+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<template>
2+
<div class="flex items-center min-h-screen px-4 py-12 sm:px-6 md:px-8 lg:px-12 xl:px-16">
3+
<div class="w-full space-y-6 text-center">
4+
<div class="space-y-3">
5+
<h1 class="text-4xl font-bold tracking-tighter sm:text-5xl transition-transform hover:scale-110">
6+
404
7+
</h1>
8+
<p class="text-gray-500">
9+
{{ t('notFound.description') }}
10+
</p>
11+
</div>
12+
<Button
13+
@click="returnToWebsite"
14+
class="inline-flex h-10 items-center rounded-md bg-gray-900 px-8 text-sm font-medium text-gray-50 shadow transition-colors hover:bg-gray-900/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:pointer-events-none disabled:opacity-50 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90 dark:focus-visible:ring-gray-300"
15+
>
16+
{{ t('notFound.returnButton') }}
17+
</Button>
18+
</div>
19+
</div>
20+
</template>
21+
22+
<script setup lang="ts">
23+
import { useRouter } from 'vue-router'
24+
import { useI18n } from 'vue-i18n'
25+
import { Button } from '@/components/ui/button'
26+
27+
const router = useRouter()
28+
const { t } = useI18n()
29+
30+
const returnToWebsite = () => {
31+
router.push('/dashboard')
32+
}
33+
</script>

0 commit comments

Comments
 (0)