diff --git a/app/(dashboard)/page.tsx b/app/(dashboard)/page.tsx index 983e3d8..7344860 100644 --- a/app/(dashboard)/page.tsx +++ b/app/(dashboard)/page.tsx @@ -4,7 +4,7 @@ // Dashboard Home Page // ============================================================================ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { Card, @@ -13,7 +13,15 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { analyticsApi } from "@/lib/api"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { analyticsApi, channelsApi } from "@/lib/api"; import { formatCurrency, formatNumber, @@ -26,15 +34,87 @@ import { Users, ShoppingCart, Loader2, + ExternalLink, + CheckCircle2, } from "lucide-react"; import type { SpendingAnalytics } from "@/lib/types/api"; import { useTargetChannel } from "@/components/providers/target-channel-provider"; +import { BOT_USERNAME } from "@/lib/utils/constants"; export default function DashboardPage() { - const { selectedChannel } = useTargetChannel(); + const { + selectedChannel, + channels, + isLoading: channelsLoading, + } = useTargetChannel(); const [spending, setSpending] = useState(null); const [loading, setLoading] = useState(true); + // Для модального окна добавления канала + const [showAddChannelDialog, setShowAddChannelDialog] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [initialChannelCount, setInitialChannelCount] = useState(0); + const pollingIntervalRef = useRef(null); + + // Показываем модальное окно, если нет каналов + useEffect(() => { + if (!channelsLoading && channels.length === 0) { + setShowAddChannelDialog(true); + setInitialChannelCount(0); + } + }, [channelsLoading, channels]); + + // Автоматический polling при открытом окне + useEffect(() => { + if (showAddChannelDialog && !isSuccess && initialChannelCount === 0) { + pollingIntervalRef.current = setInterval(() => { + checkForNewChannels(); + }, 1000); + + return () => { + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current); + pollingIntervalRef.current = null; + } + }; + } + }, [showAddChannelDialog, isSuccess, initialChannelCount]); + + // Очистка при размонтировании + useEffect(() => { + return () => { + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current); + } + }; + }, []); + + const checkForNewChannels = async () => { + try { + const response = await channelsApi.list(); + const activeChannels = response.target_channels.filter( + (c) => c.is_active + ); + + if (activeChannels.length > initialChannelCount) { + setIsSuccess(true); + + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current); + pollingIntervalRef.current = null; + } + + setTimeout(() => { + setShowAddChannelDialog(false); + setIsSuccess(false); + window.location.reload(); + }, 2000); + } + } catch (err) { + console.error("Error polling channels:", err); + } + }; + useEffect(() => { if (!selectedChannel) { setLoading(false); @@ -59,10 +139,85 @@ export default function DashboardPage() { loadData(); }, [selectedChannel]); - if (!selectedChannel) { - return ( - <> - + return ( + <> + + + {/* Модальное окно добавления первого канала */} + {}}> + e.preventDefault()} + onEscapeKeyDown={(e) => e.preventDefault()} + > + + Добро пожаловать! + + Для начала работы добавьте ваш первый целевой канал + + + + {isSuccess ? ( +
+
+ +
+
+

+ Канал успешно добавлен! +

+

+ Страница обновится автоматически... +

+
+
+ ) : ( +
+ + +

Шаги для добавления канала:

+
    +
  1. Откройте свой Telegram канал
  2. +
  3. Перейдите в настройки канала → Администраторы
  4. +
  5. + Добавьте бота{" "} + + @{BOT_USERNAME} + + {" "} + как администратора +
  6. +
  7. + Предоставьте боту права:{" "} + + Публикация сообщений, Удаление сообщений + +
  8. +
+
+
+ +
+ +

+ Ждем добавления бота... +
+ + Как только вы добавите бота, канал автоматически появится + +

+
+
+ )} +
+
+ + {!selectedChannel ? (

@@ -73,139 +228,138 @@ export default function DashboardPage() {

- - ); - } + ) : ( +
+
+ + + + Всего закупов + + + + + {loading ? ( + + ) : ( + <> +
+ {formatNumber(spending?.total_cost)} +
+

+ {formatNumber(0 || 0, true)} за прошлый месяц +

+ + )} +
+
- return ( - <> - -
-
- - - - Всего закупов - - - - - {loading ? ( - - ) : ( - <> -
- {formatNumber(spending?.total_cost)} -
-

- {formatNumber(0 || 0, true)} за прошлый месяц -

- - )} -
-
+ + + + Подписчиков привлечено + + + + + {loading ? ( + + ) : ( + <> +
+ {formatNumber(spending?.total_subscriptions)} +
+

+ {formatNumber(0 || 0, true)} за прошлый месяц +

+ + )} +
+
- - - - Подписчиков привлечено - - - - - {loading ? ( - - ) : ( - <> -
- {formatNumber(spending?.total_subscriptions)} -
-

- {formatNumber(0 || 0, true)} за прошлый месяц -

- - )} -
-
+ + + + Средний CPF + + + + + {loading ? ( + + ) : ( + <> +
+ ₽{formatMetric(spending?.avg_cpf)} +
+

+ {formatPercent(0 || 0, true)} от прошлого месяца +

+ + )} +
+
- - - Средний CPF - - - - {loading ? ( - - ) : ( - <> -
- ₽{formatMetric(spending?.avg_cpf)} -
-

- {formatPercent(0 || 0, true)} от прошлого месяца -

- - )} -
-
+ + + + Средний CPM + + + + + {loading ? ( + + ) : ( + <> +
+ ₽{formatMetric(spending?.avg_cpm)} +
+

+ {formatPercent(0 || 0, true)} от прошлого месяца +

+ + )} +
+
+
- - - Средний CPM - - - - {loading ? ( - - ) : ( - <> -
- ₽{formatMetric(spending?.avg_cpm)} -
-

- {formatPercent(0 || 0, true)} от прошлого месяца -

- - )} -
-
+
- - -
+ )} ); } diff --git a/components/target-channel-selector.tsx b/components/target-channel-selector.tsx index 556b4b7..3fa6dfc 100644 --- a/components/target-channel-selector.tsx +++ b/components/target-channel-selector.tsx @@ -47,7 +47,7 @@ export const TargetChannelSelector: React.FC = () => { // Автоматический запуск polling при открытии диалога useEffect(() => { - if (isAddDialogOpen && !isSuccess && initialChannelCount > 0) { + if (isAddDialogOpen && !isSuccess && initialChannelCount >= 0) { pollingIntervalRef.current = setInterval(() => { checkForNewChannels(); }, 1000);