Migration to nuxt 4
Used codemods CLI and reworked most alias'd path that stopped working
This commit is contained in:
33
app/pages/about.vue
Normal file
33
app/pages/about.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
const { locale } = useI18n()
|
||||
|
||||
const { data: page, refresh } = await useAsyncData(`about-content-${locale}`, () => {
|
||||
return queryCollection("sections").path(`/sections/${locale.value}/legal`).first()
|
||||
})
|
||||
|
||||
watch(locale, () => refresh())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="py-8 px-5 md:px-8 overflow-y-auto">
|
||||
<Head>
|
||||
<Title>{{ $t("pages.about.title") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="container max-md:px-0">
|
||||
<Spacing size="lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="md:hidden">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("pages.about.title") }}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<ContentRenderer v-if="page" :value="page" class="content" />
|
||||
</Spacing>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
84
app/pages/calendars/[id].vue
Normal file
84
app/pages/calendars/[id].vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhArrowBendDoubleUpLeft, PhCalendarX, PhCircleNotch } from "@phosphor-icons/vue";
|
||||
import type { Calendar } from "@@/models/CalendarConfig";
|
||||
|
||||
const route = useRoute()
|
||||
const shortId = route.params.id
|
||||
|
||||
const user = useSupabaseUser()
|
||||
|
||||
const {
|
||||
data: calendar,
|
||||
status: calendarStatus,
|
||||
refresh: calRefresh
|
||||
} = await useLazyFetch<{ data: Calendar }>("/api/calendars/query",
|
||||
{
|
||||
key: "active-calendar",
|
||||
query: {
|
||||
shortId,
|
||||
full: true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isLoading = computed(() => calendarStatus.value === "pending")
|
||||
|
||||
watch(user, () => calRefresh())
|
||||
|
||||
const { setActiveCalendar } = useCalendar()
|
||||
watch(isLoading, (n) => {
|
||||
if (!n && calendar.value?.data) {
|
||||
setActiveCalendar(calendar.value?.data)
|
||||
}
|
||||
}, { immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isLoading" class="h-full w-full grid place-items-center">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.calendar.nameSingular") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="grid gap-2 justify-items-center opacity-50">
|
||||
<p>
|
||||
{{ $t('entity.calendar.isLoading') }}
|
||||
</p>
|
||||
<PhCircleNotch size="50" class="animate-spin"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="calendar?.data" class="h-full w-full pt-8">
|
||||
<Head>
|
||||
<Title>{{ calendar.data.name }}</Title>
|
||||
</Head>
|
||||
|
||||
<Calendar />
|
||||
</div>
|
||||
|
||||
<div v-else class="h-full w-full grid place-items-center">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.calendar.notFound") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="grid justify-items-center opacity-80">
|
||||
<PhCalendarX size="75" class="opacity-60" />
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("entity.calendar.notFound") }}
|
||||
</Heading>
|
||||
|
||||
<p>
|
||||
{{ $t('entity.calendar.notFoundDescription') }}
|
||||
</p>
|
||||
|
||||
<UiButton variant="default" class="mt-4 gap-2" as-child>
|
||||
<RouterLink to="/explore">
|
||||
<PhArrowBendDoubleUpLeft size="24" />
|
||||
|
||||
{{ $t('entity.calendar.backToList') }}
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
46
app/pages/explore.vue
Normal file
46
app/pages/explore.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Calendar } from "@@/models/CalendarConfig";
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["reset-menu"]
|
||||
})
|
||||
|
||||
const { data: availableCalendars, status: calendarStatus } = await useLazyFetch<{ data: Calendar[] }>("/api/calendars/query", { key: "explore-calendars", query: { full: true } })
|
||||
|
||||
const isLoading = computed(() => calendarStatus.value === "pending")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="py-8 px-5 md:px-8">
|
||||
<Head>
|
||||
<Title>{{ $t("pages.explore.title") }}</Title>
|
||||
</Head>
|
||||
|
||||
<Spacing size="lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="md:hidden">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("pages.explore.title") }}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<Spacing size="lg">
|
||||
<Heading level="h2">
|
||||
{{ $t("entity.calendar.namePublicPlural") }}
|
||||
</Heading>
|
||||
|
||||
<div v-if="isLoading" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
<LoadingCard />
|
||||
</div>
|
||||
<ul v-else-if="availableCalendars?.data" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
<li v-for="calendar in availableCalendars.data" :key="calendar.shortId">
|
||||
<CalendarPreviewCard :calendar="calendar" :gm-id="calendar.world?.gmId" />
|
||||
</li>
|
||||
</ul>
|
||||
</Spacing>
|
||||
</Spacing>
|
||||
</main>
|
||||
</template>
|
||||
22
app/pages/index.vue
Normal file
22
app/pages/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
titleTemplate: null
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["reset-menu"]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="py-8 px-5 md:px-8">
|
||||
<Head>
|
||||
<Title>
|
||||
TTTools — {{ $t('head.title') }}
|
||||
</Title>
|
||||
<Meta name="description" :content="$t('head.description')" />
|
||||
</Head>
|
||||
|
||||
<Heading level="h1">TTTools</Heading>
|
||||
</main>
|
||||
</template>
|
||||
102
app/pages/my/calendars/[id].vue
Normal file
102
app/pages/my/calendars/[id].vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhArrowBendDoubleUpLeft, PhCalendarX, PhCircleNotch } from "@phosphor-icons/vue";
|
||||
import type { Calendar } from "@@/models/CalendarConfig";
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["auth-guard"]
|
||||
})
|
||||
|
||||
const user = useSupabaseUser()
|
||||
|
||||
// Redirect user back home when they log out on the page
|
||||
watch(user, (n) => {
|
||||
if (!n) {
|
||||
navigateTo("/")
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const id = route.params.id
|
||||
|
||||
const {
|
||||
data: calendar,
|
||||
status: calendarStatus
|
||||
} = await useLazyFetch<{ data: Calendar }>("/api/calendars/query",
|
||||
{
|
||||
key: "active-calendar",
|
||||
query: {
|
||||
id,
|
||||
full: true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isLoading = computed(() => calendarStatus.value === "pending")
|
||||
|
||||
const { setActiveCalendar } = useCalendar()
|
||||
watch([calendar], () => {
|
||||
if (calendar.value?.data) {
|
||||
setActiveCalendar(calendar.value?.data)
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isLoading" class="h-full w-full grid place-items-center">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.calendar.nameSingular") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="grid gap-2 justify-items-center opacity-50">
|
||||
<p>
|
||||
{{ $t('entity.calendar.isLoading') }}
|
||||
</p>
|
||||
<PhCircleNotch size="50" class="animate-spin"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="calendar?.data" class="h-full w-full">
|
||||
<Head>
|
||||
<Title>{{ calendar.data.name }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="h-full grid grid-rows-[auto_1fr] pt-8 md:gap-y-2">
|
||||
<div class="px-5 md:px-8 max-md:mb-2">
|
||||
<Breadcrumb
|
||||
v-if="calendar.data.world"
|
||||
:items="[
|
||||
{ label: calendar.data.world.name, to: `/my/worlds/${calendar.data.world.id}` },
|
||||
{ label: calendar.data.name },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Calendar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="h-full w-full grid place-items-center">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.calendar.notFound") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="grid justify-items-center opacity-80">
|
||||
<PhCalendarX size="75" class="opacity-60" />
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("entity.calendar.notFound") }}
|
||||
</Heading>
|
||||
|
||||
<p>
|
||||
{{ $t('entity.calendar.notFoundDescription') }}
|
||||
</p>
|
||||
|
||||
<UiButton variant="default" class="mt-4 gap-2" as-child>
|
||||
<RouterLink to="/my">
|
||||
<PhArrowBendDoubleUpLeft size="24" />
|
||||
|
||||
{{ $t('entity.calendar.backToList') }}
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
</div>
|
||||
</div></template>
|
||||
163
app/pages/my/index.vue
Normal file
163
app/pages/my/index.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RealtimeChannel } from "@supabase/supabase-js"
|
||||
import type { World, WorldChannelPayload } from "@@/models/World";
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["auth-guard", "reset-menu"]
|
||||
})
|
||||
|
||||
const { data: worlds, status: worldStatus } = await useLazyFetch<{ data: World[] }>("/api/worlds/query", { query: { gmId: user?.value!.id }, key: "user-worlds", keepalive: true })
|
||||
const sortedWorlds = computed(() => worlds.value?.data ? [...worlds.value.data].sort((a, b) => (a.id ?? 0) - (b.id ?? 0)) : [])
|
||||
|
||||
const isLoading = computed(() => worldStatus.value === "idle")
|
||||
|
||||
// Redirect user back home when they log out on the page
|
||||
watch(user, (n) => {
|
||||
if (!n) {
|
||||
navigateTo("/")
|
||||
}
|
||||
})
|
||||
|
||||
const isCreateWorldModalOpen = ref<boolean>(false)
|
||||
|
||||
function hideCreateDialog() {
|
||||
isCreateWorldModalOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* === World subscriptions ===
|
||||
*/
|
||||
/** Active world channel */
|
||||
let worldChannel: RealtimeChannel
|
||||
|
||||
/** Handles world insertion realtime events */
|
||||
function handleInsertedWorld(newWorld: WorldChannelPayload) {
|
||||
if (!worlds.value?.data) return
|
||||
|
||||
newWorld.createdAt = newWorld.created_at;
|
||||
newWorld.gmId = newWorld.gm_id;
|
||||
|
||||
worlds.value?.data.push(newWorld)
|
||||
}
|
||||
|
||||
/** Handles world deletion realtime events */
|
||||
function handleDeletedWorld(id: number) {
|
||||
if (!worlds.value?.data) return
|
||||
|
||||
worlds.value.data.splice(worlds.value.data.findIndex(w => w.id === id), 1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
worldChannel = supabase.channel("realtime-world-channel")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{ event: "*", schema: "public", table: "worlds" },
|
||||
async (payload) => {
|
||||
switch (payload.eventType) {
|
||||
case "INSERT":
|
||||
handleInsertedWorld(payload.new as World)
|
||||
break
|
||||
|
||||
case "DELETE":
|
||||
handleDeletedWorld(payload.old.id)
|
||||
break
|
||||
|
||||
case "UPDATE":
|
||||
if (!worlds.value?.data) return
|
||||
|
||||
worlds.value.data = (await $fetch("/api/worlds/query", { query: { gmId: user?.value!.id } })).data as World[]
|
||||
break
|
||||
|
||||
default:
|
||||
console.log("Unknown event has been triggered. This should not happen unless Supabase added one somehow.")
|
||||
console.log(payload)
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// Unsubscribe from realtime
|
||||
supabase.removeChannel(worldChannel)
|
||||
})
|
||||
|
||||
const markedWorld = ref<World | null>(null)
|
||||
const isEditWorldModalOpen = ref<boolean>(false)
|
||||
const isDeleteWorldModalOpen = ref<boolean>(false)
|
||||
|
||||
function deployDeleteModal(world: World) {
|
||||
isDeleteWorldModalOpen.value = true
|
||||
markedWorld.value = world
|
||||
}
|
||||
|
||||
function hideDeleteModal() {
|
||||
isDeleteWorldModalOpen.value = false
|
||||
markedWorld.value = null
|
||||
}
|
||||
|
||||
function deployEditModal(world: World) {
|
||||
isEditWorldModalOpen.value = true
|
||||
markedWorld.value = world
|
||||
}
|
||||
|
||||
function hideEditModal() {
|
||||
isEditWorldModalOpen.value = false
|
||||
markedWorld.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="py-8 px-5 md:px-8">
|
||||
<Head>
|
||||
<Title>{{ $t("pages.profile.metaTitle") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="md:hidden">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("pages.profile.title", { user: user?.user_metadata.full_name }) }}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<section class="mt-4">
|
||||
<Spacing size="lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<Heading level="h2">
|
||||
{{ $t('entity.world.namePlural') }}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
<LoadingCard />
|
||||
</div>
|
||||
<ul v-else-if="worlds?.data" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
<li v-for="world in sortedWorlds" :key="world.id">
|
||||
<WorldPreviewCard :world="world" @on-edit="() => deployEditModal(world)" @on-delete="() => deployDeleteModal(world)" />
|
||||
</li>
|
||||
<li class="xl:w-fit">
|
||||
<AddCard @on-click="() => isCreateWorldModalOpen = true">
|
||||
<template v-if="worlds?.data?.length > 0">
|
||||
{{ $t('entity.world.addSingle') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('entity.world.addSingleFirst') }}
|
||||
</template>
|
||||
</AddCard>
|
||||
</li>
|
||||
</ul>
|
||||
</Spacing>
|
||||
</section>
|
||||
|
||||
<WorldDialogCreate :modal-state="isCreateWorldModalOpen" @on-close="hideCreateDialog" />
|
||||
<WorldDialogEdit :world="markedWorld" :modal-state="isEditWorldModalOpen" @on-close="hideEditModal" />
|
||||
<WorldDialogDelete :world="markedWorld" :modal-state="isDeleteWorldModalOpen" @on-close="hideDeleteModal" />
|
||||
</main>
|
||||
</template>
|
||||
281
app/pages/my/worlds/[id].vue
Normal file
281
app/pages/my/worlds/[id].vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RealtimeChannel } from "@supabase/supabase-js"
|
||||
import type { World } from "@@/models/World";
|
||||
import type { Calendar, CalendarChannelPayload } from "@@/models/CalendarConfig";
|
||||
import { PhArrowBendDoubleUpLeft, PhGlobeHemisphereWest, PhPencil } from "@phosphor-icons/vue";
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const id = route.params.id
|
||||
|
||||
const { data: world, status } = await useFetch<{ data: World }>("/api/worlds/query", { query: { id, full: true } })
|
||||
const sortedCalendars = computed(() => world.value?.data.calendars ? [...world.value.data.calendars].sort((a, b) => (a.id ?? 0) - (b.id ?? 0)) : [])
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["auth-guard"]
|
||||
})
|
||||
|
||||
const user = useSupabaseUser()
|
||||
|
||||
// Redirect user back home when they log out on the page
|
||||
watch(user, (n) => {
|
||||
if (!n) {
|
||||
navigateTo("/")
|
||||
}
|
||||
})
|
||||
|
||||
// Set custom menu
|
||||
// This should be reserved for actions, not for breadcrumbs
|
||||
//
|
||||
// const { setCurrentMenu } = useUiStore()
|
||||
|
||||
// setCurrentMenu([
|
||||
// {
|
||||
// tooltip: t("entity.world.backToMy"),
|
||||
// to: "/my",
|
||||
// phIcon: "universe",
|
||||
// highlight: true
|
||||
// },
|
||||
// ])
|
||||
|
||||
/**
|
||||
* === Subscriptions ===
|
||||
*/
|
||||
|
||||
/** Active calendar channel */
|
||||
let calendarChannel: RealtimeChannel
|
||||
/** Active world channel */
|
||||
let worldChannel: RealtimeChannel
|
||||
|
||||
/** Handles calendar insertion realtime events */
|
||||
function handleInsertedCalendar(newCalendar: CalendarChannelPayload) {
|
||||
if (!world.value) return
|
||||
|
||||
newCalendar.createdAt = newCalendar.created_at
|
||||
newCalendar.eventNb = [{ count: 0 }]
|
||||
|
||||
world.value.data.calendars?.push(newCalendar)
|
||||
}
|
||||
|
||||
/** Handles calendar deletion realtime events */
|
||||
function handleDeletedCalendar(id: number) {
|
||||
if (!world.value) return
|
||||
|
||||
world.value.data.calendars?.splice(world.value.data.calendars.findIndex(c => c.id === id), 1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
calendarChannel = supabase.channel("realtime-calendar-channel")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{ event: "*", schema: "public", table: "calendars" },
|
||||
async (payload) => {
|
||||
switch (payload.eventType) {
|
||||
case "INSERT":
|
||||
handleInsertedCalendar(payload.new as Calendar)
|
||||
break
|
||||
|
||||
case "DELETE":
|
||||
handleDeletedCalendar(payload.old.id)
|
||||
break
|
||||
|
||||
// Maybe this case could be handled better than doing a separate API call
|
||||
case "UPDATE":
|
||||
if (!world.value?.data) return
|
||||
|
||||
world.value.data = (await $fetch<{ data: World }>("/api/worlds/query", { query: { id, full: true } })).data
|
||||
break
|
||||
|
||||
default:
|
||||
console.log("Unknown event has been triggered. This should not happen unless Supabase added one somehow.")
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
// Unsubscribe from realtime
|
||||
supabase.removeChannel(calendarChannel)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
worldChannel = supabase.channel("realtime-world-channel")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{ event: "*", schema: "public", table: "worlds" },
|
||||
async (payload) => {
|
||||
switch (payload.eventType) {
|
||||
case "UPDATE":
|
||||
if (!world.value?.data) return
|
||||
|
||||
world.value.data = (await $fetch<{ data: World }>("/api/worlds/query", { query: { id, full: true } })).data
|
||||
break
|
||||
|
||||
default:
|
||||
console.log("Unknown event has been triggered. This should not happen unless Supabase added one somehow.")
|
||||
console.log(payload)
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
// Unsubscribe from realtime
|
||||
supabase.removeChannel(worldChannel)
|
||||
})
|
||||
|
||||
const markedCalendar = ref<Calendar | null>(null)
|
||||
const isEditWorldModalOpen = ref<boolean>(false)
|
||||
|
||||
const isCreateCalendarModalOpen = ref<boolean>(false)
|
||||
const isUpdateCalendarModalOpen = ref<boolean>(false)
|
||||
const isDeleteCalendarModalOpen = ref<boolean>(false)
|
||||
|
||||
function hideCreateDialog() {
|
||||
isCreateCalendarModalOpen.value = false
|
||||
}
|
||||
|
||||
function deployUpdateDialog(calendar: Calendar) {
|
||||
markedCalendar.value = calendar
|
||||
isUpdateCalendarModalOpen.value = true
|
||||
}
|
||||
function hideUpdateDialog() {
|
||||
isUpdateCalendarModalOpen.value = false
|
||||
}
|
||||
|
||||
function deployDeleteCalendarModal(calendar: Calendar) {
|
||||
isDeleteCalendarModalOpen.value = true
|
||||
markedCalendar.value = calendar
|
||||
}
|
||||
function hideDeleteCalendarModal() {
|
||||
isDeleteCalendarModalOpen.value = false
|
||||
markedCalendar.value = null
|
||||
}
|
||||
|
||||
function deployEditModal() {
|
||||
isEditWorldModalOpen.value = true
|
||||
}
|
||||
function hideEditModal() {
|
||||
isEditWorldModalOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="py-8 px-5 md:px-8">
|
||||
<template v-if="status === 'pending'">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.world.namePlural") }}</Title>
|
||||
</Head>
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t('entity.isLoading') }}
|
||||
</Heading>
|
||||
</template>
|
||||
<template v-else-if="world?.data">
|
||||
<Head>
|
||||
<Title>{{ world.data.name }}</Title>
|
||||
</Head>
|
||||
|
||||
<header class="mb-8">
|
||||
<Spacing size="lg">
|
||||
<Breadcrumb
|
||||
:items="[
|
||||
{ label: world.data.name }
|
||||
]"
|
||||
/>
|
||||
|
||||
<div class="lg:w-1/2">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<Heading level="h1">{{ world.data.name }}</Heading>
|
||||
</div>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton size="icon" class="rounded-full size-8" @click="deployEditModal">
|
||||
<PhPencil size="17" weight="fill" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side-offset="12" side="right">
|
||||
<p>
|
||||
{{ $t('entity.world.editSingle') }}
|
||||
</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</div>
|
||||
|
||||
<p>{{ world.data.description }}</p>
|
||||
</div>
|
||||
</Spacing>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<Spacing size="lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<Heading level="h2">
|
||||
{{ $t('entity.calendar.namePlural') }}
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<ul class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
<li v-for="calendar in sortedCalendars" :key="calendar.id">
|
||||
<CalendarPreviewCard
|
||||
:calendar="calendar"
|
||||
:gm-id="world.data.gmId"
|
||||
show-actions
|
||||
@on-edit="() => deployUpdateDialog(calendar)"
|
||||
@on-delete="() => deployDeleteCalendarModal(calendar)" />
|
||||
</li>
|
||||
|
||||
<li class="xl:w-fit">
|
||||
<AddCard @on-click="() => isCreateCalendarModalOpen = true">
|
||||
<template v-if="sortedCalendars.length > 0">
|
||||
{{ $t('entity.calendar.addSingle') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('entity.calendar.addSingleFirst') }}
|
||||
</template>
|
||||
</AddCard>
|
||||
</li>
|
||||
</ul>
|
||||
</Spacing>
|
||||
</section>
|
||||
|
||||
<WorldDialogEdit :world="world.data" :modal-state="isEditWorldModalOpen" @on-close="hideEditModal" />
|
||||
<CalendarDialogCreate :world="world.data" :modal-state="isCreateCalendarModalOpen" @on-close="hideCreateDialog" />
|
||||
<CalendarDialogUpdate :world="world.data" :calendar="markedCalendar" :modal-state="isUpdateCalendarModalOpen" @on-close="hideUpdateDialog" />
|
||||
<CalendarDialogDelete :calendar="markedCalendar" :modal-state="isDeleteCalendarModalOpen" @on-close="hideDeleteCalendarModal"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="h-full w-full grid place-items-center">
|
||||
<Head>
|
||||
<Title>{{ $t("entity.world.notFound") }}</Title>
|
||||
</Head>
|
||||
|
||||
<div class="grid justify-items-center opacity-80">
|
||||
<PhGlobeHemisphereWest size="75" class="opacity-60" weight="fill" />
|
||||
|
||||
<Heading level="h1">
|
||||
{{ $t("entity.world.notFound") }}
|
||||
</Heading>
|
||||
|
||||
<p>
|
||||
{{ $t('entity.world.notFoundDescription') }}
|
||||
</p>
|
||||
|
||||
<UiButton variant="default" class="mt-4 gap-2" as-child>
|
||||
<RouterLink to="/my">
|
||||
<PhArrowBendDoubleUpLeft size="24" />
|
||||
|
||||
{{ $t('entity.world.backToList') }}
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
Reference in New Issue
Block a user