Files
web-frontend/app/pages/profile/notifications.vue
2026-04-06 21:44:24 +07:00

240 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { useMutation, useQuery } from '@vue/apollo-composable';
import {
DeleteMyMessengerConnectionDocument,
MeDocument,
MyMessengerConnectionsDocument,
} from '~/composables/graphql/generated';
import {
messengerConnectionAvatarSrc,
messengerConnectionHandle,
messengerConnectionInitials,
messengerConnectionName,
} from '~/composables/useMessengerConnectionPresentation';
import { useMessengerStart } from '~/composables/useMessengerStart';
type MessengerChannel = 'TELEGRAM' | 'MAX';
type MessengerItem = {
id: string;
type: MessengerChannel;
isActive: boolean;
channelId: string;
displayName?: string | null;
username?: string | null;
avatarAvailable?: boolean | null;
};
type MessengerOption = {
channel: MessengerChannel;
label: string;
buttonClass: string;
iconClass: string;
unavailableText: string;
};
const config = useRuntimeConfig();
const feedback = ref('');
const meQuery = useQuery(MeDocument);
const connectionsQuery = useQuery(MyMessengerConnectionsDocument);
const deleteConnectionMutation = useMutation(DeleteMyMessengerConnectionDocument);
const { openMessengerBot, pendingChannel } = useMessengerStart();
const telegramConnection = computed(() =>
connectionsQuery.result.value?.myMessengerConnections?.find(
(item: MessengerItem) => item.type === 'TELEGRAM' && item.isActive,
) ?? null,
);
const maxConnection = computed(() =>
connectionsQuery.result.value?.myMessengerConnections?.find(
(item: MessengerItem) => item.type === 'MAX' && item.isActive,
) ?? null,
);
const messengerOptions: MessengerOption[] = [
{
channel: 'TELEGRAM',
label: 'Telegram',
buttonClass: 'bg-[#1a9c63] text-white hover:bg-[#148553]',
iconClass: 'bg-[#123824] text-white',
unavailableText: 'Telegram пока не настроен в окружении фронта.',
},
{
channel: 'MAX',
label: 'MAX',
buttonClass: 'bg-[#2b7fff] text-white hover:bg-[#1d6df1]',
iconClass: 'bg-[#2b7fff] text-white',
unavailableText: 'MAX пока не настроен в окружении фронта.',
},
];
function buildBotConnectUrl(baseUrl: string) {
const accountEmail = meQuery.result.value?.me?.email?.trim().toLowerCase();
if (!accountEmail || !baseUrl) {
return '';
}
return baseUrl;
}
const telegramConnectUrl = computed(() => buildBotConnectUrl(config.public.telegramBotUrl || ''));
const maxConnectUrl = computed(() => buildBotConnectUrl(config.public.maxBotUrl || ''));
function connectUrl(channel: MessengerChannel) {
return channel === 'TELEGRAM' ? telegramConnectUrl.value : maxConnectUrl.value;
}
function connectionFor(channel: MessengerChannel) {
return channel === 'TELEGRAM' ? telegramConnection.value : maxConnection.value;
}
const activeConnections = computed(() => messengerOptions
.map((option) => ({
option,
connection: connectionFor(option.channel),
}))
.filter((item) => Boolean(item.connection)));
const availableOptions = computed(() => messengerOptions
.filter((option) => !connectionFor(option.channel)));
async function connectMessenger(channel: MessengerChannel) {
feedback.value = '';
const baseUrl = connectUrl(channel);
if (!baseUrl) {
feedback.value = channel === 'MAX'
? 'MAX не откроется, пока не задан NUXT_PUBLIC_MAX_BOT_URL.'
: 'Telegram не откроется, пока не задан NUXT_PUBLIC_TELEGRAM_BOT_URL.';
return;
}
await openMessengerBot({
channel,
baseUrl,
redirectPath: `/profile/notifications/success?connected=${channel.toLowerCase()}`,
});
}
async function removeConnection(connectionId: string) {
feedback.value = '';
const result = await deleteConnectionMutation.mutate({
connectionId,
});
if (!result?.data?.deleteMyMessengerConnection) {
feedback.value = 'Не удалось отключить аккаунт. Попробуйте еще раз.';
return;
}
await connectionsQuery.refetch();
}
</script>
<template>
<section class="space-y-6">
<UiBackHeader
to="/profile"
back-label="Назад в профиль"
title="Уведомления"
subtitle="Подключите мессенджер, чтобы получать уведомления по заказам."
/>
<div
v-if="feedback"
class="rounded-[24px] border px-4 py-3 text-sm font-medium"
:class="feedback.includes('Не удалось') || feedback.includes('не откроется')
? 'border-[#f1d1c7] bg-[#fff3ef] text-[#9d4426]'
: 'border-[#cbe9d6] bg-[#f1fbf5] text-[#1c6b45]'"
>
{{ feedback }}
</div>
<div
v-if="activeConnections.length > 0"
class="space-y-4"
>
<article
v-for="{ option, connection } in activeConnections"
:key="connection!.id"
class="rounded-[28px] bg-white px-5 py-4 shadow-[0_18px_38px_rgba(18,56,36,0.08)]"
>
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div class="flex min-w-0 items-center gap-4">
<div v-if="messengerConnectionAvatarSrc(connection)" class="avatar">
<div class="h-14 w-14 rounded-[20px]">
<img :src="messengerConnectionAvatarSrc(connection)" :alt="messengerConnectionName(connection)">
</div>
</div>
<div
v-else
class="flex h-14 w-14 items-center justify-center rounded-[20px] text-sm font-black"
:class="option.iconClass"
>
{{ messengerConnectionInitials(connection, option.channel === 'TELEGRAM' ? 'TG' : 'MX') }}
</div>
<div class="min-w-0">
<p class="truncate text-lg font-bold text-[#123824]">{{ messengerConnectionName(connection) }}</p>
<p class="truncate text-sm text-[#557562]">{{ messengerConnectionHandle(connection) || connection.channelId }}</p>
</div>
</div>
<button
class="btn rounded-full border border-[#e5cfc7] bg-[#fff5f1] px-5 text-[#a64d2d] hover:border-[#deb5a8] hover:bg-[#ffe8e0]"
:disabled="deleteConnectionMutation.loading.value"
@click="removeConnection(connection!.id)"
>
{{ deleteConnectionMutation.loading.value ? 'Отключаем...' : 'Отключить' }}
</button>
</div>
</article>
</div>
<p
v-else
class="text-sm leading-6 text-[#557562]"
>
Пока ничего не подключено.
</p>
<div
v-if="availableOptions.length > 0"
class="space-y-4"
>
<button
v-for="option in availableOptions"
:key="option.channel"
class="flex w-full items-center justify-between rounded-[28px] border-0 px-5 py-5 text-left shadow-[0_18px_38px_rgba(18,56,36,0.08)] transition"
:class="[option.buttonClass, { 'opacity-60': !connectUrl(option.channel) }]"
:disabled="pendingChannel === option.channel || !connectUrl(option.channel)"
@click="connectMessenger(option.channel)"
>
<div class="flex items-center gap-4">
<div
class="inline-flex h-11 min-w-11 items-center justify-center rounded-2xl bg-white text-sm font-black"
:class="option.channel === 'TELEGRAM' ? 'text-[#1a9c63]' : 'text-[#2b7fff]'"
>
{{ option.channel === 'TELEGRAM' ? 'TG' : 'MAX' }}
</div>
<p class="text-base font-semibold text-white">
{{ pendingChannel === option.channel ? `Открываем ${option.label}...` : `Подключить ${option.label}` }}
</p>
</div>
</button>
<div class="space-y-2">
<p
v-for="option in availableOptions.filter((item) => !connectUrl(item.channel))"
:key="`${option.channel}-hint`"
class="text-sm text-[#8b5a49]"
>
{{ option.unavailableText }}
</p>
</div>
</div>
</section>
</template>