20 Commits

Author SHA1 Message Date
Alexis
25a9099a18 Added rich text to event description 2025-08-08 10:48:29 +02:00
Alexis
c208393f9f Bigger richtext window 2025-08-07 23:01:34 +02:00
Alexis
d75f3b5423 Added control options 2025-08-07 22:55:53 +02:00
Alexis
97ce5b3553 Added simple tiptap component 2025-08-07 22:37:49 +02:00
Alexis
0a35f441e0 Merge branch 'code/cleanup' into dev 2025-08-04 22:43:49 +02:00
Alexis
c8a345406c Updated packages and some other colours 2025-08-04 22:43:38 +02:00
Alexis
119968e7fc Fixed a bunch of color transition issues on theme switch 2025-08-04 21:48:57 +02:00
Alexis
9b8f48ad8e Fixed tooltip position on client side component 2025-08-04 16:45:46 +02:00
Alexis
2199359b96 Fixed badge color 2025-08-04 13:41:43 +02:00
Alexis
422673cc4c Updated color scheme 2025-08-04 12:31:28 +02:00
Alexis
6aad01c96f Updated color scheme and identity 2025-08-03 19:42:43 +02:00
Alexis
0c96466263 Updated data loading strategy on world page 2025-08-02 22:42:23 +02:00
Alexis
7697e818bf Fixed supabase channel conflicts 2025-08-01 17:18:06 +02:00
Alexis
fd766b9f42 Updated packages 2025-07-29 22:45:22 +02:00
Alexis
0e7b60235a Fixed log in animation 2025-07-29 22:30:14 +02:00
Alexis
300507bbed Made sidebar image smaller 2025-07-29 21:59:36 +02:00
Alexis
693f506610 Reworked data fetching for explore page 2025-07-27 18:57:31 +02:00
Alexis
f2076e413c Made about page more reactive 2025-07-27 18:17:13 +02:00
Alexis
6b02629b01 Fixed nuxt database warning 2025-07-27 17:31:21 +02:00
Alexis
1d8b56c58e Merge branch 'upgrade/nuxt-4' into dev 2025-07-27 15:14:33 +02:00
65 changed files with 2596 additions and 1520 deletions

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import { ConfigProvider } from "radix-vue"
import { cn } from "./lib/utils";
useHead({
titleTemplate: (titleChunk) => {
@@ -27,33 +26,17 @@ useHead({
})
const useIdFunction = () => useId()
const { isSidebarOpened } = storeToRefs(useUiStore())
</script>
<template>
<div class="h-screen">
<NuxtLoadingIndicator />
<NuxtLayout>
<ConfigProvider :use-id="useIdFunction">
<div
class="h-full grid md:grid-cols-[auto_1fr] dark:bg-black shadow-body-light dark:shadow-body-dark transition-colors after:absolute after:transition-colors"
:class="cn({
'max-md:after:bg-transparent': isSidebarOpened,
'has-sidebar max-md:after:bg-black/20 md:after:opacity-0 md:after:pointer-events-none': isSidebarOpened
})"
>
<Sidebar />
<div
class="wrapper max-h-screen transition-all overflow-y-auto"
>
<NuxtPage />
</div>
</div>
</ConfigProvider>
</NuxtLayout>
<ConfigProvider :use-id="useIdFunction">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</ConfigProvider>
<ClientOnly>
<UiToaster />

View File

@@ -116,6 +116,11 @@
--color-event-white-background: var(--event-white-background);
}
::selection {
color: var(--color-background);
background: color-mix(in srgb, var(--color-primary), var(--color-foreground) 20%)
}
.event-button,
.event-popover,
.event-callout,

View File

@@ -2,3 +2,9 @@
--text-2xs: 0.7rem;
--text-2xs--line-height: 0.75rem;
}
@layer utilities {
.flow > * + * {
margin-top: 1em;
}
}

View File

