refactor: use i18n for disconnected account error message
This commit is contained in:
parent
39302564de
commit
b9443d33ce
10 changed files with 192 additions and 203 deletions
|
|
@ -34,7 +34,7 @@ public function __construct(public PostPlatform $postPlatform) {}
|
|||
public function handle(): void
|
||||
{
|
||||
if ($this->postPlatform->socialAccount->isDisconnected()) {
|
||||
$this->postPlatform->markAsFailed('Social account is disconnected');
|
||||
$this->postPlatform->markAsFailed(__('posts.errors.account_disconnected'));
|
||||
$this->updatePostStatus();
|
||||
$this->broadcastStatus();
|
||||
|
||||
|
|
|
|||
|
|
@ -212,4 +212,8 @@
|
|||
'cannot_edit_published' => 'Published posts cannot be edited.',
|
||||
'connect_first' => 'Connect at least one social network before creating a post.',
|
||||
],
|
||||
|
||||
'errors' => [
|
||||
'account_disconnected' => 'Social account is disconnected',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -212,4 +212,8 @@
|
|||
'cannot_edit_published' => 'Posts publicados não podem ser editados.',
|
||||
'connect_first' => 'Conecte pelo menos uma rede social antes de criar um post.',
|
||||
],
|
||||
|
||||
'errors' => [
|
||||
'account_disconnected' => 'Conta social está desconectada',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -221,8 +221,9 @@ function handleLogout() {
|
|||
</Avatar>
|
||||
<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">{{ currentWorkspace?.name ||
|
||||
$t('sidebar.select_workspace') }}</span>
|
||||
<span class="truncate text-xs">
|
||||
{{ currentWorkspace?.name || $t('sidebar.select_workspace') }}
|
||||
</span>
|
||||
</div>
|
||||
<IconSelector class="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
|
|
|
|||
|
|
@ -134,33 +134,22 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
|
||||
<template>
|
||||
<div class="grid gap-4" :class="gridClass">
|
||||
<div
|
||||
v-for="platform in platforms"
|
||||
:key="platform.value"
|
||||
class="group relative overflow-hidden rounded-xl border bg-card transition-all hover:shadow-md"
|
||||
:class="{
|
||||
<div v-for="platform in platforms" :key="platform.value"
|
||||
class="group relative overflow-hidden rounded-xl border bg-card transition-all hover:shadow-md" :class="{
|
||||
'border-green-500/30 bg-green-50/50 dark:bg-green-950/20': platform.connected && !isDisconnected(platform.account),
|
||||
'border-red-500/30 bg-red-50/50 dark:bg-red-950/20': platform.connected && isDisconnected(platform.account),
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<!-- Platform Header -->
|
||||
<div class="flex items-center gap-3 p-4">
|
||||
<div class="relative">
|
||||
<img
|
||||
:src="getPlatformLogo(platform.value)"
|
||||
:alt="platform.label"
|
||||
class="h-12 w-12 rounded-lg object-contain"
|
||||
/>
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<img :src="getPlatformLogo(platform.value)" :alt="platform.label"
|
||||
class="h-12 w-12 rounded-lg object-contain" />
|
||||
<div 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-neutral-900">
|
||||
<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"
|
||||
>
|
||||
<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-neutral-900">
|
||||
<IconAlertCircle class="h-3 w-3" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -180,7 +169,8 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
<!-- Connected State -->
|
||||
<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">
|
||||
<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">
|
||||
<IconAlertCircle class="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium">{{ trans('accounts.connection_lost') }}</p>
|
||||
|
|
@ -207,10 +197,8 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
<TooltipProvider v-if="showReconnect && isDisconnected(platform.account)">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
@click="openOAuthPopup(platform.value)"
|
||||
class="p-2 text-amber-600 hover:text-amber-700 transition-colors"
|
||||
>
|
||||
<button @click="openOAuthPopup(platform.value)"
|
||||
class="p-2 text-amber-600 hover:text-amber-700 transition-colors">
|
||||
<IconRefresh class="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
|
|
@ -219,14 +207,12 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider v-if="showViewProfile && getProfileUrl(platform.value, platform.account.username)">
|
||||
<TooltipProvider
|
||||
v-if="showViewProfile && getProfileUrl(platform.value, platform.account.username)">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
:href="getProfileUrl(platform.value, platform.account.username)!"
|
||||
target="_blank"
|
||||
class="p-2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<a :href="getProfileUrl(platform.value, platform.account.username)!" target="_blank"
|
||||
class="p-2 text-muted-foreground hover:text-foreground transition-colors">
|
||||
<IconExternalLink class="h-4 w-4" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
|
|
@ -238,10 +224,8 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
<TooltipProvider v-if="showDisconnect">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
@click="emit('disconnect', platform.account.id)"
|
||||
class="p-2 text-muted-foreground hover:text-red-600 transition-colors"
|
||||
>
|
||||
<button @click="emit('disconnect', platform.account.id)"
|
||||
class="p-2 text-muted-foreground hover:text-red-600 transition-colors">
|
||||
<IconTrash class="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
|
|
@ -262,4 +246,4 @@ const isDisconnected = (account: SocialAccount | null): boolean => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -27,7 +27,7 @@ defineProps<Props>();
|
|||
<div class="w-full h-full bg-white dark:bg-[#0a1424] text-black dark:text-white overflow-hidden flex flex-col">
|
||||
<!-- Bluesky Mobile Header -->
|
||||
<div
|
||||
class="flex-shrink-0 h-11 flex items-center justify-between px-4 border-b border-gray-200 dark:border-[#1e3a5f]">
|
||||
class="flex-shrink-0 h-11 flex items-center justify-between px-4 border-b border-neutral-200 dark:border-[#1e3a5f]">
|
||||
<!-- Back arrow -->
|
||||
<button class="p-1 -ml-1">
|
||||
<svg class="h-5 w-5 text-black dark:text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
|
|
@ -68,14 +68,15 @@ defineProps<Props>();
|
|||
<div class="font-semibold text-[15px] text-black dark:text-white">
|
||||
{{ socialAccount.display_name }}
|
||||
</div>
|
||||
<div class="text-[14px] text-gray-500 dark:text-[#7b8d9e] truncate">
|
||||
<div class="text-[14px] text-neutral-500 dark:text-[#7b8d9e] truncate">
|
||||
@{{ socialAccount.username || 'handle' }}.bsky.social
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post content -->
|
||||
<div v-if="content" class="mt-3 text-[16px] text-black dark:text-white leading-[22px] whitespace-pre-wrap">
|
||||
<div v-if="content"
|
||||
class="mt-3 text-[16px] text-black dark:text-white leading-[22px] whitespace-pre-wrap">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
|
|
@ -84,8 +85,8 @@ defineProps<Props>();
|
|||
<div class="rounded-lg overflow-hidden" :class="{
|
||||
'grid grid-cols-2 gap-0.5': media.length >= 2,
|
||||
}">
|
||||
<div v-for="(item, index) in media.slice(0, 4)" :key="item.id"
|
||||
class="relative overflow-hidden" :class="{
|
||||
<div v-for="(item, index) in media.slice(0, 4)" :key="item.id" class="relative overflow-hidden"
|
||||
:class="{
|
||||
'aspect-[4/3]': media.length === 1,
|
||||
'aspect-square': media.length > 1,
|
||||
'col-span-2': media.length === 3 && index === 0,
|
||||
|
|
@ -103,25 +104,25 @@ defineProps<Props>();
|
|||
</div>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<div class="mt-3 text-[13px] text-gray-500 dark:text-[#7b8d9e]">
|
||||
<div class="mt-3 text-[13px] text-neutral-500 dark:text-[#7b8d9e]">
|
||||
4:18 PM · Jan 21, 2026
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Engagement Actions -->
|
||||
<div class="border-t border-gray-200 dark:border-[#1e3a5f]">
|
||||
<div class="border-t border-neutral-200 dark:border-[#1e3a5f]">
|
||||
<div class="flex items-center justify-between px-3 py-1">
|
||||
<!-- Reply -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Repost -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#00ba7c]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#00ba7c]"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M17 1l4 4-4 4" />
|
||||
<path d="M3 11V9a4 4 0 0 1 4-4h14" />
|
||||
|
|
@ -131,8 +132,8 @@ defineProps<Props>();
|
|||
</button>
|
||||
|
||||
<!-- Like -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#f91880]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#f91880]"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path
|
||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
||||
|
|
@ -140,16 +141,16 @@ defineProps<Props>();
|
|||
</button>
|
||||
|
||||
<!-- Bookmark -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Share -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
|
||||
<polyline points="16 6 12 2 8 6" />
|
||||
|
|
@ -158,8 +159,8 @@ defineProps<Props>();
|
|||
</button>
|
||||
|
||||
<!-- More -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#1e3a5f]/50 rounded-full group">
|
||||
<svg class="h-5 w-5 text-neutral-500 dark:text-[#7b8d9e] group-hover:text-[#1185fe]"
|
||||
viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="1.5" />
|
||||
<circle cx="19" cy="12" r="1.5" />
|
||||
|
|
@ -170,14 +171,14 @@ defineProps<Props>();
|
|||
</div>
|
||||
|
||||
<!-- Reply input -->
|
||||
<div class="px-4 py-3 border-t border-gray-200 dark:border-[#1e3a5f] flex items-center gap-3">
|
||||
<div class="px-4 py-3 border-t border-neutral-200 dark:border-[#1e3a5f] flex items-center gap-3">
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url" :alt="socialAccount.display_name"
|
||||
class="h-8 w-8 rounded-full object-cover shrink-0" />
|
||||
<div v-else
|
||||
class="h-8 w-8 rounded-full bg-gradient-to-br from-[#0085ff] to-[#00d4ff] flex items-center justify-center text-white text-xs font-semibold shrink-0">
|
||||
{{ socialAccount.display_name?.charAt(0) }}
|
||||
</div>
|
||||
<div class="flex-1 text-[15px] text-gray-400 dark:text-[#7b8d9e]">
|
||||
<div class="flex-1 text-[15px] text-neutral-400 dark:text-[#7b8d9e]">
|
||||
Write your reply
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,27 +67,32 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full bg-[#f0f2f5] dark:bg-[#18191a] text-[#050505] dark:text-[#e4e6eb] overflow-hidden flex flex-col">
|
||||
<div
|
||||
class="w-full h-full bg-[#f0f2f5] dark:bg-[#18191a] text-[#050505] dark:text-[#e4e6eb] overflow-hidden flex flex-col">
|
||||
|
||||
<!-- ==================== FEED POST ==================== -->
|
||||
<template v-if="isFeed">
|
||||
<!-- Facebook Header -->
|
||||
<div class="flex-shrink-0 h-11 bg-white dark:bg-[#242526] border-b border-[#dddfe2] dark:border-[#3e4042] flex items-center px-3">
|
||||
<div
|
||||
class="flex-shrink-0 h-11 bg-white dark:bg-[#242526] border-b border-[#dddfe2] dark:border-[#3e4042] flex items-center px-3">
|
||||
<svg class="h-6 w-auto text-[#1877f2]" viewBox="0 0 36 36" fill="currentColor">
|
||||
<path d="M20.18 35.87C28.872 34.632 35.87 27.1 35.87 18c0-9.882-8.01-17.89-17.9-17.89C8.09.11.08 8.12.08 18c0 8.06 5.33 14.87 12.66 17.11v-12.3h-3.81V18h3.81v-3.64c0-3.76 2.24-5.84 5.67-5.84 1.64 0 3.36.29 3.36.29v3.69h-1.89c-1.86 0-2.45 1.16-2.45 2.35V18h4.16l-.67 4.81h-3.49v12.3c1.08-.17 2.13-.44 3.14-.79l.56-.45z"/>
|
||||
<path
|
||||
d="M20.18 35.87C28.872 34.632 35.87 27.1 35.87 18c0-9.882-8.01-17.89-17.9-17.89C8.09.11.08 8.12.08 18c0 8.06 5.33 14.87 12.66 17.11v-12.3h-3.81V18h3.81v-3.64c0-3.76 2.24-5.84 5.67-5.84 1.64 0 3.36.29 3.36.29v3.69h-1.89c-1.86 0-2.45 1.16-2.45 2.35V18h4.16l-.67 4.81h-3.49v12.3c1.08-.17 2.13-.44 3.14-.79l.56-.45z" />
|
||||
</svg>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<!-- Search -->
|
||||
<div class="w-8 h-8 rounded-full bg-[#e4e6eb] dark:bg-[#3a3b3c] flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-[#050505] dark:text-[#e4e6eb]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="m21 21-4.35-4.35"/>
|
||||
<svg class="w-4 h-4 text-[#050505] dark:text-[#e4e6eb]" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2.5">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.35-4.35" />
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Messenger -->
|
||||
<div class="w-8 h-8 rounded-full bg-[#e4e6eb] dark:bg-[#3a3b3c] flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-[#050505] dark:text-[#e4e6eb]" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.04 2 11c0 2.76 1.36 5.22 3.5 6.87V22l3.75-2.06c.98.27 2.01.43 3.08.43h.34c5.52 0 10-4.04 10-9s-4.48-9-10-9h-.67zm1.13 12.13l-2.54-2.71-4.96 2.71 5.46-5.79 2.6 2.71 4.9-2.71-5.46 5.79z"/>
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.04 2 11c0 2.76 1.36 5.22 3.5 6.87V22l3.75-2.06c.98.27 2.01.43 3.08.43h.34c5.52 0 10-4.04 10-9s-4.48-9-10-9h-.67zm1.13 12.13l-2.54-2.71-4.96 2.71 5.46-5.79 2.6 2.71 4.9-2.71-5.46 5.79z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -100,13 +105,10 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<!-- Post Header -->
|
||||
<div class="flex-shrink-0 flex items-center px-3 py-2">
|
||||
<div class="flex items-center gap-2 flex-1">
|
||||
<img
|
||||
v-if="socialAccount.avatar_url"
|
||||
:src="socialAccount.avatar_url"
|
||||
:alt="displayName"
|
||||
class="w-10 h-10 rounded-full object-cover"
|
||||
/>
|
||||
<div v-else class="w-10 h-10 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold">
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url" :alt="displayName"
|
||||
class="w-10 h-10 rounded-full object-cover" />
|
||||
<div v-else
|
||||
class="w-10 h-10 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold">
|
||||
{{ displayName?.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
<div class="flex flex-col min-w-0">
|
||||
|
|
@ -116,7 +118,8 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<span>·</span>
|
||||
<!-- Globe icon -->
|
||||
<svg class="w-3 h-3" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm-.5 14.5v-2h1v2a6.5 6.5 0 0 1-1 0zm4.7-1.2a6.5 6.5 0 0 1-3.2.7v-1h2a1 1 0 0 0 1-1V9.5h1.5a6.5 6.5 0 0 1-1.3 3.8zM14.5 8a6.5 6.5 0 0 1-.5 2.5H12V9a1 1 0 0 0-1-1h-1V6a1 1 0 0 0-1-1H6.5V3.5h2a1 1 0 0 0 1-1V1.7a6.5 6.5 0 0 1 5 6.3zM1.5 8a6.5 6.5 0 0 1 3-5.5V4a1 1 0 0 0 1 1h1v2a1 1 0 0 0 1 1h2v3H6a1 1 0 0 0-1 1v2.5A6.5 6.5 0 0 1 1.5 8z"/>
|
||||
<path
|
||||
d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm-.5 14.5v-2h1v2a6.5 6.5 0 0 1-1 0zm4.7-1.2a6.5 6.5 0 0 1-3.2.7v-1h2a1 1 0 0 0 1-1V9.5h1.5a6.5 6.5 0 0 1-1.3 3.8zM14.5 8a6.5 6.5 0 0 1-.5 2.5H12V9a1 1 0 0 0-1-1h-1V6a1 1 0 0 0-1-1H6.5V3.5h2a1 1 0 0 0 1-1V1.7a6.5 6.5 0 0 1 5 6.3zM1.5 8a6.5 6.5 0 0 1 3-5.5V4a1 1 0 0 0 1 1h1v2a1 1 0 0 0 1 1h2v3H6a1 1 0 0 0-1 1v2.5A6.5 6.5 0 0 1 1.5 8z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -128,80 +131,86 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<div v-if="content" class="flex-shrink-0 px-3 pb-2">
|
||||
<p class="text-[14px] leading-snug">
|
||||
{{ truncatedContent }}
|
||||
<span v-if="content.length > 100" class="text-[#65676b] dark:text-[#b0b3b8] cursor-pointer">See more</span>
|
||||
<span v-if="content.length > 100"
|
||||
class="text-[#65676b] dark:text-[#b0b3b8] cursor-pointer">See more</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Post Media -->
|
||||
<div class="flex-1 relative bg-black min-h-0">
|
||||
<template v-if="media.length > 0">
|
||||
<img
|
||||
v-if="media[currentIndex]?.type === 'image'"
|
||||
:src="media[currentIndex].url"
|
||||
:alt="media[currentIndex].original_filename"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<video
|
||||
v-else-if="media[currentIndex]"
|
||||
:src="media[currentIndex].url"
|
||||
class="w-full h-full object-cover"
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
/>
|
||||
<img v-if="media[currentIndex]?.type === 'image'" :src="media[currentIndex].url"
|
||||
:alt="media[currentIndex].original_filename" class="w-full h-full object-cover" />
|
||||
<video v-else-if="media[currentIndex]" :src="media[currentIndex].url"
|
||||
class="w-full h-full object-cover" muted loop playsinline />
|
||||
<!-- Multiple images indicator -->
|
||||
<div v-if="media.length > 1" class="absolute top-2 right-2 bg-black/60 text-white text-[10px] font-semibold px-2 py-0.5 rounded-full">
|
||||
<div v-if="media.length > 1"
|
||||
class="absolute top-2 right-2 bg-black/60 text-white text-[10px] font-semibold px-2 py-0.5 rounded-full">
|
||||
{{ currentIndex + 1 }}/{{ media.length }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="w-full h-full flex items-center justify-center bg-[#f0f2f5] dark:bg-[#3a3b3c]">
|
||||
<svg class="w-12 h-12 text-[#bcc0c4] dark:text-[#4e4f50]" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
||||
<div v-else
|
||||
class="w-full h-full flex items-center justify-center bg-[#f0f2f5] dark:bg-[#3a3b3c]">
|
||||
<svg class="w-12 h-12 text-[#bcc0c4] dark:text-[#4e4f50]" viewBox="0 0 24 24"
|
||||
fill="currentColor">
|
||||
<path
|
||||
d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reactions Bar -->
|
||||
<div class="flex-shrink-0 px-3 py-1.5 flex items-center justify-between border-b border-[#dddfe2] dark:border-[#3e4042]">
|
||||
<div
|
||||
class="flex-shrink-0 px-3 py-1.5 flex items-center justify-between border-b border-[#dddfe2] dark:border-[#3e4042]">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex -space-x-1">
|
||||
<!-- Like reaction -->
|
||||
<div class="w-[18px] h-[18px] rounded-full bg-[#1877f2] flex items-center justify-center ring-2 ring-white dark:ring-[#242526]">
|
||||
<div
|
||||
class="w-[18px] h-[18px] rounded-full bg-[#1877f2] flex items-center justify-center ring-2 ring-white dark:ring-[#242526]">
|
||||
<svg class="w-2.5 h-2.5 text-white" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M1 7.75v6.5c0 .41.34.75.75.75h2.5a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75h-2.5a.75.75 0 0 0-.75.75zm13.47-2.58l-3.25.01-.42-1.48a3.51 3.51 0 0 0-3.38-2.56H7.2a.75.75 0 0 0-.7.47l-1.32 3.45a1.75 1.75 0 0 0-.1.56V12c0 .97.78 1.75 1.75 1.75h4.67a1.75 1.75 0 0 0 1.7-1.34l1.3-5.21a1.75 1.75 0 0 0-1.03-2.08z"/>
|
||||
<path
|
||||
d="M1 7.75v6.5c0 .41.34.75.75.75h2.5a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75h-2.5a.75.75 0 0 0-.75.75zm13.47-2.58l-3.25.01-.42-1.48a3.51 3.51 0 0 0-3.38-2.56H7.2a.75.75 0 0 0-.7.47l-1.32 3.45a1.75 1.75 0 0 0-.1.56V12c0 .97.78 1.75 1.75 1.75h4.67a1.75 1.75 0 0 0 1.7-1.34l1.3-5.21a1.75 1.75 0 0 0-1.03-2.08z" />
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Love reaction -->
|
||||
<div class="w-[18px] h-[18px] rounded-full bg-[#f33e58] flex items-center justify-center ring-2 ring-white dark:ring-[#242526]">
|
||||
<div
|
||||
class="w-[18px] h-[18px] rounded-full bg-[#f33e58] flex items-center justify-center ring-2 ring-white dark:ring-[#242526]">
|
||||
<svg class="w-2.5 h-2.5 text-white" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 15.25s-6.75-4.47-6.75-9A3.38 3.38 0 0 1 4.62.75 3.43 3.43 0 0 1 8 2.93 3.43 3.43 0 0 1 11.38.75a3.38 3.38 0 0 1 3.37 3.5c0 4.53-6.75 9-6.75 9z"/>
|
||||
<path
|
||||
d="M8 15.25s-6.75-4.47-6.75-9A3.38 3.38 0 0 1 4.62.75 3.43 3.43 0 0 1 8 2.93 3.43 3.43 0 0 1 11.38.75a3.38 3.38 0 0 1 3.37 3.5c0 4.53-6.75 9-6.75 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[12px] text-[#65676b] dark:text-[#b0b3b8] ml-1">{{ formatNumber(156) }}</span>
|
||||
<span class="text-[12px] text-[#65676b] dark:text-[#b0b3b8] ml-1">{{ formatNumber(156)
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="text-[12px] text-[#65676b] dark:text-[#b0b3b8]">23 comments · 5 shares</span>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex-shrink-0 flex items-center px-2 py-1 border-b border-[#dddfe2] dark:border-[#3e4042]">
|
||||
<button class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<div
|
||||
class="flex-shrink-0 flex items-center px-2 py-1 border-b border-[#dddfe2] dark:border-[#3e4042]">
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/>
|
||||
<path
|
||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
|
||||
</svg>
|
||||
<span class="text-[12px] font-semibold">Like</span>
|
||||
</button>
|
||||
<button class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
<span class="text-[12px] font-semibold">Comment</span>
|
||||
</button>
|
||||
<button class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-[#65676b] dark:text-[#b0b3b8] hover:bg-[#f0f2f5] dark:hover:bg-[#3a3b3c] rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/>
|
||||
<polyline points="16 6 12 2 8 6"/>
|
||||
<line x1="12" y1="2" x2="12" y2="15"/>
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
|
||||
<polyline points="16 6 12 2 8 6" />
|
||||
<line x1="12" y1="2" x2="12" y2="15" />
|
||||
</svg>
|
||||
<span class="text-[12px] font-semibold">Share</span>
|
||||
</button>
|
||||
|
|
@ -216,46 +225,40 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<!-- Video/Media - Full screen -->
|
||||
<div class="absolute inset-0">
|
||||
<template v-if="media.length > 0">
|
||||
<img
|
||||
v-if="media[0].type === 'image'"
|
||||
:src="media[0].url"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<video
|
||||
v-else
|
||||
:src="media[0].url"
|
||||
class="w-full h-full object-cover"
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
/>
|
||||
<img v-if="media[0].type === 'image'" :src="media[0].url" class="w-full h-full object-cover" />
|
||||
<video v-else :src="media[0].url" class="w-full h-full object-cover" muted loop playsinline />
|
||||
</template>
|
||||
<div v-else class="w-full h-full flex items-center justify-center bg-[#18191a]">
|
||||
<svg class="w-12 h-12 text-white/30" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gradient overlay -->
|
||||
<div v-if="media.length > 0" class="absolute inset-x-0 bottom-0 h-48 bg-gradient-to-t from-black/70 via-black/30 to-transparent pointer-events-none z-[5]" />
|
||||
<div v-if="media.length > 0"
|
||||
class="absolute inset-x-0 bottom-0 h-48 bg-gradient-to-t from-black/70 via-black/30 to-transparent pointer-events-none z-[5]" />
|
||||
|
||||
<!-- Top Bar -->
|
||||
<div v-if="media.length > 0" class="absolute top-1 left-0 right-0 px-3 flex items-center justify-between z-10">
|
||||
<div v-if="media.length > 0"
|
||||
class="absolute top-1 left-0 right-0 px-3 flex items-center justify-between z-10">
|
||||
<span class="text-white text-[14px] font-semibold drop-shadow-lg">Reels</span>
|
||||
<svg class="w-5 h-5 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M16.5 7.5L21 3M21 3v4M21 3h-4"/>
|
||||
<svg class="w-5 h-5 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M16.5 7.5L21 3M21 3v4M21 3h-4" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar Actions -->
|
||||
<div v-if="media.length > 0" class="absolute right-2 bottom-[68px] flex flex-col items-center gap-4 z-10">
|
||||
<div v-if="media.length > 0"
|
||||
class="absolute right-2 bottom-[68px] flex flex-col items-center gap-4 z-10">
|
||||
<!-- Like -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 flex items-center justify-center">
|
||||
<svg class="w-7 h-7 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-white text-[10px] font-semibold drop-shadow">{{ formatNumber(12453) }}</span>
|
||||
|
|
@ -264,7 +267,7 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 flex items-center justify-center">
|
||||
<svg class="w-7 h-7 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
|
||||
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-white text-[10px] font-semibold drop-shadow">{{ formatNumber(892) }}</span>
|
||||
|
|
@ -273,7 +276,9 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<div class="flex flex-col items-center">
|
||||
<div class="w-10 h-10 flex items-center justify-center">
|
||||
<svg class="w-7 h-7 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" transform="rotate(90 12 12)"/>
|
||||
<path
|
||||
d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
|
||||
transform="rotate(90 12 12)" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -282,13 +287,12 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<IconDots class="w-6 h-6 text-white drop-shadow-lg" />
|
||||
</div>
|
||||
<!-- Audio spinning icon -->
|
||||
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-gray-600 to-gray-900 border border-white/30 overflow-hidden animate-[spin_3s_linear_infinite]">
|
||||
<img
|
||||
v-if="socialAccount.avatar_url"
|
||||
:src="socialAccount.avatar_url"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div v-else class="w-full h-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-xs">
|
||||
<div
|
||||
class="w-9 h-9 rounded-lg bg-gradient-to-br from-neutral-600 to-neutral-900 border border-white/30 overflow-hidden animate-[spin_3s_linear_infinite]">
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url"
|
||||
class="w-full h-full object-cover" />
|
||||
<div v-else
|
||||
class="w-full h-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-xs">
|
||||
{{ displayName?.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -297,12 +301,10 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<!-- Bottom Info -->
|
||||
<div v-if="media.length > 0" class="absolute left-0 right-14 bottom-[68px] px-3 z-10">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<img
|
||||
v-if="socialAccount.avatar_url"
|
||||
:src="socialAccount.avatar_url"
|
||||
class="w-8 h-8 rounded-full object-cover border border-white/30"
|
||||
/>
|
||||
<div v-else class="w-8 h-8 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-[12px] border border-white/30">
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url"
|
||||
class="w-8 h-8 rounded-full object-cover border border-white/30" />
|
||||
<div v-else
|
||||
class="w-8 h-8 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-[12px] border border-white/30">
|
||||
{{ displayName?.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
<span class="text-white text-[13px] font-semibold drop-shadow-lg">{{ displayName }}</span>
|
||||
|
|
@ -316,7 +318,8 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-1 bg-white/20 backdrop-blur-sm rounded-full px-2 py-0.5">
|
||||
<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
|
||||
<path
|
||||
d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" />
|
||||
</svg>
|
||||
<span class="text-white text-[10px] truncate max-w-[120px]">Original audio</span>
|
||||
</div>
|
||||
|
|
@ -324,33 +327,34 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
</div>
|
||||
|
||||
<!-- Bottom Navigation Bar -->
|
||||
<div class="absolute bottom-0 left-0 right-0 h-[50px] bg-black/80 backdrop-blur-sm flex items-center justify-around px-4 z-20">
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 h-[50px] bg-black/80 backdrop-blur-sm flex items-center justify-around px-4 z-20">
|
||||
<!-- Home -->
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
</svg>
|
||||
<!-- Friends -->
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M2 21v-2a4 4 0 0 1 4-4h6"/>
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M2 21v-2a4 4 0 0 1 4-4h6" />
|
||||
</svg>
|
||||
<!-- Reels - Active -->
|
||||
<svg class="w-6 h-6 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<rect x="2" y="2" width="20" height="20" rx="4"/>
|
||||
<polygon points="10,7 10,17 17,12" fill="black"/>
|
||||
<rect x="2" y="2" width="20" height="20" rx="4" />
|
||||
<polygon points="10,7 10,17 17,12" fill="black" />
|
||||
</svg>
|
||||
<!-- Marketplace -->
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<svg class="w-6 h-6 text-white/70" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
</svg>
|
||||
<!-- Profile -->
|
||||
<div class="w-6 h-6 rounded-full overflow-hidden ring-[1.5px] ring-white/50">
|
||||
<img
|
||||
v-if="socialAccount.avatar_url"
|
||||
:src="socialAccount.avatar_url"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div v-else class="w-full h-full bg-gray-600" />
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url"
|
||||
class="w-full h-full object-cover" />
|
||||
<div v-else class="w-full h-full bg-neutral-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -362,23 +366,14 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<!-- Media - Full screen -->
|
||||
<div class="absolute inset-0">
|
||||
<template v-if="media.length > 0">
|
||||
<img
|
||||
v-if="media[0].type === 'image'"
|
||||
:src="media[0].url"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<video
|
||||
v-else
|
||||
:src="media[0].url"
|
||||
class="w-full h-full object-cover"
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
/>
|
||||
<img v-if="media[0].type === 'image'" :src="media[0].url" class="w-full h-full object-cover" />
|
||||
<video v-else :src="media[0].url" class="w-full h-full object-cover" muted loop playsinline />
|
||||
</template>
|
||||
<div v-else class="w-full h-full flex items-center justify-center bg-gradient-to-b from-[#1877f2]/50 to-[#833ab4]/50">
|
||||
<div v-else
|
||||
class="w-full h-full flex items-center justify-center bg-gradient-to-b from-[#1877f2]/50 to-[#833ab4]/50">
|
||||
<svg class="w-12 h-12 text-white/30" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
||||
<path
|
||||
d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -391,15 +386,14 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
</div>
|
||||
|
||||
<!-- User Info -->
|
||||
<div v-if="media.length > 0" class="absolute top-3 left-0 right-0 px-3 flex items-center justify-between z-10">
|
||||
<div v-if="media.length > 0"
|
||||
class="absolute top-3 left-0 right-0 px-3 flex items-center justify-between z-10">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="p-[2px] bg-[#1877f2] rounded-full">
|
||||
<img
|
||||
v-if="socialAccount.avatar_url"
|
||||
:src="socialAccount.avatar_url"
|
||||
class="w-8 h-8 rounded-full object-cover border-2 border-black"
|
||||
/>
|
||||
<div v-else class="w-8 h-8 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-[11px] border-2 border-black">
|
||||
<img v-if="socialAccount.avatar_url" :src="socialAccount.avatar_url"
|
||||
class="w-8 h-8 rounded-full object-cover border-2 border-black" />
|
||||
<div v-else
|
||||
class="w-8 h-8 rounded-full bg-[#1877f2] flex items-center justify-center text-white font-bold text-[11px] border-2 border-black">
|
||||
{{ displayName?.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -413,7 +407,8 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
|
||||
<!-- Caption overlay (if content exists and media) -->
|
||||
<div v-if="media.length > 0 && content" class="absolute bottom-20 left-3 right-3 z-10">
|
||||
<p class="text-white text-[13px] text-center drop-shadow-lg bg-black/20 backdrop-blur-sm rounded-xl px-3 py-2 line-clamp-2">
|
||||
<p
|
||||
class="text-white text-[13px] text-center drop-shadow-lg bg-black/20 backdrop-blur-sm rounded-xl px-3 py-2 line-clamp-2">
|
||||
{{ content }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -424,13 +419,14 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc
|
|||
<span class="text-white/80 text-[13px]">Reply to {{ displayName }}...</span>
|
||||
</div>
|
||||
<svg class="w-7 h-7 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
||||
</svg>
|
||||
<svg class="w-7 h-7 text-white drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2 21l21-9L2 3v7l15 2-15 2v7z"/>
|
||||
<path d="M2 21l21-9L2 3v7l15 2-15 2v7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -58,8 +58,8 @@ defineProps<Props>();
|
|||
<div class="rounded-lg overflow-hidden" :class="{
|
||||
'grid grid-cols-2 gap-0.5': media.length >= 2,
|
||||
}">
|
||||
<div v-for="(item, index) in media.slice(0, 4)" :key="item.id"
|
||||
class="relative overflow-hidden" :class="{
|
||||
<div v-for="(item, index) in media.slice(0, 4)" :key="item.id" class="relative overflow-hidden"
|
||||
:class="{
|
||||
'aspect-[4/3]': media.length === 1,
|
||||
'aspect-square': media.length > 1,
|
||||
}">
|
||||
|
|
@ -68,7 +68,8 @@ defineProps<Props>();
|
|||
<video v-else :src="item.url" class="w-full h-full object-cover bg-black" muted loop
|
||||
playsinline />
|
||||
<!-- Hide button -->
|
||||
<button class="absolute top-2 right-2 bg-black/60 text-white text-[12px] px-2 py-0.5 rounded">
|
||||
<button
|
||||
class="absolute top-2 right-2 bg-black/60 text-white text-[12px] px-2 py-0.5 rounded">
|
||||
Hide
|
||||
</button>
|
||||
<div v-if="media.length > 4 && index === 3"
|
||||
|
|
@ -140,7 +141,7 @@ defineProps<Props>();
|
|||
</svg>
|
||||
</button>
|
||||
<!-- More -->
|
||||
<button class="p-2 hover:bg-gray-100 dark:hover:bg-[#313543] rounded-full">
|
||||
<button class="p-2 hover:bg-neutral-100 dark:hover:bg-[#313543] rounded-full">
|
||||
<svg class="h-5 w-5 text-[#606984] dark:text-[#9baec8]" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="1.5" />
|
||||
<circle cx="6" cy="12" r="1.5" />
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ const getPlatformLogo = (platform: string): string => {
|
|||
|
||||
const getStatusConfig = (status: string) => {
|
||||
const configs: Record<string, { color: string; icon: typeof IconFileText }> = {
|
||||
'draft': { color: 'bg-gray-100 text-gray-800', icon: IconFileText },
|
||||
'draft': { color: 'bg-neutral-100 text-neutral-800', icon: IconFileText },
|
||||
'scheduled': { color: 'bg-blue-100 text-blue-800', icon: IconClock },
|
||||
'publishing': { color: 'bg-yellow-100 text-yellow-800', icon: IconLoader2 },
|
||||
'published': { color: 'bg-green-100 text-green-800', icon: IconCircleCheck },
|
||||
|
|
@ -179,7 +179,9 @@ const handleDelete = (post: Post) => {
|
|||
<IconFileText class="h-12 w-12 text-muted-foreground" />
|
||||
<h3 class="mt-4 text-lg font-semibold">{{ $t('posts.no_posts') }}</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
{{ currentStatus ? $t('posts.no_posts_status', { status: $t(`posts.status.${currentStatus}`) }) : $t('posts.start_creating') }}
|
||||
{{ currentStatus ? $t('posts.no_posts_status', {
|
||||
status: $t(`posts.status.${currentStatus}`)
|
||||
}) : $t('posts.start_creating') }}
|
||||
</p>
|
||||
<Link v-if="!currentStatus" :href="storePost.url()" method="post" class="mt-4">
|
||||
<Button>
|
||||
|
|
@ -286,11 +288,7 @@ const handleDelete = (post: Post) => {
|
|||
</div>
|
||||
</AppLayout>
|
||||
|
||||
<ConfirmDeleteModal
|
||||
ref="deleteModal"
|
||||
:title="$t('posts.edit.delete_modal.title')"
|
||||
:description="$t('posts.edit.delete_modal.description')"
|
||||
:action="$t('posts.edit.delete_modal.action')"
|
||||
:cancel="$t('posts.edit.delete_modal.cancel')"
|
||||
/>
|
||||
<ConfirmDeleteModal ref="deleteModal" :title="$t('posts.edit.delete_modal.title')"
|
||||
:description="$t('posts.edit.delete_modal.description')" :action="$t('posts.edit.delete_modal.action')"
|
||||
:cancel="$t('posts.edit.delete_modal.cancel')" />
|
||||
</template>
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
|
||||
$this->postPlatform->refresh();
|
||||
expect($this->postPlatform->status)->toBe('failed');
|
||||
expect($this->postPlatform->error_message)->toBe('Social account is disconnected');
|
||||
expect($this->postPlatform->error_message)->toBe(__('posts.errors.account_disconnected'));
|
||||
});
|
||||
|
||||
test('publish to social platform skips publishing when account token is expired', function () {
|
||||
|
|
@ -193,5 +193,5 @@
|
|||
|
||||
$this->postPlatform->refresh();
|
||||
expect($this->postPlatform->status)->toBe('failed');
|
||||
expect($this->postPlatform->error_message)->toBe('Social account is disconnected');
|
||||
expect($this->postPlatform->error_message)->toBe(__('posts.errors.account_disconnected'));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue