Add Telegram connection UI (connect dialog + code polling)
This commit is contained in:
parent
a4cf8aa4ce
commit
79acdccc45
6 changed files with 434 additions and 67 deletions
|
|
@ -51,6 +51,7 @@
|
|||
'pinterest' => 'Connect your Pinterest account',
|
||||
'bluesky' => 'Connect your Bluesky account',
|
||||
'mastodon' => 'Connect your Mastodon account',
|
||||
'telegram' => 'Connect a Telegram channel or group',
|
||||
],
|
||||
|
||||
'disconnect_modal' => [
|
||||
|
|
@ -82,6 +83,22 @@
|
|||
'submitting' => 'Connecting...',
|
||||
],
|
||||
|
||||
'telegram' => [
|
||||
'title' => 'Connect Telegram',
|
||||
'description' => 'Link a channel or group',
|
||||
'step_admin' => 'Add :bot as an administrator to your Telegram channel or group.',
|
||||
'step_command' => 'Post this command in the channel or group:',
|
||||
'waiting' => 'Waiting for the channel to connect…',
|
||||
'connected' => 'Channel connected!',
|
||||
'connected_toast' => 'Telegram channel connected successfully!',
|
||||
'copied_toast' => 'Command copied to clipboard',
|
||||
'expired' => 'This code has expired. Generate a new one to try again.',
|
||||
'new_code' => 'Generate a new code',
|
||||
'retry' => 'Try again',
|
||||
'error_generic' => 'Could not start the connection. Please try again.',
|
||||
'close' => 'Close',
|
||||
],
|
||||
|
||||
'facebook' => [
|
||||
'title' => 'Select Facebook Page',
|
||||
'description' => 'Choose which page you want to connect',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
'pinterest' => 'Conecta tu cuenta de Pinterest',
|
||||
'bluesky' => 'Conecta tu cuenta de Bluesky',
|
||||
'mastodon' => 'Conecta tu cuenta de Mastodon',
|
||||
'telegram' => 'Conecta un canal o grupo de Telegram',
|
||||
],
|
||||
|
||||
'disconnect_modal' => [
|
||||
|
|
@ -82,6 +83,22 @@
|
|||
'submitting' => 'Conectando...',
|
||||
],
|
||||
|
||||
'telegram' => [
|
||||
'title' => 'Conectar Telegram',
|
||||
'description' => 'Vincula un canal o grupo',
|
||||
'step_admin' => 'Añade :bot como administrador de tu canal o grupo de Telegram.',
|
||||
'step_command' => 'Publica este comando en el canal o grupo:',
|
||||
'waiting' => 'Esperando a que el canal se conecte…',
|
||||
'connected' => '¡Canal conectado!',
|
||||
'connected_toast' => '¡Canal de Telegram conectado correctamente!',
|
||||
'copied_toast' => 'Comando copiado al portapapeles',
|
||||
'expired' => 'Este código ha caducado. Genera uno nuevo para volver a intentarlo.',
|
||||
'new_code' => 'Generar un nuevo código',
|
||||
'retry' => 'Reintentar',
|
||||
'error_generic' => 'No se pudo iniciar la conexión. Inténtalo de nuevo.',
|
||||
'close' => 'Cerrar',
|
||||
],
|
||||
|
||||
'facebook' => [
|
||||
'title' => 'Seleccionar página de Facebook',
|
||||
'description' => 'Elige qué página deseas conectar',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
'pinterest' => 'Conecte sua conta do Pinterest',
|
||||
'bluesky' => 'Conecte sua conta do Bluesky',
|
||||
'mastodon' => 'Conecte sua conta do Mastodon',
|
||||
'telegram' => 'Conecte um canal ou grupo do Telegram',
|
||||
],
|
||||
|
||||
'disconnect_modal' => [
|
||||
|
|
@ -82,6 +83,22 @@
|
|||
'submitting' => 'Conectando...',
|
||||
],
|
||||
|
||||
'telegram' => [
|
||||
'title' => 'Conectar Telegram',
|
||||
'description' => 'Vincule um canal ou grupo',
|
||||
'step_admin' => 'Adicione :bot como administrador do seu canal ou grupo do Telegram.',
|
||||
'step_command' => 'Publique este comando no canal ou grupo:',
|
||||
'waiting' => 'Aguardando o canal conectar…',
|
||||
'connected' => 'Canal conectado!',
|
||||
'connected_toast' => 'Canal do Telegram conectado com sucesso!',
|
||||
'copied_toast' => 'Comando copiado para a área de transferência',
|
||||
'expired' => 'Este código expirou. Gere um novo para tentar de novo.',
|
||||
'new_code' => 'Gerar um novo código',
|
||||
'retry' => 'Tentar novamente',
|
||||
'error_generic' => 'Não foi possível iniciar a conexão. Tente novamente.',
|
||||
'close' => 'Fechar',
|
||||
],
|
||||
|
||||
'facebook' => [
|
||||
'title' => 'Selecionar Página do Facebook',
|
||||
'description' => 'Escolha qual página você deseja conectar',
|
||||
|
|
|
|||
10
public/images/accounts/telegram.svg
Normal file
10
public/images/accounts/telegram.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 240 240" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="tg" x1="120" y1="0" x2="120" y2="240" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2AABEE" />
|
||||
<stop offset="1" stop-color="#229ED9" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="120" cy="120" r="120" fill="url(#tg)" />
|
||||
<path fill="#fff" d="M54 118.7c35-15.2 58.3-25.3 70-30.2 33.3-13.9 40.2-16.3 44.7-16.4 1 0 3.2.2 4.7 1.4.8.7 1.2 1.6 1.4 2.6.2 1 .4 3.2.2 4.9-1.8 19-9.7 65.1-13.7 86.4-1.7 9-5 12.1-8.2 12.4-7 .6-12.3-4.6-19-9-10.6-7-16.6-11.3-26.9-18.1-11.9-7.8-4.2-12.1 2.6-19.1 1.8-1.8 32.5-29.8 33.1-32.3.1-.3.1-1.5-.6-2.1-.7-.6-1.7-.4-2.5-.2-1.1.2-17.9 11.4-50.6 33.4-4.8 3.3-9.1 4.9-13 4.8-4.3-.1-12.5-2.4-18.6-4.4-7.5-2.4-13.5-3.7-13-7.9.3-2.2 3.3-4.4 9.1-6.6z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 862 B |
|
|
@ -2,8 +2,9 @@
|
|||
import { router } from '@inertiajs/vue3';
|
||||
import { IconPlus } from '@tabler/icons-vue';
|
||||
import { trans } from 'laravel-vue-i18n';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import TelegramConnectDialog from '@/components/accounts/TelegramConnectDialog.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -32,24 +33,92 @@ const getPlatformDescription = (platform: string): string =>
|
|||
// + ink 2px border + slight rotation per platform, real PNG logo inside.
|
||||
// `linkedin-page` / `instagram-facebook` fall back to the base brand
|
||||
// image and same color since they're variants of the same network.
|
||||
const platformTheme: Record<string, { bg: string; rotate: string; image: string }> = {
|
||||
instagram: { bg: 'bg-pink-200', rotate: '-rotate-2', image: '/images/accounts/instagram.png' },
|
||||
'instagram-facebook': { bg: 'bg-pink-200', rotate: '-rotate-2', image: '/images/accounts/instagram.png' },
|
||||
facebook: { bg: 'bg-sky-200', rotate: 'rotate-1', image: '/images/accounts/facebook.png' },
|
||||
linkedin: { bg: 'bg-blue-200', rotate: '-rotate-1', image: '/images/accounts/linkedin.png' },
|
||||
'linkedin-page': { bg: 'bg-blue-200', rotate: '-rotate-1', image: '/images/accounts/linkedin.png' },
|
||||
x: { bg: 'bg-amber-200', rotate: 'rotate-2', image: '/images/accounts/x.png' },
|
||||
tiktok: { bg: 'bg-fuchsia-200', rotate: '-rotate-1', image: '/images/accounts/tiktok.png' },
|
||||
youtube: { bg: 'bg-red-200', rotate: 'rotate-1', image: '/images/accounts/youtube.png' },
|
||||
pinterest: { bg: 'bg-rose-200', rotate: '-rotate-2', image: '/images/accounts/pinterest.png' },
|
||||
threads: { bg: 'bg-emerald-200', rotate: 'rotate-2', image: '/images/accounts/threads.png' },
|
||||
bluesky: { bg: 'bg-cyan-200', rotate: '-rotate-1', image: '/images/accounts/bluesky.png' },
|
||||
mastodon: { bg: 'bg-violet-200', rotate: 'rotate-1', image: '/images/accounts/mastodon.png' },
|
||||
const platformTheme: Record<
|
||||
string,
|
||||
{ bg: string; rotate: string; image: string }
|
||||
> = {
|
||||
instagram: {
|
||||
bg: 'bg-pink-200',
|
||||
rotate: '-rotate-2',
|
||||
image: '/images/accounts/instagram.png',
|
||||
},
|
||||
'instagram-facebook': {
|
||||
bg: 'bg-pink-200',
|
||||
rotate: '-rotate-2',
|
||||
image: '/images/accounts/instagram.png',
|
||||
},
|
||||
facebook: {
|
||||
bg: 'bg-sky-200',
|
||||
rotate: 'rotate-1',
|
||||
image: '/images/accounts/facebook.png',
|
||||
},
|
||||
linkedin: {
|
||||
bg: 'bg-blue-200',
|
||||
rotate: '-rotate-1',
|
||||
image: '/images/accounts/linkedin.png',
|
||||
},
|
||||
'linkedin-page': {
|
||||
bg: 'bg-blue-200',
|
||||
rotate: '-rotate-1',
|
||||
image: '/images/accounts/linkedin.png',
|
||||
},
|
||||
x: {
|
||||
bg: 'bg-amber-200',
|
||||
rotate: 'rotate-2',
|
||||
image: '/images/accounts/x.png',
|
||||
},
|
||||
tiktok: {
|
||||
bg: 'bg-fuchsia-200',
|
||||
rotate: '-rotate-1',
|
||||
image: '/images/accounts/tiktok.png',
|
||||
},
|
||||
youtube: {
|
||||
bg: 'bg-red-200',
|
||||
rotate: 'rotate-1',
|
||||
image: '/images/accounts/youtube.png',
|
||||
},
|
||||
pinterest: {
|
||||
bg: 'bg-rose-200',
|
||||
rotate: '-rotate-2',
|
||||
image: '/images/accounts/pinterest.png',
|
||||
},
|
||||
threads: {
|
||||
bg: 'bg-emerald-200',
|
||||
rotate: 'rotate-2',
|
||||
image: '/images/accounts/threads.png',
|
||||
},
|
||||
bluesky: {
|
||||
bg: 'bg-cyan-200',
|
||||
rotate: '-rotate-1',
|
||||
image: '/images/accounts/bluesky.png',
|
||||
},
|
||||
mastodon: {
|
||||
bg: 'bg-violet-200',
|
||||
rotate: 'rotate-1',
|
||||
image: '/images/accounts/mastodon.png',
|
||||
},
|
||||
telegram: {
|
||||
bg: 'bg-sky-200',
|
||||
rotate: '-rotate-2',
|
||||
image: '/images/accounts/telegram.svg',
|
||||
},
|
||||
};
|
||||
|
||||
const themeFor = (value: string) =>
|
||||
platformTheme[value] ?? { bg: 'bg-muted', rotate: '', image: '' };
|
||||
|
||||
const telegramOpen = ref(false);
|
||||
|
||||
const connectPlatform = (platformValue: string) => {
|
||||
if (platformValue === 'telegram') {
|
||||
open.value = false;
|
||||
telegramOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
openOAuthPopup(platformValue);
|
||||
};
|
||||
|
||||
const openOAuthPopup = (platformValue: string) => {
|
||||
const url = `/connect/${platformValue}`;
|
||||
const width = 600;
|
||||
|
|
@ -84,64 +153,76 @@ onUnmounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ $t('accounts.add_social_title') }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ $t('accounts.add_social_description') }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<div>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{
|
||||
$t('accounts.add_social_title')
|
||||
}}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ $t('accounts.add_social_description') }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div
|
||||
v-for="platform in platforms"
|
||||
:key="platform.value"
|
||||
class="group relative flex flex-col items-center gap-3 rounded-xl border-2 border-foreground bg-card p-4 text-center shadow-xs transition-shadow hover:shadow-md"
|
||||
class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4"
|
||||
>
|
||||
<!-- "+" sticker badge appears only on hover so the grid doesn't feel cluttered. -->
|
||||
<span
|
||||
class="pointer-events-none absolute -top-2 -right-2 inline-flex size-6 items-center justify-center rounded-full border-2 border-foreground bg-violet-200 text-foreground opacity-0 shadow-2xs transition-all group-hover:rotate-90 group-hover:scale-110 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<IconPlus class="size-3.5" stroke-width="3" />
|
||||
</span>
|
||||
|
||||
<div
|
||||
:class="[
|
||||
themeFor(platform.value).bg,
|
||||
themeFor(platform.value).rotate,
|
||||
'inline-flex size-16 items-center justify-center rounded-2xl border-2 border-foreground shadow-sm transition-transform group-hover:!rotate-0',
|
||||
]"
|
||||
v-for="platform in platforms"
|
||||
:key="platform.value"
|
||||
class="group relative flex flex-col items-center gap-3 rounded-xl border-2 border-foreground bg-card p-4 text-center shadow-xs transition-shadow hover:shadow-md"
|
||||
>
|
||||
<img
|
||||
:src="themeFor(platform.value).image"
|
||||
:alt="platform.label"
|
||||
class="size-9 rounded-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<span class="block text-sm font-semibold text-foreground">
|
||||
<template v-if="platform.label.includes('(')">
|
||||
{{ platform.label.split('(')[0].trim() }}
|
||||
</template>
|
||||
<template v-else>{{ platform.label }}</template>
|
||||
<!-- "+" sticker badge appears only on hover so the grid doesn't feel cluttered. -->
|
||||
<span
|
||||
class="pointer-events-none absolute -top-2 -right-2 inline-flex size-6 items-center justify-center rounded-full border-2 border-foreground bg-violet-200 text-foreground opacity-0 shadow-2xs transition-all group-hover:scale-110 group-hover:rotate-90 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<IconPlus class="size-3.5" stroke-width="3" />
|
||||
</span>
|
||||
<p class="mt-0.5 line-clamp-2 text-xs leading-tight text-foreground/60">
|
||||
{{ getPlatformDescription(platform.value) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
class="mt-auto w-full"
|
||||
@click="openOAuthPopup(platform.value)"
|
||||
>
|
||||
{{ $t('accounts.connect_cta') }}
|
||||
</Button>
|
||||
<div
|
||||
:class="[
|
||||
themeFor(platform.value).bg,
|
||||
themeFor(platform.value).rotate,
|
||||
'inline-flex size-16 items-center justify-center rounded-2xl border-2 border-foreground shadow-sm transition-transform group-hover:!rotate-0',
|
||||
]"
|
||||
>
|
||||
<img
|
||||
:src="themeFor(platform.value).image"
|
||||
:alt="platform.label"
|
||||
class="size-9 rounded-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<span
|
||||
class="block text-sm font-semibold text-foreground"
|
||||
>
|
||||
<template v-if="platform.label.includes('(')">
|
||||
{{ platform.label.split('(')[0].trim() }}
|
||||
</template>
|
||||
<template v-else>{{ platform.label }}</template>
|
||||
</span>
|
||||
<p
|
||||
class="mt-0.5 line-clamp-2 text-xs leading-tight text-foreground/60"
|
||||
>
|
||||
{{ getPlatformDescription(platform.value) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
class="mt-auto w-full"
|
||||
@click="connectPlatform(platform.value)"
|
||||
>
|
||||
{{ $t('accounts.connect_cta') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<TelegramConnectDialog v-model:open="telegramOpen" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
225
resources/js/components/accounts/TelegramConnectDialog.vue
Normal file
225
resources/js/components/accounts/TelegramConnectDialog.vue
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<script setup lang="ts">
|
||||
import { router, useHttp } from '@inertiajs/vue3';
|
||||
import { IconCheck, IconCopy, IconLoader2 } from '@tabler/icons-vue';
|
||||
import { trans } from 'laravel-vue-i18n';
|
||||
import { onUnmounted, ref, watch } from 'vue';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { copyToClipboard } from '@/lib/utils';
|
||||
import {
|
||||
connect as connectTelegram,
|
||||
status as telegramStatus,
|
||||
} from '@/routes/app/social/telegram';
|
||||
|
||||
const open = defineModel<boolean>('open', { required: true });
|
||||
|
||||
type Phase = 'loading' | 'ready' | 'connected' | 'expired' | 'error';
|
||||
|
||||
const phase = ref<Phase>('loading');
|
||||
const code = ref('');
|
||||
const botUsername = ref('');
|
||||
const errorMessage = ref('');
|
||||
|
||||
const httpConnect = useHttp<
|
||||
Record<string, never>,
|
||||
{ code: string; bot_username: string; expires_at: string }
|
||||
>({});
|
||||
const httpStatus = useHttp<Record<string, never>, { status: string }>({});
|
||||
|
||||
let pollTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const stopPolling = () => {
|
||||
if (pollTimer !== null) {
|
||||
clearTimeout(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const poll = async () => {
|
||||
if (phase.value !== 'ready') return;
|
||||
|
||||
try {
|
||||
const response = await httpStatus.get(
|
||||
telegramStatus.url({ query: { code: code.value } }),
|
||||
);
|
||||
|
||||
if (response?.status === 'connected') {
|
||||
phase.value = 'connected';
|
||||
stopPolling();
|
||||
toast.success(trans('accounts.telegram.connected_toast'));
|
||||
setTimeout(() => {
|
||||
open.value = false;
|
||||
router.reload();
|
||||
}, 1200);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response?.status === 'expired' || response?.status === 'unknown') {
|
||||
phase.value = 'expired';
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Transient polling failures are ignored; the next tick retries.
|
||||
}
|
||||
|
||||
pollTimer = setTimeout(poll, 3000);
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
phase.value = 'loading';
|
||||
errorMessage.value = '';
|
||||
|
||||
try {
|
||||
const response = await httpConnect.post(connectTelegram.url());
|
||||
code.value = response.code;
|
||||
botUsername.value = response.bot_username;
|
||||
phase.value = 'ready';
|
||||
poll();
|
||||
} catch (error) {
|
||||
phase.value = 'error';
|
||||
errorMessage.value =
|
||||
(error as { response?: { data?: { message?: string } } })?.response
|
||||
?.data?.message ?? trans('accounts.telegram.error_generic');
|
||||
}
|
||||
};
|
||||
|
||||
const copyCommand = () => {
|
||||
copyToClipboard(`/connect ${code.value}`);
|
||||
toast.success(trans('accounts.telegram.copied_toast'));
|
||||
};
|
||||
|
||||
watch(open, (isOpen) => {
|
||||
if (isOpen) {
|
||||
start();
|
||||
} else {
|
||||
stopPolling();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(stopPolling);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
src="/images/accounts/telegram.svg"
|
||||
alt="Telegram"
|
||||
class="size-10"
|
||||
/>
|
||||
<div class="text-left">
|
||||
<DialogTitle>{{
|
||||
$t('accounts.telegram.title')
|
||||
}}</DialogTitle>
|
||||
<DialogDescription>{{
|
||||
$t('accounts.telegram.description')
|
||||
}}</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div
|
||||
v-if="phase === 'loading'"
|
||||
class="flex items-center justify-center py-10"
|
||||
>
|
||||
<IconLoader2
|
||||
class="size-6 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="phase === 'error'" class="space-y-4 py-2">
|
||||
<p class="text-sm text-destructive">{{ errorMessage }}</p>
|
||||
<Button class="w-full" @click="start">{{
|
||||
$t('accounts.telegram.retry')
|
||||
}}</Button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="phase === 'expired'" class="space-y-4 py-2">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ $t('accounts.telegram.expired') }}
|
||||
</p>
|
||||
<Button class="w-full" @click="start">{{
|
||||
$t('accounts.telegram.new_code')
|
||||
}}</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="phase === 'connected'"
|
||||
class="flex flex-col items-center gap-3 py-8 text-center"
|
||||
>
|
||||
<span
|
||||
class="inline-flex size-12 items-center justify-center rounded-full bg-emerald-100 text-emerald-600"
|
||||
>
|
||||
<IconCheck class="size-6" stroke-width="3" />
|
||||
</span>
|
||||
<p class="text-sm font-medium">
|
||||
{{ $t('accounts.telegram.connected') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-5 py-2">
|
||||
<ol class="space-y-4 text-sm">
|
||||
<li class="flex gap-3">
|
||||
<span
|
||||
class="flex size-6 shrink-0 items-center justify-center rounded-full border-2 border-foreground text-xs font-semibold"
|
||||
>1</span
|
||||
>
|
||||
<span
|
||||
v-html="
|
||||
trans('accounts.telegram.step_admin', {
|
||||
bot: `@${botUsername}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span
|
||||
class="flex size-6 shrink-0 items-center justify-center rounded-full border-2 border-foreground text-xs font-semibold"
|
||||
>2</span
|
||||
>
|
||||
<div class="flex-1 space-y-2">
|
||||
<span>{{
|
||||
$t('accounts.telegram.step_command')
|
||||
}}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="group flex w-full items-center justify-between gap-2 rounded-lg border bg-muted px-3 py-2 text-left font-mono text-sm transition-colors hover:bg-muted/70"
|
||||
@click="copyCommand"
|
||||
>
|
||||
<span>/connect {{ code }}</span>
|
||||
<IconCopy
|
||||
class="size-4 shrink-0 text-muted-foreground group-hover:text-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 text-sm text-muted-foreground"
|
||||
>
|
||||
<IconLoader2 class="size-4 animate-spin" />
|
||||
{{ $t('accounts.telegram.waiting') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="open = false">{{
|
||||
$t('accounts.telegram.close')
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Loading…
Reference in a new issue