mirror of
https://github.com/ArthurDanjou/arthome.git
synced 2026-01-14 12:14:33 +01:00
working
This commit is contained in:
@@ -6,18 +6,21 @@ defineProps<{
|
||||
dropdownItems: { label: string, icon: string, color: string, click: (category: CategoryType) => void }[]
|
||||
}>()
|
||||
defineEmits(['createTab'])
|
||||
|
||||
const { canCreateTabInCategory } = await useUserLimit()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="category" class="flex items-center mb-4" :class="category.nameVisible ? 'justify-between' : 'justify-end'">
|
||||
<div v-if="category.nameVisible" class="flex items-center gap-2 mb-4" :class="`text-${category.color}-500`">
|
||||
<UIcon :name="`i-ph:${category.icon}`" size="28" />
|
||||
<UIcon :name="category.icon" size="28" />
|
||||
<h1 class="font-bold text-2xl">
|
||||
{{ category.name }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<UButton
|
||||
v-if="canCreateTabInCategory(category.id)"
|
||||
color="gray"
|
||||
variant="solid"
|
||||
label="New tab"
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
const { user, loggedIn, clear } = useUserSession()
|
||||
|
||||
const isSettingsOpen = ref(false)
|
||||
|
||||
const isDark = computed(() => colorMode.preference === 'dark')
|
||||
const items = [
|
||||
[{
|
||||
slot: 'account',
|
||||
@@ -13,26 +13,28 @@ const items = [
|
||||
label: 'Home',
|
||||
icon: 'i-ph:house-line-duotone',
|
||||
action: () => navigateTo('/'),
|
||||
shortcut: 'H',
|
||||
}, {
|
||||
label: 'Settings',
|
||||
icon: 'i-ph:gear-six-duotone',
|
||||
action: () => {
|
||||
isSettingsOpen.value = true
|
||||
},
|
||||
action: () => isSettingsOpen.value = true,
|
||||
shortcut: 'S',
|
||||
}, {
|
||||
label: 'Profile',
|
||||
icon: 'i-ph:person-arms-spread-duotone',
|
||||
action: () => navigateTo('/profile'),
|
||||
label: isDark.value ? 'Light mode' : 'Dark mode',
|
||||
icon: isDark.value ? 'i-ph:moon-duotone' : 'i-ph:sun-duotone',
|
||||
action: () => toggleColorMode(),
|
||||
shortcut: 'T',
|
||||
}],
|
||||
[{
|
||||
slot: 'logout',
|
||||
label: 'Sign out',
|
||||
icon: 'i-ph:sign-out-bold',
|
||||
shortcut: 'L',
|
||||
}],
|
||||
]
|
||||
|
||||
function toggleColorMode() {
|
||||
colorMode.preference = colorMode.preference === 'dark' ? 'light' : 'dark'
|
||||
colorMode.preference = isDark.value ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
@@ -43,6 +45,8 @@ async function logout() {
|
||||
|
||||
defineShortcuts({
|
||||
t: () => toggleColorMode(),
|
||||
s: () => isSettingsOpen.value = !isSettingsOpen.value,
|
||||
l: async () => await logout(),
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -58,7 +62,13 @@ defineShortcuts({
|
||||
</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<ClientOnly>
|
||||
<UDropdown v-if="loggedIn" :items="items" mode="hover" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
|
||||
<UDropdown
|
||||
v-if="loggedIn"
|
||||
:items="items"
|
||||
mode="hover"
|
||||
:ui="{ item: { disabled: 'cursor-text select-text' } }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
>
|
||||
<UAvatar :src="user.avatar" />
|
||||
|
||||
<template #account>
|
||||
@@ -74,26 +84,28 @@ defineShortcuts({
|
||||
|
||||
<template #item="{ item }">
|
||||
<div class="w-full flex justify-between items-center" @click.prevent="item.action()">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
<div class="gap-2 flex items-center">
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
</div>
|
||||
<UKbd v-if="item.shortcut">
|
||||
{{ item.shortcut }}
|
||||
</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #logout="{ item }">
|
||||
<div class="w-full flex justify-between items-center" @click="logout()">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
<div class="flex gap-2 items-center">
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500" />
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
</div>
|
||||
<UKbd v-if="item.shortcut">
|
||||
{{ item.shortcut }}
|
||||
</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
</UDropdown>
|
||||
<UButton
|
||||
:icon="$colorMode.preference === 'dark' ? 'i-ph:moon-duotone' : 'i-ph:sun-duotone'"
|
||||
color="gray"
|
||||
square
|
||||
size="md"
|
||||
variant="ghost"
|
||||
@click="toggleColorMode"
|
||||
/>
|
||||
</clientonly>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,10 +123,12 @@ defineShortcuts({
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
Hey
|
||||
Delete account
|
||||
Change user details
|
||||
{{ user }}
|
||||
<div class="space-y-12">
|
||||
<div>
|
||||
Delete account
|
||||
Change user details
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
|
||||
@@ -5,8 +5,16 @@ defineProps<{
|
||||
tab: TabType
|
||||
}>()
|
||||
|
||||
const { setTabPrimary } = await useTabs()
|
||||
|
||||
// DropDown Items
|
||||
const items = [[
|
||||
{
|
||||
label: 'Toggle favorite',
|
||||
icon: 'i-ph:star-duotone',
|
||||
color: 'yellow',
|
||||
click: tab => setTabPrimary(tab, !tab.primary),
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
icon: 'i-ph:pencil-duotone',
|
||||
@@ -56,12 +64,12 @@ function openDeleteTabModal(tab: TabType) {
|
||||
<div class="flex items-center justify-between h-full">
|
||||
<div class="flex gap-4 items-center h-full">
|
||||
<UBadge :color="tab.color" class="p-2" variant="soft">
|
||||
<UIcon :name="`i-ph:${tab.icon}`" size="32" />
|
||||
<UIcon :name="tab.icon" size="32" />
|
||||
</UBadge>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div :class="`text-${tab.color}-400`" class="text-xl font-medium">
|
||||
<p>{{ tab.name }}</p>
|
||||
</div>
|
||||
<p :class="`text-${tab.color}-400`" class="text-xl font-medium truncate">
|
||||
{{ tab.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<UDropdown
|
||||
@@ -71,8 +79,10 @@ function openDeleteTabModal(tab: TabType) {
|
||||
>
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-ph:dots-three-outline-vertical-duotone"
|
||||
variant="ghost"
|
||||
:padded="false"
|
||||
size="sm"
|
||||
icon="i-ph:dots-three-outline-duotone"
|
||||
/>
|
||||
|
||||
<template #item="{ item }">
|
||||
|
||||
@@ -20,6 +20,8 @@ async function handleCreate(event: FormSubmitEvent<CreateCategorySchemaType>) {
|
||||
state.icon = undefined
|
||||
state.name = undefined
|
||||
}
|
||||
|
||||
const { loading, search } = useIcons()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,11 +44,35 @@ async function handleCreate(event: FormSubmitEvent<CreateCategorySchemaType>) {
|
||||
<template #default>
|
||||
<UForm :schema="CreateCategorySchema" :state="state" class="space-y-4" @submit="handleCreate">
|
||||
<UFormGroup label="Name" name="name">
|
||||
<UInput v-model="state.name" type="text" variant="outline" />
|
||||
<UInput v-model="state.name" placeholder="Enter name" type="text" variant="outline" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Icon " name="icon" help="Get icon from the Phosphor Collection">
|
||||
<UInput v-model="state.icon" type="text" variant="outline" />
|
||||
<UFormGroup label="Icon " name="icon">
|
||||
<USelectMenu
|
||||
v-model="state.icon"
|
||||
:loading="loading"
|
||||
:searchable="search"
|
||||
placeholder="Select an icon"
|
||||
searchable-placeholder="Search an icon"
|
||||
>
|
||||
<template #label>
|
||||
<div v-if="state.icon" class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${state.icon}`" />
|
||||
<span class="truncate">{{ state.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${option}`" />
|
||||
<span class="truncate">{{ option }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
Enter an icon name, keyword or tag
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Color " name="color">
|
||||
|
||||
@@ -33,6 +33,8 @@ async function handleCreate(event: FormSubmitEvent<CreateTabSchemaType>) {
|
||||
state.primary = false
|
||||
state.categoryId = props.category?.id
|
||||
}
|
||||
|
||||
const { loading, search } = useIcons()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -59,7 +61,31 @@ async function handleCreate(event: FormSubmitEvent<CreateTabSchemaType>) {
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Icon " name="icon">
|
||||
<UInput v-model="state.icon" type="text" />
|
||||
<USelectMenu
|
||||
v-model="state.icon"
|
||||
:loading="loading"
|
||||
:searchable="search"
|
||||
placeholder="Select an icon"
|
||||
searchable-placeholder="Search an icon"
|
||||
>
|
||||
<template #label>
|
||||
<div v-if="state.icon" class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${state.icon}`" />
|
||||
<span class="truncate">{{ state.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${option}`" />
|
||||
<span class="truncate">{{ option }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
Enter an icon name, keyword or tag
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Color " name="color">
|
||||
|
||||
@@ -8,12 +8,13 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits(['closeModal'])
|
||||
const { updateCategory } = await useCategories()
|
||||
const { loading, search } = useIcons()
|
||||
|
||||
const state = reactive({
|
||||
name: props.category?.name,
|
||||
icon: props.category?.icon,
|
||||
color: props.category?.color,
|
||||
nameVisible: props.category?.nameVisible,
|
||||
name: undefined,
|
||||
icon: undefined,
|
||||
color: COLORS[0],
|
||||
nameVisible: undefined,
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -60,9 +61,32 @@ async function handleUpdate(event: FormSubmitEvent<UpdateCategorySchema>) {
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Icon " name="icon">
|
||||
<UInput v-model="state.icon" type="text" />
|
||||
</UFormGroup>
|
||||
<USelectMenu
|
||||
v-model="state.icon"
|
||||
:loading="loading"
|
||||
:searchable="search"
|
||||
placeholder="Select an icon"
|
||||
searchable-placeholder="Search an icon"
|
||||
>
|
||||
<template #label>
|
||||
<div v-if="state.icon" class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${state.icon}`" />
|
||||
<span class="truncate">{{ state.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${option}`" />
|
||||
<span class="truncate">{{ option }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
Enter an icon name, keyword or tag
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup>
|
||||
<UCheckbox v-model="state.nameVisible" :color="state.color" label="Is the category name visible?" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
import type { COLORS, TabType, UpdateTabSchemaType } from '~~/types/types'
|
||||
import { UpdateTabSchema } from '~~/types/types'
|
||||
import type { TabType, UpdateTabSchemaType } from '~~/types/types'
|
||||
import { COLORS, UpdateTabSchema } from '~~/types/types'
|
||||
|
||||
const props = defineProps<{
|
||||
tab: TabType | null
|
||||
@@ -10,13 +10,14 @@ const props = defineProps<{
|
||||
const emit = defineEmits(['closeModal'])
|
||||
const { categories } = await useCategories()
|
||||
const { updateTab } = await useTabs()
|
||||
const { loading, search } = useIcons()
|
||||
|
||||
const state = reactive({
|
||||
name: props.tab?.name,
|
||||
icon: props.tab?.icon,
|
||||
color: props.tab?.color,
|
||||
primary: props.tab?.primary,
|
||||
categoryId: props.tab?.categoryId,
|
||||
name: undefined,
|
||||
icon: undefined,
|
||||
color: COLORS[0],
|
||||
primary: undefined,
|
||||
categoryId: undefined,
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -65,7 +66,31 @@ async function handleUpdate(event: FormSubmitEvent<UpdateTabSchemaType>) {
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Icon " name="icon">
|
||||
<UInput v-model="state.icon" type="text" />
|
||||
<USelectMenu
|
||||
v-model="state.icon"
|
||||
:loading="loading"
|
||||
:searchable="search"
|
||||
placeholder="Select an icon"
|
||||
searchable-placeholder="Search an icon"
|
||||
>
|
||||
<template #label>
|
||||
<div v-if="state.icon" class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${state.icon}`" />
|
||||
<span class="truncate">{{ state.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon size="20" :name="`i-${option}`" />
|
||||
<span class="truncate">{{ option }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
Enter an icon name, keyword or tag
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Category " name="category">
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { AppType } from '~~/types/types'
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
apps: AppType[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h1 class="font-bold text-xl mb-4">
|
||||
{{ title }}
|
||||
</h1>
|
||||
<div v-if="apps" class="grid grid-cols-1 auto-rows-auto sm:grid-cols-3 md:grid-cols-5 gap-4">
|
||||
<ULink v-for="app in apps" :key="app.name" :to="app.url" class="relative" target="_blank">
|
||||
<div v-show="app.primary === true" class="absolute flex h-4 w-4 -right-2 -top-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
|
||||
<span class="relative inline-flex rounded-full h-4 w-4 bg-green-500" />
|
||||
</div>
|
||||
<UCard
|
||||
:ui="{ body: { base: 'h-full' }, background: 'h-full duration-300 bg-white hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800' }"
|
||||
>
|
||||
<div class="flex gap-4 items-center h-full">
|
||||
<UBadge :color="app.color" class="p-2" variant="soft">
|
||||
<UIcon :name="app.icon" size="32" />
|
||||
</UBadge>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div v-if="app.nuxt" class="text-xl flex gap-1 items-center">
|
||||
<div class="flex">
|
||||
<p>{{ app.name }}</p>
|
||||
<p :class="`text-${app.color}-400 font-medium`">
|
||||
{{ app.nuxt }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :class="`text-${app.color}-400`" class="text-xl font-medium">
|
||||
<p>{{ app.name }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-1">
|
||||
<UBadge
|
||||
v-for="tag in app.tags"
|
||||
:key="tag.name"
|
||||
:color="tag.color"
|
||||
:label="tag.name"
|
||||
variant="soft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</ULink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -4,7 +4,7 @@ export async function useCategories() {
|
||||
const { data: categories, refresh }
|
||||
= await useAsyncData<CategoryType[]>(async () => await useRequestFetch()('/api/categories'))
|
||||
|
||||
async function getCategory(id: number) {
|
||||
async function getCategory(id: number): CategoryType {
|
||||
return categories.data.value.find(category => category.id === id)
|
||||
}
|
||||
|
||||
|
||||
18
app/composables/icons.ts
Normal file
18
app/composables/icons.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export function useIcons() {
|
||||
const loading = ref(false)
|
||||
async function search(query: string) {
|
||||
if (query) {
|
||||
loading.value = true
|
||||
}
|
||||
const response = await $fetch('/api/icons/search', {
|
||||
query: { query },
|
||||
})
|
||||
loading.value = false
|
||||
return response.icons
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
search,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CreateTabSchema, TabType, UpdateTabSchema } from '~~/types/types'
|
||||
import type { CreateTabSchema, type TabType, UpdateTabSchema } from '~~/types/types'
|
||||
|
||||
export async function useTabs() {
|
||||
const { data: tabs, refresh }
|
||||
@@ -32,6 +32,21 @@ export async function useTabs() {
|
||||
.catch(error => useErrorToast('Tab update failed!', `Error: ${error}`))
|
||||
}
|
||||
|
||||
async function setTabPrimary(tab, primary: boolean) {
|
||||
await $fetch(`/api/tabs/${tab.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
primary,
|
||||
categoryId: tab.categoryId,
|
||||
}),
|
||||
})
|
||||
.then(async () => {
|
||||
await refresh()
|
||||
useSuccessToast('Tab favorite toggled with success!')
|
||||
})
|
||||
.catch(error => useErrorToast('Cannot toggle Tab favorite!', `Error: ${error}`))
|
||||
}
|
||||
|
||||
async function deleteTab(id: number) {
|
||||
await $fetch(`/api/tabs/${id}`, {
|
||||
method: 'DELETE',
|
||||
@@ -47,5 +62,6 @@ export async function useTabs() {
|
||||
deleteTab,
|
||||
getTabsForCategory,
|
||||
updateTab,
|
||||
setTabPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
export function useUserLimit() {
|
||||
function hasUserFreePlan() {
|
||||
return true
|
||||
export async function useUserLimit() {
|
||||
const { user } = useUserSession()
|
||||
const { categories } = await useCategories()
|
||||
const { tabs } = await useTabs()
|
||||
|
||||
const hasPaidPlan = computed(() => user.value.subscription !== 'free')
|
||||
|
||||
function canCreateCategory() {
|
||||
if (hasPaidPlan.value)
|
||||
return true
|
||||
return categories.value.length < 3
|
||||
}
|
||||
|
||||
function getRemainingCategories() {
|
||||
if (!hasUserFreePlan())
|
||||
return -1
|
||||
return 3
|
||||
}
|
||||
|
||||
function getRemainingTabs(category_id: number) {
|
||||
if (!hasUserFreePlan())
|
||||
return -1
|
||||
return category_id * 3
|
||||
function canCreateTabInCategory(categoryId: number): boolean {
|
||||
if (hasPaidPlan.value)
|
||||
return true
|
||||
return tabs.filter(tab => tab.categoryId === categoryId).length < 5
|
||||
}
|
||||
|
||||
return {
|
||||
getRemainingCategories,
|
||||
getRemainingTabs,
|
||||
hasPaidPlan,
|
||||
userLimits,
|
||||
canCreateCategory,
|
||||
canCreateTabInCategory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CategoryType } from '~~/types/types'
|
||||
import CategoryHeader from '~/components/CategoryHeader.vue'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
@@ -14,6 +13,7 @@ onMounted(() => {
|
||||
const { user } = useUserSession()
|
||||
const { categories } = await useCategories()
|
||||
const { getTabsForCategory } = await useTabs()
|
||||
const { canCreateCategory } = await useUserLimit()
|
||||
|
||||
// Modals
|
||||
const createCategoryModal = ref(false)
|
||||
@@ -59,7 +59,11 @@ const items = [[
|
||||
]]
|
||||
|
||||
defineShortcuts({
|
||||
c: () => createCategoryModal.value = true,
|
||||
c: () => {
|
||||
if (canCreateCategory()) {
|
||||
createCategoryModal.value = true
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -77,6 +81,7 @@ defineShortcuts({
|
||||
</div>
|
||||
<div class="flex justify-end mb-8 gap-4">
|
||||
<UButton
|
||||
v-if="canCreateCategory()"
|
||||
icon="i-ph:folder-simple-plus-duotone"
|
||||
color="black"
|
||||
variant="solid"
|
||||
@@ -86,6 +91,19 @@ defineShortcuts({
|
||||
Create Category
|
||||
<UKbd>C</UKbd>
|
||||
</UButton>
|
||||
<UTooltip v-else text="You can't create more categories on free plan. ❌">
|
||||
<UButton
|
||||
icon="i-ph:folder-simple-plus-duotone"
|
||||
color="black"
|
||||
variant="solid"
|
||||
size="lg"
|
||||
disabled
|
||||
@click.prevent="createCategoryModal = true"
|
||||
>
|
||||
Create Category
|
||||
<UKbd>C</UKbd>
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<section v-if="categories">
|
||||
<div v-if="categories.length > 0" class="space-y-12">
|
||||
@@ -93,12 +111,12 @@ defineShortcuts({
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
>
|
||||
<CategoryHeader
|
||||
<AppCategory
|
||||
:dropdown-items="items"
|
||||
:category="category"
|
||||
@create-tab="openCreateTab(category)"
|
||||
/>
|
||||
<div v-if="getTabsForCategory(category.id).length > 0" class="grid grid-cols-1 auto-rows-auto sm:grid-cols-3 md:grid-cols-5 gap-4">
|
||||
<div v-if="getTabsForCategory(category.id).length > 0" class="grid grid-cols-1 auto-rows-auto sm:grid-cols-3 md:grid-cols-4 gap-4">
|
||||
<AppTab
|
||||
v-for="tab in getTabsForCategory(category.id)"
|
||||
:key="tab.id"
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { user, loggedIn, clear } = useUserSession()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
User: {{ user }}
|
||||
</div>
|
||||
<div>
|
||||
LoggedIn: {{ loggedIn }}
|
||||
</div>
|
||||
<div @click="clear">
|
||||
clear
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user