feat: improvements on ui
This commit is contained in:
parent
9f67f4042d
commit
1b7e161daa
58 changed files with 331 additions and 277 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -16,7 +16,6 @@
|
|||
"embla-carousel-vue": "^8.6.0",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"laravel-vue-i18n": "^2.8.0",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"maska": "^3.2.0",
|
||||
"reka-ui": "^2.7.0",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
|
|
@ -5439,15 +5438,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lucide-vue-next": {
|
||||
"version": "0.468.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.468.0.tgz",
|
||||
"integrity": "sha512-quV/6T8YB1XK0VOEnebg3Byd8Rsan5/m95cvjnuHV4vcS3qEnLAybkrSh0hk3ppavx+V7R1PjNW+mGDvcBdz4A==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@
|
|||
"embla-carousel-vue": "^8.6.0",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"laravel-vue-i18n": "^2.8.0",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"maska": "^3.2.0",
|
||||
"reka-ui": "^2.7.0",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { AlertCircle } from 'lucide-vue-next';
|
||||
import { IconAlertCircle } from '@tabler/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
|
|
@ -18,7 +18,7 @@ const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
|
|||
|
||||
<template>
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle class="size-4" />
|
||||
<IconAlertCircle class="size-4" />
|
||||
<AlertTitle>{{ title }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { InertiaLinkProps } from '@inertiajs/vue3';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { Calendar, Menu, Settings, Share2, Users } from 'lucide-vue-next';
|
||||
import { IconCalendar, IconMenu2, IconSettings, IconShare, IconUsers } from '@tabler/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||
|
|
@ -55,22 +55,22 @@ const mainNavItems: NavItem[] = [
|
|||
{
|
||||
title: 'Calendar',
|
||||
href: '/calendar',
|
||||
icon: Calendar,
|
||||
icon: IconCalendar,
|
||||
},
|
||||
{
|
||||
title: 'Accounts',
|
||||
href: '/accounts',
|
||||
icon: Share2,
|
||||
icon: IconShare,
|
||||
},
|
||||
{
|
||||
title: 'Members',
|
||||
href: '/members',
|
||||
icon: Users,
|
||||
icon: IconUsers,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
href: '/settings',
|
||||
icon: Settings,
|
||||
icon: IconSettings,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
|
@ -84,7 +84,7 @@ const mainNavItems: NavItem[] = [
|
|||
<Sheet>
|
||||
<SheetTrigger :as-child="true">
|
||||
<Button variant="ghost" size="icon" class="mr-2 h-9 w-9">
|
||||
<Menu class="h-5 w-5" />
|
||||
<IconMenu2 class="h-5 w-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" class="w-[300px] p-6">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Link, router, usePage } from '@inertiajs/vue3';
|
||||
import { IconCalendar, IconCheck, IconSelector, IconFileText, IconHash, IconLogout, IconPlus, IconSettings, IconShare2, IconPencil, IconFileCheck, IconTag, IconUser, IconClock, IconMessageCircle, IconBell, IconBook, IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-vue';
|
||||
import { IconBrandDiscord, IconCalendar, IconCheck, IconSelector, IconFileText, IconHash, IconLogout, IconPlus, IconSettings, IconAffiliate, IconPencil, IconFileCheck, IconTag, IconUser, IconClock, IconMessageCircle, IconBell, IconBook, IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { create as createPost } from '@/actions/App/Http/Controllers/PostController';
|
||||
import { computed } from 'vue';
|
||||
|
|
@ -102,7 +102,7 @@ const configNavItems = computed(() => {
|
|||
{
|
||||
title: 'Connections',
|
||||
href: accounts.url(),
|
||||
icon: IconShare2,
|
||||
icon: IconAffiliate,
|
||||
},
|
||||
{
|
||||
title: 'Hashtags',
|
||||
|
|
@ -128,6 +128,11 @@ const configNavItems = computed(() => {
|
|||
});
|
||||
|
||||
const supportNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Discord',
|
||||
href: 'https://trypost.it/discord',
|
||||
icon: IconBrandDiscord,
|
||||
},
|
||||
{
|
||||
title: 'Share feedback',
|
||||
href: 'https://github.com/trypost-it/trypost/discussions',
|
||||
|
|
@ -156,7 +161,8 @@ function createWorkspace() {
|
|||
}
|
||||
|
||||
function isActive(href: string): boolean {
|
||||
return page.url.startsWith(href);
|
||||
// Exact match or match with query string
|
||||
return page.url === href || page.url.startsWith(href + '?');
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
|
|
@ -203,7 +209,7 @@ function handleLogout() {
|
|||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ auth.user.name }}</span>
|
||||
<span class="truncate text-xs text-muted-foreground">{{ auth.user.email
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
|
|
|
|||
|
|
@ -174,27 +174,15 @@ const displayText = computed(() => {
|
|||
<template>
|
||||
<Popover v-model:open="popoverOpen">
|
||||
<PopoverTrigger as-child :disabled="disabled">
|
||||
<Button
|
||||
:id="name"
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="justify-between text-left font-normal"
|
||||
:class="{ 'text-muted-foreground': !displayText }"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<Button :id="name" type="button" variant="outline" class="justify-between text-left font-normal"
|
||||
:class="{ 'text-muted-foreground': !displayText }" :disabled="disabled">
|
||||
<span>{{ displayText || placeholder }}</span>
|
||||
<IconCalendar class="ml-2 h-4 w-4" />
|
||||
<IconCalendar class="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0" :align="align">
|
||||
<Calendar
|
||||
v-model="internalDate"
|
||||
:placeholder="internalDate"
|
||||
layout="month-and-year"
|
||||
locale="en-US"
|
||||
calendar-label="Date picker"
|
||||
initial-focus
|
||||
/>
|
||||
<Calendar v-model="internalDate" :placeholder="internalDate" layout="month-and-year" locale="en-US"
|
||||
calendar-label="Date picker" initial-focus />
|
||||
<!-- Time Picker -->
|
||||
<div v-if="showTime" class="border-t p-3">
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -223,4 +211,4 @@ const displayText = computed(() => {
|
|||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import * as icons from 'lucide-vue-next';
|
||||
import * as icons from '@tabler/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
|
@ -21,7 +21,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
const className = computed(() => cn('h-4 w-4', props.class));
|
||||
|
||||
const icon = computed(() => {
|
||||
const iconName = props.name.charAt(0).toUpperCase() + props.name.slice(1);
|
||||
// Tabler icons are prefixed with 'Icon', e.g., 'IconUser', 'IconSettings'
|
||||
const iconName = 'Icon' + props.name.charAt(0).toUpperCase() + props.name.slice(1);
|
||||
return (icons as Record<string, any>)[iconName];
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { ChevronsUpDown } from 'lucide-vue-next';
|
||||
import { IconSelector } from '@tabler/icons-vue';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -33,7 +33,7 @@ const { isMobile, state } = useSidebar();
|
|||
data-test="sidebar-menu-button"
|
||||
>
|
||||
<UserInfo :user="user" />
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
<IconSelector class="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import axios from 'axios';
|
||||
import { Trash2, Upload, User } from 'lucide-vue-next';
|
||||
import { IconTrash, IconUpload, IconUser } from '@tabler/icons-vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -164,7 +164,7 @@ const closeDialog = () => {
|
|||
roundedClasses[rounded],
|
||||
]"
|
||||
>
|
||||
<User class="h-8 w-8 text-muted-foreground" />
|
||||
<IconUser class="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ const closeDialog = () => {
|
|||
:disabled="uploading"
|
||||
@click="openDialog"
|
||||
>
|
||||
<Upload class="h-4 w-4" />
|
||||
<IconUpload class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -213,7 +213,7 @@ const closeDialog = () => {
|
|||
class="px-2"
|
||||
@click="removePhoto"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -232,7 +232,7 @@ const closeDialog = () => {
|
|||
|
||||
<div class="flex flex-col items-center gap-4 py-8">
|
||||
<div class="rounded-full bg-muted p-6">
|
||||
<Upload class="h-12 w-12 text-muted-foreground" />
|
||||
<IconUpload class="h-12 w-12 text-muted-foreground" />
|
||||
</div>
|
||||
<p class="text-center text-sm text-muted-foreground">
|
||||
Select an image from your computer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { AlertCircle, Check, ExternalLink, RefreshCw, Trash2 } from 'lucide-vue-next';
|
||||
import { IconAlertCircle, IconCheck, IconExternalLink, IconRefresh, IconTrash } from '@tabler/icons-vue';
|
||||
import { computed, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
|
|
@ -154,13 +154,13 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
v-if="platform.connected && !isDisconnected(platform.account)"
|
||||
class="absolute -bottom-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white ring-2 ring-white dark:ring-gray-900"
|
||||
>
|
||||
<Check class="h-3 w-3" />
|
||||
<IconCheck class="h-3 w-3" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="platform.connected && isDisconnected(platform.account)"
|
||||
class="absolute -bottom-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-white ring-2 ring-white dark:ring-gray-900"
|
||||
>
|
||||
<AlertCircle class="h-3 w-3" />
|
||||
<IconAlertCircle class="h-3 w-3" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
|
|
@ -180,7 +180,7 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
<div v-if="platform.connected && platform.account" class="border-t px-4 py-3">
|
||||
<!-- Disconnected Warning -->
|
||||
<div v-if="isDisconnected(platform.account)" class="mb-3 flex items-start gap-2 rounded-lg bg-red-100 p-2 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-400">
|
||||
<AlertCircle class="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<IconAlertCircle class="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium">Connection lost</p>
|
||||
<p v-if="platform.account.error_message" class="text-xs truncate opacity-80">
|
||||
|
|
@ -210,7 +210,7 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
@click="openOAuthPopup(platform.value)"
|
||||
class="p-2 text-amber-600 hover:text-amber-700 transition-colors"
|
||||
>
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
<IconRefresh class="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -226,7 +226,7 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
target="_blank"
|
||||
class="p-2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<ExternalLink class="h-4 w-4" />
|
||||
<IconExternalLink class="h-4 w-4" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -241,7 +241,7 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
@click="emit('disconnect', platform.account.id)"
|
||||
class="p-2 text-muted-foreground hover:text-red-600 transition-colors"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Link, router } from '@inertiajs/vue3';
|
||||
import { LogOut, Settings } from 'lucide-vue-next';
|
||||
import { IconLogout, IconSettings } from '@tabler/icons-vue';
|
||||
|
||||
import {
|
||||
DropdownMenuGroup,
|
||||
|
|
@ -34,7 +34,7 @@ defineProps<Props>();
|
|||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem :as-child="true">
|
||||
<Link class="block w-full cursor-pointer" :href="edit()" prefetch>
|
||||
<Settings class="mr-2 h-4 w-4" />
|
||||
<IconSettings class="mr-2 h-4 w-4" />
|
||||
Settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -48,7 +48,7 @@ defineProps<Props>();
|
|||
as="button"
|
||||
data-test="logout-button"
|
||||
>
|
||||
<LogOut class="mr-2 h-4 w-4" />
|
||||
<IconLogout class="mr-2 h-4 w-4" />
|
||||
Log out
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { router, usePage } from '@inertiajs/vue3';
|
||||
import { Check, ChevronsUpDown, Plus } from 'lucide-vue-next';
|
||||
import { IconCheck, IconPlus, IconSelector } from '@tabler/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -39,7 +39,7 @@ function createWorkspace() {
|
|||
<DropdownMenuTrigger :as-child="true">
|
||||
<Button variant="outline" class="w-full justify-between gap-2 px-3" :class="{ 'text-muted-foreground': !currentWorkspace }">
|
||||
<span class="truncate">{{ currentWorkspace?.name || 'Select workspace' }}</span>
|
||||
<ChevronsUpDown class="h-4 w-4 shrink-0 opacity-50" />
|
||||
<IconSelector class="h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" class="w-56">
|
||||
|
|
@ -53,12 +53,12 @@ function createWorkspace() {
|
|||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<span class="truncate">{{ workspace.name }}</span>
|
||||
<Check v-if="currentWorkspace?.id === workspace.id" class="h-4 w-4 shrink-0" />
|
||||
<IconCheck v-if="currentWorkspace?.id === workspace.id" class="h-4 w-4 shrink-0" />
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem class="cursor-pointer" @click="createWorkspace">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
Create workspace
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { MoreHorizontal } from "lucide-vue-next"
|
||||
import { IconDots } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -16,7 +16,7 @@ const props = defineProps<{
|
|||
:class="cn('flex size-9 items-center justify-center', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<MoreHorizontal class="size-4" />
|
||||
<IconDots class="size-4" />
|
||||
</slot>
|
||||
<span class="sr-only">More</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import { IconChevronRight } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -16,7 +16,7 @@ const props = defineProps<{
|
|||
:class="cn('[&>svg]:size-3.5', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRight />
|
||||
<IconChevronRight />
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { CalendarNextProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import { IconChevronRight } from "@tabler/icons-vue"
|
||||
import { CalendarNext, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
|
@ -25,7 +25,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRight class="size-4" />
|
||||
<IconChevronRight class="size-4" />
|
||||
</slot>
|
||||
</CalendarNext>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { CalendarPrevProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronLeft } from "lucide-vue-next"
|
||||
import { IconChevronLeft } from "@tabler/icons-vue"
|
||||
import { CalendarPrev, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
|
@ -25,7 +25,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronLeft class="size-4" />
|
||||
<IconChevronLeft class="size-4" />
|
||||
</slot>
|
||||
</CalendarPrev>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { WithClassAsProps } from "./interface"
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { ArrowRight } from "lucide-vue-next"
|
||||
import { IconArrowRight } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useCarousel } from "./useCarousel"
|
||||
|
|
@ -34,7 +34,7 @@ const { orientation, canScrollNext, scrollNext } = useCarousel()
|
|||
@click="scrollNext"
|
||||
>
|
||||
<slot>
|
||||
<ArrowRight />
|
||||
<IconArrowRight />
|
||||
<span class="sr-only">Next Slide</span>
|
||||
</slot>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { WithClassAsProps } from "./interface"
|
||||
import type { ButtonVariants } from '@/components/ui/button'
|
||||
import { ArrowLeft } from "lucide-vue-next"
|
||||
import { IconArrowLeft } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useCarousel } from "./useCarousel"
|
||||
|
|
@ -34,7 +34,7 @@ const { orientation, canScrollPrev, scrollPrev } = useCarousel()
|
|||
@click="scrollPrev"
|
||||
>
|
||||
<slot>
|
||||
<ArrowLeft />
|
||||
<IconArrowLeft />
|
||||
<span class="sr-only">Previous Slide</span>
|
||||
</slot>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { CheckboxRootEmits, CheckboxRootProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Check } from "lucide-vue-next"
|
||||
import { IconCheck } from "@tabler/icons-vue"
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
class="grid place-content-center text-current transition-none"
|
||||
>
|
||||
<slot v-bind="slotProps">
|
||||
<Check class="size-3.5" />
|
||||
<IconCheck class="size-3.5" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
</CheckboxRoot>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { ListboxFilterProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Search } from "lucide-vue-next"
|
||||
import { IconSearch } from "@tabler/icons-vue"
|
||||
import { ListboxFilter, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useCommand } from "."
|
||||
|
|
@ -27,7 +27,7 @@ const { filterState } = useCommand()
|
|||
data-slot="command-input-wrapper"
|
||||
class="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<Search class="size-4 shrink-0 opacity-50" />
|
||||
<IconSearch class="size-4 shrink-0 opacity-50" />
|
||||
<ListboxFilter
|
||||
v-bind="{ ...forwardedProps, ...$attrs }"
|
||||
v-model="filterState.search"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { ContextMenuCheckboxItemEmits, ContextMenuCheckboxItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Check } from "lucide-vue-next"
|
||||
import { IconCheck } from "@tabler/icons-vue"
|
||||
import {
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuItemIndicator,
|
||||
|
|
@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Check class="size-4" />
|
||||
<IconCheck class="size-4" />
|
||||
</slot>
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { ContextMenuRadioItemEmits, ContextMenuRadioItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Circle } from "lucide-vue-next"
|
||||
import { IconCircleFilled } from "@tabler/icons-vue"
|
||||
import {
|
||||
ContextMenuItemIndicator,
|
||||
ContextMenuRadioItem,
|
||||
|
|
@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Circle class="size-2 fill-current" />
|
||||
<IconCircleFilled class="size-2" />
|
||||
</slot>
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { ContextMenuSubTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import { IconChevronRight } from "@tabler/icons-vue"
|
||||
import {
|
||||
ContextMenuSubTrigger,
|
||||
useForwardProps,
|
||||
|
|
@ -27,6 +27,6 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto" />
|
||||
<IconChevronRight class="ml-auto" />
|
||||
</ContextMenuSubTrigger>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import { IconX } from "@tabler/icons-vue"
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
|
|
@ -45,7 +45,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
data-slot="dialog-close"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<X />
|
||||
<IconX />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import { IconX } from "@tabler/icons-vue"
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
|
|
@ -50,7 +50,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<DialogClose
|
||||
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
|
||||
>
|
||||
<X class="w-4 h-4" />
|
||||
<IconX class="w-4 h-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Check } from "lucide-vue-next"
|
||||
import { IconCheck } from "@tabler/icons-vue"
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuItemIndicator,
|
||||
|
|
@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Check class="size-4" />
|
||||
<IconCheck class="size-4" />
|
||||
</slot>
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Circle } from "lucide-vue-next"
|
||||
import { IconCircleFilled } from "@tabler/icons-vue"
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
|
|
@ -31,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Circle class="size-2 fill-current" />
|
||||
<IconCircleFilled class="size-2" />
|
||||
</slot>
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DropdownMenuSubTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import { IconChevronRight } from "@tabler/icons-vue"
|
||||
import {
|
||||
DropdownMenuSubTrigger,
|
||||
useForwardProps,
|
||||
|
|
@ -25,6 +25,6 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto size-4" />
|
||||
<IconChevronRight class="ml-auto size-4" />
|
||||
</DropdownMenuSubTrigger>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { MinusIcon } from "lucide-vue-next"
|
||||
import { IconMinus } from "@tabler/icons-vue"
|
||||
import { useForwardProps } from "reka-ui"
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
|
|
@ -15,7 +15,7 @@ const forwarded = useForwardProps(props)
|
|||
v-bind="forwarded"
|
||||
>
|
||||
<slot>
|
||||
<MinusIcon />
|
||||
<IconMinus />
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { AcceptableValue } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit, useVModel } from "@vueuse/core"
|
||||
import { ChevronDownIcon } from "lucide-vue-next"
|
||||
import { IconChevronDown } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
defineOptions({
|
||||
|
|
@ -41,7 +41,7 @@ const delegatedProps = reactiveOmit(props, "class")
|
|||
>
|
||||
<slot />
|
||||
</select>
|
||||
<ChevronDownIcon
|
||||
<IconChevronDown
|
||||
class="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
|
||||
aria-hidden="true"
|
||||
data-slot="native-select-icon"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { NavigationMenuTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronDown } from "lucide-vue-next"
|
||||
import { IconChevronDown } from "@tabler/icons-vue"
|
||||
import {
|
||||
NavigationMenuTrigger,
|
||||
useForwardProps,
|
||||
|
|
@ -24,7 +24,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
:class="cn(navigationMenuTriggerStyle(), 'group', props.class)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronDown
|
||||
<IconChevronDown
|
||||
class="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { RadioGroupItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { CircleIcon } from "lucide-vue-next"
|
||||
import { IconCircleFilled } from "@tabler/icons-vue"
|
||||
import {
|
||||
RadioGroupIndicator,
|
||||
RadioGroupItem,
|
||||
|
|
@ -33,7 +33,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
class="relative flex items-center justify-center"
|
||||
>
|
||||
<slot>
|
||||
<CircleIcon class="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
<IconCircleFilled class="text-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</slot>
|
||||
</RadioGroupIndicator>
|
||||
</RadioGroupItem>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { RangeCalendarNextProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import { IconChevronRight } from "@tabler/icons-vue"
|
||||
import { RangeCalendarNext, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
|
@ -26,7 +26,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRight class="size-4" />
|
||||
<IconChevronRight class="size-4" />
|
||||
</slot>
|
||||
</RangeCalendarNext>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { RangeCalendarPrevProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronLeft } from "lucide-vue-next"
|
||||
import { IconChevronLeft } from "@tabler/icons-vue"
|
||||
import { RangeCalendarPrev, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
|
@ -26,7 +26,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronLeft class="size-4" />
|
||||
<IconChevronLeft class="size-4" />
|
||||
</slot>
|
||||
</RangeCalendarPrev>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { SplitterResizeHandleEmits, SplitterResizeHandleProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { GripVertical } from "lucide-vue-next"
|
||||
import { IconGripVertical } from "@tabler/icons-vue"
|
||||
import { SplitterResizeHandle, useForwardPropsEmits } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<template v-if="props.withHandle">
|
||||
<div class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||
<slot>
|
||||
<GripVertical class="size-2.5" />
|
||||
<IconGripVertical class="size-2.5" />
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { SelectItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Check } from "lucide-vue-next"
|
||||
import { IconCheck } from "@tabler/icons-vue"
|
||||
import {
|
||||
SelectItem,
|
||||
SelectItemIndicator,
|
||||
|
|
@ -32,7 +32,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Check class="size-4" />
|
||||
<IconCheck class="size-4" />
|
||||
</slot>
|
||||
</SelectItemIndicator>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { SelectScrollDownButtonProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronDown } from "lucide-vue-next"
|
||||
import { IconChevronDown } from "@tabler/icons-vue"
|
||||
import { SelectScrollDownButton, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronDown class="size-4" />
|
||||
<IconChevronDown class="size-4" />
|
||||
</slot>
|
||||
</SelectScrollDownButton>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { SelectScrollUpButtonProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronUp } from "lucide-vue-next"
|
||||
import { IconChevronUp } from "@tabler/icons-vue"
|
||||
import { SelectScrollUpButton, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronUp class="size-4" />
|
||||
<IconChevronUp class="size-4" />
|
||||
</slot>
|
||||
</SelectScrollUpButton>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { SelectTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronDown } from "lucide-vue-next"
|
||||
import { IconChevronDown } from "@tabler/icons-vue"
|
||||
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
>
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="size-4 opacity-50" />
|
||||
<IconChevronDown class="size-4 opacity-50" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import { IconX } from "@tabler/icons-vue"
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
|
|
@ -54,7 +54,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
<DialogClose
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
|
||||
>
|
||||
<X class="size-4" />
|
||||
<IconX class="size-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { PanelLeftClose, PanelLeftOpen } from "lucide-vue-next"
|
||||
import { IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useSidebar } from "./utils"
|
||||
|
|
@ -21,8 +21,8 @@ const { isMobile, state, toggleSidebar } = useSidebar()
|
|||
:class="cn('h-7 w-7', props.class)"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<PanelLeftOpen v-if="isMobile || state === 'collapsed'" />
|
||||
<PanelLeftClose v-else />
|
||||
<IconLayoutSidebarLeftExpand v-if="isMobile || state === 'collapsed'" />
|
||||
<IconLayoutSidebarLeftCollapse v-else />
|
||||
<span class="sr-only">Toggle Sidebar</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ToasterProps } from "vue-sonner"
|
||||
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from "lucide-vue-next"
|
||||
import { IconCircleCheck, IconInfoCircle, IconLoader2, IconAlertOctagon, IconAlertTriangle, IconX } from "@tabler/icons-vue"
|
||||
import { Toaster as Sonner } from "vue-sonner"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -19,24 +19,24 @@ const props = defineProps<ToasterProps>()
|
|||
v-bind="props"
|
||||
>
|
||||
<template #success-icon>
|
||||
<CircleCheckIcon class="size-4" />
|
||||
<IconCircleCheck class="size-4" />
|
||||
</template>
|
||||
<template #info-icon>
|
||||
<InfoIcon class="size-4" />
|
||||
<IconInfoCircle class="size-4" />
|
||||
</template>
|
||||
<template #warning-icon>
|
||||
<TriangleAlertIcon class="size-4" />
|
||||
<IconAlertTriangle class="size-4" />
|
||||
</template>
|
||||
<template #error-icon>
|
||||
<OctagonXIcon class="size-4" />
|
||||
<IconAlertOctagon class="size-4" />
|
||||
</template>
|
||||
<template #loading-icon>
|
||||
<div>
|
||||
<Loader2Icon class="size-4 animate-spin" />
|
||||
<IconLoader2 class="size-4 animate-spin" />
|
||||
</div>
|
||||
</template>
|
||||
<template #close-icon>
|
||||
<XIcon class="size-4" />
|
||||
<IconX class="size-4" />
|
||||
</template>
|
||||
</Sonner>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { Loader2Icon } from "lucide-vue-next"
|
||||
import { IconLoader2 } from "@tabler/icons-vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -9,7 +9,7 @@ const props = defineProps<{
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Loader2Icon
|
||||
<IconLoader2
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
:class="cn('size-4 animate-spin', props.class)"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { TagsInputItemDeleteProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import { IconX } from "@tabler/icons-vue"
|
||||
import { TagsInputItemDelete, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
<template>
|
||||
<TagsInputItemDelete v-bind="forwardedProps" :class="cn('flex rounded bg-transparent mr-1', props.class)">
|
||||
<slot>
|
||||
<X class="w-4 h-4" />
|
||||
<IconX class="w-4 h-4" />
|
||||
</slot>
|
||||
</TagsInputItemDelete>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { Facebook, Check } from 'lucide-vue-next';
|
||||
import { IconBrandFacebook, IconCheck } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
|
@ -62,7 +62,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
|
||||
<div v-if="pages.length === 0 && !error" class="text-center py-12">
|
||||
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-muted">
|
||||
<Facebook class="h-7 w-7 text-muted-foreground" />
|
||||
<IconBrandFacebook class="h-7 w-7 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 class="mt-4 text-lg font-semibold">No pages found</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
|
|
@ -81,7 +81,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="page.picture" :src="page.picture" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-blue-100 dark:bg-blue-900">
|
||||
<Facebook class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
<IconBrandFacebook class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="flex-1 min-w-0">
|
||||
|
|
@ -97,7 +97,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
<div
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground"
|
||||
>
|
||||
<Check class="h-4 w-4" />
|
||||
<IconCheck class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { Building2, Check } from 'lucide-vue-next';
|
||||
import { IconBuilding, IconCheck } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
|
@ -65,7 +65,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
|
||||
<div v-if="organizations.length === 0 && !error" class="text-center py-12">
|
||||
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-muted">
|
||||
<Building2 class="h-7 w-7 text-muted-foreground" />
|
||||
<IconBuilding class="h-7 w-7 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 class="mt-4 text-lg font-semibold">No pages found</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
|
|
@ -84,7 +84,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="org.logo" :src="org.logo" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-blue-100 dark:bg-blue-900">
|
||||
<Building2 class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
<IconBuilding class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="flex-1 min-w-0">
|
||||
|
|
@ -100,7 +100,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
<div
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground"
|
||||
>
|
||||
<Check class="h-4 w-4" />
|
||||
<IconCheck class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { CreditCard, FileText, Building2, ExternalLink, Sparkles } from 'lucide-vue-next';
|
||||
import { IconCreditCard, IconFileText, IconBuilding, IconExternalLink, IconSparkles } from '@tabler/icons-vue';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
|
@ -86,7 +86,7 @@ function getStatusVariant(status: string): 'default' | 'secondary' | 'destructiv
|
|||
</div>
|
||||
|
||||
<Alert v-if="onTrial" class="border-primary/50 bg-primary/5">
|
||||
<Sparkles class="h-4 w-4 text-primary" />
|
||||
<IconSparkles class="h-4 w-4 text-primary" />
|
||||
<AlertTitle>Trial period active</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your trial ends on <strong>{{ trialEndsAt }}</strong>.
|
||||
|
|
@ -99,7 +99,7 @@ function getStatusVariant(status: string): 'default' | 'secondary' | 'destructiv
|
|||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Building2 class="h-5 w-5" />
|
||||
<IconBuilding class="h-5 w-5" />
|
||||
Your Subscription
|
||||
</CardTitle>
|
||||
<Badge :variant="getStatusVariant(subscription?.stripe_status || '')">
|
||||
|
|
@ -120,7 +120,7 @@ function getStatusVariant(status: string): 'default' | 'secondary' | 'destructiv
|
|||
</div>
|
||||
|
||||
<div v-if="defaultPaymentMethod" class="flex items-center gap-3 p-3 border rounded-lg">
|
||||
<CreditCard class="h-8 w-8 text-muted-foreground" />
|
||||
<IconCreditCard class="h-8 w-8 text-muted-foreground" />
|
||||
<div>
|
||||
<p class="font-medium capitalize">{{ defaultPaymentMethod.brand }} **** {{ defaultPaymentMethod.last4 }}</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
|
|
@ -137,7 +137,7 @@ function getStatusVariant(status: string): 'default' | 'secondary' | 'destructiv
|
|||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button @click="openPortal" variant="outline" class="w-full">
|
||||
<ExternalLink class="mr-2 h-4 w-4" />
|
||||
<IconExternalLink class="mr-2 h-4 w-4" />
|
||||
Manage on Stripe
|
||||
</Button>
|
||||
</CardFooter>
|
||||
|
|
@ -146,7 +146,7 @@ function getStatusVariant(status: string): 'default' | 'secondary' | 'destructiv
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="h-5 w-5" />
|
||||
<IconFileText class="h-5 w-5" />
|
||||
Invoices
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, router } from '@inertiajs/vue3';
|
||||
import { useEcho } from '@laravel/echo-vue';
|
||||
import { Loader2, CheckCircle, XCircle } from 'lucide-vue-next';
|
||||
import { IconLoader2, IconCircleCheck, IconCircleX } from '@tabler/icons-vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -62,7 +62,7 @@ function retry() {
|
|||
<!-- Processing -->
|
||||
<template v-if="currentStatus === 'processing'">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-primary/10 mb-6">
|
||||
<Loader2 class="w-10 h-10 text-primary animate-spin" />
|
||||
<IconLoader2 class="w-10 h-10 text-primary animate-spin" />
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold tracking-tight mb-3">Processing your subscription</h1>
|
||||
<p class="text-muted-foreground">
|
||||
|
|
@ -73,7 +73,7 @@ function retry() {
|
|||
<!-- Success -->
|
||||
<template v-else-if="currentStatus === 'success'">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-green-100 mb-6">
|
||||
<CheckCircle class="w-10 h-10 text-green-600" />
|
||||
<IconCircleCheck class="w-10 h-10 text-green-600" />
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold tracking-tight mb-3">You're all set!</h1>
|
||||
<p class="text-muted-foreground">
|
||||
|
|
@ -84,7 +84,7 @@ function retry() {
|
|||
<!-- Cancelled -->
|
||||
<template v-else-if="currentStatus === 'cancelled'">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-red-100 mb-6">
|
||||
<XCircle class="w-10 h-10 text-red-600" />
|
||||
<IconCircleX class="w-10 h-10 text-red-600" />
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold tracking-tight mb-3">Checkout cancelled</h1>
|
||||
<p class="text-muted-foreground mb-6">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, router } from '@inertiajs/vue3';
|
||||
import { Sparkles, Calendar, Users, ImageIcon, Video, Clock, BarChart3 } from 'lucide-vue-next';
|
||||
import { IconSparkles, IconCalendar, IconUsers, IconPhoto, IconVideo, IconClock, IconChartBar } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -31,12 +31,12 @@ const platforms = [
|
|||
];
|
||||
|
||||
const features = [
|
||||
{ icon: Calendar, title: 'Visual Calendar', description: 'Plan and schedule your content with an intuitive drag-and-drop calendar' },
|
||||
{ icon: Clock, title: 'Unlimited Scheduling', description: 'Schedule as many posts as you want, whenever you want' },
|
||||
{ icon: ImageIcon, title: 'Images & Carousels', description: 'Share single images or create engaging carousel posts' },
|
||||
{ icon: Video, title: 'Video Publishing', description: 'Upload and publish videos across all your social accounts' },
|
||||
{ icon: Users, title: 'Team Collaboration', description: 'Invite your team members and work together seamlessly' },
|
||||
{ icon: BarChart3, title: 'Analytics', description: 'Track your post performance and engagement metrics' },
|
||||
{ icon: IconCalendar, title: 'Visual Calendar', description: 'Plan and schedule your content with an intuitive drag-and-drop calendar' },
|
||||
{ icon: IconClock, title: 'Unlimited Scheduling', description: 'Schedule as many posts as you want, whenever you want' },
|
||||
{ icon: IconPhoto, title: 'Images & Carousels', description: 'Share single images or create engaging carousel posts' },
|
||||
{ icon: IconVideo, title: 'Video Publishing', description: 'Upload and publish videos across all your social accounts' },
|
||||
{ icon: IconUsers, title: 'Team Collaboration', description: 'Invite your team members and work together seamlessly' },
|
||||
{ icon: IconChartBar, title: 'Analytics', description: 'Track your post performance and engagement metrics' },
|
||||
];
|
||||
</script>
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ const features = [
|
|||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-2xl bg-primary/10 mb-6">
|
||||
<Sparkles class="w-10 h-10 text-primary" />
|
||||
<IconSparkles class="w-10 h-10 text-primary" />
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight mb-3">Welcome to TryPost!</h1>
|
||||
<p class="text-xl text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { Plus, Hash, Pencil, Trash2 } from 'lucide-vue-next';
|
||||
import { IconPlus, IconHash, IconPencil, IconTrash } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import ConfirmDeleteModal from '@/components/ConfirmDeleteModal.vue';
|
||||
|
|
@ -69,21 +69,21 @@ const getHashtagCount = (hashtags: string): number => {
|
|||
</p>
|
||||
</div>
|
||||
<Button @click="isCreateDialogOpen = true">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
New Group
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="hashtags.length === 0" class="flex flex-col items-center justify-center py-16">
|
||||
<div class="h-16 w-16 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<Hash class="h-8 w-8 text-muted-foreground" />
|
||||
<IconHash class="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">No hashtag groups yet</h3>
|
||||
<p class="text-muted-foreground mb-4 text-center max-w-sm">
|
||||
Create hashtag groups to quickly add popular hashtags to your posts
|
||||
</p>
|
||||
<Button @click="isCreateDialogOpen = true">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
Create your first group
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -100,7 +100,7 @@ const getHashtagCount = (hashtags: string): number => {
|
|||
class="h-8 w-8"
|
||||
@click="openEditDialog(hashtag)"
|
||||
>
|
||||
<Pencil class="h-4 w-4" />
|
||||
<IconPencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -108,7 +108,7 @@ const getHashtagCount = (hashtags: string): number => {
|
|||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
@click="handleDelete(hashtag.id)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { Plus, Tag, Pencil, Trash2 } from 'lucide-vue-next';
|
||||
import { IconPlus, IconTag, IconPencil, IconTrash } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import ConfirmDeleteModal from '@/components/ConfirmDeleteModal.vue';
|
||||
|
|
@ -65,21 +65,21 @@ const handleDelete = (labelId: string) => {
|
|||
</p>
|
||||
</div>
|
||||
<Button @click="isCreateDialogOpen = true">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
New Label
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="labels.length === 0" class="flex flex-col items-center justify-center py-16">
|
||||
<div class="h-16 w-16 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<Tag class="h-8 w-8 text-muted-foreground" />
|
||||
<IconTag class="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">No labels yet</h3>
|
||||
<p class="text-muted-foreground mb-4 text-center max-w-sm">
|
||||
Create labels to organize and categorize your posts
|
||||
</p>
|
||||
<Button @click="isCreateDialogOpen = true">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
Create your first label
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -102,7 +102,7 @@ const handleDelete = (labelId: string) => {
|
|||
class="h-8 w-8"
|
||||
@click="openEditDialog(label)"
|
||||
>
|
||||
<Pencil class="h-4 w-4" />
|
||||
<IconPencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -110,7 +110,7 @@ const handleDelete = (labelId: string) => {
|
|||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
@click="handleDelete(label.id)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, useForm } from '@inertiajs/vue3';
|
||||
import { Building, Building2, Rocket, Sparkles, Store, User } from 'lucide-vue-next';
|
||||
import { IconBuilding, IconRocket, IconSparkles, IconBuildingStore, IconUser } from '@tabler/icons-vue';
|
||||
|
||||
import { storeStep1 } from '@/actions/App/Http/Controllers/OnboardingController';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -23,13 +23,13 @@ const form = useForm({
|
|||
persona: '',
|
||||
});
|
||||
|
||||
const icons: Record<string, typeof Rocket> = {
|
||||
rocket: Rocket,
|
||||
sparkles: Sparkles,
|
||||
building: Building,
|
||||
'building-2': Building2,
|
||||
store: Store,
|
||||
user: User,
|
||||
const icons: Record<string, typeof IconRocket> = {
|
||||
rocket: IconRocket,
|
||||
sparkles: IconSparkles,
|
||||
building: IconBuilding,
|
||||
'building-2': IconBuilding,
|
||||
store: IconBuildingStore,
|
||||
user: IconUser,
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, Link, router } from '@inertiajs/vue3';
|
||||
import { ChevronLeft, ChevronRight, Plus } from 'lucide-vue-next';
|
||||
import { IconChevronLeft, IconChevronRight, IconPlus } from '@tabler/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -202,10 +202,10 @@ const formatTime = (scheduledAt: string): string => {
|
|||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-1">
|
||||
<Button variant="outline" size="icon" @click="navigate(-1)">
|
||||
<ChevronLeft class="h-4 w-4" />
|
||||
<IconChevronLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" @click="navigate(1)">
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
<IconChevronRight class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" @click="goToToday">
|
||||
|
|
@ -224,7 +224,7 @@ const formatTime = (scheduledAt: string): string => {
|
|||
</Tabs>
|
||||
<Link :href="createPost.url()">
|
||||
<Button>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
New Post
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -254,7 +254,7 @@ const formatTime = (scheduledAt: string): string => {
|
|||
<!-- Add Post Button -->
|
||||
<Link :href="createPost.url({ query: { date: day.format('YYYY-MM-DD') } })"
|
||||
class="flex items-center justify-center p-2 rounded border border-dashed border-muted-foreground/30 text-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-colors">
|
||||
<Plus class="h-4 w-4" />
|
||||
<IconPlus class="h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
<!-- Posts -->
|
||||
|
|
@ -319,7 +319,7 @@ const formatTime = (scheduledAt: string): string => {
|
|||
</span>
|
||||
<Link :href="createPost.url({ query: { date: day.format('YYYY-MM-DD') } })"
|
||||
class="opacity-0 group-hover:opacity-100 focus:opacity-100 p-1 rounded text-muted-foreground hover:text-primary hover:bg-primary/10 transition-all">
|
||||
<Plus class="h-4 w-4" />
|
||||
<IconPlus class="h-4 w-4" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,21 @@ import {
|
|||
IconBrandX,
|
||||
IconBrandYoutube,
|
||||
} from '@tabler/icons-vue';
|
||||
import { Clock, AlertCircle, Trash2, Send, Link2, MoreHorizontal, Plus, CheckCircle, ExternalLink, Loader2 } from 'lucide-vue-next';
|
||||
import { computed, ref, watch, type Component } from 'vue';
|
||||
import {
|
||||
IconClock,
|
||||
IconAlertCircle,
|
||||
IconTrash,
|
||||
IconSend,
|
||||
IconLink,
|
||||
IconDots,
|
||||
IconPlus,
|
||||
IconCircleCheck,
|
||||
IconExternalLink,
|
||||
IconLoader2,
|
||||
} from '@tabler/icons-vue';
|
||||
import { computed, onUnmounted, ref, watch, type Component } from 'vue';
|
||||
|
||||
import debounce from '@/debounce';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
|
|
@ -48,7 +61,8 @@ import { Switch } from '@/components/ui/switch';
|
|||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import dayjs from '@/dayjs';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { destroy as destroyPost, update as updatePost } from '@/routes/posts';
|
||||
import { destroy as destroyPost, index as postsIndex, update as updatePost } from '@/routes/posts';
|
||||
import { type BreadcrumbItemType } from '@/types';
|
||||
|
||||
interface SocialAccount {
|
||||
id: string;
|
||||
|
|
@ -118,6 +132,11 @@ interface Props {
|
|||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const breadcrumbs: BreadcrumbItemType[] = [
|
||||
{ title: 'Posts', href: postsIndex.url() },
|
||||
{ title: 'Edit Post', href: '#' },
|
||||
];
|
||||
|
||||
// Reactive state for real-time updates
|
||||
const post = ref(props.post);
|
||||
|
||||
|
|
@ -178,7 +197,12 @@ const {
|
|||
postPlatforms: postPlatformsRef,
|
||||
});
|
||||
|
||||
// Check if any platform is currently uploading
|
||||
const isAnyUploading = computed(() => Object.values(isUploading.value).some(v => v));
|
||||
|
||||
const isSubmitting = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const showSaved = ref(false);
|
||||
const deleteModal = ref<InstanceType<typeof ConfirmDeleteModal> | null>(null);
|
||||
const showEnableSyncDialog = ref(false);
|
||||
const showDisableSyncDialog = ref(false);
|
||||
|
|
@ -263,12 +287,12 @@ const getPlatformIcon = (platform: string): Component => {
|
|||
|
||||
const getStatusConfig = (status: string) => {
|
||||
const configs: Record<string, { label: string; color: string; icon: any }> = {
|
||||
'draft': { label: 'Draft', color: 'bg-gray-100 text-gray-800', icon: Clock },
|
||||
'scheduled': { label: 'Scheduled', color: 'bg-blue-100 text-blue-800', icon: Clock },
|
||||
'publishing': { label: 'Publishing', color: 'bg-yellow-100 text-yellow-800', icon: Loader2 },
|
||||
'published': { label: 'Published', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||
'partially_published': { label: 'Partially Published', color: 'bg-orange-100 text-orange-800', icon: AlertCircle },
|
||||
'failed': { label: 'Failed', color: 'bg-red-100 text-red-800', icon: AlertCircle },
|
||||
'draft': { label: 'Draft', color: 'bg-gray-100 text-gray-800', icon: IconClock },
|
||||
'scheduled': { label: 'Scheduled', color: 'bg-blue-100 text-blue-800', icon: IconClock },
|
||||
'publishing': { label: 'Publishing', color: 'bg-yellow-100 text-yellow-800', icon: IconLoader2 },
|
||||
'published': { label: 'Published', color: 'bg-green-100 text-green-800', icon: IconCircleCheck },
|
||||
'partially_published': { label: 'Partially Published', color: 'bg-orange-100 text-orange-800', icon: IconAlertCircle },
|
||||
'failed': { label: 'Failed', color: 'bg-red-100 text-red-800', icon: IconAlertCircle },
|
||||
};
|
||||
return configs[status] || configs['draft'];
|
||||
};
|
||||
|
|
@ -364,6 +388,67 @@ const getContent = (platformId: string): string => {
|
|||
return platformContents.value[platformId] || '';
|
||||
};
|
||||
|
||||
// === Autosave Logic ===
|
||||
const getSubmitData = () => {
|
||||
const platforms = post.value.post_platforms
|
||||
.filter(pp => selectedPlatformIds.value.includes(pp.id))
|
||||
.map(pp => ({
|
||||
id: pp.id,
|
||||
content: synced.value ? globalContent.value : platformContents.value[pp.id],
|
||||
content_type: platformContentTypes.value[pp.id],
|
||||
meta: platformMeta.value[pp.id] || {},
|
||||
}));
|
||||
|
||||
return { platforms, scheduled_at: scheduledDateTime.value || null };
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (isSubmitting.value || isReadOnly.value || isSaving.value) return;
|
||||
|
||||
const { platforms, scheduled_at } = getSubmitData();
|
||||
|
||||
isSaving.value = true;
|
||||
showSaved.value = false;
|
||||
|
||||
router.put(updatePost.url(post.value.id), {
|
||||
status: post.value.status,
|
||||
synced: synced.value,
|
||||
scheduled_at,
|
||||
platforms,
|
||||
}, {
|
||||
preserveScroll: true,
|
||||
onFinish: () => {
|
||||
isSaving.value = false;
|
||||
showSaved.value = true;
|
||||
setTimeout(() => {
|
||||
showSaved.value = false;
|
||||
}, 2000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedSave = debounce(() => {
|
||||
if (!isReadOnly.value && !isAnyUploading.value && !isSubmitting.value) {
|
||||
save();
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
const triggerAutosave = () => {
|
||||
if (!isReadOnly.value) {
|
||||
showSaved.value = false;
|
||||
debouncedSave();
|
||||
}
|
||||
};
|
||||
|
||||
// Watch other form data for autosave
|
||||
watch([selectedPlatformIds, synced, scheduledDateTime], triggerAutosave, { deep: true });
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
debouncedSave.cancel();
|
||||
});
|
||||
|
||||
// === Content Setters ===
|
||||
// Set content for a platform
|
||||
const setContent = (platformId: string, value: string) => {
|
||||
if (synced.value) {
|
||||
|
|
@ -375,6 +460,7 @@ const setContent = (platformId: string, value: string) => {
|
|||
} else {
|
||||
platformContents.value[platformId] = value;
|
||||
}
|
||||
triggerAutosave();
|
||||
};
|
||||
|
||||
// Active platform
|
||||
|
|
@ -398,6 +484,13 @@ const currentContentType = computed(() => {
|
|||
// Set content type for a platform
|
||||
const setContentType = (platformId: string, value: string) => {
|
||||
platformContentTypes.value[platformId] = value;
|
||||
triggerAutosave();
|
||||
};
|
||||
|
||||
// Set meta for a platform
|
||||
const setMeta = (platformId: string, value: Record<string, any>) => {
|
||||
platformMeta.value[platformId] = value;
|
||||
triggerAutosave();
|
||||
};
|
||||
|
||||
// Selected platforms (for tabs)
|
||||
|
|
@ -545,42 +638,12 @@ const togglePlatform = (platformId: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getSubmitData = () => {
|
||||
const platforms = post.value.post_platforms
|
||||
.filter(pp => selectedPlatformIds.value.includes(pp.id))
|
||||
.map(pp => ({
|
||||
id: pp.id,
|
||||
content: synced.value ? globalContent.value : platformContents.value[pp.id],
|
||||
content_type: platformContentTypes.value[pp.id],
|
||||
meta: platformMeta.value[pp.id] || {},
|
||||
}));
|
||||
|
||||
return { platforms, scheduled_at: scheduledDateTime.value || null };
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (isSubmitting.value || isReadOnly.value) return;
|
||||
|
||||
const { platforms, scheduled_at } = getSubmitData();
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
||||
router.put(updatePost.url(post.value.id), {
|
||||
status: post.value.status,
|
||||
synced: synced.value,
|
||||
scheduled_at,
|
||||
platforms,
|
||||
}, {
|
||||
preserveScroll: true,
|
||||
onFinish: () => {
|
||||
isSubmitting.value = false;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const submit = (status: string = 'scheduled') => {
|
||||
if (isSubmitting.value || isReadOnly.value) return;
|
||||
|
||||
// Cancel any pending autosave
|
||||
debouncedSave.cancel();
|
||||
|
||||
const { platforms, scheduled_at } = getSubmitData();
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
|
@ -609,7 +672,7 @@ const deletePost = () => {
|
|||
|
||||
<Head :title="isReadOnly ? 'View Post' : 'Edit Post'" />
|
||||
|
||||
<AppLayout>
|
||||
<AppLayout :breadcrumbs="breadcrumbs">
|
||||
<div class="flex flex-col h-screen">
|
||||
<!-- Top Bar with Tabs and Actions -->
|
||||
<div class="relative flex items-center justify-between border-b px-4 py-2 bg-background">
|
||||
|
|
@ -665,7 +728,7 @@ const deletePost = () => {
|
|||
<TooltipTrigger asChild>
|
||||
<button type="button" @click="showPlatformsDialog = true"
|
||||
class="p-2 rounded-lg hover:bg-muted/50 transition-colors">
|
||||
<MoreHorizontal class="h-5 w-5 text-muted-foreground" />
|
||||
<IconDots class="h-5 w-5 text-muted-foreground" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -682,7 +745,7 @@ const deletePost = () => {
|
|||
<Switch id="sync-content" v-model="synced" />
|
||||
</div>
|
||||
<Label for="sync-content" class="text-sm cursor-pointer flex items-center gap-1.5">
|
||||
<Link2 class="h-4 w-4" />
|
||||
<IconLink class="h-4 w-4" />
|
||||
<span>Sync</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<component v-for="op in otherSelectedPlatforms.slice(0, 3)" :key="op.id"
|
||||
|
|
@ -707,24 +770,20 @@ const deletePost = () => {
|
|||
<span class="text-muted-foreground">|</span>
|
||||
|
||||
<Button type="button" variant="outline" size="icon-sm" @click="deletePost"
|
||||
:disabled="isSaving || isSubmitting"
|
||||
class="text-muted-foreground hover:text-destructive">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outline" size="sm"
|
||||
:disabled="selectedPlatformIds.length === 0 || isSubmitting" @click="save">
|
||||
{{ isSubmitting ? 'Saving...' : 'Save' }}
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="secondary" size="sm" :disabled="!canSubmit || isSubmitting"
|
||||
<Button type="button" variant="secondary" size="sm" :disabled="!canSubmit || isSubmitting || isSaving"
|
||||
@click="submit('scheduled')">
|
||||
<Clock class="mr-2 h-4 w-4" />
|
||||
<IconClock class="mr-2 h-4 w-4" />
|
||||
Schedule
|
||||
</Button>
|
||||
|
||||
<Button type="button" size="sm" :disabled="!canSubmit || isSubmitting"
|
||||
<Button type="button" size="sm" :disabled="!canSubmit || isSubmitting || isSaving"
|
||||
@click="submit('publishing')">
|
||||
<Send class="mr-2 h-4 w-4" />
|
||||
<IconSend class="mr-2 h-4 w-4" />
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -740,7 +799,18 @@ const deletePost = () => {
|
|||
<div class="flex-1 overflow-hidden">
|
||||
<div v-if="activePlatform && selectedPlatformIds.length > 0" class="h-full flex">
|
||||
<!-- Left Side: Form -->
|
||||
<div class="w-1/2 border-r overflow-y-auto">
|
||||
<div class="w-1/2 border-r overflow-y-auto relative">
|
||||
<!-- Autosave indicator -->
|
||||
<div v-if="!isReadOnly && (isSaving || showSaved)" class="absolute top-4 left-6 z-10">
|
||||
<span v-if="isSaving" class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<IconLoader2 class="h-3 w-3 animate-spin" />
|
||||
Saving...
|
||||
</span>
|
||||
<span v-else-if="showSaved" class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<IconCircleCheck class="h-3 w-3 text-green-500" />
|
||||
Saved
|
||||
</span>
|
||||
</div>
|
||||
<div class="max-w-lg mx-auto py-8 px-6">
|
||||
<!-- Publication Result (read-only mode) -->
|
||||
<div v-if="isReadOnly" class="mb-6 space-y-3">
|
||||
|
|
@ -765,14 +835,14 @@ const deletePost = () => {
|
|||
<a v-if="activePlatform.platform_url" :href="activePlatform.platform_url"
|
||||
target="_blank"
|
||||
class="p-2 rounded-lg hover:bg-muted/50 transition-colors text-muted-foreground hover:text-foreground">
|
||||
<ExternalLink class="h-4 w-4" />
|
||||
<IconExternalLink class="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<Alert v-if="activePlatform.error_message" variant="destructive">
|
||||
<AlertCircle class="h-4 w-4" />
|
||||
<IconAlertCircle class="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{{ activePlatform.error_message }}
|
||||
</AlertDescription>
|
||||
|
|
@ -822,14 +892,14 @@ const deletePost = () => {
|
|||
:is-uploading="isUploading[activePlatform.id]" :disabled="isReadOnly"
|
||||
@update:content="setContent(activePlatform.id, $event)"
|
||||
@update:content-type="setContentType(activePlatform.id, $event)"
|
||||
@update:meta="platformMeta[activePlatform.id] = $event"
|
||||
@update:meta="setMeta(activePlatform.id, $event)"
|
||||
@upload="uploadMedia($event, activePlatform.id)"
|
||||
@remove-media="removeMedia(activePlatform.id, $event)"
|
||||
@reorder-media="reorderMedia(activePlatform.id, $event)" />
|
||||
|
||||
<!-- Media Validation Errors -->
|
||||
<Alert v-if="!isReadOnly && mediaValidation.length > 0" variant="destructive" class="mt-4">
|
||||
<AlertCircle class="h-4 w-4" />
|
||||
<IconAlertCircle class="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<ul class="list-disc list-inside">
|
||||
<li v-for="error in mediaValidation" :key="error">{{ error }}</li>
|
||||
|
|
@ -855,7 +925,7 @@ const deletePost = () => {
|
|||
<!-- Empty State -->
|
||||
<div v-else class="flex flex-col items-center justify-center h-full text-center p-8">
|
||||
<div class="h-16 w-16 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<Plus class="h-8 w-8 text-muted-foreground" />
|
||||
<IconPlus class="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">No platforms selected</h3>
|
||||
<p class="text-muted-foreground mb-4">Select at least one platform to create your post</p>
|
||||
|
|
@ -893,7 +963,7 @@ const deletePost = () => {
|
|||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction @click="confirmEnableSync">
|
||||
<Link2 class="mr-2 h-4 w-4" />
|
||||
<IconLink class="mr-2 h-4 w-4" />
|
||||
Sync with {{ getPlatformLabel(firstSelectedPlatform?.platform || '') }}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, useForm, router } from '@inertiajs/vue3';
|
||||
import { UserPlus, Users, Mail, Trash2, Crown, User, Shield } from 'lucide-vue-next';
|
||||
import { IconUserPlus, IconUsers, IconMail, IconTrash, IconCrown, IconUser, IconShield } from '@tabler/icons-vue';
|
||||
|
||||
import HeadingSmall from '@/components/HeadingSmall.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
|
@ -93,8 +93,8 @@ function getRoleLabel(role: string): string {
|
|||
}
|
||||
|
||||
function getRoleIcon(role: string) {
|
||||
if (role === 'admin') return Shield;
|
||||
return User;
|
||||
if (role === 'admin') return IconShield;
|
||||
return IconUser;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ function getRoleIcon(role: string) {
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<UserPlus class="h-5 w-5" />
|
||||
<IconUserPlus class="h-5 w-5" />
|
||||
Invite Member
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
|
|
@ -156,7 +156,7 @@ function getRoleIcon(role: string) {
|
|||
</div>
|
||||
|
||||
<Button type="submit" :disabled="form.processing" class="w-full">
|
||||
<Mail class="mr-2 h-4 w-4" />
|
||||
<IconMail class="mr-2 h-4 w-4" />
|
||||
Send Invite
|
||||
</Button>
|
||||
</form>
|
||||
|
|
@ -166,7 +166,7 @@ function getRoleIcon(role: string) {
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Mail class="h-5 w-5" />
|
||||
<IconMail class="h-5 w-5" />
|
||||
Pending Invites
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
|
|
@ -194,7 +194,7 @@ function getRoleIcon(role: string) {
|
|||
size="icon"
|
||||
@click="cancelInvite(invite.id)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4 text-red-500" />
|
||||
<IconTrash class="h-4 w-4 text-red-500" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -204,7 +204,7 @@ function getRoleIcon(role: string) {
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Users class="h-5 w-5" />
|
||||
<IconUsers class="h-5 w-5" />
|
||||
Members
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
|
|
@ -216,7 +216,7 @@ function getRoleIcon(role: string) {
|
|||
<div class="flex items-center justify-between p-3 rounded-lg border bg-muted/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
<Crown class="h-5 w-5" />
|
||||
<IconCrown class="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium">{{ owner.name }}</p>
|
||||
|
|
@ -247,7 +247,7 @@ function getRoleIcon(role: string) {
|
|||
size="icon"
|
||||
@click="handleRemoveMember(member.id)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4 text-red-500" />
|
||||
<IconTrash class="h-4 w-4 text-red-500" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, Link, router } from '@inertiajs/vue3';
|
||||
import { Plus, Calendar, Users, Settings } from 'lucide-vue-next';
|
||||
import { IconPlus, IconCalendar, IconUsers, IconSettings } from '@tabler/icons-vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
|
@ -51,7 +51,7 @@ function switchAndNavigate(workspaceId: string, destination: string) {
|
|||
</div>
|
||||
<Link :href="createWorkspace.url()">
|
||||
<Button>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
Novo Workspace
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -65,7 +65,7 @@ function switchAndNavigate(workspaceId: string, destination: string) {
|
|||
</p>
|
||||
<Link :href="createWorkspace.url()">
|
||||
<Button>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
<IconPlus class="mr-2 h-4 w-4" />
|
||||
Criar Workspace
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -84,19 +84,19 @@ function switchAndNavigate(workspaceId: string, destination: string) {
|
|||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span class="flex items-center gap-1">
|
||||
<Calendar class="h-4 w-4" />
|
||||
<IconCalendar class="h-4 w-4" />
|
||||
{{ workspace.posts_count }} posts
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="sm" @click="switchAndNavigate(workspace.id, calendar.url())">
|
||||
<Calendar class="h-4 w-4" />
|
||||
<IconCalendar class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="switchAndNavigate(workspace.id, accounts.url())">
|
||||
<Users class="h-4 w-4" />
|
||||
<IconUsers class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="switchAndNavigate(workspace.id, settings.url())">
|
||||
<Settings class="h-4 w-4" />
|
||||
<IconSettings class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { Calendar, Users, FileText, CheckCircle, Clock, AlertCircle, Settings } from 'lucide-vue-next';
|
||||
import { IconCalendar, IconUsers, IconFileText, IconCircleCheck, IconClock, IconAlertCircle, IconSettings } from '@tabler/icons-vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
|
@ -57,18 +57,18 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
<div class="flex gap-2">
|
||||
<Link :href="settings.url()">
|
||||
<Button variant="ghost" size="icon">
|
||||
<Settings class="h-4 w-4" />
|
||||
<IconSettings class="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link :href="accounts.url()">
|
||||
<Button variant="outline">
|
||||
<Users class="mr-2 h-4 w-4" />
|
||||
<IconUsers class="mr-2 h-4 w-4" />
|
||||
Accounts
|
||||
</Button>
|
||||
</Link>
|
||||
<Link :href="calendar.url()">
|
||||
<Button>
|
||||
<Calendar class="mr-2 h-4 w-4" />
|
||||
<IconCalendar class="mr-2 h-4 w-4" />
|
||||
Calendar
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -79,7 +79,7 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Total de Posts</CardTitle>
|
||||
<FileText class="h-4 w-4 text-muted-foreground" />
|
||||
<IconFileText class="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ stats.total_posts }}</div>
|
||||
|
|
@ -89,7 +89,7 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Agendados</CardTitle>
|
||||
<Clock class="h-4 w-4 text-muted-foreground" />
|
||||
<IconClock class="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ stats.scheduled_posts }}</div>
|
||||
|
|
@ -99,7 +99,7 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Publicados</CardTitle>
|
||||
<CheckCircle class="h-4 w-4 text-muted-foreground" />
|
||||
<IconCircleCheck class="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ stats.published_posts }}</div>
|
||||
|
|
@ -109,7 +109,7 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Contas Conectadas</CardTitle>
|
||||
<Users class="h-4 w-4 text-muted-foreground" />
|
||||
<IconUsers class="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ stats.connected_accounts }}</div>
|
||||
|
|
@ -119,14 +119,14 @@ const breadcrumbs: BreadcrumbItemType[] = [
|
|||
|
||||
<Card v-if="workspace.social_accounts.length === 0">
|
||||
<CardContent class="flex flex-col items-center justify-center py-12">
|
||||
<AlertCircle class="h-12 w-12 text-muted-foreground" />
|
||||
<IconAlertCircle class="h-12 w-12 text-muted-foreground" />
|
||||
<h3 class="mt-4 text-lg font-semibold">Nenhuma conta conectada</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
Conecte suas redes sociais para começar a agendar posts.
|
||||
</p>
|
||||
<Link :href="accounts.url()" class="mt-4">
|
||||
<Button>
|
||||
<Users class="mr-2 h-4 w-4" />
|
||||
<IconUsers class="mr-2 h-4 w-4" />
|
||||
Conectar Contas
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
4
resources/js/types/index.d.ts
vendored
4
resources/js/types/index.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import { InertiaLinkProps } from '@inertiajs/vue3';
|
||||
import type { LucideIcon } from 'lucide-vue-next';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export interface Auth {
|
||||
user: User;
|
||||
|
|
@ -14,7 +14,7 @@ export interface BreadcrumbItem {
|
|||
export interface NavItem {
|
||||
title: string;
|
||||
href: NonNullable<InertiaLinkProps['href']>;
|
||||
icon?: LucideIcon;
|
||||
icon?: Component;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue