Migration to nuxt 4
Used codemods CLI and reworked most alias'd path that stopped working
This commit is contained in:
21
app/components/global/AddCard.vue
Normal file
21
app/components/global/AddCard.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhPlusCircle } from "@phosphor-icons/vue";
|
||||
|
||||
const emit = defineEmits(["on-click"])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCard
|
||||
class="h-full lg:w-fit transition-all bg-transparent hover:text-slate-900 text-slate-500 dark:hover:text-slate-100 dark:text-slate-500 dark:focus-within:outline-gray-900 hover:-translate-y-0"
|
||||
has-click
|
||||
@on-click="emit('on-click')"
|
||||
>
|
||||
<UiCardHeader class="h-full justify-center items-center text-center gap-0">
|
||||
<PhPlusCircle size="38" weight="fill" />
|
||||
|
||||
<UiCardTitle class="text-xl">
|
||||
<slot />
|
||||
</UiCardTitle>
|
||||
</UiCardHeader>
|
||||
</UiCard>
|
||||
</template>
|
||||
66
app/components/global/Breadcrumb.vue
Normal file
66
app/components/global/Breadcrumb.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { PhCaretRight } from "@phosphor-icons/vue";
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
export type BreadcrumbItem = {
|
||||
label?: string;
|
||||
translateKey?: string;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
items?: BreadcrumbItem[];
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="md:hidden">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
|
||||
<UiBreadcrumb>
|
||||
<UiBreadcrumbList>
|
||||
<UiBreadcrumbItem v-if="route.path !== '/my'">
|
||||
<UiBreadcrumbLink as-child>
|
||||
<NuxtLink to="/my">
|
||||
{{ $t("breadcrumbs.profile") }}
|
||||
</NuxtLink>
|
||||
</UiBreadcrumbLink>
|
||||
</UiBreadcrumbItem>
|
||||
<UiBreadcrumbItem v-else>
|
||||
<UiBreadcrumbPage>
|
||||
{{ $t("breadcrumbs.profile") }}
|
||||
</UiBreadcrumbPage>
|
||||
</UiBreadcrumbItem>
|
||||
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<UiBreadcrumbSeparator>
|
||||
<PhCaretRight />
|
||||
</UiBreadcrumbSeparator>
|
||||
<UiBreadcrumbItem>
|
||||
<UiBreadcrumbLink v-if="item.to" as-child>
|
||||
<NuxtLink :to="item.to">
|
||||
<template v-if="item.label">
|
||||
{{ item.label }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t(`breadcrumbs.${item.translateKey}`) }}
|
||||
</template>
|
||||
</NuxtLink>
|
||||
</UiBreadcrumbLink>
|
||||
<UiBreadcrumbPage v-else>
|
||||
<template v-if="item.label">
|
||||
{{ item.label }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t(`breadcrumbs.${item.translateKey}`) }}
|
||||
</template>
|
||||
</UiBreadcrumbPage>
|
||||
</UiBreadcrumbItem>
|
||||
</template>
|
||||
</UiBreadcrumbList>
|
||||
</UiBreadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
26
app/components/global/Heading.vue
Normal file
26
app/components/global/Heading.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
type HeadingLevel = "h0" | "h1" | "h2" | "h3"
|
||||
|
||||
interface HeadingProps {
|
||||
level?: HeadingLevel
|
||||
}
|
||||
|
||||
withDefaults(defineProps<HeadingProps>(), {
|
||||
level: "h2"
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 v-if="level === 'h0'" class="text-4xl md:text-6xl font-bold flex">
|
||||
<slot />
|
||||
</h1>
|
||||
<h1 v-else-if="level === 'h1'" class="text-2xl md:text-4xl font-bold flex">
|
||||
<slot />
|
||||
</h1>
|
||||
<h2 v-else-if="level === 'h2'" class="text-xl md:text-2xl font-bold flex">
|
||||
<slot />
|
||||
</h2>
|
||||
<h3 v-if="level === 'h3'" class="text-lg md:text-xl font-bold flex">
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
21
app/components/global/LoadingCard.vue
Normal file
21
app/components/global/LoadingCard.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
const { lineNb = 3 } = defineProps<{
|
||||
lineNb?: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCard class="w-full h-full flex flex-col">
|
||||
<UiCardHeader>
|
||||
<UiCardTitle>
|
||||
<UiSkeleton class="w-2/3 max-w-full h-8" />
|
||||
</UiCardTitle>
|
||||
</UiCardHeader>
|
||||
<UiCardContent class="grow grid gap-2">
|
||||
<UiSkeleton class="w-full max-w-full h-6" />
|
||||
<template v-for="(l, i) in lineNb" :key="i">
|
||||
<UiSkeleton class="max-w-full h-6" :style="`width: ${Math.floor(Math.random() * 100) + 1}%`" />
|
||||
</template>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</template>
|
||||
30
app/components/global/Spacing.vue
Normal file
30
app/components/global/Spacing.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SpacingProps {
|
||||
size?: "xs" | "sm" | "md" | "lg" | "xlg" | "2xl" | "3xl"
|
||||
}
|
||||
|
||||
const props = defineProps<SpacingProps>()
|
||||
|
||||
let spacingClass: string
|
||||
|
||||
switch (props.size) {
|
||||
case "lg":
|
||||
spacingClass = "space-y-4"
|
||||
break;
|
||||
|
||||
case "md":
|
||||
default:
|
||||
spacingClass = "space-y-2"
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="cn(spacingClass)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
41
app/components/global/input/Color.vue
Normal file
41
app/components/global/input/Color.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type RPGColor, rpgColors } from "@@/models/Color";
|
||||
|
||||
const { id, theme = "normal", position = "popper" } = defineProps<{
|
||||
id: string
|
||||
theme?: "normal" | "subtle"
|
||||
position?: "item-aligned" | "popper" | undefined
|
||||
}>();
|
||||
|
||||
const model = defineModel<RPGColor>({ default: "white" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiSelect v-model="model">
|
||||
<UiSelectTrigger :id :class="cn({ 'h-9': theme === 'subtle' })">
|
||||
<UiSelectValue
|
||||
:placeholder="$t('ui.colors.selectOne')"
|
||||
class="bgc"
|
||||
:class="cn(`element-${model}`)"
|
||||
/>
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent :position>
|
||||
<UiSelectGroup>
|
||||
<UiSelectItem
|
||||
v-for="color in rpgColors"
|
||||
:key="color"
|
||||
:value="color"
|
||||
class="bgc"
|
||||
:class="
|
||||
cn(
|
||||
`element-${color}`,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ $t(`ui.colors.${color}`) }}
|
||||
</UiSelectItem>
|
||||
</UiSelectGroup>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</template>
|
||||
30
app/components/global/input/ContentState.vue
Normal file
30
app/components/global/input/ContentState.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { contentStates, type ContentState } from "@@/models/Entity";
|
||||
|
||||
defineProps<{
|
||||
id: string
|
||||
}>();
|
||||
|
||||
const model = defineModel<ContentState>({ default: "draft" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiSelect v-model="model">
|
||||
<UiSelectTrigger :id>
|
||||
<UiSelectValue
|
||||
:placeholder="$t('ui.contentState.selectOne')"
|
||||
/>
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent position="popper">
|
||||
<UiSelectGroup>
|
||||
<UiSelectItem
|
||||
v-for="state in contentStates"
|
||||
:key="state"
|
||||
:value="state"
|
||||
>
|
||||
{{ $t(`ui.contentState.${state}`) }}
|
||||
</UiSelectItem>
|
||||
</UiSelectGroup>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</template>
|
||||
190
app/components/global/sidebar/Sidebar.vue
Normal file
190
app/components/global/sidebar/Sidebar.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhCompass, PhGlobeHemisphereEast, PhHurricane, PhInfo, PhX } from "@phosphor-icons/vue"
|
||||
import type { SidebarMenuActionType, SidebarMenuIcon } from "./SidebarProps";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { breakpointsTailwind } from "@vueuse/core"
|
||||
|
||||
const { revealAdvancedSearch } = useCalendar()
|
||||
const { toggleSidebar } = useUiStore()
|
||||
const { currentMenu, isSidebarOpened } = storeToRefs(useUiStore())
|
||||
|
||||
function handleMenuItemAction(actionType: SidebarMenuActionType) {
|
||||
if (actionType === "event-search") {
|
||||
revealAdvancedSearch()
|
||||
}
|
||||
}
|
||||
|
||||
function computeMenuItemIcon(iconString: SidebarMenuIcon) {
|
||||
switch (iconString) {
|
||||
case "universe":
|
||||
return PhHurricane
|
||||
case "world":
|
||||
return PhGlobeHemisphereEast
|
||||
default:
|
||||
return PhCompass
|
||||
}
|
||||
}
|
||||
|
||||
const breakpoints = useBreakpoints(
|
||||
breakpointsTailwind
|
||||
)
|
||||
|
||||
// const sidebarRef = ref(null)
|
||||
|
||||
// onClickOutside(sidebarRef, () => {
|
||||
// isSidebarOpened.value = false
|
||||
// })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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
|
||||
['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
|
||||
}
|
||||
)"
|
||||
>
|
||||
<menu class="flex flex-col gap-4 max-md:items-center">
|
||||
<li class="mb-12 mt-4 max-md:self-start">
|
||||
<UiButton
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="md:hidden size-9 border-background/30"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<PhX size="19" />
|
||||
</UiButton>
|
||||
</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="/explore">
|
||||
<PhCompass size="24" weight="fill" />
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
<RouterLink
|
||||
to="/explore"
|
||||
class="md:hidden flex items-center gap-[.6ch] underline-offset-4 hover:underline"
|
||||
>
|
||||
<PhCompass size="22" weight="fill" />
|
||||
|
||||
<span class="text-[.9em]">
|
||||
{{ $t('pages.explore.menuLabel') }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'" :side-offset="6">
|
||||
<p>
|
||||
{{ $t('pages.explore.menuLabel') }}
|
||||
</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</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">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
v-if="item.to"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
as-child
|
||||
>
|
||||
<RouterLink :to="item.to">
|
||||
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="item.action"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
@click="handleMenuItemAction(item.action!)"
|
||||
>
|
||||
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'" :side-offset="6">
|
||||
<p>{{ item.tooltip }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li>
|
||||
</ClientOnly>
|
||||
</menu>
|
||||
|
||||
<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>
|
||||
17
app/components/global/sidebar/SidebarProps.ts
Normal file
17
app/components/global/sidebar/SidebarProps.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export type SidebarMenuActionType = "event-search"
|
||||
|
||||
export type SidebarMenuIcon = "universe" | "world"
|
||||
|
||||
export interface SidebarMenuItem {
|
||||
phIcon: SidebarMenuIcon,
|
||||
phIconWeight?: "regular" | "light" | "fill" | "duotone" | "bold" | "thin"
|
||||
highlight?: boolean
|
||||
tooltip: string
|
||||
action?: SidebarMenuActionType
|
||||
to?: string
|
||||
}
|
||||
|
||||
export interface SidebarProps {
|
||||
menuItems: SidebarMenuItem[],
|
||||
isHome?: boolean
|
||||
}
|
||||
15
app/components/global/sidebar/SidebarToggle.vue
Normal file
15
app/components/global/sidebar/SidebarToggle.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhList } from "@phosphor-icons/vue"
|
||||
|
||||
const { toggleSidebar } = useUiStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiButton
|
||||
size="icon"
|
||||
variant="outline"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<PhList size="19" weight="light" />
|
||||
</UiButton>
|
||||
</template>
|
||||
204
app/components/global/user/CTA.vue
Normal file
204
app/components/global/user/CTA.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
|
||||
import { PhCheckCircle, PhUser, PhLaptop, PhMoon, PhPalette, PhSignIn, PhSignOut, PhSun, PhTranslate, PhUserCircle } from "@phosphor-icons/vue"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { auth } = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const userMeta = computed(() => user.value?.user_metadata)
|
||||
const profileUrl: string = `${useRequestURL().origin}/my/`
|
||||
|
||||
const { locale, setLocale } = useI18n()
|
||||
|
||||
const menuOpened = ref<boolean>(false)
|
||||
|
||||
function closeMenu() {
|
||||
menuOpened.value = false
|
||||
}
|
||||
watch(user, closeMenu)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
const { error } = await auth.signOut()
|
||||
|
||||
if (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
type AvailableRoutes = "/my" | "/my/settings"
|
||||
|
||||
function pushRoute(to: AvailableRoutes) {
|
||||
router.push({ path: to })
|
||||
|
||||
closeMenu()
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
<UiDropdownMenuContent class="w-72 p-0 pb-1" :align="'start'" :side="'top'" :side-offset="10" :align-offset="25" :collision-padding="40">
|
||||
<template v-if="user">
|
||||
<p class="p-2 text-[.7em] opacity-75">
|
||||
{{ $t('ui.greeting', { user: user?.email }) }}
|
||||
</p>
|
||||
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my')">
|
||||
<PhUser size="20" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.profile') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<p class="p-2 text-[.7em] opacity-75">
|
||||
{{ $t('ui.anonymousGreeting') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<UiDropdownMenuSub>
|
||||
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
|
||||
<PhPalette size="20" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.appearance') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuSubTrigger>
|
||||
<UiDropdownMenuPortal>
|
||||
<UiDropdownMenuSubContent>
|
||||
<UiDropdownMenuItem
|
||||
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||
:class="cn({ 'text-emerald-600': $colorMode.preference === 'dark' })"
|
||||
@select.prevent="$colorMode.preference = 'dark'"
|
||||
>
|
||||
<PhCheckCircle v-if="$colorMode.preference === 'dark'" size="20" weight="fill" />
|
||||
<PhMoon v-else size="20" />
|
||||
|
||||
<span>
|
||||
{{ $t('ui.dark') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
<UiDropdownMenuItem
|
||||
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||
:class="cn({ 'text-emerald-600': $colorMode.preference === 'light' })"
|
||||
@select.prevent="$colorMode.preference = 'light'"
|
||||
>
|
||||
<PhCheckCircle v-if="$colorMode.preference === 'light'" size="20" weight="fill" />
|
||||
<PhSun v-else size="20" />
|
||||
|
||||
<span>
|
||||
{{ $t('ui.light') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
<UiDropdownMenuItem
|
||||
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||
:class="cn({ 'text-emerald-600': $colorMode.preference === 'system' })"
|
||||
@select.prevent="$colorMode.preference = 'system'"
|
||||
>
|
||||
<PhCheckCircle v-if="$colorMode.preference === 'system'" size="20" weight="fill" />
|
||||
<PhLaptop v-else size="20" />
|
||||
|
||||
<span>
|
||||
{{ $t('ui.system') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuSubContent>
|
||||
</UiDropdownMenuPortal>
|
||||
</UiDropdownMenuSub>
|
||||
|
||||
<UiDropdownMenuSub>
|
||||
<UiDropdownMenuSubTrigger class="p-0 rounded-none">
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center pointer-events-none">
|
||||
<PhTranslate size="20" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.language') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuSubTrigger>
|
||||
<UiDropdownMenuPortal>
|
||||
<UiDropdownMenuSubContent>
|
||||
<UiDropdownMenuSubContent>
|
||||
<UiDropdownMenuItem
|
||||
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||
:class="cn({ 'text-emerald-600': locale === 'fr' })"
|
||||
@select.prevent="setLocale('fr')"
|
||||
>
|
||||
<PhCheckCircle v-if="locale === 'fr'" size="20" weight="fill" />
|
||||
<img v-else src="/images/flag-fr.png" width="20" alt="" loading="eager">
|
||||
<span>Français</span>
|
||||
</UiDropdownMenuItem>
|
||||
<UiDropdownMenuItem
|
||||
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||
:class="cn({ 'text-emerald-600': locale === 'en' })"
|
||||
@select.prevent="setLocale('en')"
|
||||
>
|
||||
<PhCheckCircle v-if="locale === 'en'" size="20" weight="fill" />
|
||||
<img v-else src="/images/flag-uk.png" width="20" alt="" loading="eager">
|
||||
<span>English</span>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuSubContent>
|
||||
</UiDropdownMenuSubContent>
|
||||
</UiDropdownMenuPortal>
|
||||
</UiDropdownMenuSub>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
|
||||
<template v-if="user">
|
||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="handleLogout">
|
||||
<PhSignOut size="20" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.logout') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
</template>
|
||||
|
||||
<UiDropdownMenuItem v-if="!user" class="flex gap-[.5ch] items-center rounded-none" @click="handleGoogleLogin">
|
||||
<PhSignIn size="18" weight="fill" />
|
||||
<span>
|
||||
{{ $t('ui.sidebarMenu.login') }}
|
||||
</span>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
Reference in New Issue
Block a user