feat(app): auth, orgs/events UI, router guards, and dev tooling

- Add Sanctum auth flow (store, composables, login, axios interceptors)
- Add dashboard, organisation list/detail, events CRUD dialogs
- Wire router guards, navigation, organisation switcher in layout
- Replace Vuexy @db types in NavSearchBar; add @iconify/types; themeConfig title typing
- Vuetify settings.scss + resolve configFile via fileURLToPath; drop dead path aliases
- Root index redirects to dashboard; fix events table route name
- API: DevSeeder + DatabaseSeeder updates; docs TEST_SCENARIO; corporate identity assets

Made-with: Cursor
This commit is contained in:
2026-04-07 21:51:10 +02:00
parent 0d24506c89
commit c417a6647a
45 changed files with 11554 additions and 832 deletions

View File

@@ -6,6 +6,7 @@ import { themeConfig } from '@themeConfig'
import Footer from '@/layouts/components/Footer.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import OrganisationSwitcher from '@/components/layout/OrganisationSwitcher.vue'
import NavBarI18n from '@core/components/I18n.vue'
// @layouts plugin
@@ -14,6 +15,12 @@ import { VerticalNavLayout } from '@layouts'
<template>
<VerticalNavLayout :nav-items="navItems">
<!-- 👉 Organisation switcher -->
<template #before-vertical-nav-items>
<OrganisationSwitcher />
<div class="vertical-nav-items-shadow" />
</template>
<!-- 👉 navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center">

View File

@@ -1,41 +1,7 @@
<template>
<div class="h-100 d-flex align-center justify-md-space-between justify-center">
<!-- 👉 Footer: left content -->
<span class="d-flex align-center text-medium-emphasis">
&copy;
{{ new Date().getFullYear() }}
Made With
<VIcon
icon="tabler-heart-filled"
color="error"
size="1.25rem"
class="mx-1"
/>
By <a
href="https://pixinvent.com"
target="_blank"
rel="noopener noreferrer"
class="text-primary ms-1"
>Pixinvent</a>
</span>
<!-- 👉 Footer: right content -->
<span class="d-md-flex gap-x-4 text-primary d-none">
<a
href="https://themeforest.net/licenses/standard"
target="noopener noreferrer"
>License</a>
<a
href="https://1.envato.market/pixinvent_portfolio"
target="noopener noreferrer"
>More Themes</a>
<a
href="https://demos.pixinvent.com/vuexy-vuejs-admin-template/documentation/"
target="noopener noreferrer"
>Documentation</a>
<a
href="https://pixinvent.ticksy.com/"
target="noopener noreferrer"
>Support</a>
<div class="h-100 d-flex align-center justify-center">
<span class="text-medium-emphasis">
&copy; {{ new Date().getFullYear() }} Crewli
</span>
</div>
</template>

View File

@@ -2,9 +2,21 @@
import Shepherd from 'shepherd.js'
import { withQuery } from 'ufo'
import type { RouteLocationRaw } from 'vue-router'
import type { SearchResults } from '@db/app-bar-search/types'
import AppBarSearch from '@core/components/AppBarSearch.vue'
import { useConfigStore } from '@core/stores/config'
/** App bar API search group (replaces Vuexy @db mock types). */
interface AppBarSearchResultChild {
title: string
icon: string
url: RouteLocationRaw
}
interface SearchResults {
title: string
children: AppBarSearchResultChild[]
}
interface Suggestion {
icon: string
title: string
@@ -117,7 +129,6 @@ const redirectToSuggestedPage = (selected: Suggestion) => {
closeSearchBar()
}
const LazyAppBarSearch = defineAsyncComponent(() => import('@core/components/AppBarSearch.vue'))
</script>
<template>
@@ -144,7 +155,7 @@ const LazyAppBarSearch = defineAsyncComponent(() => import('@core/components/App
</div>
<!-- 👉 App Bar Search -->
<LazyAppBarSearch
<AppBarSearch
v-model:is-dialog-visible="isAppSearchBarVisible"
:search-results="searchResult"
:is-loading="isLoading"
@@ -238,7 +249,7 @@ const LazyAppBarSearch = defineAsyncComponent(() => import('@core/components/App
</VListItemTitle>
</VListItem>
</template>
</LazyAppBarSearch>
</AppBarSearch>
</template>
<style lang="scss">

View File

@@ -1,5 +1,19 @@
<script setup lang="ts">
import avatar1 from '@images/avatars/avatar-1.png'
import { useLogout } from '@/composables/api/useAuth'
import { useAuthStore } from '@/stores/useAuthStore'
const router = useRouter()
const authStore = useAuthStore()
const { mutate: logout, isPending: isLoggingOut } = useLogout()
function handleLogout() {
logout(undefined, {
onSettled: () => {
router.replace('/login')
},
})
}
</script>
<template>
@@ -48,9 +62,9 @@ import avatar1 from '@images/avatars/avatar-1.png'
</template>
<VListItemTitle class="font-weight-semibold">
John Doe
{{ authStore.user?.name ?? 'User' }}
</VListItemTitle>
<VListItemSubtitle>Admin</VListItemSubtitle>
<VListItemSubtitle>{{ authStore.currentOrganisation?.role ?? '' }}</VListItemSubtitle>
</VListItem>
<VDivider class="my-2" />
@@ -81,37 +95,14 @@ import avatar1 from '@images/avatars/avatar-1.png'
<VListItemTitle>Settings</VListItemTitle>
</VListItem>
<!-- 👉 Pricing -->
<VListItem link>
<template #prepend>
<VIcon
class="me-2"
icon="tabler-currency-dollar"
size="22"
/>
</template>
<VListItemTitle>Pricing</VListItemTitle>
</VListItem>
<!-- 👉 FAQ -->
<VListItem link>
<template #prepend>
<VIcon
class="me-2"
icon="tabler-help"
size="22"
/>
</template>
<VListItemTitle>FAQ</VListItemTitle>
</VListItem>
<!-- Divider -->
<VDivider class="my-2" />
<!-- 👉 Logout -->
<VListItem to="/login">
<VListItem
:disabled="isLoggingOut"
@click="handleLogout"
>
<template #prepend>
<VIcon
class="me-2"