@@ -4,6 +4,7 @@
@import "./theme.css" layer(theme);
@import "./_typography.css" layer(theme);
@import "./_typography.css" layer(utilities);
@import "./_colors.css" layer(theme);
@import "./_colors.css" layer(utilities);
@@ -33,18 +34,30 @@ body {
}
.fade-enter-active,
.fade-leave-active {
.fade-leave-active,
.fade-group-enter-active,
.fade-group-leave-active {
transition: all .5s ease;
}
.fade-delay-enter-active,
.fade-delay-leave-active {
.fade-delay-leave-active,
.fade-group-delay-enter-active,
.fade-group-delay-leave-active {
transition: all .5s ease 1s;
}
.fade-enter-from,
.fade-leave-to,
.fade-delay-enter-from,
.fade-delay-leave-to {
.fade-delay-leave-to,
.fade-group-enter-from,
.fade-group-leave-to,
.fade-group-delay-enter-from,
.fade-group-delay-leave-to {
opacity: 0;
visibility: hidden;
}
.fade-group-leave-active {
position: absolute;
}

View File

@@ -7,6 +7,7 @@
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(222.2 84% 4.9%);
--popover-border: rgb(175, 224, 200);
--card: hsl(0 0% 100%);
--card-foreground: hsl(222.2 84% 4.9%);
@@ -14,16 +15,16 @@
--border: hsl(214.3 31.8% 91.4%);
--input: hsl(214.3 31.8% 91.4%);
--primary: hsl(245 58% 51%);
--primary-foreground: hsl(0 0% 100%);
--primary: rgb(101, 230, 166);
--primary-foreground: hsl(222.2 84% 4.9%);
--secondary: hsl(210 50% 95%);
--secondary-foreground: hsl(222.2 47.4% 11.2%);
--secondary-foreground: hsl(222.2 84% 4.9%);
--accent: hsl(210 40% 96.1%);
--accent-foreground: hsl(222.2 47.4% 11.2%);
--accent: hsl(11, 100%, 86%);
--accent-foreground: hsl(222.2 84% 4.9%);
--destructive: hsl(0 84.2% 60.2%);
--destructive: hsl(0, 100%, 63%);
--destructive-foreground: hsl(210 40% 98%);
--success: hsl(156 72% 67%);
@@ -42,25 +43,26 @@
--muted: hsl(0, 0%, 12%);
--muted-foreground: hsl(215 20.2% 65.1%);
--popover: hsl(180 0 4.9%);
--popover: hsl(165, 47%, 5%);
--popover-foreground: hsl(210 40% 98%);
--popover-border: hsl(165, 47%, 20%);
--card: hsl(180 0 4.9%);
--card-foreground: hsl(210 40% 98%);
--border: hsl(0, 0%, 16%);
--input: hsl(244 47% 20%);
--border: hsl(0, 0%, 22.5%);
--input: hsl(165, 47%, 20%);
--primary: hsl(245 58% 51%);
--primary-foreground: hsl(0 0% 100%);
--primary: rgb(101, 230, 166);
--primary-foreground: hsl(180 0 4.9%);
--secondary: hsl(244 47% 20%);
--secondary: hsl(222.2 47.4% 11.2%);
--secondary-foreground: hsl(210 40% 98%);
--accent: hsl(244 47% 20%);
--accent-foreground: hsl(210 40% 98%);
--accent: hsl(11, 87%, 69%);
--accent-foreground: hsl(222.2 84% 4.9%);
--destructive: hsl(0 62.8% 30.6%);
--destructive: hsl(0, 62%, 49%);
--destructive-foreground: hsl(210 40% 98%);
--success: hsl(142 72% 29%);
@@ -81,6 +83,7 @@
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-popover-border: var(--popover-border);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);

View File

@@ -6,20 +6,20 @@ const { isReadOnly } = storeToRefs(useCalendar())
</script>
<template>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<ClientOnly>
<ClientOnly>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton v-if="!isReadOnly" variant="secondary" size="icon" @click="toggleCategoriesModal(true)">
<PhTag size="20" weight="light" />
</UiButton>
</ClientOnly>
</UiTooltipTrigger>
<UiTooltipContent>
<p>
{{ $t('entity.calendar.seeCategories') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</UiTooltipTrigger>
<UiTooltipContent>
<p>
{{ $t('entity.calendar.seeCategories') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</ClientOnly>
</template>

View File

@@ -51,7 +51,7 @@ function closeDeleteDialog() {
<UiDialogContent
:disable-outside-pointer-events="true"
:trap-focus="true"
class="min-w-96 border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
class="min-w-96"
@escape-key-down="closeDeleteDialog"
@focus-outside="closeDeleteDialog"
@interact-outside="closeDeleteDialog"

View File

@@ -86,7 +86,7 @@ async function submitNew() {
<div class="border-y-[1px] border-b-foreground/10 mr-4">
<form
ref="rowRef"
class="grid grid-cols-12 items-center gap-4 p-1 bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900"
class="grid grid-cols-12 items-center gap-4 p-1 bg-primary/0 hover:bg-primary/15"
@submit.prevent="submitNew"
>
<div v-if="currentMode === 'add'" class="col-span-1" />

View File

@@ -103,8 +103,8 @@ function handleQueryDelete() {
<form
class="grid grid-cols-12 items-center gap-4 p-1 border-b-[1px] border-b-foreground/10"
:class="cn(
{ 'bg-slate-100 hover:bg-slate-200 dark:bg-slate-900 dark:hover:bg-slate-800': currentMode === 'edit' },
{ 'bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900': currentMode !== 'edit' }
{ 'bg-primary/10 hover:bg-primary/20': currentMode === 'edit' },
{ 'bg-primary/0 hover:bg-primary/15': currentMode !== 'edit' }
)"
@submit.prevent="submitUpdate"
>
@@ -162,9 +162,9 @@ function handleQueryDelete() {
v-if="currentMode === 'edit' && categorySkeleton"
>
<UiButton
variant="secondary"
variant="default"
size="icon"
class="w-6 h-6 rounded-full bg-emerald-500 hover:bg-emerald-600 text-white"
class="w-6 h-6 rounded-full"
:title="$t('ui.actions.edit')"
@click="submitUpdate"
>
@@ -173,9 +173,9 @@ function handleQueryDelete() {
</li>
<li v-else-if="rowHovered">
<UiButton
variant="secondary"
variant="destructive"
size="icon"
class="w-6 h-6 rounded-full hover:bg-red-600 hover:text-white"
class="w-6 h-6 rounded-full"
:title="$t('ui.actions.delete')"
@click="handleQueryDelete"
>

View File

@@ -59,7 +59,7 @@ watch(currentRPGDate, () => {
:collision-padding="60"
:disable-outside-pointer-events="true"
:trap-focus="true"
class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
class="pl-3 w-[30rem] max-w-full"
@escape-key-down.prevent="handleClosing"
@pointer-down-outside.prevent="handleClosing"
>

View File

@@ -27,7 +27,7 @@ function handleClosing(e: Event): void {
<UiAlertDialogContent
:disable-outside-pointer-events="true"
:trap-focus="true"
class="min-w-96 border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
class="min-w-96"
@escape-key-down="handleClosing"
@focus-outside="handleClosing"
@interact-outside="handleClosing"

View File

@@ -160,8 +160,8 @@ function deployDeleteModal() {
<template v-if="event.description">
<hr class="border-border mt-2" >
<div class="max-h-48 overflow-y-auto mt-2 text-sm text-slate-600 dark:text-slate-300">
{{ event.description }}
<div class="max-h-48 overflow-y-auto mt-2 text-sm opacity-80">
<div class="content-editor" v-html="event.description" />
</div>
</template>
</div>
@@ -187,19 +187,19 @@ function deployDeleteModal() {
</menu>
<nav v-if="event.startDate && event.endDate" class="mt-2 flex gap-2">
<UiBadge class="hover:bg-indigo-400 dark:hover:bg-slate-300" as-child>
<UiBadge as-child>
<button
class="flex gap-1"
title="Naviguer au début"
class="flex gap-1 cursor-pointer"
:title="$t('entity.calendar.event.gotoStart')"
@click="handleJumpToDate(event.startDate!)"
>
<PhHourglassHigh size="16" weight="fill" /> {{ $t('entity.calendar.event.isStart') }}
</button>
</UiBadge>
<UiBadge class="hover:bg-indigo-400 dark:hover:bg-slate-300" as-child title="Naviguer à la fin">
<UiBadge as-child>
<button
class="flex gap-1"
title="Naviguer à la fin"
class="flex gap-1 cursor-pointer"
:title="$t('entity.calendar.event.gotoEnd')"
@click="handleJumpToDate(event.endDate!)"
>
<PhHourglassLow size="16" weight="fill" /> {{ $t('entity.calendar.event.isEnd') }}

View File

@@ -109,7 +109,7 @@ function handleFormCancel() {
name="new-calendar-name"
required
:placeholder="$t('common.title')"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
@input="handleNameChange"
>

View File

@@ -66,7 +66,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.title')"
:maxlength="120"
pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,120}"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
>
<div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.title') }}
@@ -74,14 +74,11 @@ function handleCancel() {
</div>
<div class="col-span-2 my-2 pl-8">
<textarea
id="new-event-description"
<InputEditor
disable-blocks
v-model="eventSkeleton.description"
name="new-event-description"
:placeholder="$t('entity.addDescription')"
:maxlength="1200"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
/>
<div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.description') }}
</div>
@@ -132,7 +129,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.addLocation')"
:maxlength="160"
pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,160}"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
>
<div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.location') }}

View File

@@ -91,7 +91,7 @@ function handleFormCancel() {
name="new-calendar-name"
required
:placeholder="$t('common.title')"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
@input="handleNameChange"
>

View File

@@ -82,7 +82,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.title')"
:minlength="3"
:maxlength="120"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
>
<div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.title') }}
@@ -92,14 +92,11 @@ function handleCancel() {
</div>
<div class="col-span-2 my-2 ml-8">
<textarea
id="new-event-description"
<InputEditor
disable-blocks
v-model="eventSkeleton.description"
name="new-event-description"
:placeholder="$t('entity.addDescription')"
:maxlength="1200"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
/>
<div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.description') }}
</div>
@@ -152,7 +149,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.addLocation')"
:minlength="3"
:maxlength="160"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
>
<div class="mt-2 mb-1 text-xs opacity-50">

View File

@@ -37,7 +37,7 @@ const filteredCategories = computed(() =>
role="combobox"
:aria-expanded="isPopoverOpen"
aria-controls="event-categories"
class="relative w-full max-w-full h-fit justify-between"
class="relative w-full max-w-full h-fit justify-between hover:bg-secondary hover:text-primary-foreground"
>
<template v-if="!model.length">
{{ props.placeholder }}
@@ -57,7 +57,7 @@ const filteredCategories = computed(() =>
</UiPopoverTrigger>
<UiPopoverContent
id="event-categories"
align="start"
:align="'start'"
side="bottom"
:collision-padding="50"
class="w-fit h-[33vh] p-0"

View File

@@ -36,7 +36,7 @@ const filteredCategories = computed(() =>
role="combobox"
:aria-expanded="isPopoverOpen"
aria-controls="event-category"
class="w-full max-w-full justify-between"
class="w-full max-w-full justify-between hover:bg-secondary hover:text-primary-foreground"
>
<template v-if="!model">
{{ props.placeholder }}

View File

@@ -4,6 +4,7 @@ import type { RPGDate } from "@@/models/Date";
import {
PhXCircle
} from "@phosphor-icons/vue"
import { cn } from "~/lib/utils";
const model = defineModel<RPGDate | null>()
@@ -117,7 +118,6 @@ function handleValueReset() {
v-if="model"
align="start"
side="bottom"
class="border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
>
<div class="grid grid-cols-2 items-center gap-x-2 gap-y-3">
<UiSelect @update:model-value="handleMonthChange">
@@ -135,7 +135,7 @@ function handleValueReset() {
<UiInput v-model="model.year" type="number" />
<hr class="col-span-2" >
<hr class="col-span-2" />
<div class="col-span-2">
<div v-if="monthData" class="grid grid-cols-7 gap-1">
@@ -143,10 +143,10 @@ function handleValueReset() {
v-for="day in monthData.days"
:key="`popover-day-grid-${id}-${day}`"
class="aspect-square rounded-full text-[.8em] transition-colors"
:class="{
'hover:bg-indigo-200 dark:hover:bg-indigo-700': day !== model.day,
'bg-indigo-500 hover:bg-indigo-700 text-white': day === model.day
}"
:class="cn({
'hover:bg-accent hover:text-accent-foreground': day !== model.day,
'bg-primary text-primary-foreground hover:bg-accent hover:text-accent-foreground': day === model.day
})"
@click="setModelDay(day)"
>
{{ day }}

View File

@@ -139,7 +139,7 @@ function toFutureFar(): void {
<template>
<div class="flex gap-2">
<div class="grid items-center w-40 px-3 md:px-4 py-2 bg-white dark:bg-black border-border border-x-[1px] border-t-[1px] rounded-t-sm text-sm max-md:text-xs transition-colors">
<div class="grid items-center w-40 px-3 md:px-4 py-2 bg-background border-border border-x-[1px] border-t-[1px] rounded-t-sm text-sm max-md:text-xs transition-colors">
<ClientOnly>
<span>{{ currentDate.currentDateTitle }}</span>

View File

@@ -16,10 +16,10 @@ const breakpoints = useBreakpoints(
</script>
<template>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<ClientOnly>
<ClientOnly>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton
:size="breakpoints.md.value ? 'default' : 'icon'"
variant="secondary"
@@ -31,11 +31,11 @@ const breakpoints = useBreakpoints(
{{ $t('entity.calendar.date.today') }}
</span>
</UiButton>
</ClientOnly>
</UiTooltipTrigger>
<UiTooltipContent>
<p>{{ defaultDateFormatted }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</UiTooltipTrigger>
<UiTooltipContent>
<p>{{ defaultDateFormatted }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</ClientOnly>
</template>

View File

@@ -113,9 +113,7 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
<div v-if="event.description" class="text-xs md:text-sm">
<hr class="my-3 md:my-2 border-white opacity-50" >
<span class="opacity-75">
{{ event.description }}
</span>
<div class="content-editor opacity-75" v-html="event.description" />
</div>
</button>
</template>

View File

@@ -81,7 +81,7 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
<template>
<div
ref="calendarTile"
class="tile relative p-1 md:p-2 transition-colors"
class="tile relative p-1 md:p-2 transition-all"
:class="{
'text-slate-300 dark:text-slate-500': props.faded,
'text-slate-500 dark:text-slate-300': !props.faded
@@ -93,10 +93,10 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
>
<ClientOnly>
<span
class="inline-flex size-7 md:size-8 aspect-square items-center justify-center rounded-full border-1 md:border-2 border-transparent font-bold transition-colors group-hover:border-indigo-300 dark:group-hover:border-indigo-700"
class="inline-flex size-7 md:size-8 aspect-square items-center justify-center rounded-full border-1 md:border-2 border-transparent font-bold transition-colors group-hover:border-primary dark:group-hover:border-primary"
:class="{
'text-white bg-slate-600 dark:bg-slate-800': isDefaultDate && !isSelectedDate,
'text-white bg-indigo-500': isSelectedDate
'bg-secondary dark:bg-slate-700': isDefaultDate && !isSelectedDate,
'bg-primary text-primary-foreground': isSelectedDate
}"
>
{{ date.day }}

View File

@@ -2,6 +2,7 @@
import type { RPGDate } from "@@/models/Date"
import { storeToRefs } from "pinia"
import { computed, type ComputedRef } from "vue"
import { cn } from "~/lib/utils"
const { currentDate, defaultDate, selectDate, areDatesIdentical } = useCalendar()
const { selectedDate, currentEvents } = storeToRefs(useCalendar())
@@ -38,14 +39,14 @@ const hasAtLeastOneEvent = computed<boolean>(() => {
<template>
<button
class="relative grid place-items-center aspect-square rounded-full border-2 border-transparent transition-colors after:content-[''] after:absolute after:top-1 after:right-1 after:w-[.3rem] after:h-[.3rem] after:rounded-full after:transition-colors"
:class="{
'text-slate-500 hover:border-indigo-300 hover:text-slate-900': !isDefaultDate && !isSelectedDate,
'font-semibold text-white bg-slate-600 dark:bg-slate-800 hover:border-indigo-400': isDefaultDate && !isSelectedDate,
'font-semibold text-white bg-indigo-500 hover:bg-indigo-600 hover:border-indigo-500': isSelectedDate,
'after:bg-green-600': hasAtLeastOneEvent,
'after:bg-slate-950': hasAtLeastOneEvent && isSelectedDate
}"
class="relative grid place-items-center aspect-square rounded-full border-2 border-transparent transition-colors after:content-[''] after:absolute after:top-1 after:right-1 after:w-[.3rem] after:h-[.3rem] after:rounded-full after:transition-colors cursor-pointer"
:class="cn({
'text-foreground/75 hover:border-primary hover:text-foreground': !isDefaultDate && !isSelectedDate,
'font-semibold text-foreground bg-secondary hover:border-primary/70': isDefaultDate && !isSelectedDate,
'font-semibold text-primary-foreground bg-primary hover:border-primary/70': isSelectedDate,
'after:bg-primary': hasAtLeastOneEvent,
'after:bg-primary-foreground': hasAtLeastOneEvent && isSelectedDate
})"
@click="selectDate(tileDate)"
>
<span ref="tileRef" class="text-[.85em]">{{ dayNumber }}</span>

View File

@@ -11,7 +11,7 @@ defineProps<{
<div class="font-medium">
{{ month.name }}
</div>
<div class="grid grid-cols-7 gap-1">
<div class="grid grid-cols-7">
<CalendarStateYearlyDayTile
v-for="day in month.days"
:key="day"

View File

@@ -0,0 +1,22 @@
<script lang="ts" setup>
import { PhCloudX } from '@phosphor-icons/vue';
</script>
<template>
<UiCard class="w-full h-full flex flex-col border-rose-200 bg-rose-50 dark:border-rose-900 dark:bg-rose-950">
<template v-if="$slots.title">
<UiCardHeader class="flex-row items-center gap-2">
<PhCloudX :size="32"/>
<UiCardTitle>
<slot name="title" />
</UiCardTitle>
</UiCardHeader>
</template>
<template v-if="$slots.content">
<UiCardContent class="grow grid gap-2">
<slot name="content" />
</UiCardContent>
</template>
</UiCard>
</template>

View File

@@ -13,7 +13,15 @@ const model = defineModel<RPGColor>({ default: "white" });
<template>
<UiSelect v-model="model">
<UiSelectTrigger :id :class="cn({ 'h-9': theme === 'subtle' })">
<UiSelectTrigger
:id
:class="cn(
['hover:bg-secondary hover:text-primary-foreground'],
{
'h-9': theme === 'subtle'
}
)"
>
<UiSelectValue
:placeholder="$t('ui.colors.selectOne')"
class="bgc"

View File

@@ -0,0 +1,244 @@
<script lang="ts" setup>
import { useEditor, EditorContent, type Content } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { PhArrowUDownRight, PhArrowUUpLeft, PhListBullets, PhListNumbers, PhQuotes, PhTextB, PhTextItalic, PhTextStrikethrough, PhTextTSlash } from '@phosphor-icons/vue'
const props = defineProps<{
disableInlines?: boolean
disableBlocks?: boolean
}>()
const model = defineModel<any>()
const editor = useEditor({
content: model.value as Content,
extensions: [
StarterKit
],
onUpdate: ({ editor }) => {
model.value = editor.getHTML()
}
})
function clearFormatting() {
editor.value?.chain().focus().unsetAllMarks().run()
editor.value?.chain().focus().clearNodes().run()
}
</script>
<template>
<div v-if="editor">
<!-- Toolbar -->
<div class="flex gap-1.5">
<!-- Inline styles -->
<div class="flex" v-if="!props.disableInlines">
<UiButton
@click.prevent="editor.chain().focus().toggleBold().run()"
:disabled="!editor.can().chain().focus().toggleBold().run()"
:variant="editor.isActive('bold') ? 'default' : 'secondary'"
class="size-7 rounded-e-none"
size="icon"
>
<PhTextB weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().toggleItalic().run()"
:disabled="!editor.can().chain().focus().toggleItalic().run()"
:variant="editor.isActive('italic') ? 'default' : 'secondary'"
class="size-7 rounded-none"
size="icon"
>
<PhTextItalic weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().toggleStrike().run()"
:disabled="!editor.can().chain().focus().toggleStrike().run()"
:variant="editor.isActive('strike') ? 'default' : 'secondary'"
class="size-7 rounded-s-none"
size="icon"
>
<PhTextStrikethrough weight="bold" />
</UiButton>
</div>
<!-- Block styles -->
<div class="flex" v-if="!props.disableBlocks">
<UiButton
@click.prevent="editor.chain().focus().toggleBulletList().run()"
:variant="editor.isActive('bulletList') ? 'default' : 'secondary'"
class="size-7 rounded-e-none"
size="icon"
>
<PhListBullets weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().toggleOrderedList().run()"
:variant="editor.isActive('orderedList') ? 'default' : 'secondary'"
class="size-7 rounded-none"
size="icon"
>
<PhListNumbers weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().toggleBlockquote().run()"
:variant="editor.isActive('blockquote') ? 'default' : 'secondary'"
class="size-7 rounded-s-none"
size="icon"
>
<PhQuotes weight="fill" />
</UiButton>
</div>
<div class="flex">
<UiButton
@click.prevent="clearFormatting"
variant="secondary"
class="size-7 rounded-e-none"
size="icon"
>
<PhTextTSlash weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().undo().run()"
:disabled="!editor.can().chain().focus().undo().run()"
variant="secondary"
class="size-7 rounded-none"
size="icon"
>
<PhArrowUUpLeft weight="bold" />
</UiButton>
<UiButton
@click.prevent="editor.chain().focus().redo().run()"
:disabled="!editor.can().chain().focus().redo().run()"
variant="secondary"
class="size-7 rounded-s-none"
size="icon"
>
<PhArrowUDownRight weight="bold" />
</UiButton>
</div>
</div>
<EditorContent
:editor
class="overflow-y-auto md:text-sm border-b-1 border-border"
/>
</div>
</template>
<style lang="scss">
.tiptap,
.content-editor {
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}
/* Basic editor styles */
.tiptap {
padding-block: 0.75rem;
min-height: 7rem;
max-height: 12rem;
overflow-y: auto;
:first-child {
margin-top: 0;
}
}
</style>

View File

@@ -40,13 +40,13 @@ const breakpoints = useBreakpoints(
<nav
ref="sidebarRef"
:class="cn(
['md:relative md:isolate w-16 py-6 grid gap-4 grid-rows-[1fr_auto] justify-center md:transition-none'], // Base appearance
['after:opacity-50 after:contrast-125 dark:after:opacity-75 dark:after:contrast-175 after:-hue-rotate-60'], // After styling
['border-r-[1px] bg-indigo-700 dark:bg-black text-white border-r-indigo-700 dark:border-r-indigo-950 shadow-navbar-light dark:shadow-navbar-dark'], // Colours
['md:relative md:isolate w-16 py-6 grid gap-4 grid-rows-[1fr_auto] justify-center md:transition-colors'], // Base appearance
['after:opacity-50 after:contrast-125 dark:after:opacity-75'], // After styling
['border-r-[1px] border-r-border dark:border-r-border'], // Colours
['max-md:justify-stretch max-md:px-4 max-md:py-4 max-md:absolute max-md:left-0 max-md:inset-0 max-md:z-50 max-md:w-40 max-md:max-w-full max-md:transition-all'], // Responsive behaviours
{
'max-md:-translate-x-40': !isSidebarOpened,
'max-md:-translate-x-0 shadow-navbar-dark dark:bg-slate-950': isSidebarOpened
'max-md:-translate-x-0': isSidebarOpened
}
)"
>
@@ -72,11 +72,11 @@ const breakpoints = useBreakpoints(
class="rounded-full max-md:hidden"
as-child
>
<RouterLink to="/explore">
<NuxtLink to="/explore">
<PhCompass size="24" weight="fill" />
</RouterLink>
</NuxtLink>
</UiButton>
<RouterLink
<NuxtLink
to="/explore"
class="md:hidden flex items-center gap-[.6ch] underline-offset-4 hover:underline"
>
@@ -85,7 +85,7 @@ const breakpoints = useBreakpoints(
<span class="text-[.9em]">
{{ $t('pages.explore.menuLabel') }}
</span>
</RouterLink>
</NuxtLink>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'" :side-offset="6">
<p>
@@ -96,40 +96,6 @@ const breakpoints = useBreakpoints(
</UiTooltipProvider>
</li>
<li class="max-md:self-start">
<UiTooltipProvider :delay-duration="50" :disabled="!breakpoints.md.value">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton
variant="ghost"
size="icon"
class="rounded-full max-md:hidden"
as-child
>
<RouterLink to="/about">
<PhInfo size="24" weight="fill" />
</RouterLink>
</UiButton>
<RouterLink
to="/about"
class="md:hidden flex items-center gap-[.6ch] underline-offset-4 hover:underline"
>
<PhInfo size="22" weight="fill" />
<span class="text-[.9em]">
{{ $t('pages.about.menuLabel') }}
</span>
</RouterLink>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'" :side-offset="6">
<p>
{{ $t('pages.about.menuLabel') }}
</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</li>
<ClientOnly>
<li v-for="(item, i) in currentMenu" :key="i">
<UiTooltipProvider :delay-duration="50">
@@ -142,9 +108,9 @@ const breakpoints = useBreakpoints(
class="rounded-full"
as-child
>
<RouterLink :to="item.to">
<NuxtLink :to="item.to">
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
</RouterLink>
</NuxtLink>
</UiButton>
<UiButton
v-if="item.action"
@@ -168,23 +134,3 @@ const breakpoints = useBreakpoints(
<UserCTA />
</nav>
</template>
<style lang="scss" scoped>
nav {
&::after {
display: block;
content: '';
position: absolute;
bottom: 0;
inset-inline: 0;
height: 25rem;
max-height: 100%;
background-image: url("/images/sidebar.png");
background-position: bottom;
background-size: cover;
background-repeat: no-repeat;
mask-image: linear-gradient(to top, black 25%, transparent 50%, transparent);
z-index: -1;
}
}
</style>

View File

@@ -57,20 +57,33 @@ function pushRoute(to: AvailableRoutes) {
<template>
<ClientOnly>
<UiDropdownMenu v-model:open="menuOpened">
<UiDropdownMenuTrigger>
<UiAvatar v-if="user" id="user-avatar" class="ring-[.2rem] ring-indigo-700 dark:ring-neutral-900 cursor-pointer">
<UiAvatarImage
:src="userMeta?.avatar_url"
:alt="userMeta?.full_name"
referrerpolicy="no-referrer"
/>
<UiAvatarFallback>
{{ $t('ui.sidebarMenu.avatarFallback') }}
</UiAvatarFallback>
</UiAvatar>
<UiButton v-else variant="outline" size="icon" class="rounded-full border-indigo-200 bg-indigo-700 dark:border-slate-300 dark:bg-neutral-950 dark:hover:bg-slate-50 dark:hover:text-slate-950 cursor-pointer">
<PhUserCircle size="24" />
</UiButton>
<UiDropdownMenuTrigger class="aspect-square">
<TransitionGroup name="fade-group" appear>
<UiAvatar
v-if="user"
id="user-avatar"
class="ring-[.2rem] ring-primary hover:bg-accent hover:ring-accent transition-all cursor-pointer"
:class="cn({ 'ring-accent bg-accent dark:bg-accent': menuOpened })"
>
<UiAvatarImage
:src="userMeta?.avatar_url"
:alt="userMeta?.full_name"
referrerpolicy="no-referrer"
/>
<UiAvatarFallback>
{{ $t('ui.sidebarMenu.avatarFallback') }}
</UiAvatarFallback>
</UiAvatar>
<UiButton
v-else
variant="outline"
size="icon"
class="ring-[.2rem] ring-primary hover:bg-accent hover:ring-accent border-none dark:bg-background rounded-full bg-primary transition-all cursor-pointer"
:class="cn({ 'ring-accent bg-accent dark:bg-accent': menuOpened })"
>
<PhUserCircle size="24" />
</UiButton>
</TransitionGroup>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent class="w-72 p-0 pb-1" :align="'start'" :side="'top'" :side-offset="10" :align-offset="25" :collision-padding="40">

View File

@@ -31,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-3xl -translate-x-1/2 -translate-y-1/2 gap-4 border border-indigo-200 bg-background dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-3xl -translate-x-1/2 -translate-y-1/2 gap-4 border border-popover-border bg-popover p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
props.class,
)
"

View File

@@ -9,7 +9,7 @@ const props = defineProps<{
<template>
<ol
:class="cn('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', props.class)"
:class="cn('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground transition-colors sm:gap-2.5', props.class)"
>
<slot />
</ol>

View File

@@ -12,7 +12,7 @@ const props = defineProps<{
role="link"
aria-disabled="true"
aria-current="page"
:class="cn('font-normal text-foreground', props.class)"
:class="cn('font-normal text-foreground transition-colors', props.class)"
>
<slot />
</span>

View File

@@ -7,11 +7,11 @@ export const buttonVariants = cva(
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
default: "bg-primary text-primary-foreground hover:bg-primary/70",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
outline: "border border-input hover:bg-primary hover:text-primary-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-primary/20",
ghost: "hover:bg-accent hover:text-accent-foreground",
ghost: "hover:bg-primary/70 hover:text-primary-foreground",
link: "text-primary underline-offset-4 hover:underline"
},
size: {

View File

@@ -33,7 +33,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-4xl -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background border-indigo-200 dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-4xl -translate-x-1/2 -translate-y-1/2 gap-4 border border-popover-border bg-popover p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
props.class
)
"

View File

@@ -32,7 +32,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 transition-all',
props.class
)
"

View File

@@ -19,6 +19,6 @@ const delegatedProps = computed(() => {
<template>
<DropdownMenuSeparator
v-bind="delegatedProps"
:class="cn('-mx-1 my-1 h-px bg-muted', props.class)"
:class="cn('-mx-1 my-1 h-px bg-muted transition-colors', props.class)"
/>
</template>

View File

@@ -25,7 +25,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 transition-all',
props.class
)
"

View File

@@ -24,7 +24,7 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps"
:class="
cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:bg-accent transition-colors',
props.class
)
"

View File

@@ -37,7 +37,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="{ ...forwarded, ...$attrs }"
:class="
cn(
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 w-72 rounded-md border border-popover-border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class
)
"

View File

@@ -39,7 +39,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
style="max-height: var(--radix-select-content-available-height)"
:class="
cn(
'select-content relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'select-content relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border border-popover-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class

View File

@@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps"
:class="
cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background dark:bg-slate-950 contrast-more:dark:bg-slate-900 px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background hover:bg-secondary hover:text-primary-foreground px-3 py-2 text-sm ring-offset-background transition-colors placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50',
props.class
)
"

View File

@@ -47,7 +47,7 @@ const updatedAt = computed<string>(() => props.world.updatedAt ? DateTime.fromIS
</UiCardHeader>
<UiCardContent class="grow">
<p class="italic">{{ world.description }}</p>
<div class="md:text-sm" v-html="world.description"></div>
<div
v-if="isCardHovered || isCardFocused"

View File

@@ -67,16 +67,13 @@ function handleFormCancel() {
name="new-world-name"
required
:placeholder="$t('common.title')"
class="w-full -my-1 mb-4 py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
class="w-full -my-1 py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
@input="handleNameChange"
>
<textarea
id="new-world-description"
<InputEditor
disable-blocks
v-model="worldSkeleton.description"
name="new-world-description"
:placeholder="$t('entity.addDescription')"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
/>
<div class="-mx-1 grid gap-3">

View File

@@ -38,6 +38,8 @@ async function handleSubmit() {
if (error) {
console.log(error.message)
isLoading.value = false
return
}
toast({
@@ -79,16 +81,13 @@ function handleFormCancel() {
name="new-world-name"
required
:placeholder="$t('common.title')"
class="w-full -my-1 py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
class="w-full -my-1 py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-primary invalid:border-destructive"
@input="handleNameChange"
>
<textarea
id="new-world-description"
<InputEditor
disable-blocks
v-model="worldSkeleton.description"
name="new-world-description"
:placeholder="$t('entity.addDescription')"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
/>
<div class="-mx-1 grid gap-3">

View File

@@ -60,7 +60,7 @@ switch (statusCode) {
<template>
<div class="h-screen">
<div class="h-full grid grid-cols-[auto_1fr] dark:bg-black transition-colors">
<div class="h-full grid grid-cols-[auto_1fr] transition-colors">
<Sidebar />
<div class="wrapper shadow-body-light dark:shadow-body-dark transition-all">

23
app/layouts/default.vue Normal file
View File

@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { cn } from '~/lib/utils';
const { isSidebarOpened } = storeToRefs(useUiStore())
</script>
<template>
<div
class="h-full grid md:grid-cols-[auto_1fr] bg-background transition-colors after:absolute after:transition-colors"
:class="cn({
'max-md:after:bg-transparent': isSidebarOpened,
'has-sidebar max-md:after:bg-black/20 md:after:opacity-0 md:after:pointer-events-none': isSidebarOpened
})"
>
<Sidebar />
<div
class="wrapper max-h-screen transition-all overflow-y-auto"
>
<slot />
</div>
</div>
</template>

7
app/layouts/public.vue Normal file
View File

@@ -0,0 +1,7 @@
<template>
<div
class="wrapper max-h-screen h-full transition-all overflow-y-auto"
>
<slot />
</div>
</template>

View File

@@ -1,7 +1,13 @@
<script lang="ts" setup>
import { PhCircleNotch } from '@phosphor-icons/vue'
const { locale } = useI18n()
const { data: page, refresh } = await useAsyncData(`about-content-${locale}`, () => {
definePageMeta({
layout: "public"
})
const { data: page, refresh, status } = useLazyAsyncData(`about-content-${locale}`, () => {
return queryCollection("sections").path(`/sections/${locale.value}/legal`).first()
})
@@ -9,25 +15,25 @@ 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>
<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>
<main class="overflow-y-auto py-8 px-5 md:px-8 grid gap-4 grid-rows-[auto_1fr]">
<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>
<Heading level="h1">
{{ $t("pages.about.title") }}
</Heading>
</div>
<div v-if="status === 'pending'" class="grid place-items-center">
<PhCircleNotch size="50" class="opacity-33 animate-spin"/>
</div>
<LazyContentRenderer v-else-if="status === 'success' && page" :value="page" class="content max-w-5xl" />
</main>
</template>

View File

@@ -44,7 +44,7 @@ watch(isLoading, (n) => {
<p>
{{ $t('entity.calendar.isLoading') }}
</p>
<PhCircleNotch size="50" class="animate-spin"/>
<PhCircleNotch size="50" class="opacity-33 animate-spin"/>
</div>
</div>

View File

@@ -2,12 +2,13 @@
import type { Calendar } from "@@/models/CalendarConfig";
definePageMeta({
middleware: ["reset-menu"]
layout: "public"
})
const { data: availableCalendars, status: calendarStatus } = await useLazyFetch<{ data: Calendar[] }>("/api/calendars/query", { key: "explore-calendars", query: { full: true } })
const isLoading = computed(() => calendarStatus.value === "pending")
// const { data: availableCalendars, status: calendarStatus } = useLazyFetch<{ data: Calendar[] }>("/api/calendars/query", { key: "explore-calendars", query: { full: true } })
const { data: calendars, status } = useLazyAsyncData<{ data: Calendar[] }>("explore-calendars", () => {
return $fetch("/api/calendars/query", { query: { full: true }})
})
</script>
<template>
@@ -32,14 +33,17 @@ const isLoading = computed(() => calendarStatus.value === "pending")
{{ $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 v-if="status === 'pending'" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
<LazyCardLoading />
</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>
<template v-else-if="status === 'success' && calendars?.data">
<ul class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
<li v-for="calendar in calendars.data" :key="calendar.shortId">
<LazyCalendarPreviewCard :calendar="calendar" :gm-id="calendar.world?.gmId" />
</li>
</ul>
</template>
</Spacing>
</Spacing>
</main>

View File

@@ -4,12 +4,33 @@ useHead({
})
definePageMeta({
middleware: ["reset-menu"]
layout: "public"
})
const { auth } = useSupabaseClient()
const user = useSupabaseUser()
const profileUrl: string = `${useRequestURL().origin}/my/`
async function handleGoogleLogin() {
const { error } = await auth.signInWithOAuth({
provider: "google",
options: {
queryParams: {
access_type: "offline",
prompt: "consent"
},
redirectTo: profileUrl
}
})
if (error) {
console.log(error.message)
}
}
</script>
<template>
<main class="py-8 px-5 md:px-8">
<main class="h-full grid place-items-center py-8 px-5 md:px-8">
<Head>
<Title>
TTTools {{ $t('head.title') }}
@@ -17,6 +38,22 @@ definePageMeta({
<Meta name="description" :content="$t('head.description')" />
</Head>
<Heading level="h1">TTTools</Heading>
<div class="py-8 md:py-24 px-4">
<div class="flow text-center max-w-2xl mx-auto">
<h1 class="text-3xl md:text-5xl font-bold md:font-semibold">A home for your creativity</h1>
<p class="text-sm">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ipsam eos rem accusantium voluptates? Inventore dolorum reprehenderit quibusdam consequatur, totam iusto velit neque dolor dicta possimus cum, deleniti, saepe expedita culpa!</p>
<TransitionGroup name="fade-group" appear>
<UiButton v-if="user" as-child>
<NuxtLink to="/my">
Dashboard
</NuxtLink>
</UiButton>
<UiButton v-else @click="handleGoogleLogin">
Log in
</UiButton>
</TransitionGroup>
</div>
</div>
</main>
</template>

View File

@@ -51,7 +51,7 @@ watch([calendar], () => {
<p>
{{ $t('entity.calendar.isLoading') }}
</p>
<PhCircleNotch size="50" class="animate-spin"/>
<PhCircleNotch size="50" class="opacity-33 animate-spin"/>
</div>
</div>

View File

@@ -9,10 +9,10 @@ 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 { data: worlds, status: worldStatus, refresh } = 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")
const isLoading = computed(() => worldStatus.value === "pending")
// Redirect user back home when they log out on the page
watch(user, (n) => {
@@ -33,49 +33,13 @@ function hideCreateDialog() {
/** 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")
worldChannel = supabase.channel("realtime-worlds-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
}
refresh()
}
)
.subscribe()
@@ -136,21 +100,21 @@ function hideEditModal() {
</div>
<div v-if="isLoading" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
<LoadingCard />
<LazyCardLoading />
</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">
<LazyCardAdd @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>
</LazyCardAdd>
</li>
</ul>
</Spacing>

View File

@@ -8,13 +8,6 @@ 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
@@ -24,105 +17,45 @@ watch(user, (n) => {
}
})
// Set custom menu
// This should be reserved for actions, not for breadcrumbs
//
// const { setCurrentMenu } = useUiStore()
definePageMeta({
middleware: ["auth-guard"]
})
// setCurrentMenu([
// {
// tooltip: t("entity.world.backToMy"),
// to: "/my",
// phIcon: "universe",
// highlight: true
// },
// ])
const { data: world, status, refresh } = await useAsyncData<{ data: World }>('user-world', () => {
return $fetch("/api/worlds/query", { query: { id }})
})
const worldGmId = world.value?.data.gmId
const { data: calendars, status: calendarStatus, refresh: refreshCalendars } = useLazyFetch<{ data: Calendar[] }>('/api/calendars/query', { query: { worldId: world.value?.data.id, full: true }})
const sortedCalendars = computed(() => calendars.value?.data ? calendars.value.data.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)) : [])
/**
* === 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)
})
// Register channels
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
}
}
async () => refresh()
)
.subscribe()
calendarChannel = supabase.channel("realtime-calendar-channel")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "calendars" },
async () => refreshCalendars()
)
.subscribe()
})
// Unsubscribe from realtime
onUnmounted(() => {
// Unsubscribe from realtime
supabase.removeChannel(calendarChannel)
supabase.removeChannel(worldChannel)
})
@@ -169,11 +102,60 @@ function hideEditModal() {
<Title>{{ $t("entity.world.namePlural") }}</Title>
</Head>
<Heading level="h1">
{{ $t('entity.isLoading') }}
</Heading>
<header class="mb-8">
<Spacing size="lg">
<Breadcrumb
:items="[]"
/>
<div class="lg:w-1/2">
<div class="flex items-center gap-2 mb-2">
<div class="flex items-center gap-3">
<UiSkeleton class="h-10 w-32 max-md:max-w-full" />
</div>
<UiButton size="icon" class="rounded-full size-8" disabled>
<PhPencil size="17" weight="fill" />
</UiButton>
</div>
<div class="grid gap-1">
<UiSkeleton class="h-6 w-full max-md:max-w-full" />
<UiSkeleton class="h-6 w-8/12 max-md:max-w-full" />
<UiSkeleton class="h-6 w-7/12 max-md:max-w-full" />
</div>
</div>
</Spacing>
</header>
</template>
<template v-else-if="world?.data">
<template v-else-if="!world?.data && status === 'error'">
<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>
<template v-else-if="world?.data && status === 'success'">
<Head>
<Title>{{ world.data.name }}</Title>
</Head>
@@ -208,74 +190,75 @@ function hideEditModal() {
</UiTooltipProvider>
</div>
<p>{{ world.data.description }}</p>
<div class="content-editor" v-html="world.data.description" />
</div>
</Spacing>
</header>
</template>
<section>
<Spacing size="lg">
<div class="flex items-center gap-3">
<Heading level="h2">
{{ $t('entity.calendar.namePlural') }}
</Heading>
<template v-if="world?.data">
<LazyWorldDialogEdit :world="world.data" :modal-state="isEditWorldModalOpen" @on-close="hideEditModal" />
<LazyCalendarDialogCreate :world="world.data" :modal-state="isCreateCalendarModalOpen" @on-close="hideCreateDialog" />
<LazyCalendarDialogUpdate :world="world.data" :calendar="markedCalendar" :modal-state="isUpdateCalendarModalOpen" @on-close="hideUpdateDialog" />
<LazyCalendarDialogDelete :calendar="markedCalendar" :modal-state="isDeleteCalendarModalOpen" @on-close="hideDeleteCalendarModal"/>
</template>
<section>
<Spacing size="lg">
<div class="flex items-center gap-3">
<Heading level="h2">
{{ $t('entity.calendar.namePlural') }}
</Heading>
</div>
<template v-if="calendarStatus === 'pending'">
<ul class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
<li>
<LazyCardLoading />
</li>
<li class="w-64 max-w-full">
<LazyCardLoading />
</li>
</ul>
</template>
<template v-else-if="!calendars?.data && calendarStatus === 'error'">
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
<LazyCardError>
<template #title>
{{ $t('error.default.title') }}
</template>
<template #content>
{{ $t('entity.calendar.error.cannotFindAny') }}
</template>
</LazyCardError>
</div>
</template>
<template v-else-if="calendars?.data && calendarStatus === 'success' && worldGmId">
<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
<LazyCalendarPreviewCard
:calendar="calendar"
:gm-id="world.data.gmId"
:gm-id="worldGmId"
show-actions
@on-edit="() => deployUpdateDialog(calendar)"
@on-delete="() => deployDeleteCalendarModal(calendar)" />
</li>
<li class="xl:w-fit">
<AddCard @on-click="() => isCreateCalendarModalOpen = true">
<LazyCardAdd @on-click="() => isCreateCalendarModalOpen = true">
<template v-if="sortedCalendars.length > 0">
{{ $t('entity.calendar.addSingle') }}
</template>
<template v-else>
{{ $t('entity.calendar.addSingleFirst') }}
</template>
</AddCard>
</LazyCardAdd>
</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>
</template>
</Spacing>
</section>
</main>
</template>

View File

@@ -14,4 +14,4 @@
"components": "@/components",
"utils": "@/lib/utils"
}
}
}

View File

@@ -206,6 +206,8 @@ export default defineI18nConfig(() => ({
title: "Event title",
isStart: "Start",
isEnd: "End",
gotoStart: "Go to the start of this event",
gotoEnd: "Go to the end of this event",
isHidden: "Hidden",
isPublic: "Public",
hiddenLabel: "Hide this event",
@@ -241,6 +243,9 @@ export default defineI18nConfig(() => ({
title: "The event \"{event}\" has been successfuly deleted.",
},
},
error: {
cannotFindAny: "We couldn't fetch your calendars…"
},
actionDialog: {
tabs: {
general: {
@@ -536,6 +541,8 @@ export default defineI18nConfig(() => ({
title: "Titre de l'évènement",
isStart: "Début",
isEnd: "Fin",
gotoStart: "Aller au début de l'évènement",
gotoEnd: "Aller à la fin de l'évènement",
isHidden: "Privé",
isPublic: "Public",
hiddenLabel: "Rendre l'évènement privé",
@@ -571,6 +578,9 @@ export default defineI18nConfig(() => ({
title: "L'évènement \"{event}\" a été supprimé avec succès.",
},
},
error: {
cannotFindAny: "Nous n'avons pas pu récupérer vos calendriers…"
},
actionDialog: {
tabs: {
general: {

View File

@@ -12,8 +12,9 @@ export default defineNuxtConfig({
"@nuxt/eslint",
"shadcn-nuxt",
"@nuxtjs/i18n",
"@nuxthub/core",
"@nuxt/content",
"@nuxthub/core",
"@nuxt/image",
],
css: ["@/assets/main.css"],
@@ -67,5 +68,5 @@ export default defineNuxtConfig({
},
eslint: {},
compatibilityDate: "2025-04-16",
compatibilityDate: "latest",
})

View File

@@ -15,21 +15,25 @@
"dependencies": {
"@intlify/message-compiler": "^11.1.11",
"@nuxt/content": "3.6.3",
"@nuxt/eslint": "^1.7.1",
"@nuxt/eslint": "^1.8.0",
"@nuxt/image": "1.11.0",
"@nuxthub/core": "^0.9.0",
"@nuxtjs/i18n": "^10.0.3",
"@phosphor-icons/vue": "^2.2.1",
"@pinia/nuxt": "^0.11.2",
"@tailwindcss/vite": "^4.1.11",
"@vueuse/core": "^13.5.0",
"@vueuse/integrations": "^13.5.0",
"@vueuse/nuxt": "^13.5.0",
"@tiptap/pm": "^3.0.9",
"@tiptap/starter-kit": "^3.0.9",
"@tiptap/vue-3": "^3.0.9",
"@vueuse/core": "^13.6.0",
"@vueuse/integrations": "^13.6.0",
"@vueuse/nuxt": "^13.6.0",
"better-sqlite3": "^12.2.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-vue-next": "^0.526.0",
"lucide-vue-next": "^0.537.0",
"luxon": "^3.7.1",
"nuxt": "^4.0.1",
"nuxt": "^4.0.3",
"pinia": "^3.0.3",
"radix-vue": "^1.9.17",
"shadcn-nuxt": "^2.2.0",
@@ -38,26 +42,26 @@
"tailwindcss-animate": "^1.0.7",
"vue": "^3.5.18",
"vue-router": "^4.5.1",
"zod": "^4.0.10"
"zod": "^4.0.15"
},
"devDependencies": {
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/supabase": "^1.6.0",
"@nuxtjs/tailwindcss": "^6.14.0",
"@stylistic/eslint-plugin-js": "^4.4.1",
"@types/luxon": "^3.6.2",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"@types/luxon": "^3.7.1",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-vue": "^10.3.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.4.0",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"sass": "^1.89.2",
"supabase": "^2.31.8",
"sass": "^1.90.0",
"supabase": "^2.33.9",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
"wrangler": "^4.26.0"
"typescript": "^5.9.2",
"wrangler": "^4.28.1"
}
}

2896
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -41,12 +41,7 @@ export default defineEventHandler(async (event) => {
state,
createdAt:created_at,
updatedAt:updated_at,
calendars (
createdAt:created_at,
updatedAt:updated_at,
months:calendar_months(*),
eventNb:calendar_events(count)
)
gmId:gm_id
`
let output

View File

@@ -43,7 +43,7 @@ create table public.worlds (
id bigint generated by default as identity primary key,
name text not null,
description text,
color app_colors default 'black',
color app_colors default 'emerald',
state world_state default 'draft',
created_at timestamptz default now(),
updated_at timestamptz,
@@ -69,7 +69,7 @@ create table public.calendars (
short_id text unique,
name text not null,
today json not null,
color app_colors default 'black',
color app_colors default 'emerald',
state calendar_state default 'draft',
created_at timestamptz default now(),
updated_at timestamptz,