@finema/finework-layer 0.1.1 → 0.1.3
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.
- package/.playground/app/pages/layout-admin.vue +2 -6
- package/CHANGELOG.md +8 -0
- package/app/app.config.ts +2 -1
- package/app/components/Layout/Admin/Sidebar.vue +67 -59
- package/app/components/Layout/Admin/index.vue +30 -50
- package/app/components/Layout/User/index.vue +99 -0
- package/package.json +1 -1
- /package/app/components/Layout/{Admin/Apps.vue → Apps.vue} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.3](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.1.2...0.1.3) (2025-10-15)
|
|
4
|
+
|
|
5
|
+
## [0.1.2](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.1.1...0.1.2) (2025-10-14)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* layout user ([6bf83a1](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/6bf83a15db0747323e6fd02555eab0b0c4e3e64e))
|
|
10
|
+
|
|
3
11
|
## [0.1.1](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.0.16...0.1.1) (2025-10-14)
|
|
4
12
|
|
|
5
13
|
### Bug Fixes
|
package/app/app.config.ts
CHANGED
|
@@ -195,7 +195,8 @@ export default defineAppConfig({
|
|
|
195
195
|
variants: {
|
|
196
196
|
size: {
|
|
197
197
|
xl: {
|
|
198
|
-
base: 'py-2.5 disabled:bg-[#F5F5F5] disabled:text-[#737373] disabled:cursor-not-allowed w-full',
|
|
198
|
+
base: 'py-2.5 min-h-[100px] items-start disabled:bg-[#F5F5F5] disabled:text-[#737373] disabled:cursor-not-allowed w-full',
|
|
199
|
+
item: 'bg-white',
|
|
199
200
|
},
|
|
200
201
|
},
|
|
201
202
|
},
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<aside
|
|
3
|
-
:class="[
|
|
3
|
+
:class="[
|
|
4
|
+
`
|
|
4
5
|
flex h-full flex-col overflow-y-auto bg-white
|
|
5
6
|
transition-all duration-300
|
|
6
|
-
|
|
7
|
+
`,
|
|
8
|
+
]"
|
|
7
9
|
>
|
|
8
10
|
<div
|
|
9
|
-
:class="[
|
|
10
|
-
'
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
:class="[
|
|
12
|
+
'flex h-[72px] items-center max-sm:pr-5',
|
|
13
|
+
{
|
|
14
|
+
'justify-center': isCollapsed,
|
|
15
|
+
'w-[260px] justify-between pl-5': !isCollapsed,
|
|
16
|
+
},
|
|
17
|
+
]"
|
|
13
18
|
>
|
|
14
19
|
<NuxtLink :to="routes.home.to">
|
|
15
20
|
<img
|
|
@@ -22,8 +27,10 @@
|
|
|
22
27
|
|
|
23
28
|
<Navbar />
|
|
24
29
|
</div>
|
|
25
|
-
<div
|
|
26
|
-
|
|
30
|
+
<div
|
|
31
|
+
class="bg-primary flex items-center justify-between px-7 py-4 font-bold text-white"
|
|
32
|
+
>
|
|
33
|
+
{{ isCollapsed ? "" : label }}
|
|
27
34
|
|
|
28
35
|
<Icon
|
|
29
36
|
v-if="!isCollapsed"
|
|
@@ -40,15 +47,8 @@
|
|
|
40
47
|
</div>
|
|
41
48
|
|
|
42
49
|
<div class="flex-1 overflow-y-auto border-r border-gray-200 p-4">
|
|
43
|
-
<div
|
|
44
|
-
v-
|
|
45
|
-
class="space-y-4 divide-y-1 divide-[#d9d9d9]"
|
|
46
|
-
>
|
|
47
|
-
<div
|
|
48
|
-
v-for="group in navigationItems"
|
|
49
|
-
:key="group.label"
|
|
50
|
-
class="pb-4"
|
|
51
|
-
>
|
|
50
|
+
<div v-if="isGroup" class="space-y-4 divide-y-1 divide-[#d9d9d9]">
|
|
51
|
+
<div v-for="group in navigationItems" :key="group.label" class="pb-4">
|
|
52
52
|
<div class="mt-2 mb-2 text-[10px] font-semibold text-gray-400">
|
|
53
53
|
{{ group.label }}
|
|
54
54
|
</div>
|
|
@@ -69,8 +69,10 @@
|
|
|
69
69
|
'hover:text-primary',
|
|
70
70
|
'data-active:before:bg-white data-active:before:rounded-lg data-active:text-primary font-semibold',
|
|
71
71
|
],
|
|
72
|
-
linkLeadingIcon:
|
|
73
|
-
|
|
72
|
+
linkLeadingIcon:
|
|
73
|
+
'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary',
|
|
74
|
+
childList:
|
|
75
|
+
'border-none ms-0 pl-8 bg-gray-100 mt-2 py-1 rounded-lg',
|
|
74
76
|
childLink: 'ps-0',
|
|
75
77
|
childItem: 'ps-0',
|
|
76
78
|
}"
|
|
@@ -96,7 +98,8 @@
|
|
|
96
98
|
'hover:text-primary',
|
|
97
99
|
'data-active:before:bg-white data-active:before:rounded-lg data-active:text-primary font-semibold',
|
|
98
100
|
],
|
|
99
|
-
linkLeadingIcon:
|
|
101
|
+
linkLeadingIcon:
|
|
102
|
+
'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary ',
|
|
100
103
|
childList: 'border-none ms-0 pl-8 bg-gray-100 mt-2 py-1 rounded-lg',
|
|
101
104
|
childLink: 'ps-0',
|
|
102
105
|
childItem: 'ps-0',
|
|
@@ -104,10 +107,7 @@
|
|
|
104
107
|
class="w-full justify-center"
|
|
105
108
|
/>
|
|
106
109
|
</div>
|
|
107
|
-
<div
|
|
108
|
-
v-if="isMobile"
|
|
109
|
-
class="border-t border-gray-100 p-3"
|
|
110
|
-
>
|
|
110
|
+
<div v-if="isMobile" class="border-t border-gray-100 p-3">
|
|
111
111
|
<div class="flex items-center justify-between gap-2">
|
|
112
112
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
113
113
|
<Avatar
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
{{ auth.me.value?.display_name || auth.me.value?.full_name }}
|
|
121
121
|
</p>
|
|
122
122
|
<p class="text-muted truncate text-xs">
|
|
123
|
-
{{ auth.me.value?.email ||
|
|
123
|
+
{{ auth.me.value?.email || "" }}
|
|
124
124
|
</p>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
@@ -149,7 +149,7 @@ import type { NavigationMenuItem } from '@nuxt/ui'
|
|
|
149
149
|
import { computed } from 'vue'
|
|
150
150
|
import { useRoute } from 'vue-router'
|
|
151
151
|
import type { Permission, UserModule } from '#imports'
|
|
152
|
-
import Navbar from '
|
|
152
|
+
import Navbar from '../Apps.vue'
|
|
153
153
|
|
|
154
154
|
defineEmits<{
|
|
155
155
|
'toggle-collapsed': []
|
|
@@ -166,7 +166,6 @@ const props = defineProps<{
|
|
|
166
166
|
const route = useRoute()
|
|
167
167
|
const auth = useAuth()
|
|
168
168
|
const userMenuItems = [
|
|
169
|
-
|
|
170
169
|
{
|
|
171
170
|
label: 'View profile',
|
|
172
171
|
icon: 'i-lucide-user',
|
|
@@ -193,13 +192,16 @@ const mappedItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
|
|
|
193
192
|
|
|
194
193
|
return {
|
|
195
194
|
active: isChildCurrentlyActive,
|
|
196
|
-
class:
|
|
195
|
+
class:
|
|
196
|
+
'hover:bg-transparent hover:text-gray-700 hover:font-bold py-2 data-active:before:bg-transparent data-active:text-gray-700 data-active:font-bold',
|
|
197
197
|
icon: '',
|
|
198
198
|
...child,
|
|
199
199
|
}
|
|
200
200
|
})
|
|
201
201
|
|
|
202
|
-
const selfIsActive = item.to
|
|
202
|
+
const selfIsActive = item.to
|
|
203
|
+
? route.path.startsWith(String(item.to))
|
|
204
|
+
: false
|
|
203
205
|
|
|
204
206
|
const itemIsActive = selfIsActive || isAnyChildActive // A root item is active if its own link matches OR if any child is active
|
|
205
207
|
|
|
@@ -221,46 +223,52 @@ const mappedItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
|
|
|
221
223
|
|
|
222
224
|
// filter items base on auth.hasPermission and nested children , permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER']
|
|
223
225
|
const filterItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
|
|
224
|
-
return items
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
return items
|
|
227
|
+
.filter((item) => {
|
|
228
|
+
if (item.permissions && Array.isArray(item.permissions)) {
|
|
229
|
+
// permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER'] => module: 'pmo', permissions: ['USER', 'ADMIN', 'SUPER']
|
|
230
|
+
const permissionStrings = item.permissions as string[]
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
// Extract module from first permission (all should have same module)
|
|
233
|
+
const firstPermission = permissionStrings[0]
|
|
231
234
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
if (!firstPermission) {
|
|
236
|
+
return true
|
|
237
|
+
}
|
|
235
238
|
|
|
236
|
-
|
|
239
|
+
const [moduleStr] = firstPermission.split(':')
|
|
237
240
|
|
|
238
|
-
|
|
239
|
-
|
|
241
|
+
// Extract all permission levels
|
|
242
|
+
const permissionLevels = permissionStrings.map(
|
|
243
|
+
(p) => p.split(':')[1],
|
|
244
|
+
) as Permission[]
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
return auth.hasPermission(moduleStr as UserModule, ...permissionLevels)
|
|
247
|
+
}
|
|
243
248
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
// Recursively filter children
|
|
250
|
+
if (item.children) {
|
|
251
|
+
const filteredChildren = filterItems(
|
|
252
|
+
item.children as NavigationMenuItem[],
|
|
253
|
+
)
|
|
247
254
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
255
|
+
// Only include parent if it has visible children or no permission requirement
|
|
256
|
+
return filteredChildren.length > 0
|
|
257
|
+
}
|
|
251
258
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
return true
|
|
260
|
+
})
|
|
261
|
+
.map((item) => {
|
|
262
|
+
// Apply filtering to children
|
|
263
|
+
if (item.children) {
|
|
264
|
+
return {
|
|
265
|
+
...item,
|
|
266
|
+
children: filterItems(item.children as NavigationMenuItem[]),
|
|
267
|
+
}
|
|
259
268
|
}
|
|
260
|
-
}
|
|
261
269
|
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
return item
|
|
271
|
+
})
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
const navigationItems = computed<NavigationMenuItem[]>(() => {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex min-h-screen flex-1">
|
|
3
3
|
<div
|
|
4
|
-
:class="[
|
|
4
|
+
:class="[
|
|
5
|
+
`
|
|
5
6
|
fixed inset-0 z-30 hidden w-auto
|
|
6
7
|
lg:block
|
|
7
|
-
`,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
`,
|
|
9
|
+
{
|
|
10
|
+
'max-w-[88px]': isCollapsed,
|
|
11
|
+
'max-w-[260px]': !isCollapsed,
|
|
12
|
+
},
|
|
13
|
+
]"
|
|
11
14
|
>
|
|
12
15
|
<Sidebar
|
|
13
16
|
:label="label"
|
|
@@ -18,18 +21,17 @@
|
|
|
18
21
|
/>
|
|
19
22
|
</div>
|
|
20
23
|
<div
|
|
21
|
-
:class="[
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
:class="[
|
|
25
|
+
'w-full bg-gray-50',
|
|
26
|
+
{
|
|
27
|
+
'lg:pl-[88px]': isCollapsed,
|
|
28
|
+
'lg:pl-[260px]': !isCollapsed,
|
|
29
|
+
},
|
|
30
|
+
]"
|
|
25
31
|
>
|
|
26
32
|
<nav
|
|
27
|
-
class="
|
|
28
|
-
|
|
29
|
-
gap-4 bg-white px-5 lg:min-h-[72px]
|
|
30
|
-
lg:justify-end
|
|
31
|
-
"
|
|
32
|
-
style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04);"
|
|
33
|
+
class="fixed top-0 left-0 z-20 flex min-h-[64px] w-screen items-center justify-between gap-4 bg-white px-5 lg:min-h-[72px] lg:justify-end"
|
|
34
|
+
style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04)"
|
|
33
35
|
>
|
|
34
36
|
<div class="flex items-center gap-4">
|
|
35
37
|
<Slideover
|
|
@@ -41,10 +43,7 @@
|
|
|
41
43
|
side="left"
|
|
42
44
|
>
|
|
43
45
|
<svg
|
|
44
|
-
class="
|
|
45
|
-
cursor-pointer
|
|
46
|
-
lg:hidden
|
|
47
|
-
"
|
|
46
|
+
class="cursor-pointer lg:hidden"
|
|
48
47
|
width="19"
|
|
49
48
|
height="18"
|
|
50
49
|
viewBox="0 0 19 18"
|
|
@@ -74,10 +73,7 @@
|
|
|
74
73
|
<img
|
|
75
74
|
src="/logo-mini.png"
|
|
76
75
|
alt="logo_mini_color"
|
|
77
|
-
class="
|
|
78
|
-
w-[126px] min-w-[126px] lg:hidden
|
|
79
|
-
lg:w-[172px] lg:min-w-[172px]
|
|
80
|
-
"
|
|
76
|
+
class="w-[126px] min-w-[126px] lg:hidden lg:w-[172px] lg:min-w-[172px]"
|
|
81
77
|
/>
|
|
82
78
|
</NuxtLink>
|
|
83
79
|
<Apps class="lg:hidden" />
|
|
@@ -96,14 +92,12 @@
|
|
|
96
92
|
}"
|
|
97
93
|
>
|
|
98
94
|
<div class="relative flex cursor-pointer items-center gap-2">
|
|
99
|
-
<div
|
|
100
|
-
class="hidden flex-col justify-end text-right lg:flex"
|
|
101
|
-
>
|
|
95
|
+
<div class="hidden flex-col justify-end text-right lg:flex">
|
|
102
96
|
<p class="font-bold">
|
|
103
97
|
{{ auth.me.value?.display_name || auth.me.value?.full_name }}
|
|
104
98
|
</p>
|
|
105
99
|
<p class="text-muted text-sm">
|
|
106
|
-
{{ auth.me.value?.email ||
|
|
100
|
+
{{ auth.me.value?.email || "" }}
|
|
107
101
|
</p>
|
|
108
102
|
</div>
|
|
109
103
|
<Avatar
|
|
@@ -111,22 +105,17 @@
|
|
|
111
105
|
icon="ri:user-line"
|
|
112
106
|
:src="auth.me.value?.avatar_url || ''"
|
|
113
107
|
/>
|
|
114
|
-
<Icon
|
|
115
|
-
name="i-ph:caret-down-light"
|
|
116
|
-
class="size-5"
|
|
117
|
-
/>
|
|
108
|
+
<Icon name="i-ph:caret-down-light" class="size-5" />
|
|
118
109
|
</div>
|
|
119
110
|
</DropdownMenu>
|
|
120
111
|
</div>
|
|
121
112
|
</nav>
|
|
122
113
|
<div class="w-full bg-gray-50 pt-[64px] lg:pt-[72px]">
|
|
123
|
-
<main
|
|
124
|
-
class="
|
|
125
|
-
mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8
|
|
126
|
-
"
|
|
127
|
-
>
|
|
114
|
+
<main class="mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8">
|
|
128
115
|
<Breadcrumb
|
|
129
|
-
v-if="
|
|
116
|
+
v-if="
|
|
117
|
+
!app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1
|
|
118
|
+
"
|
|
130
119
|
:items="breadcrumbsItems"
|
|
131
120
|
class="mb-6"
|
|
132
121
|
:ui="{
|
|
@@ -136,17 +125,11 @@
|
|
|
136
125
|
/>
|
|
137
126
|
<div
|
|
138
127
|
v-if="app.pageMeta.title"
|
|
139
|
-
class="
|
|
140
|
-
mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4
|
|
141
|
-
lg:flex-row lg:items-start
|
|
142
|
-
"
|
|
128
|
+
class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-start"
|
|
143
129
|
>
|
|
144
130
|
<div class="flex flex-1 flex-col">
|
|
145
131
|
<h1
|
|
146
|
-
class="
|
|
147
|
-
text-3xl font-bold wrap-break-word
|
|
148
|
-
lg:max-w-2/3
|
|
149
|
-
"
|
|
132
|
+
class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
|
|
150
133
|
:title="app.pageMeta.title"
|
|
151
134
|
>
|
|
152
135
|
{{ app.pageMeta.title }}
|
|
@@ -154,10 +137,7 @@
|
|
|
154
137
|
</h1>
|
|
155
138
|
|
|
156
139
|
<div id="page-subtitle" />
|
|
157
|
-
<p
|
|
158
|
-
v-if="app.pageMeta.sub_title"
|
|
159
|
-
class="text-[#475467]"
|
|
160
|
-
>
|
|
140
|
+
<p v-if="app.pageMeta.sub_title" class="text-[#475467]">
|
|
161
141
|
{{ app.pageMeta.sub_title }}
|
|
162
142
|
</p>
|
|
163
143
|
</div>
|
|
@@ -174,7 +154,7 @@
|
|
|
174
154
|
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
|
175
155
|
import { computed } from 'vue'
|
|
176
156
|
import Sidebar from './Sidebar.vue'
|
|
177
|
-
import Apps from '
|
|
157
|
+
import Apps from '../Apps.vue'
|
|
178
158
|
|
|
179
159
|
defineProps<{
|
|
180
160
|
label: string
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="overflow-hidden pt-[80px]">
|
|
3
|
+
<div class="flex min-h-[calc(100vh-80px)] flex-col">
|
|
4
|
+
<nav
|
|
5
|
+
class="fixed top-0 left-0 z-20 flex min-h-[80px] w-screen items-center justify-center bg-white"
|
|
6
|
+
style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04)"
|
|
7
|
+
>
|
|
8
|
+
<div class="mx-auto flex w-full px-4 md:px-8">
|
|
9
|
+
<div class="flex items-center gap-4">
|
|
10
|
+
<NuxtLink :to="routes.home.to">
|
|
11
|
+
<img
|
|
12
|
+
src="/logo-mini.png"
|
|
13
|
+
alt="logo_mini"
|
|
14
|
+
class="h-[27px] w-[172px] cursor-pointer"
|
|
15
|
+
/>
|
|
16
|
+
</NuxtLink>
|
|
17
|
+
<Apps />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="flex flex-1 items-center justify-end gap-2">
|
|
21
|
+
<DropdownMenu
|
|
22
|
+
:items="userMenuItems"
|
|
23
|
+
size="xl"
|
|
24
|
+
:content="{
|
|
25
|
+
align: 'end',
|
|
26
|
+
side: 'bottom',
|
|
27
|
+
}"
|
|
28
|
+
:ui="{
|
|
29
|
+
content: 'w-48',
|
|
30
|
+
}"
|
|
31
|
+
>
|
|
32
|
+
<div class="relative flex cursor-pointer items-center gap-2">
|
|
33
|
+
<div class="hidden flex-col justify-end text-right lg:flex">
|
|
34
|
+
<p class="text-sm font-bold">
|
|
35
|
+
{{
|
|
36
|
+
auth.me.value?.display_name || auth.me.value?.full_name
|
|
37
|
+
}}
|
|
38
|
+
</p>
|
|
39
|
+
<p class="text-muted text-xs">
|
|
40
|
+
{{ auth.me.value?.email }}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
<Avatar
|
|
44
|
+
class="border-muted size-[36px] border text-2xl"
|
|
45
|
+
icon="ri:user-line"
|
|
46
|
+
:src="auth.me.value?.avatar_url"
|
|
47
|
+
/>
|
|
48
|
+
<Icon name="i-ph:caret-down-light" class="size-5" />
|
|
49
|
+
</div>
|
|
50
|
+
</DropdownMenu>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</nav>
|
|
54
|
+
<main class="mx-auto w-full max-w-7xl flex-1 px-4 pb-20 2xl:max-w-7/10">
|
|
55
|
+
<div
|
|
56
|
+
v-if="app.pageMeta.title"
|
|
57
|
+
class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-center"
|
|
58
|
+
>
|
|
59
|
+
<div class="flex flex-1 flex-col">
|
|
60
|
+
<h1
|
|
61
|
+
class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
|
|
62
|
+
:title="app.pageMeta.title"
|
|
63
|
+
>
|
|
64
|
+
{{ app.pageMeta.title }}
|
|
65
|
+
<span id="page-title-extra" />
|
|
66
|
+
</h1>
|
|
67
|
+
|
|
68
|
+
<div id="page-subtitle" />
|
|
69
|
+
</div>
|
|
70
|
+
<div id="page-header" />
|
|
71
|
+
</div>
|
|
72
|
+
<slot />
|
|
73
|
+
</main>
|
|
74
|
+
<div id="page-footer" />
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<script lang="ts" setup>
|
|
80
|
+
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
81
|
+
import Apps from '../Apps.vue'
|
|
82
|
+
|
|
83
|
+
const app = useApp()
|
|
84
|
+
const auth = useAuth()
|
|
85
|
+
const userMenuItems: DropdownMenuItem[] = [
|
|
86
|
+
{
|
|
87
|
+
label: 'View profile',
|
|
88
|
+
icon: 'i-lucide-user',
|
|
89
|
+
to: routes.account.profile.to,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
label: routes.logout.label,
|
|
94
|
+
icon: 'i-lucide-log-out',
|
|
95
|
+
to: routes.logout.to,
|
|
96
|
+
external: true,
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
</script>
|
package/package.json
CHANGED
|
File without changes
|