Rebuild General Layout to Nuxt UI PRO Dashboard
This commit is contained in:
107
spaces/components/HelpSlideover.vue
Normal file
107
spaces/components/HelpSlideover.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
const shortcuts = ref(false)
|
||||
const query = ref('')
|
||||
|
||||
const links = [{
|
||||
label: 'Shortcuts',
|
||||
icon: 'i-heroicons-key',
|
||||
trailingIcon: 'i-heroicons-arrow-right-20-solid',
|
||||
color: 'gray',
|
||||
onClick: () => {
|
||||
shortcuts.value = true
|
||||
}
|
||||
}, {
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: 'https://ui.nuxt.com/pro/guide',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'GitHub repository',
|
||||
icon: 'i-simple-icons-github',
|
||||
to: 'https://github.com/nuxt/ui-pro',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Buy Nuxt UI Pro',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
to: 'https://ui.nuxt.com/pro/purchase',
|
||||
target: '_blank'
|
||||
}]
|
||||
|
||||
const categories = computed(() => [{
|
||||
title: 'General',
|
||||
items: [
|
||||
{ shortcuts: [metaSymbol.value, 'K'], name: 'Command menu' },
|
||||
{ shortcuts: ['N'], name: 'Notifications' },
|
||||
{ shortcuts: ['?'], name: 'Help & Support' },
|
||||
{ shortcuts: ['/'], name: 'Search' }
|
||||
]
|
||||
}, {
|
||||
title: 'Navigation',
|
||||
items: [
|
||||
{ shortcuts: ['G', 'H'], name: 'Go to Home' },
|
||||
{ shortcuts: ['G', 'I'], name: 'Go to Inbox' },
|
||||
{ shortcuts: ['G', 'U'], name: 'Go to Users' },
|
||||
{ shortcuts: ['G', 'S'], name: 'Go to Settings' }
|
||||
]
|
||||
}, {
|
||||
title: 'Inbox',
|
||||
items: [
|
||||
{ shortcuts: ['↑'], name: 'Prev notification' },
|
||||
{ shortcuts: ['↓'], name: 'Next notification' }
|
||||
]
|
||||
}])
|
||||
|
||||
const filteredCategories = computed(() => {
|
||||
return categories.value.map(category => ({
|
||||
title: category.title,
|
||||
items: category.items.filter(item => {
|
||||
return item.name.search(new RegExp(query.value, 'i')) !== -1
|
||||
})
|
||||
})).filter(category => !!category.items.length)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardSlideover v-model="isHelpSlideoverOpen">
|
||||
<template #title>
|
||||
<UButton
|
||||
v-if="shortcuts"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon="i-heroicons-arrow-left-20-solid"
|
||||
@click="shortcuts = false"
|
||||
/>
|
||||
|
||||
{{ shortcuts ? 'Shortcuts' : 'Hilfe & Support' }}
|
||||
</template>
|
||||
|
||||
<div v-if="shortcuts" class="space-y-6">
|
||||
<UInput v-model="query" icon="i-heroicons-magnifying-glass" placeholder="Search..." autofocus color="gray" />
|
||||
|
||||
<div v-for="(category, index) in filteredCategories" :key="index">
|
||||
<p class="mb-3 text-sm text-gray-900 dark:text-white font-semibold">
|
||||
{{ category.title }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, i) in category.items" :key="i" class="flex items-center justify-between">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">{{ item.name }}</span>
|
||||
|
||||
<div class="flex items-center justify-end flex-shrink-0 gap-0.5">
|
||||
<UKbd v-for="(shortcut, j) in item.shortcuts" :key="j">
|
||||
{{ shortcut }}
|
||||
</UKbd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-y-3">
|
||||
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
|
||||
</div>
|
||||
</UDashboardSlideover>
|
||||
</template>
|
||||
29
spaces/components/NotificationsSlideover.vue
Normal file
29
spaces/components/NotificationsSlideover.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { formatTimeAgo } from '@vueuse/core'
|
||||
import type { Notification } from '~/types'
|
||||
|
||||
const { isNotificationsSlideoverOpen } = useDashboard()
|
||||
|
||||
const { data: notifications } = await useFetch<Notification[]>('/api/notifications')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardSlideover v-model="isNotificationsSlideoverOpen" title="Notifications">
|
||||
<NuxtLink v-for="notification in notifications" :key="notification.id" :to="`/inbox?id=${notification.id}`" class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative">
|
||||
<UChip color="red" :show="!!notification.unread" inset>
|
||||
<UAvatar v-bind="notification.sender.avatar" :alt="notification.sender.name" size="md" />
|
||||
</UChip>
|
||||
|
||||
<div class="text-sm flex-1">
|
||||
<p class="flex items-center justify-between">
|
||||
<span class="text-gray-900 dark:text-white font-medium">{{ notification.sender.name }}</span>
|
||||
|
||||
<time :datetime="notification.date" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.date))" />
|
||||
</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
{{ notification.body }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</UDashboardSlideover>
|
||||
</template>
|
||||
46
spaces/components/TeamsDropdown.vue
Normal file
46
spaces/components/TeamsDropdown.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
const teams = [{
|
||||
label: 'Nuxt',
|
||||
/*avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/23360933?s=200&v=4'
|
||||
},*/
|
||||
click: () => {
|
||||
team.value = teams[0]
|
||||
}
|
||||
}, {
|
||||
label: 'NuxtLabs',
|
||||
/*avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/62017400?s=200&v=4'
|
||||
},*/
|
||||
click: () => {
|
||||
team.value = teams[1]
|
||||
}
|
||||
}]
|
||||
|
||||
const actions = [{
|
||||
label: 'Create team',
|
||||
icon: 'i-heroicons-plus-circle'
|
||||
}, {
|
||||
label: 'Manage teams',
|
||||
icon: 'i-heroicons-cog-8-tooth'
|
||||
}]
|
||||
|
||||
const team = ref(teams[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown
|
||||
v-slot="{ open }"
|
||||
mode="hover"
|
||||
:items="[teams]"
|
||||
class="w-full"
|
||||
:ui="{ width: 'w-full' }"
|
||||
:popper="{ strategy: 'absolute' }"
|
||||
>
|
||||
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
|
||||
<UAvatar v-if="team.avatar" :src="team.avatar.src" size="2xs" />
|
||||
|
||||
<span class="truncate text-gray-900 dark:text-white font-semibold">{{ team.label }}</span>
|
||||
</UButton>
|
||||
</UDropdown>
|
||||
</template>
|
||||
84
spaces/components/UserDropdown.vue
Normal file
84
spaces/components/UserDropdown.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const { isDashboardSearchModalOpen } = useUIState()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
const user = useSupabaseUser()
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
|
||||
const items = computed(() => [
|
||||
[{
|
||||
slot: 'account',
|
||||
label: '',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Settings',
|
||||
icon: 'i-heroicons-cog-8-tooth',
|
||||
to: '/settings'
|
||||
}, {
|
||||
label: 'Command menu',
|
||||
icon: 'i-heroicons-command-line',
|
||||
shortcuts: [metaSymbol.value, 'K'],
|
||||
click: () => {
|
||||
isDashboardSearchModalOpen.value = true
|
||||
}
|
||||
}, {
|
||||
label: 'Help & Support',
|
||||
icon: 'i-heroicons-question-mark-circle',
|
||||
shortcuts: ['?'],
|
||||
click: () => isHelpSlideoverOpen.value = true
|
||||
}], [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: 'https://ui.nuxt.com/pro/guide',
|
||||
target: '_blank'
|
||||
},/* {
|
||||
label: 'GitHub repository',
|
||||
icon: 'i-simple-icons-github',
|
||||
to: 'https://github.com/nuxt/ui-pro',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Buy Nuxt UI Pro',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
to: 'https://ui.nuxt.com/pro/purchase',
|
||||
target: '_blank'
|
||||
}*/], [{
|
||||
label: 'Abmelden',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||
click: async () => {
|
||||
await supabase.auth.signOut()
|
||||
await dataStore.clearStore()
|
||||
await router.push('/login')
|
||||
|
||||
}
|
||||
}]
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" class="w-full" :label="dataStore.getProfileById(user.id).fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<template #leading>
|
||||
<UAvatar :alt="dataStore.getProfileById(user.id) ? dataStore.getProfileById(user.id).fullName : ''" size="xs" />
|
||||
</template>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" />
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #account>
|
||||
<div class="text-left">
|
||||
<p>
|
||||
Angemeldet als
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{dataStore.getProfileById(user.id).email}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
||||
Reference in New Issue
Block a user