feat: Added frontend for The Catalyst tab (#10)
Added frontend for "The Catalyst" tab. Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
214
app/src/store/useMarketingStore.ts
Normal file
214
app/src/store/useMarketingStore.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { create } from 'zustand';
|
||||
import type {
|
||||
Campaign,
|
||||
MarketingAsset,
|
||||
AdInsight,
|
||||
LiveOptimizationEvent,
|
||||
CatalystSettings,
|
||||
LiveEventType,
|
||||
} from '@/types';
|
||||
|
||||
// ── Mock Data ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const mockCampaigns: Campaign[] = [
|
||||
{
|
||||
id: 'c1',
|
||||
name: '3BHK Prestige Launch — Dubai Marina',
|
||||
objective: 'OUTCOME_LEADS',
|
||||
status: 'ACTIVE',
|
||||
dailyBudget: 50000, // AED 500
|
||||
lifetimeSpend: 2340000,
|
||||
impressions: 487200,
|
||||
clicks: 9744,
|
||||
ctr: 2.0,
|
||||
cpa: 240,
|
||||
roi: 18.5,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
name: 'Penthouse Whale Retarget — Instagram',
|
||||
objective: 'OUTCOME_SALES',
|
||||
status: 'ACTIVE',
|
||||
dailyBudget: 100000, // AED 1000
|
||||
lifetimeSpend: 5800000,
|
||||
impressions: 92400,
|
||||
clicks: 2772,
|
||||
ctr: 3.0,
|
||||
cpa: 2094,
|
||||
roi: 42.1,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
name: '1BHK Investment — Lookalike Audience',
|
||||
objective: 'OUTCOME_TRAFFIC',
|
||||
status: 'PAUSED',
|
||||
dailyBudget: 25000, // AED 250
|
||||
lifetimeSpend: 980000,
|
||||
impressions: 213000,
|
||||
clicks: 4260,
|
||||
ctr: 2.0,
|
||||
cpa: 230,
|
||||
roi: 8.2,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
const mockAssets: MarketingAsset[] = [
|
||||
{
|
||||
id: 'a1',
|
||||
name: 'Penthouse Cinematic — Sea View',
|
||||
type: 'video',
|
||||
status: 'ready',
|
||||
localUrl: '/assets/renders/penthouse_wan22_001.mp4',
|
||||
metaAssetId: 'meta_vid_83920',
|
||||
language: 'en',
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2),
|
||||
},
|
||||
{
|
||||
id: 'a2',
|
||||
name: 'Arabic Poster — 3BHK (Qwen-2512)',
|
||||
type: 'image',
|
||||
status: 'uploaded',
|
||||
localUrl: '/assets/renders/3bhk_qwen_ar.png',
|
||||
metaAssetId: 'meta_img_74811',
|
||||
language: 'ar',
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5),
|
||||
},
|
||||
{
|
||||
id: 'a3',
|
||||
name: 'Amenity Deck Reel — Wan 2.2 14B',
|
||||
type: 'video',
|
||||
status: 'rendering',
|
||||
renderMessage: 'Wan 2.2 is compositing the infinity pool reflection...',
|
||||
language: 'en',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'a4',
|
||||
name: 'English Poster — Penthouse Launch',
|
||||
type: 'image',
|
||||
status: 'queued',
|
||||
renderMessage: 'Qwen-Image 2512 queued for cinematic poster render...',
|
||||
language: 'en',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
const mockInsights: AdInsight[] = Array.from({ length: 14 }, (_, i) => {
|
||||
const d = new Date(Date.now() - 1000 * 60 * 60 * 24 * (13 - i));
|
||||
return {
|
||||
adSetId: `as_${i}`,
|
||||
adSetName: i % 2 === 0 ? '3BHK — Dubai Marina' : 'Penthouse Retarget',
|
||||
spend: 800 + Math.floor(Math.random() * 400),
|
||||
impressions: 18000 + Math.floor(Math.random() * 10000),
|
||||
clicks: 360 + Math.floor(Math.random() * 200),
|
||||
ctr: 1.8 + Math.random() * 1.5,
|
||||
cpa: 190 + Math.floor(Math.random() * 120),
|
||||
roi: 14 + Math.random() * 20,
|
||||
date: d.toISOString().split('T')[0],
|
||||
};
|
||||
});
|
||||
|
||||
function makeLiveEvent(
|
||||
type: LiveEventType,
|
||||
message: string,
|
||||
campaignName?: string,
|
||||
value?: string
|
||||
): LiveOptimizationEvent {
|
||||
return {
|
||||
id: `ev_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
type,
|
||||
message,
|
||||
campaignName,
|
||||
timestamp: new Date(),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
const mockLiveEvents: LiveOptimizationEvent[] = [
|
||||
makeLiveEvent('pause', 'Paused Ad Set B due to CPA exceeding AED 500 threshold.', '3BHK Prestige Launch', 'CPA: AED 512'),
|
||||
makeLiveEvent('shift', 'Shifted AED 200/day budget from Ad Set B to Ad Set A (lower CPA).', '3BHK Prestige Launch', '+AED 200'),
|
||||
makeLiveEvent('rotate', 'Rotated in Penthouse Cinematic (sea view) as new creative for A/B test.', 'Penthouse Whale Retarget'),
|
||||
makeLiveEvent('optimize', 'Expanded Lookalike Audience to 2% similarity — 48k new reach.', '1BHK Investment', '+48k reach'),
|
||||
makeLiveEvent('create', 'Created new Ad Set targeting High-Net-Worth Lookalike from 23 new CRM Closed/Won leads.', 'Penthouse Whale Retarget'),
|
||||
];
|
||||
|
||||
// ── Store Types ────────────────────────────────────────────────────────────────
|
||||
|
||||
interface MarketingState {
|
||||
campaigns: Campaign[];
|
||||
activeAssets: MarketingAsset[];
|
||||
adInsights: AdInsight[];
|
||||
liveEvents: LiveOptimizationEvent[];
|
||||
settings: CatalystSettings;
|
||||
activeTab: 'studio' | 'command' | 'intelligence' | 'war-room';
|
||||
|
||||
// Actions
|
||||
addCampaign: (campaign: Campaign) => void;
|
||||
updateCampaign: (id: string, updates: Partial<Campaign>) => void;
|
||||
addAsset: (asset: MarketingAsset) => void;
|
||||
updateAsset: (id: string, updates: Partial<MarketingAsset>) => void;
|
||||
updateInsights: (insights: AdInsight[]) => void;
|
||||
pushLiveEvent: (event: LiveOptimizationEvent) => void;
|
||||
setMetaSettings: (settings: Partial<CatalystSettings>) => void;
|
||||
setActiveTab: (tab: MarketingState['activeTab']) => void;
|
||||
}
|
||||
|
||||
// ── Store ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useMarketingStore = create<MarketingState>()((set) => ({
|
||||
campaigns: mockCampaigns,
|
||||
activeAssets: mockAssets,
|
||||
adInsights: mockInsights,
|
||||
liveEvents: mockLiveEvents,
|
||||
activeTab: 'studio',
|
||||
|
||||
settings: {
|
||||
metaAccessToken: '',
|
||||
metaAdAccountId: '',
|
||||
metaBusinessId: '',
|
||||
metaAppId: '',
|
||||
whatsappPhoneNumberId: '',
|
||||
isConnected: false,
|
||||
},
|
||||
|
||||
addCampaign: (campaign) =>
|
||||
set((state) => ({ campaigns: [...state.campaigns, campaign] })),
|
||||
|
||||
updateCampaign: (id, updates) =>
|
||||
set((state) => ({
|
||||
campaigns: state.campaigns.map((c) =>
|
||||
c.id === id ? { ...c, ...updates, updatedAt: new Date() } : c
|
||||
),
|
||||
})),
|
||||
|
||||
addAsset: (asset) =>
|
||||
set((state) => ({ activeAssets: [...state.activeAssets, asset] })),
|
||||
|
||||
updateAsset: (id, updates) =>
|
||||
set((state) => ({
|
||||
activeAssets: state.activeAssets.map((a) =>
|
||||
a.id === id ? { ...a, ...updates } : a
|
||||
),
|
||||
})),
|
||||
|
||||
updateInsights: (insights) => set({ adInsights: insights }),
|
||||
|
||||
pushLiveEvent: (event) =>
|
||||
set((state) => ({
|
||||
// Keep a rolling window of the latest 30 events
|
||||
liveEvents: [event, ...state.liveEvents].slice(0, 30),
|
||||
})),
|
||||
|
||||
setMetaSettings: (updates) =>
|
||||
set((state) => ({
|
||||
settings: { ...state.settings, ...updates },
|
||||
})),
|
||||
|
||||
setActiveTab: (tab) => set({ activeTab: tab }),
|
||||
}));
|
||||
Reference in New Issue
Block a user