feat: Add hamburger menu to Podcast Maker header and move Bible to AnalysisPanel
- Add hamburger menu to Header with gradient styling - Move Help, My Episodes, My Projects, New Episode into dropdown menu - Move PodcastBiblePanel into AnalysisPanel header as icon button - Display Bible details in a styled Popover - Improve overall header UX and mobile responsiveness
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Stack, Box, Typography, Divider, Chip, alpha, Button } from "@mui/material";
|
||||
import { Psychology as PsychologyIcon, Person as PersonIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Input as InputIcon, Groups as GroupsIcon, ListAlt as ListAltIcon, Lightbulb as TipsIcon, Article as ArticleIcon } from "@mui/icons-material";
|
||||
import { PodcastAnalysis, PodcastEstimate } from "./types";
|
||||
import { Stack, Box, Typography, Divider, Chip, alpha, Button, IconButton, Popover, TextField, Tooltip } from "@mui/material";
|
||||
import { Psychology as PsychologyIcon, Person as PersonIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Input as InputIcon, Groups as GroupsIcon, ListAlt as ListAltIcon, Lightbulb as TipsIcon, Article as ArticleIcon, AutoFixHigh as BibleIcon } from "@mui/icons-material";
|
||||
import { PodcastAnalysis, PodcastEstimate, PodcastBible } from "./types";
|
||||
import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui";
|
||||
import { Refresh as RefreshIcon } from "@mui/icons-material";
|
||||
import { aiApiClient } from "../../api/client";
|
||||
@@ -15,8 +15,10 @@ interface AnalysisPanelProps {
|
||||
speakers?: number;
|
||||
avatarUrl?: string | null;
|
||||
avatarPrompt?: string | null;
|
||||
bible?: PodcastBible | null;
|
||||
onRegenerate?: () => void;
|
||||
onUpdateAnalysis?: (updatedAnalysis: PodcastAnalysis) => void;
|
||||
onUpdateBible?: (updatedBible: PodcastBible) => void;
|
||||
}
|
||||
|
||||
type TabId = 'inputs' | 'audience' | 'outline' | 'details' | 'takeaways' | 'guest';
|
||||
@@ -62,17 +64,21 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
|
||||
speakers,
|
||||
avatarUrl,
|
||||
avatarPrompt,
|
||||
bible,
|
||||
onRegenerate,
|
||||
onUpdateAnalysis
|
||||
onUpdateAnalysis,
|
||||
onUpdateBible
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<TabId>('inputs');
|
||||
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | null>(null);
|
||||
const [avatarLoading, setAvatarLoading] = useState(false);
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
const [bibleAnchorEl, setBibleAnchorEl] = useState<HTMLElement | null>(null);
|
||||
|
||||
// Edit states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editedAnalysis, setEditedAnalysis] = useState<PodcastAnalysis | null>(null);
|
||||
const [editedBible, setEditedBible] = useState<PodcastBible | null>(null);
|
||||
|
||||
const tabs: TabConfig[] = [
|
||||
{ id: 'inputs', label: 'Your Inputs', icon: <InputIcon /> },
|
||||
@@ -326,6 +332,29 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1}>
|
||||
{/* Bible Button */}
|
||||
{bible && (
|
||||
<Tooltip title="Podcast Bible - Hyper-personalized context">
|
||||
<IconButton
|
||||
onClick={(e) => setBibleAnchorEl(e.currentTarget)}
|
||||
sx={{
|
||||
bgcolor: bibleAnchorEl ? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" : "rgba(102, 126, 234, 0.1)",
|
||||
border: "1px solid",
|
||||
borderColor: bibleAnchorEl ? "transparent" : "rgba(102, 126, 234, 0.3)",
|
||||
borderRadius: 2,
|
||||
p: 1,
|
||||
transition: "all 0.2s ease",
|
||||
"&:hover": {
|
||||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
borderColor: "transparent",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<BibleIcon sx={{ color: bibleAnchorEl ? "#fff" : "#667eea", fontSize: 20 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isEditing ? (
|
||||
<>
|
||||
<SecondaryButton
|
||||
@@ -442,6 +471,81 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
|
||||
<GuestTab analysis={currentAnalysis} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Bible Popover */}
|
||||
<Popover
|
||||
open={Boolean(bibleAnchorEl)}
|
||||
anchorEl={bibleAnchorEl}
|
||||
onClose={() => setBibleAnchorEl(null)}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
mt: 1,
|
||||
maxWidth: 420,
|
||||
borderRadius: 3,
|
||||
background: "linear-gradient(135deg, #1e293b 0%, #0f172a 100%)",
|
||||
border: "1px solid rgba(102, 126, 234, 0.3)",
|
||||
boxShadow: "0 10px 40px rgba(102, 126, 234, 0.25)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2.5 }}>
|
||||
<Stack spacing={2}>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<BibleIcon sx={{ color: "#a78bfa", fontSize: 24 }} />
|
||||
<Typography variant="h6" sx={{ color: "#fff", fontWeight: 700 }}>
|
||||
Podcast Bible
|
||||
</Typography>
|
||||
<Tooltip title="Hyper-personalized context derived from your onboarding data. This grounds all research and script generation.">
|
||||
<IconButton size="small" sx={{ ml: 'auto' }}>
|
||||
<Typography variant="caption" sx={{ color: "#94a3b8" }}>ℹ️</Typography>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
|
||||
{/* Host Persona */}
|
||||
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(99, 102, 241, 0.1)", border: "1px solid rgba(99, 102, 241, 0.2)" }}>
|
||||
<Typography variant="caption" sx={{ color: "#a78bfa", fontWeight: 600, mb: 0.5, display: "block" }}>
|
||||
Host Persona
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
|
||||
{bible?.host?.name || "Not set"} • {bible?.host?.background || "No background"} • {bible?.host?.vocal_style || "No style"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Audience DNA */}
|
||||
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(34, 197, 94, 0.1)", border: "1px solid rgba(34, 197, 94, 0.2)" }}>
|
||||
<Typography variant="caption" sx={{ color: "#22c55e", fontWeight: 600, mb: 0.5, display: "block" }}>
|
||||
Audience DNA
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
|
||||
{bible?.audience?.expertise_level || "General"} • {(bible?.audience?.interests || []).slice(0, 3).join(", ") || "Various interests"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Brand DNA */}
|
||||
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: "rgba(249, 115, 22, 0.1)", border: "1px solid rgba(249, 115, 22, 0.2)" }}>
|
||||
<Typography variant="caption" sx={{ color: "#f97316", fontWeight: 600, mb: 0.5, display: "block" }}>
|
||||
Brand DNA
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "rgba(255,255,255,0.8)", fontSize: "0.8rem" }}>
|
||||
{bible?.brand?.industry || "No industry"} • {bible?.brand?.tone || "No tone"} • {bible?.brand?.communication_style || "No style"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" sx={{ color: "rgba(255,255,255,0.5)", textAlign: "center", fontSize: "0.7rem" }}>
|
||||
Podcast Bible personalizes all AI generation for your unique voice
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Popover>
|
||||
</Stack>
|
||||
</GlassyCard>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { RenderQueue } from "./RenderQueue";
|
||||
import { RecentEpisodesPreview } from "./RecentEpisodesPreview";
|
||||
import { ProjectList } from "./ProjectList";
|
||||
import { PreflightBlockDialog } from "./PreflightBlockDialog";
|
||||
import { PodcastBiblePanel } from "./PodcastBiblePanel";
|
||||
import {
|
||||
Header,
|
||||
ProgressStepper,
|
||||
@@ -199,14 +198,8 @@ const PodcastDashboard: React.FC = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Podcast Bible */}
|
||||
{project && bible && (currentStep === 'analysis' || (currentStep === 'research' && !research)) && !showScriptEditor && !showRenderQueue && (
|
||||
<PodcastBiblePanel
|
||||
bible={bible}
|
||||
onUpdate={(updated) => setBible(updated)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Podcast Bible - now in AnalysisPanel header */}
|
||||
|
||||
{(workflow.isAnalyzing || workflow.isResearching || workflow.isGeneratingScript) && (
|
||||
<Stack direction="row" spacing={2} alignItems="center" sx={{ py: 1.5 }}>
|
||||
<CircularProgress size={20} sx={{ color: "#667eea" }} />
|
||||
@@ -241,8 +234,10 @@ const PodcastDashboard: React.FC = () => {
|
||||
speakers={project?.speakers}
|
||||
avatarUrl={project?.avatarUrl}
|
||||
avatarPrompt={project?.avatarPrompt}
|
||||
bible={bible}
|
||||
onRegenerate={() => setShowRegenModal(true)}
|
||||
onUpdateAnalysis={(updated) => projectState.setAnalysis(updated)}
|
||||
onUpdateBible={(updated) => setBible(updated)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import React from "react";
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { Stack, Typography, Box, IconButton, Menu, MenuItem, Divider, ListItemIcon, ListItemText } from "@mui/material";
|
||||
import {
|
||||
Mic as MicIcon,
|
||||
Info as InfoIcon,
|
||||
Menu as MenuIcon,
|
||||
Close as CloseIcon,
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
LibraryMusic as LibraryMusicIcon,
|
||||
Folder as FolderIcon,
|
||||
Help as HelpIcon,
|
||||
Add as AddIcon,
|
||||
BarChart as BarChartIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PrimaryButton, SecondaryButton } from "../ui";
|
||||
import { PrimaryButton } from "../ui";
|
||||
import HeaderControls from "../../shared/HeaderControls";
|
||||
|
||||
interface HeaderProps {
|
||||
@@ -17,6 +22,36 @@ interface HeaderProps {
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode }) => {
|
||||
const navigate = useNavigate();
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const isMenuOpen = Boolean(anchorEl);
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleHelp = () => {
|
||||
handleMenuClose();
|
||||
window.open("/docs", "_blank");
|
||||
};
|
||||
|
||||
const handleMyEpisodes = () => {
|
||||
handleMenuClose();
|
||||
navigate("/asset-library?source_module=podcast_maker&asset_type=audio");
|
||||
};
|
||||
|
||||
const handleMyProjects = () => {
|
||||
handleMenuClose();
|
||||
onShowProjects();
|
||||
};
|
||||
|
||||
const handleNewEpisode = () => {
|
||||
handleMenuClose();
|
||||
onNewEpisode();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -25,7 +60,7 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
|
||||
minWidth: 0,
|
||||
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)",
|
||||
borderRadius: 3,
|
||||
p: { xs: 2, md: 2.5 },
|
||||
p: { xs: 1.5, md: 2.5 },
|
||||
border: "1px solid rgba(102, 126, 234, 0.15)",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
@@ -45,13 +80,14 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
gap={2}
|
||||
gap={1}
|
||||
>
|
||||
{/* Logo and Title */}
|
||||
<Stack direction="row" alignItems="center" gap={1.5}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
width: { xs: 36, md: 44 },
|
||||
height: { xs: 36, md: 44 },
|
||||
borderRadius: 2,
|
||||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
display: "flex",
|
||||
@@ -60,7 +96,7 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
|
||||
boxShadow: "0 4px 12px rgba(102, 126, 234, 0.3)",
|
||||
}}
|
||||
>
|
||||
<MicIcon sx={{ color: "#fff", fontSize: 24 }} />
|
||||
<MicIcon sx={{ color: "#fff", fontSize: { xs: 20, md: 24 } }} />
|
||||
</Box>
|
||||
<Typography
|
||||
variant="h5"
|
||||
@@ -69,87 +105,131 @@ export const Header: React.FC<HeaderProps> = ({ onShowProjects, onNewEpisode })
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
fontWeight: 700,
|
||||
fontSize: { xs: "1.25rem", md: "1.5rem" },
|
||||
fontSize: { xs: "1.1rem", sm: "1.25rem", md: "1.5rem" },
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
ALwrity Podcast Maker
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
useFlexGap
|
||||
sx={{
|
||||
justifyContent: { xs: "flex-start", md: "flex-end" },
|
||||
gap: { xs: 0.5, md: 1 },
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Right side - Hamburger Menu + HeaderControls + Create */}
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
{/* Header Controls (alerts + user) */}
|
||||
<HeaderControls colorMode="light" showAlerts={true} showUser={true} />
|
||||
<SecondaryButton
|
||||
onClick={() => window.open("/docs", "_blank")}
|
||||
startIcon={<InfoIcon />}
|
||||
|
||||
{/* Hamburger Menu Button */}
|
||||
<IconButton
|
||||
onClick={handleMenuOpen}
|
||||
sx={{
|
||||
display: { xs: "none", lg: "flex" },
|
||||
borderColor: "rgba(102, 126, 234, 0.3) !important",
|
||||
color: "#667eea !important",
|
||||
background: isMenuOpen
|
||||
? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
: "rgba(102, 126, 234, 0.1)",
|
||||
border: "1px solid",
|
||||
borderColor: isMenuOpen ? "transparent" : "rgba(102, 126, 234, 0.3)",
|
||||
borderRadius: 2,
|
||||
p: 1,
|
||||
transition: "all 0.2s ease",
|
||||
"&:hover": {
|
||||
borderColor: "rgba(102, 126, 234, 0.5) !important",
|
||||
background: "rgba(102, 126, 234, 0.1) !important",
|
||||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
borderColor: "transparent",
|
||||
transform: "scale(1.05)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Help
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
onClick={() => navigate("/asset-library?source_module=podcast_maker&asset_type=audio")}
|
||||
startIcon={<LibraryMusicIcon />}
|
||||
tooltip="View all podcast episodes in Asset Library"
|
||||
sx={{
|
||||
display: { xs: "none", xl: "flex" },
|
||||
borderColor: "rgba(102, 126, 234, 0.3) !important",
|
||||
color: "#667eea !important",
|
||||
"&:hover": {
|
||||
borderColor: "rgba(102, 126, 234, 0.5) !important",
|
||||
background: "rgba(102, 126, 234, 0.1) !important",
|
||||
{isMenuOpen ? (
|
||||
<CloseIcon sx={{ color: "#fff", fontSize: 20 }} />
|
||||
) : (
|
||||
<MenuIcon sx={{ color: "#667eea", fontSize: 20 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={isMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
mt: 1,
|
||||
minWidth: 220,
|
||||
borderRadius: 2,
|
||||
background: "linear-gradient(135deg, #1e293b 0%, #0f172a 100%)",
|
||||
border: "1px solid rgba(102, 126, 234, 0.3)",
|
||||
boxShadow: "0 10px 40px rgba(102, 126, 234, 0.25)",
|
||||
"& .MuiMenuItem-root": {
|
||||
color: "rgba(255, 255, 255, 0.85)",
|
||||
px: 2,
|
||||
py: 1.5,
|
||||
transition: "all 0.15s ease",
|
||||
"&:hover": {
|
||||
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%)",
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
"& .MuiListItemIcon-root": {
|
||||
color: "#a78bfa",
|
||||
minWidth: 36,
|
||||
},
|
||||
"& .MuiDivider-root": {
|
||||
borderColor: "rgba(102, 126, 234, 0.2)",
|
||||
my: 0.5,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
My Episodes
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
onClick={onShowProjects}
|
||||
startIcon={<MicIcon />}
|
||||
tooltip="View and resume saved projects"
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
display: "flex !important",
|
||||
borderColor: "rgba(102, 126, 234, 0.3) !important",
|
||||
color: "#667eea !important",
|
||||
"&:hover": {
|
||||
borderColor: "rgba(102, 126, 234, 0.5) !important",
|
||||
background: "rgba(102, 126, 234, 0.1) !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
My Projects
|
||||
</SecondaryButton>
|
||||
<PrimaryButton
|
||||
onClick={onNewEpisode}
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
New Episode
|
||||
</PrimaryButton>
|
||||
<MenuItem onClick={handleNewEpisode}>
|
||||
<ListItemIcon>
|
||||
<AddIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="New Episode"
|
||||
primaryTypographyProps={{ fontWeight: 600 }}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleMyProjects}>
|
||||
<ListItemIcon>
|
||||
<FolderIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="My Projects"
|
||||
primaryTypographyProps={{ fontWeight: 500 }}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleMyEpisodes}>
|
||||
<ListItemIcon>
|
||||
<LibraryMusicIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="My Episodes"
|
||||
primaryTypographyProps={{ fontWeight: 500 }}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleHelp}>
|
||||
<ListItemIcon>
|
||||
<HelpIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Help & Docs"
|
||||
primaryTypographyProps={{ fontWeight: 500 }}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user