Release Candidate: Production Release with Multi-Tenant & Onboarding Enhancements
This commit is contained in:
@@ -17,12 +17,14 @@ import {
|
||||
Warning
|
||||
} from '@mui/icons-material';
|
||||
import OnboardingButton from '../common/OnboardingButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getApiKeys, completeOnboarding, getOnboardingSummary, getWebsiteAnalysisData, getResearchPreferencesData, setCurrentStep } from '../../../api/onboarding';
|
||||
import { SetupSummary, CapabilitiesOverview, AgentTeamSection } from './components';
|
||||
import { FinalStepProps, OnboardingData, Capability } from './types';
|
||||
import { getAgentTeam, type AgentTeamCatalogEntry } from '../../../api/agentsTeam';
|
||||
|
||||
const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }) => {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoading, setDataLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -297,22 +299,9 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
localStorage.setItem('onboarding_active_step', String(stepsLengthFallback()));
|
||||
} catch {}
|
||||
|
||||
// Navigate directly to dashboard without calling onContinue
|
||||
// This bypasses the wizard flow and goes straight to the dashboard
|
||||
console.log('FinalStep: Navigating to dashboard...');
|
||||
console.log('FinalStep: Setting window.location.href to /dashboard');
|
||||
|
||||
// Try multiple navigation methods to ensure redirect works
|
||||
try {
|
||||
window.location.href = '/dashboard';
|
||||
console.log('FinalStep: window.location.href set successfully');
|
||||
} catch (navError) {
|
||||
console.error('FinalStep: window.location.href failed:', navError);
|
||||
console.log('FinalStep: Trying alternative navigation method...');
|
||||
window.location.assign('/dashboard');
|
||||
}
|
||||
|
||||
console.log('FinalStep: Navigation initiated');
|
||||
// Navigate directly to dashboard using React Router
|
||||
console.log('FinalStep: Navigating to dashboard with react-router navigate("/dashboard")');
|
||||
navigate('/dashboard', { replace: true });
|
||||
} catch (e: any) {
|
||||
console.error('FinalStep: Error completing onboarding:', e);
|
||||
console.error('FinalStep: Error details:', {
|
||||
@@ -528,26 +517,27 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
onClick={handleLaunch}
|
||||
startIcon={<Rocket />}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 600,
|
||||
px: 4,
|
||||
py: 2,
|
||||
borderRadius: 2,
|
||||
textTransform: 'none',
|
||||
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow: '0 6px 16px rgba(102, 126, 234, 0.4)',
|
||||
},
|
||||
'&:disabled': {
|
||||
background: 'rgba(0,0,0,0.1)',
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
boxShadow: 'none',
|
||||
transform: 'none',
|
||||
}
|
||||
}}
|
||||
background: 'linear-gradient(135deg, #0f172a 0%, #312e81 40%, #4f46e5 100%)',
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 600,
|
||||
px: 4,
|
||||
py: 2,
|
||||
borderRadius: 999,
|
||||
textTransform: 'none',
|
||||
boxShadow: '0 10px 28px rgba(15,23,42,0.45)',
|
||||
letterSpacing: 0.2,
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #020617 0%, #1e1b4b 40%, #4338ca 100%)',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow: '0 14px 36px rgba(15,23,42,0.55)',
|
||||
},
|
||||
'&:disabled': {
|
||||
background: 'rgba(148,163,184,0.4)',
|
||||
color: 'rgba(15,23,42,0.6)',
|
||||
boxShadow: 'none',
|
||||
transform: 'none',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Launch Alwrity & Complete Setup
|
||||
</Button>
|
||||
@@ -555,12 +545,16 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
|
||||
{/* Help Text */}
|
||||
<Box sx={{ mt: 3, textAlign: 'center' }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
|
||||
This will complete your onboarding and launch Alwrity with your configured settings.
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1 }}>
|
||||
<Star sx={{ fontSize: 16 }} />
|
||||
Ready to create amazing content with AI-powered assistance
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1 }}
|
||||
>
|
||||
<Star sx={{ fontSize: 16, color: '#fbbf24' }} />
|
||||
Your SIF Agent Framework is ready to orchestrate your marketing.
|
||||
</Typography>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Chip,
|
||||
Stack,
|
||||
Divider,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import GroupIcon from "@mui/icons-material/Group";
|
||||
@@ -30,6 +31,7 @@ import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||
|
||||
import {
|
||||
aiOptimizeAgentProfile,
|
||||
@@ -242,18 +244,168 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 3, p: 3, borderRadius: 3 }}>
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1 }}>
|
||||
<GroupIcon />
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
||||
Meet {websiteName || "Your"} AI Marketing Team
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
These agents work together to help you plan, execute, and improve your digital marketing. Tools and responsibilities are locked for safety and reliability.
|
||||
</Typography>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
mt: 3,
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: "1px solid #e2e8f0",
|
||||
bgcolor: "#ffffff",
|
||||
color: "#0f172a",
|
||||
boxShadow: "0 1px 2px rgba(15,23,42,0.04)",
|
||||
"& .MuiTypography-root": {
|
||||
color: "#111827 !important",
|
||||
WebkitTextFillColor: "#111827",
|
||||
},
|
||||
"& .MuiTypography-body2": {
|
||||
color: "#4b5563 !important",
|
||||
},
|
||||
"& .MuiTypography-caption": {
|
||||
color: "#6b7280 !important",
|
||||
},
|
||||
"& .MuiFormLabel-root": {
|
||||
color: "#4b5563 !important",
|
||||
},
|
||||
"& .MuiFormLabel-root.Mui-focused": {
|
||||
color: "#4f46e5 !important",
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
color: "#111827 !important",
|
||||
},
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff !important",
|
||||
color: "#111827 !important",
|
||||
},
|
||||
"& .MuiAccordionDetails-root": {
|
||||
bgcolor: "#ffffff !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 3,
|
||||
p: 2.5,
|
||||
borderRadius: 3,
|
||||
background: "linear-gradient(135deg, #0f172a 0%, #312e81 40%, #4f46e5 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 2,
|
||||
boxShadow: "0 12px 30px rgba(15,23,42,0.45)",
|
||||
"& .MuiTypography-root": {
|
||||
color: "#e5e7eb !important",
|
||||
WebkitTextFillColor: "#e5e7eb",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: "999px",
|
||||
bgcolor: "rgba(129,140,248,0.4)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "#e5e7eb",
|
||||
}}
|
||||
>
|
||||
<GroupIcon />
|
||||
</Box>
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 800, letterSpacing: 0.2 }}>
|
||||
Meet {websiteName || "Your"} AI Marketing Team
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.92 }}>
|
||||
Enterprise-grade autonomous agents orchestrated by ALwrity's SIF framework to run your marketing.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Tooltip
|
||||
title="Semantic Intelligence Framework™ – Alwrity's orchestration layer for autonomous marketing agents."
|
||||
arrow
|
||||
placement="left"
|
||||
>
|
||||
<Chip
|
||||
size="small"
|
||||
label="SIF Agent Framework™"
|
||||
sx={{
|
||||
borderRadius: "999px",
|
||||
border: "1px solid rgba(191,219,254,0.9)",
|
||||
bgcolor: "rgba(15,23,42,0.75)",
|
||||
color: "#e5e7eb",
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.4,
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={1.5}>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
px: 0.5,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: 1.5,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap" alignItems="center">
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.6 }}>
|
||||
Agent roles
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
label="Lead"
|
||||
sx={{
|
||||
height: 22,
|
||||
borderRadius: "999px",
|
||||
bgcolor: "#eef2ff",
|
||||
color: "#312e81",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label="Strategist"
|
||||
sx={{
|
||||
height: 22,
|
||||
borderRadius: "999px",
|
||||
bgcolor: "#ecfdf5",
|
||||
color: "#047857",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label="Analyst"
|
||||
sx={{
|
||||
height: 22,
|
||||
borderRadius: "999px",
|
||||
bgcolor: "#eff6ff",
|
||||
color: "#1d4ed8",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<Stack direction="row" spacing={0.75} alignItems="center">
|
||||
<Box sx={{ width: 8, height: 8, borderRadius: "999px", bgcolor: "#22c55e" }} />
|
||||
<Typography variant="caption">Enabled</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={0.75} alignItems="center">
|
||||
<Box sx={{ width: 8, height: 8, borderRadius: "999px", bgcolor: "#e5e7eb" }} />
|
||||
<Typography variant="caption">Disabled</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={2}>
|
||||
{agents.map((agent) => {
|
||||
const displayName = resolveDisplayName(agent, websiteName);
|
||||
const scheduleText = formatSchedule(agent.profile?.schedule ?? agent.defaults?.schedule);
|
||||
@@ -261,66 +413,173 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
const warnings = draft ? lintDraft(agent, draft) : [];
|
||||
|
||||
return (
|
||||
<Accordion key={agent.agent_key} disableGutters elevation={0} sx={{ borderRadius: 2, border: "1px solid rgba(0,0,0,0.08)" }}>
|
||||
<Accordion
|
||||
key={agent.agent_key}
|
||||
disableGutters
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
border: "1px solid #e2e8f0",
|
||||
bgcolor: "#f9fafb",
|
||||
"&:before": { display: "none" },
|
||||
transition: "all 160ms ease",
|
||||
"&:hover": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 8px 24px rgba(15,23,42,0.12)",
|
||||
transform: "translateY(-1px)",
|
||||
},
|
||||
"&.Mui-expanded": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 12px 30px rgba(15,23,42,0.16)",
|
||||
bgcolor: "#ffffff",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", gap: 2 }}>
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.2 }} noWrap>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{ fontWeight: 700, lineHeight: 1.2, color: "#0f172a" }}
|
||||
noWrap
|
||||
>
|
||||
{displayName}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" noWrap>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: "#64748b" }}
|
||||
noWrap
|
||||
>
|
||||
{agent.role || agent.agent_key} • {scheduleText}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1} sx={{ flexShrink: 0 }}>
|
||||
<Chip size="small" icon={<LockIcon />} label="Tools locked" variant="outlined" />
|
||||
<Chip size="small" icon={<LockIcon />} label="Responsibilities locked" variant="outlined" />
|
||||
<Tooltip title="System tools this agent can call while executing your strategy." arrow>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<LockIcon />}
|
||||
label="Tools locked"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
borderColor: "#cbd5e1",
|
||||
bgcolor: "#e5edff",
|
||||
color: "#1e293b",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="High-level responsibilities are predefined for safety and reliability." arrow>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<LockIcon />}
|
||||
label="Responsibilities locked"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
borderColor: "#cbd5e1",
|
||||
bgcolor: "#e5edff",
|
||||
color: "#1e293b",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack spacing={2}>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<AutoFixHighIcon />}
|
||||
disabled={aiBusyKey === agent.agent_key}
|
||||
onClick={() => handleAiOptimize(agent)}
|
||||
sx={{ textTransform: "none" }}
|
||||
<Tooltip
|
||||
title="Let ALwrity refine this agent's prompts and schedule based on your brand context."
|
||||
arrow
|
||||
>
|
||||
AI Optimize
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<VisibilityIcon />}
|
||||
disabled={previewBusyKey === agent.agent_key}
|
||||
onClick={() => handlePreview(agent)}
|
||||
sx={{ textTransform: "none" }}
|
||||
<span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<AutoFixHighIcon />}
|
||||
disabled={aiBusyKey === agent.agent_key}
|
||||
onClick={() => handleAiOptimize(agent)}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
borderColor: "#4f46e5",
|
||||
color: "#4f46e5",
|
||||
"&:hover": {
|
||||
borderColor: "#4338ca",
|
||||
background: "rgba(79,70,229,0.04)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
AI Optimize
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title="Preview how this agent would respond using the current configuration."
|
||||
arrow
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
startIcon={<SaveIcon />}
|
||||
disabled={!draft || savingKey === agent.agent_key}
|
||||
onClick={() => handleSave(agent)}
|
||||
sx={{ textTransform: "none" }}
|
||||
<span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<VisibilityIcon />}
|
||||
disabled={previewBusyKey === agent.agent_key}
|
||||
onClick={() => handlePreview(agent)}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
borderColor: "#0f172a",
|
||||
color: "#0f172a",
|
||||
"&:hover": {
|
||||
borderColor: "#111827",
|
||||
background: "rgba(15,23,42,0.04)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title="Persist this agent's configuration for future sessions."
|
||||
arrow
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<RestartAltIcon />}
|
||||
disabled={savingKey === agent.agent_key}
|
||||
onClick={() => handleReset(agent)}
|
||||
sx={{ textTransform: "none" }}
|
||||
<span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
startIcon={<SaveIcon />}
|
||||
disabled={!draft || savingKey === agent.agent_key}
|
||||
onClick={() => handleSave(agent)}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
background: "linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)",
|
||||
boxShadow: "0 4px 12px rgba(79,70,229,0.35)",
|
||||
"&:hover": {
|
||||
background: "linear-gradient(135deg, #4338ca 0%, #6d28d9 100%)",
|
||||
boxShadow: "0 6px 18px rgba(79,70,229,0.45)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title="Revert this agent to its recommended default settings."
|
||||
arrow
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<RestartAltIcon />}
|
||||
disabled={savingKey === agent.agent_key}
|
||||
onClick={() => handleReset(agent)}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{warnings.length > 0 && (
|
||||
@@ -367,9 +626,34 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
<Divider />
|
||||
|
||||
{draft && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 1,
|
||||
p: 2.5,
|
||||
borderRadius: 2,
|
||||
border: "1px dashed #e5e7eb",
|
||||
bgcolor: "#f9fafb",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
mb: 1.5,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 0.75,
|
||||
}}
|
||||
>
|
||||
<EditOutlinedIcon sx={{ fontSize: 18, color: "#4f46e5" }} />
|
||||
Editable settings
|
||||
<Typography
|
||||
component="span"
|
||||
variant="caption"
|
||||
sx={{ ml: 0.75, color: "#6b7280" }}
|
||||
>
|
||||
Adjust how this agent behaves for your workspace.
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
@@ -377,6 +661,17 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
value={draft.display_name}
|
||||
onChange={(e) => setDraftField(agent.agent_key, { display_name: e.target.value })}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
@@ -388,7 +683,20 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
label="Enabled"
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<InputLabel>Schedule</InputLabel>
|
||||
<Select
|
||||
label="Schedule"
|
||||
@@ -418,12 +726,34 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
label="Time (HH:MM)"
|
||||
value={draft.schedule?.time || ""}
|
||||
onChange={(e) => setDraftField(agent.agent_key, { schedule: { ...(draft.schedule || {}), time: e.target.value } })}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -434,6 +764,17 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
value={draft.schedule?.time || ""}
|
||||
onChange={(e) => setDraftField(agent.agent_key, { schedule: { ...(draft.schedule || {}), time: e.target.value } })}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -444,6 +785,17 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
multiline
|
||||
minRows={6}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
label="Task prompt template"
|
||||
@@ -452,6 +804,17 @@ const AgentTeamSection: React.FC<Props> = ({ websiteName, agents, contextCard })
|
||||
multiline
|
||||
minRows={6}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
bgcolor: "#ffffff",
|
||||
"& fieldset": { borderColor: "#e5e7eb" },
|
||||
"&:hover fieldset": { borderColor: "#4f46e5" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#4f46e5",
|
||||
boxShadow: "0 0 0 1px rgba(79,70,229,0.25)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowForward as ArrowForwardIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
PlayArrow as PlayArrowIcon,
|
||||
// Social Media Icons
|
||||
Facebook as FacebookIcon,
|
||||
Twitter as TwitterIcon,
|
||||
@@ -31,10 +35,13 @@ import {
|
||||
Google as GoogleIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
// UI Icons
|
||||
Psychology as PsychologyIcon,
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Lightbulb as LightbulbIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Error as ErrorIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
// Import refactored components
|
||||
import EmailSection from './common/EmailSection';
|
||||
@@ -53,6 +60,7 @@ interface IntegrationsStepProps {
|
||||
onContinue: () => void;
|
||||
updateHeaderContent: (content: { title: string; description: string }) => void;
|
||||
onValidationChange?: (isValid: boolean) => void;
|
||||
onDataChange?: (data: any) => void;
|
||||
}
|
||||
|
||||
interface IntegrationPlatform {
|
||||
@@ -68,7 +76,7 @@ interface IntegrationPlatform {
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateHeaderContent, onValidationChange }) => {
|
||||
const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateHeaderContent, onValidationChange, onDataChange }) => {
|
||||
const { user } = useUser();
|
||||
const [email, setEmail] = useState<string>('');
|
||||
|
||||
@@ -102,7 +110,7 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
const { connected: wordpressConnected, sites: wordpressSites } = useWordPressOAuth();
|
||||
|
||||
// Bing OAuth hook
|
||||
const { connected: bingConnected, sites: bingSites, connect: connectBing } = useBingOAuth();
|
||||
const { connected: bingConnected, sites: bingSites, connect: connectBing, refreshStatus: refreshBingStatus } = useBingOAuth();
|
||||
|
||||
// Initialize integrations data
|
||||
const [integrations] = useState<IntegrationPlatform[]>([
|
||||
@@ -257,6 +265,17 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
}
|
||||
}, [wordpressConnected, wordpressSites, connectedPlatforms, setConnectedPlatforms, invalidateAnalyticsCache]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await refreshBingStatus();
|
||||
} catch (e) {
|
||||
console.error('Failed to refresh Bing status:', e);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Handle Bing connection status changes
|
||||
useEffect(() => {
|
||||
|
||||
@@ -354,6 +373,65 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
return sites;
|
||||
}, [wixConnected, wixSites, wordpressConnected, wordpressSites]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onDataChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const websiteIntegrations = {
|
||||
wix: wixConnected ? wixSites.map(s => ({ url: s.blog_url, name: 'Wix Site' })) : [],
|
||||
wordpress: wordpressConnected ? wordpressSites.map(s => ({ url: s.blog_url, name: 'WordPress Site' })) : [],
|
||||
primaryWebsite: primarySite || null,
|
||||
};
|
||||
|
||||
const analyticsIntegrations = {
|
||||
gsc: {
|
||||
connected: connectedPlatforms.includes('gsc'),
|
||||
sites: (gscSites || []).map((site: any) => ({
|
||||
siteUrl: site.siteUrl || site.site_url || '',
|
||||
})),
|
||||
},
|
||||
bing: {
|
||||
connected: connectedPlatforms.includes('bing') || !!bingConnected,
|
||||
sites: (bingSites || []).map((site: any) => ({
|
||||
siteUrl: site.siteUrl || site.site_url || '',
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
const socialIntegrations = {
|
||||
facebook: connectedPlatforms.includes('facebook'),
|
||||
twitter: connectedPlatforms.includes('twitter'),
|
||||
linkedin: connectedPlatforms.includes('linkedin'),
|
||||
instagram: connectedPlatforms.includes('instagram'),
|
||||
youtube: connectedPlatforms.includes('youtube'),
|
||||
tiktok: connectedPlatforms.includes('tiktok'),
|
||||
pinterest: connectedPlatforms.includes('pinterest'),
|
||||
};
|
||||
|
||||
onDataChange({
|
||||
integrations: {
|
||||
primaryWebsite: websiteIntegrations.primaryWebsite,
|
||||
websitePlatforms: websiteIntegrations,
|
||||
analyticsPlatforms: analyticsIntegrations,
|
||||
socialPlatforms: socialIntegrations,
|
||||
connectedPlatforms,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}, [
|
||||
onDataChange,
|
||||
primarySite,
|
||||
wixConnected,
|
||||
wixSites,
|
||||
wordpressConnected,
|
||||
wordpressSites,
|
||||
gscSites,
|
||||
bingConnected,
|
||||
bingSites,
|
||||
connectedPlatforms,
|
||||
]);
|
||||
|
||||
// Default to first site
|
||||
useEffect(() => {
|
||||
if (availableSites.length > 0 && !primarySite) {
|
||||
@@ -379,6 +457,30 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
}
|
||||
}, [availableSites.length, primarySite, onValidationChange]);
|
||||
|
||||
const [walkthroughStep, setWalkthroughStep] = useState<number>(0);
|
||||
const walkthroughTitles: string[] = [
|
||||
'Connect your platforms',
|
||||
'We cache your insights',
|
||||
'Agents analyze weekly',
|
||||
'We propose clear fixes',
|
||||
'You review and publish',
|
||||
];
|
||||
const walkthroughDescriptions: string[] = [
|
||||
'Link Google Search Console and Bing to unlock search signals for your site.',
|
||||
'We safely store key metrics so recommendations are quick and quota‑friendly.',
|
||||
'SIF agents look for low‑CTR pages, striking‑distance wins, declines, and overlaps.',
|
||||
'You’ll see simple suggestions: better titles/meta, refreshes, and consolidations.',
|
||||
'Pick what you like and publish; we keep the rhythm going week after week.',
|
||||
];
|
||||
const walkthroughLabels: string[] = ['Step 1 of 5', 'Step 2 of 5', 'Step 3 of 5', 'Step 4 of 5', 'Step 5 of 5'];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
setWalkthroughStep(prev => (prev + 1) % walkthroughTitles.length);
|
||||
}, 4500);
|
||||
return () => clearInterval(id);
|
||||
}, [walkthroughTitles.length]);
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', maxWidth: '100%', p: { xs: 1, sm: 2, md: 3 } }}>
|
||||
{/* Email Address Section */}
|
||||
@@ -594,6 +696,281 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
{/* Coming Soon Section */}
|
||||
<ComingSoonSection />
|
||||
|
||||
{/* Recommendation Panel */}
|
||||
<Fade in timeout={1500}>
|
||||
<div>
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
mt: 2.5,
|
||||
p: { xs: 2, md: 2.5 },
|
||||
borderRadius: 2,
|
||||
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2, gap: 1 }}>
|
||||
<AutoAwesomeIcon sx={{ color: '#7c3aed' }} />
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, color: '#111827' }}>
|
||||
How ALwrity’s SIF Agents Help You Every Week
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ color: '#334155', mb: 1.5 }}>
|
||||
Your connected analytics power a helpful weekly routine. Our SIF agent framework reads real search signals and proposes simple, high‑impact actions for your content—no jargon, just clear next steps.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', mb: 2 }}>
|
||||
<Chip icon={<AnalyticsIcon />} label="Low‑CTR pages" sx={{ bgcolor: '#eef2ff', color: '#312e81', fontWeight: 600 }} />
|
||||
<Chip icon={<AnalyticsIcon />} label="Striking‑distance wins" sx={{ bgcolor: '#ecfeff', color: '#075985', fontWeight: 600 }} />
|
||||
<Chip icon={<AnalyticsIcon />} label="Declining queries" sx={{ bgcolor: '#f0fdf4', color: '#14532d', fontWeight: 600 }} />
|
||||
<Chip icon={<AnalyticsIcon />} label="Cannibalization fixes" sx={{ bgcolor: '#fff7ed', color: '#7c2d12', fontWeight: 600 }} />
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap', mb: 2 }}>
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }}>
|
||||
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #e5e7eb', minWidth: 210, textAlign: 'center', bgcolor: '#f9fafb' }}>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 1 }}>
|
||||
GSC & Bing Metrics
|
||||
</Typography>
|
||||
<AnalyticsIcon sx={{ color: '#2563eb' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 1 }}>
|
||||
Clicks, impressions, CTR, positions
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
<ArrowForwardIcon sx={{ color: '#64748b' }} />
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.05 }}>
|
||||
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #e5e7eb', minWidth: 210, textAlign: 'center', bgcolor: '#f9fafb' }}>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 1 }}>
|
||||
SIF Agents
|
||||
</Typography>
|
||||
<PsychologyIcon sx={{ color: '#7c3aed' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 1 }}>
|
||||
Turns signals into clear suggestions
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
<ArrowForwardIcon sx={{ color: '#64748b' }} />
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, delay: 0.1 }}>
|
||||
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #e5e7eb', minWidth: 210, textAlign: 'center', bgcolor: '#f9fafb' }}>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 1 }}>
|
||||
Suggested Actions
|
||||
</Typography>
|
||||
<AutoAwesomeIcon sx={{ color: '#059669' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 1 }}>
|
||||
Better titles/meta, refreshes, consolidations
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' }, gap: 2 }}>
|
||||
<motion.div initial={{ opacity: 0, x: -12 }} animate={{ opacity: 1, x: 0 }} transition={{ duration: 0.45 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
bgcolor: '#f9fafb',
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ color: '#111827', fontWeight: 700, mb: 1 }}>
|
||||
Who does what
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.75 }}>
|
||||
<Chip size="small" label="SEO Agent" sx={{ bgcolor: '#eef2ff', color: '#312e81', fontWeight: 700 }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155' }}>
|
||||
Finds low‑CTR pages and striking‑distance queries; suggests title/meta fixes and refreshes.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Chip size="small" label="Content Agent" sx={{ bgcolor: '#ecfeff', color: '#075985', fontWeight: 700 }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155' }}>
|
||||
Recommends consolidation and internal links from cannibalization; queues refresh topics.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
<motion.div initial={{ opacity: 0, x: 12 }} animate={{ opacity: 1, x: 0 }} transition={{ duration: 0.45 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
bgcolor: '#f9fafb',
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ color: '#111827', fontWeight: 700, mb: 1 }}>
|
||||
What you get
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.75 }}>
|
||||
<CheckCircleIcon sx={{ color: '#16a34a' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155' }}>
|
||||
Clear, bite‑size fixes that improve visibility and clicks.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.75 }}>
|
||||
<CheckCircleIcon sx={{ color: '#16a34a' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155' }}>
|
||||
A weekly rhythm that keeps content fresh and organized.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon sx={{ color: '#16a34a' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155' }}>
|
||||
Caching protects your quota; agents use cached insights, not direct API calls.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.75,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
bgcolor: '#f9fafb',
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ color: '#111827', fontWeight: 700, mb: 1 }}>
|
||||
Full Flow at a Glance
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, flexWrap: 'wrap' }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.25,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
minWidth: 150,
|
||||
textAlign: 'center',
|
||||
bgcolor: '#ffffff',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 0.5 }}>
|
||||
1. Connect
|
||||
</Typography>
|
||||
<AnalyticsIcon sx={{ color: '#2563eb' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 0.5 }}>
|
||||
GSC & Bing
|
||||
</Typography>
|
||||
</Paper>
|
||||
<ArrowForwardIcon sx={{ color: '#64748b' }} />
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.25,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
minWidth: 150,
|
||||
textAlign: 'center',
|
||||
bgcolor: '#ffffff',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 0.5 }}>
|
||||
2. Cache
|
||||
</Typography>
|
||||
<AutoAwesomeIcon sx={{ color: '#0891b2' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 0.5 }}>
|
||||
Fast, quota‑safe
|
||||
</Typography>
|
||||
</Paper>
|
||||
<ArrowForwardIcon sx={{ color: '#64748b' }} />
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.25,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
minWidth: 150,
|
||||
textAlign: 'center',
|
||||
bgcolor: '#ffffff',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 0.5 }}>
|
||||
3. Analyze
|
||||
</Typography>
|
||||
<PsychologyIcon sx={{ color: '#7c3aed' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 0.5 }}>
|
||||
SIF agents
|
||||
</Typography>
|
||||
</Paper>
|
||||
<ArrowForwardIcon sx={{ color: '#64748b' }} />
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.25,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
minWidth: 150,
|
||||
textAlign: 'center',
|
||||
bgcolor: '#ffffff',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ color: '#334155', fontWeight: 700, display: 'block', mb: 0.5 }}>
|
||||
4. Suggest
|
||||
</Typography>
|
||||
<AutoAwesomeIcon sx={{ color: '#059669' }} />
|
||||
<Typography variant="body2" sx={{ color: '#334155', mt: 0.5 }}>
|
||||
Clear fixes
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 1.75,
|
||||
borderRadius: 2,
|
||||
border: '1px solid #e5e7eb',
|
||||
bgcolor: '#f9fafb',
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ color: '#111827', fontWeight: 700, mb: 1 }}>
|
||||
Guided Walkthrough
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, mb: 1.5 }}>
|
||||
<Chip
|
||||
icon={<PlayArrowIcon />}
|
||||
label="Auto walkthrough"
|
||||
sx={{ bgcolor: '#eef2ff', color: '#111827', fontWeight: 700 }}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ color: '#64748b', fontWeight: 600 }}>
|
||||
{walkthroughLabels[walkthroughStep]}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ position: 'relative', minHeight: 120 }}>
|
||||
<motion.div
|
||||
key={`walk-${walkthroughStep}`}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
>
|
||||
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px dashed #cbd5e1', bgcolor: '#f8fafc' }}>
|
||||
<Typography variant="body2" sx={{ color: '#334155', fontWeight: 600, mb: 0.5 }}>
|
||||
{walkthroughTitles[walkthroughStep]}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: '#475569' }}>
|
||||
{walkthroughDescriptions[walkthroughStep]}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</div>
|
||||
</Fade>
|
||||
|
||||
|
||||
{/* Success Toast */}
|
||||
<Snackbar
|
||||
open={showToast}
|
||||
@@ -613,4 +990,4 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
|
||||
);
|
||||
};
|
||||
|
||||
export default IntegrationsStep;
|
||||
export default IntegrationsStep;
|
||||
|
||||
@@ -67,7 +67,7 @@ interface QualityMetrics {
|
||||
type PersonalizationTab = 'text' | 'image' | 'audio';
|
||||
|
||||
const PersonalizationStep: React.FC<PersonalizationStepProps> = ({
|
||||
onContinue,
|
||||
onContinue: _onContinue,
|
||||
updateHeaderContent,
|
||||
onValidationChange,
|
||||
onDataChange,
|
||||
@@ -80,7 +80,6 @@ const PersonalizationStep: React.FC<PersonalizationStepProps> = ({
|
||||
// AI Generation state (Ported from PersonaStep)
|
||||
const [generationStep, setGenerationStep] = useState<string>('analyzing');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
@@ -94,7 +93,7 @@ const PersonalizationStep: React.FC<PersonalizationStepProps> = ({
|
||||
// UI state
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [expandedAccordion, setExpandedAccordion] = useState<string | false>('core');
|
||||
const [hasCheckedCache, setHasCheckedCache] = useState(false);
|
||||
const [, setHasCheckedCache] = useState(false);
|
||||
const [configurationOptions, setConfigurationOptions] = useState<any>(null);
|
||||
|
||||
// Asset Status State
|
||||
@@ -417,26 +416,6 @@ const PersonalizationStep: React.FC<PersonalizationStepProps> = ({
|
||||
generatePersonas();
|
||||
};
|
||||
|
||||
const handleContinue = useCallback(() => {
|
||||
if (corePersona && platformPersonas && qualityMetrics) {
|
||||
if (!brandAvatarSet || !voiceCloneSet) {
|
||||
setError('Please generate and set your Brand Avatar and Voice Clone before continuing.');
|
||||
return;
|
||||
}
|
||||
const personaData = {
|
||||
corePersona,
|
||||
platformPersonas,
|
||||
qualityMetrics,
|
||||
selectedPlatforms,
|
||||
stepType: 'personalization',
|
||||
completedAt: new Date().toISOString()
|
||||
};
|
||||
onContinue(personaData);
|
||||
} else {
|
||||
setError('Missing persona data. Please generate your brand voice first.');
|
||||
}
|
||||
}, [corePersona, platformPersonas, qualityMetrics, selectedPlatforms, onContinue, brandAvatarSet, voiceCloneSet]);
|
||||
|
||||
useEffect(() => {
|
||||
const hasValidData = !!(corePersona && platformPersonas && Object.keys(platformPersonas).length > 0 && qualityMetrics);
|
||||
const isComplete = !isGenerating && hasValidData && generationStep === 'preview' && brandAvatarSet && voiceCloneSet;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { createAvatarVideoAsync } from '../../../../api/videoStudioApi';
|
||||
import { useVideoGenerationPolling } from '../../../../hooks/usePolling';
|
||||
import { fetchMediaBlobUrl } from '../../../../utils/fetchMediaBlobUrl';
|
||||
import { VideoCameraFront, SkipNext, PlayArrow, InfoOutlined, Close as CloseIcon, HelpOutline, Refresh, RestartAlt, Undo } from '@mui/icons-material';
|
||||
import { VideoGenerationLoader } from '../../../shared/VideoGenerationLoader';
|
||||
import { OperationButton } from '../../../shared/OperationButton';
|
||||
@@ -29,6 +30,7 @@ export const TestPersonaModal: React.FC<TestPersonaModalProps> = ({
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [model, setModel] = useState<'infinitetalk' | 'hunyuan-avatar'>('infinitetalk');
|
||||
const [showCapabilities, setShowCapabilities] = useState(false);
|
||||
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | null>(null);
|
||||
const STORAGE_KEY = 'test_persona_video_url';
|
||||
const STORAGE_BACKUP_KEY = 'test_persona_video_url_backup';
|
||||
|
||||
@@ -135,9 +137,29 @@ export const TestPersonaModal: React.FC<TestPersonaModalProps> = ({
|
||||
setGeneratedVideoUrl(null);
|
||||
|
||||
try {
|
||||
// 1. Fetch blobs from URLs (works for data URIs too)
|
||||
const avatarBlob = await fetch(avatarUrl).then(r => r.blob());
|
||||
const voiceBlob = await fetch(voiceUrl).then(r => r.blob());
|
||||
let avatarBlob: Blob;
|
||||
try {
|
||||
const avatarBlobUrl = await fetchMediaBlobUrl(avatarUrl);
|
||||
if (avatarBlobUrl) {
|
||||
avatarBlob = await fetch(avatarBlobUrl).then(r => r.blob());
|
||||
} else {
|
||||
avatarBlob = await fetch(avatarUrl).then(r => r.blob());
|
||||
}
|
||||
} catch {
|
||||
avatarBlob = await fetch(avatarUrl).then(r => r.blob());
|
||||
}
|
||||
|
||||
let voiceBlob: Blob;
|
||||
try {
|
||||
const voiceBlobUrl = await fetchMediaBlobUrl(voiceUrl);
|
||||
if (voiceBlobUrl) {
|
||||
voiceBlob = await fetch(voiceBlobUrl).then(r => r.blob());
|
||||
} else {
|
||||
voiceBlob = await fetch(voiceUrl).then(r => r.blob());
|
||||
}
|
||||
} catch {
|
||||
voiceBlob = await fetch(voiceUrl).then(r => r.blob());
|
||||
}
|
||||
|
||||
// 2. Create Files
|
||||
const avatarFile = new File([avatarBlob], "avatar.png", { type: avatarBlob.type });
|
||||
@@ -175,6 +197,68 @@ export const TestPersonaModal: React.FC<TestPersonaModalProps> = ({
|
||||
}, 100);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!avatarUrl) {
|
||||
setAvatarBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avatarUrl.startsWith('data:') || avatarUrl.startsWith('blob:')) {
|
||||
setAvatarBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const isInternal =
|
||||
avatarUrl.includes('/api/podcast/') ||
|
||||
avatarUrl.includes('/api/youtube/') ||
|
||||
avatarUrl.includes('/api/story/') ||
|
||||
(avatarUrl.startsWith('/') && !avatarUrl.startsWith('//'));
|
||||
|
||||
if (!isInternal) {
|
||||
setAvatarBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let isMounted = true;
|
||||
const currentAvatarUrl = avatarUrl;
|
||||
|
||||
const loadAvatarBlob = async () => {
|
||||
try {
|
||||
const blobUrl = await fetchMediaBlobUrl(currentAvatarUrl);
|
||||
|
||||
if (!isMounted || avatarUrl !== currentAvatarUrl) {
|
||||
if (blobUrl && blobUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setAvatarBlobUrl(prev => {
|
||||
if (prev && prev !== blobUrl && prev.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(prev);
|
||||
}
|
||||
return blobUrl;
|
||||
});
|
||||
} catch {
|
||||
if (isMounted && avatarUrl === currentAvatarUrl) {
|
||||
setAvatarBlobUrl(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadAvatarBlob();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
setAvatarBlobUrl(prev => {
|
||||
if (prev && prev.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(prev);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}, [avatarUrl]);
|
||||
|
||||
const CapabilitiesModal = () => (
|
||||
<Dialog
|
||||
open={showCapabilities}
|
||||
@@ -429,7 +513,7 @@ export const TestPersonaModal: React.FC<TestPersonaModalProps> = ({
|
||||
{/* Avatar Preview */}
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
src={avatarBlobUrl || avatarUrl}
|
||||
sx={{ width: 140, height: 140, border: '4px solid #ffffff', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
||||
/>
|
||||
<Box sx={{ position: 'absolute', bottom: 0, right: 0, bgcolor: '#10b981', color: 'white', p: 0.5, borderRadius: '50%', border: '2px solid white' }}>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
|
||||
// Extracted components
|
||||
import { AnalysisResultsDisplay, AnalysisProgressDisplay } from './WebsiteStep/components';
|
||||
import type { StyleAnalysis } from './WebsiteStep/components/AnalysisResultsDisplay';
|
||||
|
||||
// Import API client for saving
|
||||
import { apiClient } from '../../api/client';
|
||||
@@ -48,104 +49,6 @@ interface WebsiteStepProps {
|
||||
onValidationChange?: (isValid: boolean) => void;
|
||||
}
|
||||
|
||||
interface StyleAnalysis {
|
||||
id?: number;
|
||||
writing_style?: {
|
||||
tone: string;
|
||||
voice: string;
|
||||
complexity: string;
|
||||
engagement_level: string;
|
||||
brand_personality?: string;
|
||||
formality_level?: string;
|
||||
emotional_appeal?: string;
|
||||
};
|
||||
content_characteristics?: {
|
||||
sentence_structure: string;
|
||||
vocabulary_level: string;
|
||||
paragraph_organization: string;
|
||||
content_flow: string;
|
||||
readability_score?: string;
|
||||
content_density?: string;
|
||||
visual_elements_usage?: string;
|
||||
};
|
||||
target_audience?: {
|
||||
demographics: string[];
|
||||
expertise_level: string;
|
||||
industry_focus: string;
|
||||
geographic_focus: string;
|
||||
psychographic_profile?: string;
|
||||
pain_points?: string[];
|
||||
motivations?: string[];
|
||||
};
|
||||
content_type?: {
|
||||
primary_type: string;
|
||||
secondary_types: string[];
|
||||
purpose: string;
|
||||
call_to_action: string;
|
||||
conversion_focus?: string;
|
||||
educational_value?: string;
|
||||
};
|
||||
brand_analysis?: {
|
||||
brand_voice: string;
|
||||
brand_values: string[];
|
||||
brand_positioning: string;
|
||||
competitive_differentiation: string;
|
||||
trust_signals: string[];
|
||||
authority_indicators: string[];
|
||||
};
|
||||
content_strategy_insights?: {
|
||||
strengths: string[];
|
||||
weaknesses: string[];
|
||||
opportunities: string[];
|
||||
threats: string[];
|
||||
recommended_improvements: string[];
|
||||
content_gaps: string[];
|
||||
};
|
||||
recommended_settings?: {
|
||||
writing_tone: string;
|
||||
target_audience: string;
|
||||
content_type: string;
|
||||
creativity_level: string;
|
||||
geographic_location: string;
|
||||
industry_context?: string;
|
||||
brand_alignment?: string;
|
||||
};
|
||||
guidelines?: {
|
||||
tone_recommendations: string[];
|
||||
structure_guidelines: string[];
|
||||
vocabulary_suggestions: string[];
|
||||
engagement_tips: string[];
|
||||
audience_considerations: string[];
|
||||
brand_alignment?: string[];
|
||||
seo_optimization?: string[];
|
||||
conversion_optimization?: string[];
|
||||
};
|
||||
best_practices?: string[];
|
||||
avoid_elements?: string[];
|
||||
content_strategy?: string;
|
||||
ai_generation_tips?: string[];
|
||||
competitive_advantages?: string[];
|
||||
content_calendar_suggestions?: string[];
|
||||
style_patterns?: {
|
||||
sentence_length: string;
|
||||
vocabulary_patterns: string[];
|
||||
rhetorical_devices: string[];
|
||||
paragraph_structure: string;
|
||||
transition_phrases: string[];
|
||||
};
|
||||
patterns?: {
|
||||
sentence_length: string;
|
||||
vocabulary_patterns: string[];
|
||||
rhetorical_devices: string[];
|
||||
paragraph_structure: string;
|
||||
transition_phrases: string[];
|
||||
};
|
||||
style_consistency?: string;
|
||||
unique_elements?: string[];
|
||||
seo_audit?: any;
|
||||
sitemap_analysis?: any;
|
||||
}
|
||||
|
||||
interface AnalysisProgress {
|
||||
step: number;
|
||||
message: string;
|
||||
@@ -189,6 +92,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [analysisWarning, setAnalysisWarning] = useState<string | null>(null);
|
||||
const [analysis, setAnalysis] = useState<StyleAnalysis | null>(null);
|
||||
const [crawlResult, setCrawlResult] = useState<any>(null);
|
||||
const [existingAnalysis, setExistingAnalysis] = useState<ExistingAnalysis | null>(null);
|
||||
@@ -290,6 +194,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
setDomainName(result.domainName || '');
|
||||
setAnalysis(result.analysis);
|
||||
setCrawlResult(result.crawlResult);
|
||||
setAnalysisWarning(result.warning || null);
|
||||
setSuccess('Loaded previous analysis successfully!');
|
||||
}
|
||||
return result;
|
||||
@@ -298,6 +203,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
const handleAnalyze = async () => {
|
||||
setError(null);
|
||||
setSuccess(null);
|
||||
setAnalysisWarning(null);
|
||||
setLoading(true);
|
||||
setAnalysis(null);
|
||||
setCrawlResult(null);
|
||||
@@ -330,6 +236,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
setDomainName(analysisResult.domainName || '');
|
||||
setAnalysis(analysisResult.analysis);
|
||||
setCrawlResult(analysisResult.crawlResult);
|
||||
setAnalysisWarning(analysisResult.warning || null);
|
||||
|
||||
// Store in localStorage for Step 3 (Competitor Analysis)
|
||||
localStorage.setItem('website_url', fixedUrl);
|
||||
@@ -404,6 +311,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
if (analysisResult.success) {
|
||||
setDomainName(analysisResult.domainName || '');
|
||||
setAnalysis(analysisResult.analysis);
|
||||
setAnalysisWarning(analysisResult.warning || null);
|
||||
|
||||
if (analysisResult.warning) {
|
||||
setSuccess(`Website style analysis completed successfully! Note: ${analysisResult.warning}`);
|
||||
@@ -754,6 +662,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
useAnalysisForGenAI={useAnalysisForGenAI}
|
||||
onUseAnalysisChange={setUseAnalysisForGenAI}
|
||||
onAnalysisUpdate={handleAnalysisUpdate}
|
||||
warning={analysisWarning || undefined}
|
||||
onSave={() => saveAnalysis(analysis)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -64,7 +64,18 @@ import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
||||
|
||||
import { apiClient } from '../../../../api/client';
|
||||
|
||||
interface StyleAnalysis {
|
||||
export interface StyleAnalysis {
|
||||
id?: number;
|
||||
guidelines?: {
|
||||
tone_recommendations?: string[];
|
||||
structure_guidelines?: string[];
|
||||
vocabulary_suggestions?: string[];
|
||||
engagement_tips?: string[];
|
||||
audience_considerations?: string[];
|
||||
brand_alignment?: string[];
|
||||
seo_optimization?: string[];
|
||||
conversion_optimization?: string[];
|
||||
} | null;
|
||||
writing_style?: {
|
||||
tone: string;
|
||||
voice: string;
|
||||
@@ -132,6 +143,7 @@ interface AnalysisResultsDisplayProps {
|
||||
onUseAnalysisChange: (use: boolean) => void;
|
||||
crawlResult?: any;
|
||||
onAnalysisUpdate?: (updatedAnalysis: StyleAnalysis) => void;
|
||||
warning?: string;
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
||||
@@ -142,12 +154,17 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
onUseAnalysisChange,
|
||||
crawlResult,
|
||||
onAnalysisUpdate,
|
||||
warning,
|
||||
onSave
|
||||
}) => {
|
||||
const styles = useOnboardingStyles();
|
||||
const [isCrawlExpanded, setIsCrawlExpanded] = useState(false);
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
|
||||
const warningParts = warning ? warning.split('|').map(part => part.trim()).filter(Boolean) : [];
|
||||
const guidelineWarning = warningParts.find(part => part.toLowerCase().startsWith('guidelines generation failed'));
|
||||
const sitemapWarning = warningParts.find(part => part.toLowerCase().startsWith('sitemap analysis failed'));
|
||||
|
||||
// Helper to handle section updates
|
||||
const handleSectionUpdate = (section: string, fieldPath: string, value: any) => {
|
||||
if (!onAnalysisUpdate) return;
|
||||
@@ -383,17 +400,25 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{renderBrandAnalysisSection(analysis)}
|
||||
</Box>
|
||||
|
||||
{/* Style Guidelines Section */}
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<SectionHeader
|
||||
title="Style Guidelines"
|
||||
icon={<AutoAwesomeIcon />}
|
||||
/>
|
||||
<EnhancedGuidelinesSection
|
||||
guidelines={analysis.style_guidelines}
|
||||
domainName={domainName}
|
||||
/>
|
||||
</Box>
|
||||
{(analysis.guidelines || guidelineWarning) && (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<SectionHeader
|
||||
title="Style Guidelines"
|
||||
icon={<AutoAwesomeIcon />}
|
||||
/>
|
||||
{guidelineWarning && (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{guidelineWarning}
|
||||
</Alert>
|
||||
)}
|
||||
{analysis.guidelines && (
|
||||
<EnhancedGuidelinesSection
|
||||
guidelines={analysis.guidelines}
|
||||
domainName={domainName}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* SEO Audit Section */}
|
||||
<Box sx={{ mt: 4 }}>
|
||||
@@ -408,12 +433,16 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Sitemap Analysis Section */}
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<SectionHeader
|
||||
title="Sitemap Analysis"
|
||||
icon={<LinkIcon />}
|
||||
/>
|
||||
{sitemapWarning && (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{sitemapWarning}
|
||||
</Alert>
|
||||
)}
|
||||
<SitemapAnalysisSection
|
||||
sitemapAnalysis={analysis.sitemap_analysis}
|
||||
domainName={domainName}
|
||||
|
||||
@@ -36,7 +36,7 @@ interface Guidelines {
|
||||
}
|
||||
|
||||
interface EnhancedGuidelinesSectionProps {
|
||||
guidelines: Guidelines;
|
||||
guidelines?: Guidelines | null;
|
||||
domainName: string;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ const EnhancedGuidelinesSection: React.FC<EnhancedGuidelinesSectionProps> = ({
|
||||
}) => {
|
||||
const styles = useOnboardingStyles();
|
||||
|
||||
if (!guidelines) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
|
||||
@@ -108,6 +108,7 @@ export const loadExistingAnalysis = async (analysisId: number, website: string):
|
||||
analysis?: any;
|
||||
domainName?: string;
|
||||
crawlResult?: any;
|
||||
warning?: string;
|
||||
error?: string;
|
||||
}> => {
|
||||
try {
|
||||
@@ -115,13 +116,12 @@ export const loadExistingAnalysis = async (analysisId: number, website: string):
|
||||
const result = response.data;
|
||||
|
||||
if (result.success && result.analysis) {
|
||||
// Extract domain name for personalization
|
||||
const extractedDomain = extractDomainName(website);
|
||||
|
||||
// Database structure: flat fields at top level
|
||||
// Need to combine them into the format expected by UI
|
||||
const comprehensiveAnalysis = {
|
||||
// Top-level style analysis fields from database
|
||||
id: result.analysis.id,
|
||||
writing_style: result.analysis.writing_style,
|
||||
content_characteristics: result.analysis.content_characteristics,
|
||||
target_audience: result.analysis.target_audience,
|
||||
@@ -151,7 +151,8 @@ export const loadExistingAnalysis = async (analysisId: number, website: string):
|
||||
success: true,
|
||||
analysis: comprehensiveAnalysis,
|
||||
domainName: extractedDomain,
|
||||
crawlResult: result.analysis.crawl_result
|
||||
crawlResult: result.analysis.crawl_result,
|
||||
warning: result.analysis.warning_message
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -212,6 +213,7 @@ export const performAnalysis = async (
|
||||
|
||||
// Combine all analysis data into a comprehensive object
|
||||
const comprehensiveAnalysis = {
|
||||
id: result.analysis_id,
|
||||
...result.style_analysis,
|
||||
seo_audit: result.seo_audit,
|
||||
sitemap_analysis: result.crawl_result?.sitemap_analysis,
|
||||
|
||||
@@ -654,6 +654,20 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for IntegrationsStep (step 4)
|
||||
if (activeStep === 4) {
|
||||
const currentData = stepDataRef.current || {};
|
||||
if (!currentStepData && currentData && typeof currentData === 'object') {
|
||||
if (currentData.integrations) {
|
||||
currentStepData = {
|
||||
integrations: currentData.integrations,
|
||||
};
|
||||
} else {
|
||||
currentStepData = currentData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store step data in state
|
||||
if (currentStepData) {
|
||||
setStepData(currentStepData);
|
||||
@@ -681,7 +695,7 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
|
||||
// Complete the current step (activeStep + 1 because steps are 1-indexed)
|
||||
const currentStepNumber = activeStep + 1;
|
||||
|
||||
const stepWasCompleted = currentStepData && typeof currentStepData === 'object' && (
|
||||
const hasCoreStepData = currentStepData && typeof currentStepData === 'object' && (
|
||||
currentStepData.website ||
|
||||
currentStepData.businessData ||
|
||||
currentStepData.competitors ||
|
||||
@@ -692,6 +706,10 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
|
||||
currentStepData.qualityMetrics
|
||||
);
|
||||
|
||||
const hasIntegrationsData = !!(currentStepData && typeof currentStepData === 'object' && currentStepData.integrations);
|
||||
|
||||
const stepWasCompleted = hasCoreStepData || hasIntegrationsData;
|
||||
|
||||
console.log('Wizard: Step completion check:', {
|
||||
currentStepNumber,
|
||||
hasData: !!currentStepData,
|
||||
@@ -881,6 +899,7 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
|
||||
onContinue={handleNext}
|
||||
updateHeaderContent={updateHeaderContent}
|
||||
onValidationChange={(isValid: boolean) => handleStepValidationChange(4, isValid)}
|
||||
onDataChange={handleStepDataChange}
|
||||
/>,
|
||||
<FinalStep key="final" onContinue={handleComplete} updateHeaderContent={updateHeaderContent} />
|
||||
];
|
||||
@@ -901,6 +920,7 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="light-theme-container"
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
|
||||
Reference in New Issue
Block a user