AI Image Studio, AI podcast Maker, AI product Marketing
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
Button,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
Stack,
|
||||
Chip,
|
||||
Divider,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Campaign,
|
||||
AutoAwesome,
|
||||
PhotoLibrary,
|
||||
Assessment,
|
||||
TrendingUp,
|
||||
CheckCircle,
|
||||
RadioButtonUnchecked,
|
||||
PhotoCamera,
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ImageStudioLayout } from '../ImageStudio/ImageStudioLayout';
|
||||
import { GlassyCard } from '../ImageStudio/ui/GlassyCard';
|
||||
import { SectionHeader } from '../ImageStudio/ui/SectionHeader';
|
||||
import { useProductMarketing } from '../../hooks/useProductMarketing';
|
||||
import { CampaignWizard } from './CampaignWizard';
|
||||
import { AssetAuditPanel } from './AssetAuditPanel';
|
||||
import { ProposalReview } from './ProposalReview';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const MotionCard = motion(Card);
|
||||
|
||||
interface CampaignSummary {
|
||||
campaign_id: string;
|
||||
campaign_name: string;
|
||||
goal: string;
|
||||
status: string;
|
||||
total_assets: number;
|
||||
completed_assets: number;
|
||||
channels: string[];
|
||||
}
|
||||
|
||||
export const ProductMarketingDashboard: React.FC = () => {
|
||||
const {
|
||||
getBrandDNA,
|
||||
brandDNA,
|
||||
isLoadingBrandDNA,
|
||||
listCampaigns,
|
||||
campaigns: apiCampaigns,
|
||||
isLoadingCampaigns,
|
||||
} = useProductMarketing();
|
||||
const [showWizard, setShowWizard] = useState(false);
|
||||
const [showAssetAudit, setShowAssetAudit] = useState(false);
|
||||
const [reviewCampaignId, setReviewCampaignId] = useState<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Load brand DNA on mount
|
||||
if (!brandDNA) {
|
||||
getBrandDNA();
|
||||
}
|
||||
// Load campaigns on mount
|
||||
listCampaigns();
|
||||
}, [brandDNA, getBrandDNA, listCampaigns]);
|
||||
|
||||
const handleCreateCampaign = () => {
|
||||
setShowWizard(true);
|
||||
};
|
||||
|
||||
const handleJourneySelect = (journey: string) => {
|
||||
if (journey === 'launch') {
|
||||
setShowWizard(true);
|
||||
} else if (journey === 'photoshoot') {
|
||||
navigate('/campaign-creator/photoshoot');
|
||||
} else if (journey === 'optimize') {
|
||||
// TODO: Show optimization insights
|
||||
alert('Optimization insights coming soon!');
|
||||
}
|
||||
};
|
||||
|
||||
const handleWizardComplete = (blueprint: any) => {
|
||||
setShowWizard(false);
|
||||
// Reload campaigns from API
|
||||
listCampaigns();
|
||||
// Navigate to proposal review
|
||||
setReviewCampaignId(blueprint.campaign_id);
|
||||
};
|
||||
|
||||
if (showWizard) {
|
||||
return <CampaignWizard onComplete={handleWizardComplete} onCancel={() => setShowWizard(false)} />;
|
||||
}
|
||||
|
||||
if (showAssetAudit) {
|
||||
return <AssetAuditPanel onClose={() => setShowAssetAudit(false)} />;
|
||||
}
|
||||
|
||||
if (reviewCampaignId) {
|
||||
return (
|
||||
<ProposalReview
|
||||
campaignId={reviewCampaignId}
|
||||
onBack={() => {
|
||||
setReviewCampaignId(null);
|
||||
listCampaigns();
|
||||
}}
|
||||
onComplete={() => {
|
||||
setReviewCampaignId(null);
|
||||
listCampaigns();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ImageStudioLayout
|
||||
headerProps={{
|
||||
title: 'AI Campaign Creator',
|
||||
subtitle:
|
||||
'Create consistent, personalized marketing campaigns across all digital platforms. AI handles the heavy lifting—you just approve.',
|
||||
}}
|
||||
>
|
||||
<GlassyCard
|
||||
sx={{
|
||||
maxWidth: 1400,
|
||||
mx: 'auto',
|
||||
p: { xs: 3, md: 5 },
|
||||
}}
|
||||
>
|
||||
{/* Brand DNA Status */}
|
||||
{isLoadingBrandDNA ? (
|
||||
<Box display="flex" justifyContent="center" py={4}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : brandDNA ? (
|
||||
<Alert severity="success" sx={{ mb: 3 }}>
|
||||
Brand DNA loaded: {brandDNA.persona?.persona_name || 'Default Persona'} •{' '}
|
||||
{brandDNA.writing_style?.tone || 'professional'} tone • {brandDNA.target_audience?.industry_focus || 'general'} industry
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert severity="info" sx={{ mb: 3 }}>
|
||||
Brand DNA not available. Complete onboarding to enable personalized campaigns.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* User Journey Selection */}
|
||||
<SectionHeader
|
||||
title="Choose Your Journey"
|
||||
subtitle="Select how you want to create marketing assets"
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
<Grid container spacing={3} sx={{ mb: 4 }}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(124, 58, 237, 0.1)',
|
||||
border: '1px solid rgba(124, 58, 237, 0.3)',
|
||||
}}
|
||||
onClick={() => handleJourneySelect('launch')}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Campaign sx={{ color: '#c4b5fd', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Journey A: Launch Campaign
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Create a new marketing campaign from scratch. AI generates personalized assets based on your brand DNA.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<AutoAwesome />} fullWidth>
|
||||
Start Campaign Wizard
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(59, 130, 246, 0.1)',
|
||||
border: '1px solid rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
onClick={() => setShowAssetAudit(true)}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<PhotoLibrary sx={{ color: '#93c5fd', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Journey B: Enhance Assets
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Upload existing assets for AI-powered quality assessment and enhancement recommendations.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<PhotoLibrary />} fullWidth>
|
||||
Upload & Audit
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(16, 185, 129, 0.1)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.3)',
|
||||
}}
|
||||
onClick={() => handleJourneySelect('photoshoot')}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<PhotoCamera sx={{ color: '#6ee7b7', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Journey C: Product Photoshoot
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Generate professional product images for e-commerce listings and marketing campaigns.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<PhotoCamera />} fullWidth>
|
||||
Launch Photoshoot Studio
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(191, 219, 254, 0.1)',
|
||||
border: '1px solid rgba(191, 219, 254, 0.3)',
|
||||
}}
|
||||
onClick={() => handleJourneySelect('optimize')}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<TrendingUp sx={{ color: '#bfdbfe', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Journey D: Optimize
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Get AI-powered insights and suggestions to optimize your existing campaigns and assets.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<TrendingUp />} fullWidth>
|
||||
View Insights
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Divider sx={{ my: 4, borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||
|
||||
{/* Quick Actions */}
|
||||
<SectionHeader
|
||||
title="Quick Actions"
|
||||
subtitle="Start a new campaign or enhance existing assets"
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
<Grid container spacing={3} sx={{ mb: 4 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(124, 58, 237, 0.1)',
|
||||
border: '1px solid rgba(124, 58, 237, 0.3)',
|
||||
}}
|
||||
onClick={handleCreateCampaign}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Campaign sx={{ color: '#c4b5fd', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Create Campaign
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Launch a new marketing campaign with AI-generated assets personalized to your brand.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<AutoAwesome />} fullWidth>
|
||||
Start Campaign Wizard
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<MotionCard
|
||||
whileHover={{ scale: 1.02 }}
|
||||
sx={{
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(59, 130, 246, 0.1)',
|
||||
border: '1px solid rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
onClick={() => setShowAssetAudit(true)}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<PhotoLibrary sx={{ color: '#93c5fd', fontSize: 32 }} />
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Audit Assets
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Upload existing assets for AI-powered quality assessment and enhancement recommendations.
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<PhotoLibrary />} fullWidth>
|
||||
Upload & Audit
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Divider sx={{ my: 4, borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||
|
||||
{/* Active Campaigns */}
|
||||
<SectionHeader
|
||||
title="Active Campaigns"
|
||||
subtitle={
|
||||
isLoadingCampaigns
|
||||
? 'Loading campaigns...'
|
||||
: apiCampaigns.length === 0
|
||||
? 'No active campaigns. Create your first campaign to get started.'
|
||||
: `${apiCampaigns.length} campaign(s) in progress`
|
||||
}
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
{isLoadingCampaigns ? (
|
||||
<Box display="flex" justifyContent="center" py={4}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : apiCampaigns.length === 0 ? (
|
||||
<Paper
|
||||
sx={{
|
||||
p: 4,
|
||||
textAlign: 'center',
|
||||
background: 'rgba(255,255,255,0.02)',
|
||||
border: '1px dashed rgba(255,255,255,0.1)',
|
||||
}}
|
||||
>
|
||||
<Campaign sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||
No campaigns yet
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Create your first campaign to start generating personalized marketing assets
|
||||
</Typography>
|
||||
<Button variant="contained" startIcon={<AutoAwesome />} onClick={handleCreateCampaign}>
|
||||
Create Campaign
|
||||
</Button>
|
||||
</Paper>
|
||||
) : (
|
||||
<Grid container spacing={2}>
|
||||
{apiCampaigns.map((campaign) => (
|
||||
<Grid item xs={12} md={6} key={campaign.campaign_id}>
|
||||
<GlassyCard sx={{ p: 3 }}>
|
||||
<Stack spacing={2}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="start">
|
||||
<Box>
|
||||
<Typography variant="h6" fontWeight={700} gutterBottom>
|
||||
{campaign.campaign_name}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{campaign.goal}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Chip
|
||||
label={campaign.status}
|
||||
size="small"
|
||||
color={campaign.status === 'ready' ? 'success' : 'default'}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||
|
||||
<Box>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
Progress
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box flex={1}>
|
||||
<Box
|
||||
sx={{
|
||||
height: 8,
|
||||
borderRadius: 1,
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: `${((campaign.asset_nodes?.filter((n: any) => n.status === 'ready' || n.status === 'approved').length || 0) / (campaign.asset_nodes?.length || 1)) * 100}%`,
|
||||
background: 'linear-gradient(90deg, #7c3aed, #a78bfa)',
|
||||
transition: 'width 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{campaign.asset_nodes?.filter((n: any) => n.status === 'ready' || n.status === 'approved').length || 0}/{campaign.asset_nodes?.length || 0}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
Channels
|
||||
</Typography>
|
||||
<Box display="flex" gap={1} flexWrap="wrap">
|
||||
{campaign.channels.map((channel) => (
|
||||
<Chip key={channel} label={channel} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
// Check if proposals exist, if so show review, otherwise show campaign
|
||||
setReviewCampaignId(campaign.campaign_id);
|
||||
}}
|
||||
>
|
||||
{campaign.asset_nodes?.some((n: any) => n.status === 'proposed') ? 'Review Proposals' : 'View Campaign'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</GlassyCard>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</GlassyCard>
|
||||
</ImageStudioLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user