diff --git a/app/dashboard/[workspaceId]/creatives/page.tsx b/app/dashboard/[workspaceId]/creatives/page.tsx
index 32aa487..9d25e00 100644
--- a/app/dashboard/[workspaceId]/creatives/page.tsx
+++ b/app/dashboard/[workspaceId]/creatives/page.tsx
@@ -74,6 +74,8 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
+import { toast } from "sonner";
+import { handleApiError } from "@/lib/utils/error-handler";
import { BOT_USERNAME } from "@/lib/utils/constants";
import { ProjectSelector } from "@/components/project-selector";
import type { Creative, CreativeAnalyticsItem } from "@/lib/types/api";
@@ -157,17 +159,21 @@ export default function CreativesPage() {
const [includeArchived, setIncludeArchived] = useState(false);
const [viewMode, setViewMode] = useState<"table" | "grid">("table");
- const canWrite = hasPermission("placements_write");
+ // Permission checks
+ const canReadCreatives = hasPermission("creatives_read");
+ const canWriteCreatives = hasPermission("creatives_write");
+ const canViewAnalytics = hasPermission("analytics_read") || hasPermission("analytics_without_clicks") || hasPermission("analytics_own_creatives");
+ const canViewSubscriptions = hasPermission("analytics_read") || hasPermission("analytics_own_creatives");
useEffect(() => {
loadCreatives();
}, [workspaceId, projectFilterId, includeArchived]);
useEffect(() => {
- if (creatives.length > 0) {
+ if (creatives.length > 0 && canViewAnalytics) {
loadAnalytics();
}
- }, [creatives, workspaceId, projectFilterId]);
+ }, [creatives, workspaceId, projectFilterId, canViewAnalytics]);
const loadCreatives = async () => {
try {
@@ -179,13 +185,18 @@ export default function CreativesPage() {
});
setCreatives(response.items);
} catch (err) {
- console.error("Failed to load creatives:", err);
+ handleApiError(err);
} finally {
setLoading(false);
}
};
const loadAnalytics = async () => {
+ if (!canViewAnalytics) {
+ setAnalyticsData([]);
+ return;
+ }
+
try {
setLoadingAnalytics(true);
const response = await demoAwareAnalyticsApi.creatives(workspaceId, {
@@ -209,24 +220,42 @@ export default function CreativesPage() {
}, [creatives, analyticsData]);
const handleArchive = async (creative: Creative) => {
- try {
- const newStatus = creative.status === "active" ? "archived" : "active";
- await creativesApi.update(workspaceId, creative.id, { status: newStatus });
- loadCreatives();
- } catch (err) {
- console.error("Failed to archive creative:", err);
- }
+ await toast.promise(
+ (async () => {
+ const newStatus = creative.status === "active" ? "archived" : "active";
+ await creativesApi.update(workspaceId, creative.id, { status: newStatus });
+ await loadCreatives();
+ })(),
+ {
+ loading: creative.status === "active" ? "Архивирование..." : "Восстановление...",
+ success: () => {
+ return creative.status === "active"
+ ? `Креатив «${creative.name}» архивирован`
+ : `Креатив «${creative.name}» восстановлен`;
+ },
+ error: (err) => {
+ handleApiError(err);
+ return "Не удалось изменить статус";
+ },
+ }
+ );
};
- const handleDelete = async (creativeId: string) => {
- if (!confirm("Удалить креатив? Это действие необратимо.")) return;
-
- try {
- await creativesApi.delete(workspaceId, creativeId);
- loadCreatives();
- } catch (err) {
- console.error("Failed to delete creative:", err);
- }
+ const handleDelete = async (creativeId: string, creativeName: string) => {
+ await toast.promise(
+ (async () => {
+ await creativesApi.delete(workspaceId, creativeId);
+ await loadCreatives();
+ })(),
+ {
+ loading: "Удаление креатива...",
+ success: () => `Креатив «${creativeName}» удалён`,
+ error: (err) => {
+ handleApiError(err);
+ return "Не удалось удалить креатив";
+ },
+ }
+ );
};
@@ -254,14 +283,16 @@ export default function CreativesPage() {
// Totals
const totals = useMemo(() => {
- const data = filteredCreatives.filter((c) => c.analytics);
+ const data = filteredCreatives.filter((c) => canViewAnalytics && c.analytics);
return {
placements: data.reduce((sum, c) => sum + (c.analytics?.placements_count || 0), 0),
cost: data.reduce((sum, c) => sum + (c.analytics?.total_cost || 0), 0),
- subscriptions: data.reduce((sum, c) => sum + (c.analytics?.total_subscriptions || 0), 0),
+ subscriptions: canViewSubscriptions
+ ? data.reduce((sum, c) => sum + (c.analytics?.total_subscriptions || 0), 0)
+ : 0,
views: data.reduce((sum, c) => sum + (c.analytics?.total_views || 0), 0),
};
- }, [filteredCreatives]);
+ }, [filteredCreatives, canViewAnalytics, canViewSubscriptions]);
return (
@@ -300,7 +331,7 @@ export default function CreativesPage() {
- {canWrite && (
+ {canWriteCreatives && (
- {canWrite && !search && (
+ {canWriteCreatives && !search && (