first commit

This commit is contained in:
2026-10-09 08:21:06 +02:00
parent db7456df87
commit d259c62304
3761 changed files with 896046 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
<script setup lang="ts">
import { Head, Link, usePage } from '@inertiajs/vue3';
import { ref, watch } from 'vue';
import TheNavbar from '@/components/TheNavbar.vue';
import TheSidebar from '@/components/TheSidebar.vue';
import ConfirmHost from '@/components/ui/ConfirmHost.vue';
import ToastHost from '@/components/ui/ToastHost.vue';
import { useToast } from '@/composables/useToast';
import { useTranslations } from '@/composables/useVanguard';
import type { SharedProps } from '@/types';
defineProps<{
title?: string;
heading?: string;
breadcrumbs?: Array<{ label: string; href?: string }>;
}>();
const page = usePage<SharedProps>();
const toast = useToast();
const { __ } = useTranslations();
const sidebarOpen = ref(false);
/**
* Flash messages arrive as a shared prop and are surfaced as toasts on every
* navigation.
*/
watch(
() => page.props.flash,
(flash) => {
for (const message of [flash?.success ?? []].flat().filter(Boolean)) {
toast.add({ severity: 'success', summary: String(message), life: 3000 });
}
for (const message of [flash?.error ?? []].flat().filter(Boolean)) {
toast.add({ severity: 'error', summary: String(message), life: 5000 });
}
},
{ immediate: true, deep: true },
);
</script>
<template>
<Head :title="title" />
<TheNavbar :heading="heading" @toggle-sidebar="sidebarOpen = !sidebarOpen">
<template #breadcrumbs>
<ol
v-if="breadcrumbs?.length"
:aria-label="__('Breadcrumb')"
class="hidden items-center gap-2 text-sm font-light text-slate-500 md:flex"
>
<li>
<Link :href="route('dashboard')" class="text-slate-400 hover:text-primary-600">
<i class="fa fa-home" />
</Link>
</li>
<li v-for="crumb in breadcrumbs" :key="crumb.label" class="flex items-center gap-2">
<span class="text-slate-300">/</span>
<Link v-if="crumb.href" :href="crumb.href" class="hover:text-primary-600">
{{ crumb.label }}
</Link>
<span v-else>{{ crumb.label }}</span>
</li>
</ol>
</template>
</TheNavbar>
<TheSidebar :open="sidebarOpen" />
<div
v-if="sidebarOpen"
class="fixed inset-0 z-[9] bg-black/40 md:hidden"
:aria-label="__('Close navigation')"
@click="sidebarOpen = false"
/>
<main class="mt-(--spacing-navbar) px-4 py-6 md:ml-(--spacing-sidebar)">
<slot />
</main>
<ToastHost />
<ConfirmHost />
</template>
+67
View File
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { Head, usePage } from "@inertiajs/vue3";
import { computed, watch } from "vue";
import AppLogo from "@/components/AppLogo.vue";
import LocaleDropdown from "@/components/LocaleDropdown.vue";
import Message from "@/components/ui/Message.vue";
import ToastHost from "@/components/ui/ToastHost.vue";
import { useToast } from "@/composables/useToast";
import type { SharedProps } from "@/types";
defineProps<{ title?: string }>();
const page = usePage<SharedProps>();
const toast = useToast();
/**
* The auth controllers report flow failures with withErrors(string), which
* Laravel files under the numeric key 0 rather than a field name. Those are
* not tied to an input, so they belong in a banner; field errors keep being
* rendered inline by the individual pages.
*/
const banner = computed<string | undefined>(() => {
const errors = page.props.errors as Record<string, string> | undefined;
return errors?.general ?? errors?.[0];
});
watch(
() => page.props.flash,
(flash) => {
for (const message of [flash?.success ?? []].flat().filter(Boolean)) {
toast.add({
severity: "success",
summary: String(message),
life: 5000,
});
}
for (const message of [flash?.error ?? []].flat().filter(Boolean)) {
toast.add({
severity: "error",
summary: String(message),
life: 8000,
});
}
},
{ immediate: true, deep: true },
);
</script>
<template>
<Head :title="title" />
<div class="bg-body min-h-screen">
<div class="flex justify-center items-center h-screen">
<div class="mx-auto my-auto w-full max-w-md px-4 pb-16">
<Message v-if="banner" severity="error" class="mb-4">
{{ banner }}
</Message>
<slot />
</div>
</div>
</div>
<ToastHost />
</template>