fix: adding first channel fix

This commit is contained in:
ivannoskov
2025-12-01 08:03:45 +03:00
parent 2ea90d1b55
commit 8e6a1fa83f
2 changed files with 290 additions and 136 deletions

View File

@@ -4,7 +4,7 @@
// Dashboard Home Page // Dashboard Home Page
// ============================================================================ // ============================================================================
import React, { useEffect, useState } from "react"; import React, { useEffect, useState, useRef } from "react";
import { DashboardHeader } from "@/components/layout/dashboard-header"; import { DashboardHeader } from "@/components/layout/dashboard-header";
import { import {
Card, Card,
@@ -13,7 +13,15 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } 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 { import {
formatCurrency, formatCurrency,
formatNumber, formatNumber,
@@ -26,15 +34,87 @@ import {
Users, Users,
ShoppingCart, ShoppingCart,
Loader2, Loader2,
ExternalLink,
CheckCircle2,
} from "lucide-react"; } from "lucide-react";
import type { SpendingAnalytics } from "@/lib/types/api"; import type { SpendingAnalytics } from "@/lib/types/api";
import { useTargetChannel } from "@/components/providers/target-channel-provider"; import { useTargetChannel } from "@/components/providers/target-channel-provider";
import { BOT_USERNAME } from "@/lib/utils/constants";
export default function DashboardPage() { export default function DashboardPage() {
const { selectedChannel } = useTargetChannel(); const {
selectedChannel,
channels,
isLoading: channelsLoading,
} = useTargetChannel();
const [spending, setSpending] = useState<SpendingAnalytics | null>(null); const [spending, setSpending] = useState<SpendingAnalytics | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
// Для модального окна добавления канала
const [showAddChannelDialog, setShowAddChannelDialog] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [initialChannelCount, setInitialChannelCount] = useState(0);
const pollingIntervalRef = useRef<NodeJS.Timeout | null>(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(() => { useEffect(() => {
if (!selectedChannel) { if (!selectedChannel) {
setLoading(false); setLoading(false);
@@ -59,10 +139,85 @@ export default function DashboardPage() {
loadData(); loadData();
}, [selectedChannel]); }, [selectedChannel]);
if (!selectedChannel) {
return ( return (
<> <>
<DashboardHeader breadcrumbs={[{ label: "Главная" }]} /> <DashboardHeader breadcrumbs={[{ label: "Главная" }]} />
{/* Модальное окно добавления первого канала */}
<Dialog open={showAddChannelDialog} onOpenChange={() => {}}>
<DialogContent
className="sm:max-w-md"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogHeader>
<DialogTitle>Добро пожаловать!</DialogTitle>
<DialogDescription>
Для начала работы добавьте ваш первый целевой канал
</DialogDescription>
</DialogHeader>
{isSuccess ? (
<div className="flex flex-col items-center justify-center py-8 space-y-4">
<div className="rounded-full bg-green-100 dark:bg-green-900 p-3">
<CheckCircle2 className="h-8 w-8 text-green-600 dark:text-green-400" />
</div>
<div className="text-center">
<h3 className="text-lg font-semibold">
Канал успешно добавлен!
</h3>
<p className="text-sm text-muted-foreground mt-1">
Страница обновится автоматически...
</p>
</div>
</div>
) : (
<div className="space-y-4">
<Alert>
<AlertDescription className="space-y-2">
<p className="font-medium">Шаги для добавления канала:</p>
<ol className="list-decimal list-inside space-y-1.5 text-sm">
<li>Откройте свой Telegram канал</li>
<li>Перейдите в настройки канала Администраторы</li>
<li>
Добавьте бота{" "}
<a
href={`https://t.me/${BOT_USERNAME}`}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-primary hover:underline inline-flex items-center gap-1"
>
@{BOT_USERNAME}
<ExternalLink className="h-3 w-3" />
</a>{" "}
как администратора
</li>
<li>
Предоставьте боту права:{" "}
<span className="font-medium">
Публикация сообщений, Удаление сообщений
</span>
</li>
</ol>
</AlertDescription>
</Alert>
<div className="flex flex-col items-center justify-center py-6 space-y-3">
<Loader2 className="h-10 w-10 animate-spin text-primary" />
<p className="text-sm text-muted-foreground text-center">
Ждем добавления бота...
<br />
<span className="text-xs">
Как только вы добавите бота, канал автоматически появится
</span>
</p>
</div>
</div>
)}
</DialogContent>
</Dialog>
{!selectedChannel ? (
<div className="flex flex-1 items-center justify-center"> <div className="flex flex-1 items-center justify-center">
<div className="text-center"> <div className="text-center">
<h2 className="text-2xl font-semibold mb-2"> <h2 className="text-2xl font-semibold mb-2">
@@ -73,13 +228,7 @@ export default function DashboardPage() {
</p> </p>
</div> </div>
</div> </div>
</> ) : (
);
}
return (
<>
<DashboardHeader breadcrumbs={[{ label: "Главная" }]} />
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4"> <div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card> <Card>
@@ -130,7 +279,9 @@ export default function DashboardPage() {
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Средний CPF</CardTitle> <CardTitle className="text-sm font-medium">
Средний CPF
</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" /> <TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -151,7 +302,9 @@ export default function DashboardPage() {
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Средний CPM</CardTitle> <CardTitle className="text-sm font-medium">
Средний CPM
</CardTitle>
<BarChart3 className="h-4 w-4 text-muted-foreground" /> <BarChart3 className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -206,6 +359,7 @@ export default function DashboardPage() {
</Card> </Card>
</div> </div>
</div> </div>
)}
</> </>
); );
} }

View File

@@ -47,7 +47,7 @@ export const TargetChannelSelector: React.FC = () => {
// Автоматический запуск polling при открытии диалога // Автоматический запуск polling при открытии диалога
useEffect(() => { useEffect(() => {
if (isAddDialogOpen && !isSuccess && initialChannelCount > 0) { if (isAddDialogOpen && !isSuccess && initialChannelCount >= 0) {
pollingIntervalRef.current = setInterval(() => { pollingIntervalRef.current = setInterval(() => {
checkForNewChannels(); checkForNewChannels();
}, 1000); }, 1000);