feat(podcast): add Get Trending Topics modal to podcast topic input
Frontend Changes: - Added TrendingTopicsModal with tabs (Interest Chart, Regions, Related Topics, Related Queries) - Reuses existing TrendsChart component from Research module - Clickable chips for related topics/queries that populate the topic input - Added 'Get Trending Topics' green button next to 'Enhance Topic With AI' - Responsive layout: buttons stack on mobile, side-by-side on desktop - Wired up modal state in CreateModal - Backend endpoint and podcastApi method committed in prior push
This commit is contained in:
@@ -15,6 +15,7 @@ import { PodcastConfiguration } from "./CreateStep/PodcastConfiguration";
|
|||||||
import { AvatarSelector } from "./CreateStep/AvatarSelector";
|
import { AvatarSelector } from "./CreateStep/AvatarSelector";
|
||||||
import { CreateActions } from "./CreateStep/CreateActions";
|
import { CreateActions } from "./CreateStep/CreateActions";
|
||||||
import { EnhancedTopicChoicesModal } from "./EnhancedTopicChoicesModal";
|
import { EnhancedTopicChoicesModal } from "./EnhancedTopicChoicesModal";
|
||||||
|
import { TrendingTopicsModal } from "./CreateStep/TrendingTopicsModal";
|
||||||
|
|
||||||
const ENHANCE_TOPIC_PROGRESS_MESSAGES = [
|
const ENHANCE_TOPIC_PROGRESS_MESSAGES = [
|
||||||
"Analyzing your topic idea...",
|
"Analyzing your topic idea...",
|
||||||
@@ -61,6 +62,10 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
|
|||||||
const [choicesModalOpen, setChoicesModalOpen] = useState(false);
|
const [choicesModalOpen, setChoicesModalOpen] = useState(false);
|
||||||
const [editedChoices, setEditedChoices] = useState<string[]>([]);
|
const [editedChoices, setEditedChoices] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Trending topics state
|
||||||
|
const [trendingModalOpen, setTrendingModalOpen] = useState(false);
|
||||||
|
const [trendingLoading, setTrendingLoading] = useState(false);
|
||||||
|
|
||||||
// Rotate placeholder every 3 seconds
|
// Rotate placeholder every 3 seconds
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!topicInput) {
|
if (!topicInput) {
|
||||||
@@ -582,9 +587,11 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
|
|||||||
isUrl={isUrl}
|
isUrl={isUrl}
|
||||||
showAIDetailsButton={showAIDetailsButton}
|
showAIDetailsButton={showAIDetailsButton}
|
||||||
onAIDetailsClick={handleAIDetailsClick}
|
onAIDetailsClick={handleAIDetailsClick}
|
||||||
|
onTrendingTopicsClick={() => setTrendingModalOpen(true)}
|
||||||
placeholderIndex={placeholderIndex}
|
placeholderIndex={placeholderIndex}
|
||||||
loading={enhancingTopic}
|
loading={enhancingTopic}
|
||||||
loadingMessage={enhanceTopicMessage}
|
loadingMessage={enhanceTopicMessage}
|
||||||
|
trendingLoading={trendingLoading}
|
||||||
estimatedCost={null}
|
estimatedCost={null}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
speakers={speakers}
|
speakers={speakers}
|
||||||
@@ -651,6 +658,14 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
|
|||||||
onSelectChoice={handleChoiceSelection}
|
onSelectChoice={handleChoiceSelection}
|
||||||
loading={enhancingTopic}
|
loading={enhancingTopic}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Trending Topics Modal */}
|
||||||
|
<TrendingTopicsModal
|
||||||
|
open={trendingModalOpen}
|
||||||
|
onClose={() => setTrendingModalOpen(false)}
|
||||||
|
onSelectTopic={(topic) => setTopicInput(topic)}
|
||||||
|
initialKeywords={topicInput}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Box, Typography, TextField, Tooltip, Button, CircularProgress, alpha, Stack, Chip } from "@mui/material";
|
import { Box, Typography, TextField, Tooltip, Button, CircularProgress, alpha, Stack, Chip } from "@mui/material";
|
||||||
import { AutoAwesome as AutoAwesomeIcon, AttachMoney as AttachMoneyIcon } from "@mui/icons-material";
|
import { AutoAwesome as AutoAwesomeIcon, AttachMoney as AttachMoneyIcon, TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
||||||
import { Knobs } from "../types";
|
import { Knobs } from "../types";
|
||||||
|
|
||||||
export const TOPIC_PLACEHOLDERS = [
|
export const TOPIC_PLACEHOLDERS = [
|
||||||
@@ -18,9 +18,11 @@ interface TopicUrlInputProps {
|
|||||||
isUrl: boolean;
|
isUrl: boolean;
|
||||||
showAIDetailsButton: boolean;
|
showAIDetailsButton: boolean;
|
||||||
onAIDetailsClick?: () => void;
|
onAIDetailsClick?: () => void;
|
||||||
|
onTrendingTopicsClick?: () => void;
|
||||||
placeholderIndex: number;
|
placeholderIndex: number;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
loadingMessage?: string;
|
loadingMessage?: string;
|
||||||
|
trendingLoading?: boolean;
|
||||||
estimatedCost?: {
|
estimatedCost?: {
|
||||||
ttsCost: number;
|
ttsCost: number;
|
||||||
avatarCost: number;
|
avatarCost: number;
|
||||||
@@ -39,9 +41,11 @@ export const TopicUrlInput: React.FC<TopicUrlInputProps> = ({
|
|||||||
isUrl,
|
isUrl,
|
||||||
showAIDetailsButton,
|
showAIDetailsButton,
|
||||||
onAIDetailsClick,
|
onAIDetailsClick,
|
||||||
|
onTrendingTopicsClick,
|
||||||
placeholderIndex,
|
placeholderIndex,
|
||||||
loading = false,
|
loading = false,
|
||||||
loadingMessage,
|
loadingMessage,
|
||||||
|
trendingLoading = false,
|
||||||
estimatedCost,
|
estimatedCost,
|
||||||
duration = 1,
|
duration = 1,
|
||||||
speakers = 1,
|
speakers = 1,
|
||||||
@@ -249,9 +253,47 @@ export const TopicUrlInput: React.FC<TopicUrlInputProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* Enhance topic with AI button - appears when user types (and not a URL) */}
|
{/* Enhance topic with AI button + Get Trending Topics - appears when user types (and not a URL) */}
|
||||||
{showAIDetailsButton && !isUrl && (
|
{showAIDetailsButton && !isUrl && (
|
||||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1.5, flexDirection: "column", alignItems: "flex-end", gap: 0.6 }}>
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 1.5, flexDirection: { xs: "column", sm: "row" }, alignItems: { xs: "stretch", sm: "flex-end" }, gap: 1 }}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
startIcon={
|
||||||
|
trendingLoading ? (
|
||||||
|
<CircularProgress size={14} thickness={5} sx={{ color: "rgba(255,255,255,0.92)" }} />
|
||||||
|
) : (
|
||||||
|
<TrendingUpIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={onTrendingTopicsClick}
|
||||||
|
disabled={trendingLoading || loading}
|
||||||
|
sx={{
|
||||||
|
textTransform: "none",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: 2.5,
|
||||||
|
color: "#f8fbff",
|
||||||
|
px: 2,
|
||||||
|
py: 0.75,
|
||||||
|
border: "1px solid rgba(16, 185, 129, 0.4)",
|
||||||
|
background: "linear-gradient(120deg, #10b981 0%, #059669 55%, #047857 100%)",
|
||||||
|
boxShadow: "0 8px 18px rgba(16, 185, 129, 0.28), inset 0 1px 0 rgba(255,255,255,0.22)",
|
||||||
|
"&:hover": {
|
||||||
|
background: "linear-gradient(120deg, #34d399 0%, #10b981 50%, #059669 100%)",
|
||||||
|
boxShadow: "0 12px 24px rgba(16, 185, 129, 0.35), inset 0 1px 0 rgba(255,255,255,0.26)",
|
||||||
|
transform: "translateY(-1px)",
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
color: "#e2e8f0",
|
||||||
|
borderColor: "rgba(110, 231, 183, 0.7)",
|
||||||
|
background: "linear-gradient(120deg, #10b981 0%, #059669 55%, #047857 100%)",
|
||||||
|
opacity: 0.78,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{trendingLoading ? "Fetching Trends..." : "Get Trending Topics"}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -263,7 +305,7 @@ export const TopicUrlInput: React.FC<TopicUrlInputProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onClick={onAIDetailsClick}
|
onClick={onAIDetailsClick}
|
||||||
disabled={loading}
|
disabled={loading || trendingLoading}
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
fontSize: "0.875rem",
|
fontSize: "0.875rem",
|
||||||
@@ -290,13 +332,13 @@ export const TopicUrlInput: React.FC<TopicUrlInputProps> = ({
|
|||||||
>
|
>
|
||||||
{loading ? "Enhancing Topic With AI..." : "Enhance Topic With AI"}
|
{loading ? "Enhancing Topic With AI..." : "Enhance Topic With AI"}
|
||||||
</Button>
|
</Button>
|
||||||
{loading && (
|
|
||||||
<Typography sx={{ fontSize: "0.75rem", color: "#1d4ed8", fontWeight: 600 }}>
|
|
||||||
{loadingMessage || "Analyzing your topic and improving clarity..."}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{loading && (
|
||||||
|
<Typography sx={{ fontSize: "0.75rem", color: "#1d4ed8", fontWeight: 600, mt: 0.5, textAlign: "right" }}>
|
||||||
|
{loadingMessage || "Analyzing your topic and improving clarity..."}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,475 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
Chip,
|
||||||
|
Stack,
|
||||||
|
CircularProgress,
|
||||||
|
Alert,
|
||||||
|
LinearProgress,
|
||||||
|
IconButton,
|
||||||
|
alpha,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
TrendingUp as TrendingUpIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
Public as PublicIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
AutoAwesome as AutoAwesomeIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { TrendsChart } from "../../Research/steps/components/TrendsChart";
|
||||||
|
import { GoogleTrendsData } from "../../Research/types/intent.types";
|
||||||
|
import { podcastApi } from "../../../services/podcastApi";
|
||||||
|
|
||||||
|
interface TrendingTopicsModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSelectTopic: (topic: string) => void;
|
||||||
|
initialKeywords: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabPanel: React.FC<TabPanelProps> = ({ children, value, index }) => (
|
||||||
|
<Box role="tabpanel" hidden={value !== index} sx={{ pt: 2 }}>
|
||||||
|
{value === index && children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TrendingTopicsModal: React.FC<TrendingTopicsModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSelectTopic,
|
||||||
|
initialKeywords,
|
||||||
|
}) => {
|
||||||
|
const [trendsData, setTrendsData] = useState<GoogleTrendsData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [tabValue, setTabValue] = useState(0);
|
||||||
|
|
||||||
|
const fetchTrends = useCallback(async () => {
|
||||||
|
if (!initialKeywords.trim()) return;
|
||||||
|
|
||||||
|
const keywords = initialKeywords
|
||||||
|
.split(/[,;]+/)
|
||||||
|
.map((k) => k.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 5);
|
||||||
|
|
||||||
|
if (keywords.length === 0) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setTrendsData(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await podcastApi.getTrendingTopics({
|
||||||
|
keywords,
|
||||||
|
timeframe: "today 12-m",
|
||||||
|
geo: "US",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success && result.data) {
|
||||||
|
setTrendsData(result.data as GoogleTrendsData);
|
||||||
|
} else {
|
||||||
|
setError(result.error || "Failed to fetch trends data");
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.response?.data?.detail || err?.message || "Failed to fetch trending topics");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [initialKeywords]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && initialKeywords.trim()) {
|
||||||
|
fetchTrends();
|
||||||
|
}
|
||||||
|
}, [open, initialKeywords, fetchTrends]);
|
||||||
|
|
||||||
|
const handleSelectTopic = (topic: string) => {
|
||||||
|
onSelectTopic(topic);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setTrendsData(null);
|
||||||
|
setError(null);
|
||||||
|
setTabValue(0);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const regions = trendsData?.interest_by_region || [];
|
||||||
|
const relatedTopics = trendsData?.related_topics || { top: [], rising: [] };
|
||||||
|
const relatedQueries = trendsData?.related_queries || { top: [], rising: [] };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: 3,
|
||||||
|
maxHeight: "90vh",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
pb: 1,
|
||||||
|
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack direction="row" alignItems="center" spacing={1.5}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 0.75,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendingUpIcon sx={{ color: "#fff", fontSize: "1.25rem" }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 700, color: "#0f172a", fontSize: "1.1rem" }}>
|
||||||
|
Trending Topics
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" sx={{ color: "#64748b" }}>
|
||||||
|
Google Trends insights for “{initialKeywords}”
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<IconButton onClick={handleClose} sx={{ color: "#64748b" }}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent sx={{ px: 3, py: 2 }}>
|
||||||
|
{loading && (
|
||||||
|
<Box sx={{ py: 4, textAlign: "center" }}>
|
||||||
|
<CircularProgress size={40} sx={{ color: "#667eea", mb: 2 }} />
|
||||||
|
<Typography variant="body2" sx={{ color: "#64748b" }}>
|
||||||
|
Fetching trending topics from Google Trends...
|
||||||
|
</Typography>
|
||||||
|
<LinearProgress sx={{ mt: 2, borderRadius: 1 }} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mt: 2, borderRadius: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && trendsData && (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
value={tabValue}
|
||||||
|
onChange={(_, v) => setTabValue(v)}
|
||||||
|
variant="scrollable"
|
||||||
|
scrollButtons="auto"
|
||||||
|
sx={{
|
||||||
|
borderBottom: "1px solid rgba(0,0,0,0.08)",
|
||||||
|
"& .MuiTab-root": {
|
||||||
|
textTransform: "none",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
},
|
||||||
|
"& .Mui-selected": {
|
||||||
|
color: "#667eea",
|
||||||
|
},
|
||||||
|
"& .MuiTabs-indicator": {
|
||||||
|
background: "linear-gradient(90deg, #667eea 0%, #764ba2 100%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab icon={<TrendingUpIcon sx={{ fontSize: "1rem" }} />} iconPosition="start" label="Interest Chart" />
|
||||||
|
<Tab icon={<PublicIcon sx={{ fontSize: "1rem" }} />} iconPosition="start" label="Regions" />
|
||||||
|
<Tab icon={<AutoAwesomeIcon sx={{ fontSize: "1rem" }} />} iconPosition="start" label="Related Topics" />
|
||||||
|
<Tab icon={<SearchIcon sx={{ fontSize: "1rem" }} />} iconPosition="start" label="Related Queries" />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={0}>
|
||||||
|
<Box sx={{ mt: 1 }}>
|
||||||
|
<TrendsChart data={trendsData} height={280} showAverage={true} />
|
||||||
|
</Box>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={1}>
|
||||||
|
{regions.length === 0 ? (
|
||||||
|
<Box sx={{ py: 3, textAlign: "center" }}>
|
||||||
|
<PublicIcon sx={{ fontSize: 40, color: "#cbd5e1", mb: 1 }} />
|
||||||
|
<Typography variant="body2" sx={{ color: "#64748b" }}>
|
||||||
|
No regional data available for this topic.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Stack spacing={1} sx={{ maxHeight: 350, overflow: "auto" }}>
|
||||||
|
{regions.slice(0, 15).map((region: any, idx: number) => {
|
||||||
|
const regionName = region.regionName || region.geoName || region.name || `Region ${idx + 1}`;
|
||||||
|
const value = region.value || region.interest || 0;
|
||||||
|
const maxVal = Math.max(...regions.slice(0, 15).map((r: any) => r.value || r.interest || 0));
|
||||||
|
const pct = maxVal > 0 ? (value / maxVal) * 100 : 0;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={idx}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1.5,
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1,
|
||||||
|
"&:hover": { background: "rgba(102, 126, 234, 0.04)" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 30, fontWeight: 600, color: "#64748b" }}>
|
||||||
|
{idx + 1}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ flex: 1, fontWeight: 500, color: "#0f172a" }}>
|
||||||
|
{regionName}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flex: 1, maxWidth: 200 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
background: "rgba(102, 126, 234, 0.1)",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: `${pct}%`,
|
||||||
|
borderRadius: 4,
|
||||||
|
background: "linear-gradient(90deg, #667eea 0%, #764ba2 100%)",
|
||||||
|
transition: "width 0.3s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 600, color: "#667eea", minWidth: 30 }}>
|
||||||
|
{value}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={2}>
|
||||||
|
{relatedTopics.top.length === 0 && relatedTopics.rising.length === 0 ? (
|
||||||
|
<Box sx={{ py: 3, textAlign: "center" }}>
|
||||||
|
<AutoAwesomeIcon sx={{ fontSize: 40, color: "#cbd5e1", mb: 1 }} />
|
||||||
|
<Typography variant="body2" sx={{ color: "#64748b" }}>
|
||||||
|
No related topics data available.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
{relatedTopics.rising.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, color: "#059669", fontWeight: 700 }}>
|
||||||
|
Rising Topics
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
||||||
|
{relatedTopics.rising.map((topic: any, idx: number) => {
|
||||||
|
const label = topic.topic_title || topic.title || topic.query || String(topic);
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={idx}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSelectTopic(label)}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%)",
|
||||||
|
color: "#059669",
|
||||||
|
border: "1px solid rgba(16, 185, 129, 0.3)",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
mb: 0.5,
|
||||||
|
"&:hover": {
|
||||||
|
background: "linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.2) 100%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{relatedTopics.top.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, color: "#667eea", fontWeight: 700 }}>
|
||||||
|
Top Topics
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
||||||
|
{relatedTopics.top.map((topic: any, idx: number) => {
|
||||||
|
const label = topic.topic_title || topic.title || topic.query || String(topic);
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={idx}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSelectTopic(label)}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)",
|
||||||
|
color: "#667eea",
|
||||||
|
border: "1px solid rgba(102, 126, 234, 0.25)",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
mb: 0.5,
|
||||||
|
"&:hover": {
|
||||||
|
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.18) 0%, rgba(118, 75, 162, 0.18) 100%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={3}>
|
||||||
|
{relatedQueries.top.length === 0 && relatedQueries.rising.length === 0 ? (
|
||||||
|
<Box sx={{ py: 3, textAlign: "center" }}>
|
||||||
|
<SearchIcon sx={{ fontSize: 40, color: "#cbd5e1", mb: 1 }} />
|
||||||
|
<Typography variant="body2" sx={{ color: "#64748b" }}>
|
||||||
|
No related queries data available.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
{relatedQueries.rising.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, color: "#059669", fontWeight: 700 }}>
|
||||||
|
Rising Queries
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
||||||
|
{relatedQueries.rising.map((query: any, idx: number) => {
|
||||||
|
const label = query.query || query.title || String(query);
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={idx}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSelectTopic(label)}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%)",
|
||||||
|
color: "#d97706",
|
||||||
|
border: "1px solid rgba(245, 158, 11, 0.25)",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
mb: 0.5,
|
||||||
|
"&:hover": {
|
||||||
|
background: "linear-gradient(135deg, rgba(245, 158, 11, 0.18) 0%, rgba(217, 119, 6, 0.18) 100%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{relatedQueries.top.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, color: "#667eea", fontWeight: 700 }}>
|
||||||
|
Top Queries
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
||||||
|
{relatedQueries.top.map((query: any, idx: number) => {
|
||||||
|
const label = query.query || query.title || String(query);
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={idx}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSelectTopic(label)}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%)",
|
||||||
|
color: "#667eea",
|
||||||
|
border: "1px solid rgba(102, 126, 234, 0.25)",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
mb: 0.5,
|
||||||
|
"&:hover": {
|
||||||
|
background: "linear-gradient(135deg, rgba(102, 126, 234, 0.18) 0%, rgba(118, 75, 162, 0.18) 100%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && !trendsData && (
|
||||||
|
<Box sx={{ py: 4, textAlign: "center" }}>
|
||||||
|
<TrendingUpIcon sx={{ fontSize: 48, color: "#cbd5e1", mb: 1 }} />
|
||||||
|
<Typography variant="body2" sx={{ color: "#64748b" }}>
|
||||||
|
Enter a topic and click “Get Trending Topics” to see Google Trends data.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions
|
||||||
|
sx={{
|
||||||
|
px: 3,
|
||||||
|
py: 2,
|
||||||
|
borderTop: "1px solid rgba(0,0,0,0.08)",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" sx={{ color: "#94a3b8" }}>
|
||||||
|
Data from Google Trends
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={{
|
||||||
|
textTransform: "none",
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#64748b",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user