Fixes to Generate Pillar Chips

This commit is contained in:
ajaysi
2025-09-06 18:34:42 +05:30
parent ae42720c2a
commit 7ac72c5382
7 changed files with 769 additions and 219 deletions

View File

@@ -22,6 +22,7 @@ import LoadingSkeleton from '../shared/LoadingSkeleton';
import ErrorDisplay from '../shared/ErrorDisplay';
import EmptyState from '../shared/EmptyState';
import ContentLifecyclePillars from './ContentLifecyclePillars';
import AnalyticsInsights from './components/AnalyticsInsights';
// Shared types and utilities
import { Tool } from '../shared/types';
@@ -244,6 +245,9 @@ const MainDashboard: React.FC = () => {
{/* Content Lifecycle Pillars - First Panel */}
<ContentLifecyclePillars />
{/* Analytics Insights - Good/Bad/Ugly */}
<AnalyticsInsights />
{/* Search and Filter */}
<SearchFilter
searchQuery={searchQuery}
@@ -267,8 +271,8 @@ const MainDashboard: React.FC = () => {
transition={{ duration: 0.5, delay: categoryIndex * 0.1 }}
>
<Box sx={{ mb: 5 }}>
{/* Only show Category Header when no specific category is selected (showing all tools) */}
{selectedCategory === null && (
{/* Show Category Header when no specific category is selected OR when searching across all categories */}
{(selectedCategory === null || searchQuery) && (
<CategoryHeader
categoryName={categoryName}
category={category}

View File

@@ -0,0 +1,433 @@
import React from 'react';
import {
Box,
Card,
CardContent,
Typography,
Button,
Tooltip,
Modal,
IconButton,
Chip,
Stack,
Divider
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { keyframes } from '@mui/system';
import {
CheckCircle as CheckIcon,
WarningAmber as WarningIcon,
Error as ErrorIcon,
Close as CloseIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Info as InfoIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
interface Insight {
id: string;
title: string;
description: string;
metric: string;
value: string;
trend: 'up' | 'down' | 'stable';
priority: 'low' | 'medium' | 'high' | 'critical';
category: 'engagement' | 'reach' | 'conversion' | 'seo' | 'content';
platform: 'facebook' | 'linkedin' | 'twitter' | 'instagram' | 'website';
detailedAnalysis: string;
recommendations: string[];
impact: string;
timeframe: string;
}
interface AnalyticsData {
theGood: Insight[];
theBad: Insight[];
theUgly: Insight[];
}
interface AnalyticsInsightsProps {
data?: AnalyticsData; // optional - falls back to mock
onActionClick?: (action: 'alwrity' | 'ignore', insight: Insight) => void;
}
const ColumnCard = styled(Card)(({ theme }) => ({
background: 'linear-gradient(180deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.08) 100%)',
border: '1px solid rgba(255,255,255,0.16)',
backdropFilter: 'blur(18px)',
WebkitBackdropFilter: 'blur(18px)',
borderRadius: theme.spacing(2),
overflow: 'hidden',
boxShadow: '0 8px 20px rgba(0,0,0,0.28), inset 0 1px 0 rgba(255,255,255,0.22)',
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: '0 12px 28px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.28)'
}
}));
const Pill = styled('div')<{ color: string }>(() => ({
width: 10,
height: 10,
borderRadius: 6,
}));
const GradientHeader = styled(Box)<{ gradient: string }>(({ gradient }) => ({
background: gradient,
padding: '8px 12px',
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 6,
}));
const Badge = styled('span')(({ theme }) => ({
background: 'rgba(255,255,255,0.15)',
border: '1px solid rgba(255,255,255,0.35)',
color: 'white',
borderRadius: 999,
padding: '1px 6px',
fontWeight: 700,
fontSize: '0.65rem'
}));
// Subtle shimmer animation for the title text
const shimmerText = keyframes`
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
`;
const mockData: AnalyticsData = {
theGood: [
{
id: 'good-1',
title: 'LinkedIn Engagement Surge',
description: 'LinkedIn engagement is up significantly this week.',
metric: 'Engagement Rate',
value: '+45%',
trend: 'up',
priority: 'high',
category: 'engagement',
platform: 'linkedin',
detailedAnalysis: 'Recent posts on AI topics resonated strongly with your B2B audience.',
recommendations: ['Post 3x/week on AI trends', 'Engage with comments within 2 hours'],
impact: 'High lead-gen potential',
timeframe: 'Last 7 days'
},
{
id: 'good-2',
title: 'Website Traffic Growth',
description: 'Organic traffic increased due to improved SEO.',
metric: 'Organic Traffic',
value: '+23%',
trend: 'up',
priority: 'medium',
category: 'seo',
platform: 'website',
detailedAnalysis: 'Technical fixes and content refresh improved rankings.',
recommendations: ['Create 2 pillar pages', 'Refresh 5 top posts'],
impact: 'Improved visibility',
timeframe: 'Last 30 days'
},
{
id: 'good-3',
title: 'Top-Performing Post',
description: 'A recent LinkedIn post outperformed baseline by 2.1x',
metric: 'Engagement Index',
value: '2.1x',
trend: 'up',
priority: 'medium',
category: 'engagement',
platform: 'linkedin',
detailedAnalysis: 'Carousel format and thought leadership angle worked well.',
recommendations: ['Use carousel weekly', 'Add CTA to subscribe'],
impact: 'Audience growth',
timeframe: 'This week'
}
],
theBad: [
{
id: 'bad-1',
title: 'Facebook Reach Decline',
description: 'Facebook post reach dropped this month.',
metric: 'Reach',
value: '-18%',
trend: 'down',
priority: 'medium',
category: 'reach',
platform: 'facebook',
detailedAnalysis: 'Algorithm change likely impacting page distribution.',
recommendations: ['Test short video posts', 'Boost first-hour engagement'],
impact: 'Lower awareness',
timeframe: 'Last 30 days'
},
{
id: 'bad-2',
title: 'Email CTR Stagnant',
description: 'Content CTR plateaued across campaigns.',
metric: 'CTR',
value: '0.9%',
trend: 'stable',
priority: 'low',
category: 'content',
platform: 'website',
detailedAnalysis: 'Subject lines lack urgency; preview text uninspiring.',
recommendations: ['A/B test subject lines', 'Add curiosity hook'],
impact: 'Reduced visits',
timeframe: 'Last 14 days'
}
],
theUgly: [
{
id: 'ugly-1',
title: 'Critical SEO Issues',
description: '15 pages have broken internal links.',
metric: 'Broken Links',
value: '15 pages',
trend: 'down',
priority: 'critical',
category: 'seo',
platform: 'website',
detailedAnalysis: 'Broken links hurt crawlability and user experience.',
recommendations: ['Fix links immediately', 'Add automated link checks'],
impact: 'Severe ranking risk',
timeframe: 'Ongoing'
},
{
id: 'ugly-2',
title: 'Declining Conversions',
description: 'Checkout conversion dropped vs prior month.',
metric: 'CVR',
value: '-12%',
trend: 'down',
priority: 'high',
category: 'conversion',
platform: 'website',
detailedAnalysis: 'Funnel analysis shows friction on payment step.',
recommendations: ['Simplify checkout', 'Add alternate payment'],
impact: 'Direct revenue impact',
timeframe: 'Last 30 days'
}
]
};
const getGradient = (type: 'good' | 'bad' | 'ugly') => {
switch (type) {
case 'good':
return 'linear-gradient(135deg, rgba(76,175,80,0.55) 0%, rgba(139,195,74,0.55) 100%)';
case 'bad':
return 'linear-gradient(135deg, rgba(255,152,0,0.55) 0%, rgba(245,124,0,0.55) 100%)';
default:
return 'linear-gradient(135deg, rgba(244,67,54,0.55) 0%, rgba(233,30,99,0.55) 100%)';
}
};
const getIcon = (type: 'good' | 'bad' | 'ugly') => {
switch (type) {
case 'good':
return <CheckIcon />;
case 'bad':
return <WarningIcon />;
default:
return <ErrorIcon />;
}
};
const TrendChip: React.FC<{ trend: Insight['trend'] }> = ({ trend }) => {
if (trend === 'up') return <Chip size="small" icon={<TrendingUpIcon />} label="Up" sx={{ color: '#4CAF50', background: '#4CAF5022', border: '1px solid #4CAF5044', fontWeight: 700, fontSize: '0.6rem', height: 18 }} />;
if (trend === 'down') return <Chip size="small" icon={<TrendingDownIcon />} label="Down" sx={{ color: '#F44336', background: '#F4433622', border: '1px solid #F4433644', fontWeight: 700, fontSize: '0.6rem', height: 18 }} />;
return <Chip size="small" icon={<InfoIcon />} label="Stable" sx={{ color: '#90CAF9', background: '#90CAF922', border: '1px solid #90CAF944', fontWeight: 700, fontSize: '0.6rem', height: 18 }} />;
};
const AnalyticsInsights: React.FC<AnalyticsInsightsProps> = ({ data, onActionClick }) => {
const [hovered, setHovered] = React.useState<'good' | 'bad' | 'ugly' | null>(null);
const [open, setOpen] = React.useState(false);
const [selected, setSelected] = React.useState<Insight | null>(null);
const insights = data || mockData;
const columns: Array<{ key: 'good' | 'bad' | 'ugly'; title: string; items: Insight[] }> = [
{ key: 'good', title: 'The Good', items: insights.theGood },
{ key: 'bad', title: 'The Bad', items: insights.theBad },
{ key: 'ugly', title: 'The Ugly', items: insights.theUgly },
];
const handleKnowMore = (insight: Insight) => {
setSelected(insight);
setOpen(true);
};
const handleClose = () => setOpen(false);
const handleAction = (action: 'alwrity' | 'ignore') => {
if (selected && onActionClick) {
onActionClick(action, selected);
}
setOpen(false);
};
return (
<Box sx={{ mt: 2, mb: 2.5 }}>
<Typography
variant="h6"
sx={{
fontWeight: 800,
mb: 1.5,
fontSize: '1.1rem',
background: 'linear-gradient(90deg, rgba(255,255,255,0.35), rgba(255,255,255,0.9) 50%, rgba(255,255,255,0.35))',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
color: 'transparent',
backgroundSize: '200% 100%',
animation: `${shimmerText} 3.2s linear infinite`,
}}
>
Analytics Insights
</Typography>
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5}>
{columns.map((col) => {
const isHovered = hovered === col.key;
const visibleItems = isHovered ? col.items : col.items.slice(0, 1);
const gradient = getGradient(col.key);
return (
<motion.div key={col.key} style={{ flex: 1 }} onMouseEnter={() => setHovered(col.key)} onMouseLeave={() => setHovered(null)}>
<ColumnCard>
<GradientHeader gradient={gradient}>
{getIcon(col.key)}
<Typography variant="subtitle1" sx={{ fontWeight: 800, fontSize: '0.9rem' }}>{col.title}</Typography>
<Badge>{col.items.length}</Badge>
</GradientHeader>
<CardContent sx={{ p: 1.5 }}>
<Stack spacing={1}>
{visibleItems.map((insight) => (
<Box key={insight.id} sx={{
background: 'rgba(255,255,255,0.08)',
border: '1px solid rgba(255,255,255,0.18)',
borderRadius: 1.5,
p: 1
}}>
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ mb: 0.25 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.95)', fontWeight: 700, fontSize: '0.8rem' }}>
{insight.title}
</Typography>
<TrendChip trend={insight.trend} />
</Stack>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.8)', fontSize: '0.7rem', lineHeight: 1.2 }}>
{insight.description}
</Typography>
<Stack direction="row" spacing={0.5} sx={{ mt: 0.5 }}>
<Chip size="small" label={`${insight.metric}: ${insight.value}`} sx={{ color: 'rgba(255,255,255,0.95)', background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.24)', fontWeight: 700, fontSize: '0.65rem', height: 20 }} />
<Chip size="small" label={insight.platform} sx={{ color: 'rgba(255,255,255,0.85)', background: 'rgba(255,255,255,0.08)', fontSize: '0.65rem', height: 20 }} />
</Stack>
</Box>
))}
</Stack>
{isHovered && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'flex-end' }}>
<Tooltip title={`Open detailed insights for ${col.title.toLowerCase()}.`}>
<span>
<Button
variant="contained"
onClick={() => handleKnowMore(col.items[0])}
size="small"
sx={{
textTransform: 'none',
fontWeight: 800,
background: gradient,
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
fontSize: '0.75rem',
px: 2,
py: 0.5
}}
>
Know More
</Button>
</span>
</Tooltip>
</Box>
)}
</CardContent>
</ColumnCard>
</motion.div>
);
})}
</Stack>
<Modal open={open} onClose={handleClose}>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: { xs: '92%', md: 900 },
maxHeight: '80vh',
overflowY: 'auto',
background: 'linear-gradient(180deg, rgba(16,24,39,0.92) 0%, rgba(26,33,56,0.92) 100%)',
border: '1px solid rgba(255,255,255,0.18)',
borderRadius: 3,
boxShadow: '0 26px 80px rgba(0,0,0,0.5)'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', p: 2.5, borderBottom: '1px solid rgba(255,255,255,0.15)' }}>
<Typography variant="h6" sx={{ color: 'rgba(255,255,255,0.95)', fontWeight: 800 }}>
{selected?.title}
</Typography>
<IconButton onClick={handleClose} sx={{ color: 'rgba(255,255,255,0.85)' }}>
<CloseIcon />
</IconButton>
</Box>
<CardContent>
<Stack spacing={1.5}>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.9)' }}>
{selected?.detailedAnalysis}
</Typography>
<Stack direction="row" spacing={1}>
<Chip size="small" label={`${selected?.metric}: ${selected?.value}`} sx={{ color: 'rgba(255,255,255,0.95)', background: 'rgba(255,255,255,0.12)', border: '1px solid rgba(255,255,255,0.24)', fontWeight: 700 }} />
{selected?.platform && (
<Chip size="small" label={selected.platform} sx={{ color: 'rgba(255,255,255,0.85)', background: 'rgba(255,255,255,0.08)' }} />
)}
{selected?.impact && (
<Chip size="small" label={`Impact: ${selected.impact}`} sx={{ color: 'rgba(255,255,255,0.85)', background: 'rgba(255,255,255,0.08)' }} />
)}
</Stack>
<Divider sx={{ my: 1.5, borderColor: 'rgba(255,255,255,0.15)' }} />
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', fontWeight: 800 }}>
Recommendations
</Typography>
<Stack spacing={0.75}>
{selected?.recommendations.map((rec, idx) => (
<Typography key={idx} variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}> {rec}</Typography>
))}
</Stack>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1.5, mt: 2 }}>
<Tooltip title="Save this as a memory for ALwrity AI to take action automatically.">
<span>
<Button variant="contained" color="success" onClick={() => handleAction('alwrity')} sx={{ textTransform: 'none', fontWeight: 800 }}>
ALwrity it
</Button>
</span>
</Tooltip>
<Tooltip title="Dismiss for now. You can revisit later in analytics.">
<span>
<Button variant="outlined" color="inherit" onClick={() => handleAction('ignore')} sx={{ textTransform: 'none', fontWeight: 800 }}>
Ignore it
</Button>
</span>
</Tooltip>
</Box>
</Stack>
</CardContent>
</Box>
</Modal>
</Box>
);
};
export default AnalyticsInsights;

View File

@@ -35,77 +35,107 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
}
return 0;
};
// Descriptions for category tooltips
const categoryDescriptions: Record<string, string> = {
'Generate Content': 'AI multimodal generators: Blog, Image, Audio, Video.',
'SEO Tools': 'Enterprise SEO analysis, technical tools, and optimization utilities.',
'Social Media': 'Platform writers for Facebook, LinkedIn, Twitter, Instagram, YouTube.',
'Dashboards': 'Analytics dashboards: SEO, Social, Website, Strategy, and Calendar.'
};
return (
<SearchContainer>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 3 }}>
<TextField
fullWidth
placeholder="Search tools..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: 'rgba(255, 255, 255, 0.7)' }} />
</InputAdornment>
),
endAdornment: searchQuery && (
<InputAdornment position="end">
<IconButton onClick={onClearSearch} size="small">
<ClearIcon sx={{ color: 'rgba(255, 255, 255, 0.7)' }} />
</IconButton>
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
color: 'white',
'& fieldset': {
borderColor: 'rgba(255, 255, 255, 0.3)',
{/* Single Row Layout: Search Input + Category Filters */}
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
{/* Search Input - Takes available space */}
<Box sx={{ flex: '1 1 300px', minWidth: '250px' }}>
<TextField
fullWidth
placeholder="Search tools..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: 'rgba(255, 255, 255, 0.85)' }} />
</InputAdornment>
),
endAdornment: searchQuery && (
<InputAdornment position="end">
<IconButton onClick={onClearSearch} size="small">
<ClearIcon sx={{ color: 'rgba(255, 255, 255, 0.85)' }} />
</IconButton>
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
color: 'white',
background: 'linear-gradient(135deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.08) 100%)',
borderRadius: 2.5,
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.2), 0 6px 18px rgba(0,0,0,0.25)',
'& fieldset': {
borderColor: 'rgba(255, 255, 255, 0.28)',
},
'&:hover fieldset': {
borderColor: 'rgba(255, 255, 255, 0.5)',
},
'&.Mui-focused fieldset': {
borderColor: 'rgba(255, 255, 255, 0.85)',
},
'& input::placeholder': {
color: 'rgba(255, 255, 255, 0.85)',
opacity: 1,
},
},
'&:hover fieldset': {
borderColor: 'rgba(255, 255, 255, 0.5)',
},
'&.Mui-focused fieldset': {
borderColor: 'rgba(255, 255, 255, 0.8)',
},
'& input::placeholder': {
color: 'rgba(255, 255, 255, 0.6)',
opacity: 1,
},
},
}}
/>
}}
/>
</Box>
{/* Filter Icon */}
<Tooltip title="Filter by category">
<IconButton sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
<FilterIcon />
</IconButton>
</Tooltip>
</Box>
{/* Enhanced Category Filter with Tool Count Badges */}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<CategoryChip
label="All Tools"
onClick={() => onCategoryChange(null)}
active={selectedCategory === null}
theme={theme}
toolCount={Object.values(toolCategories).reduce((total, category) => total + getToolCount(category), 0)}
/>
{Object.keys(toolCategories).map((category) => (
{/* Category Filter Chips - Inline with search */}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
<CategoryChip
key={category}
label={category}
onClick={() => onCategoryChange(category)}
active={selectedCategory === category}
label="All Tools"
onClick={() => onCategoryChange(null)}
active={selectedCategory === null}
theme={theme}
toolCount={getToolCount(toolCategories[category])}
toolCount={Object.values(toolCategories).reduce((total, category) => total + getToolCount(category), 0)}
/>
))}
{Object.keys(toolCategories).map((category) => {
const cat = toolCategories[category] as any;
const gradient = (cat && cat.gradient) || undefined;
const desc = categoryDescriptions[category] || `Filter tools by ${category}.`;
return (
<Tooltip
key={category}
title={`${desc} Total tools: ${getToolCount(cat)}.`}
placement="top"
arrow
enterDelay={300}
>
<CategoryChip
label={category}
onClick={() => onCategoryChange(category)}
active={selectedCategory === category}
theme={theme}
toolCount={getToolCount(cat)}
gradient={gradient}
/>
</Tooltip>
);
})}
</Box>
</Box>
{/* Sub-category Filter for SEO & Analytics */}
{selectedCategory === 'SEO & Analytics' && 'subCategories' in toolCategories['SEO & Analytics'] && (
{/* Sub-category Filter for SEO Tools */}
{selectedCategory === 'SEO Tools' && 'subCategories' in toolCategories['SEO Tools'] && (
<Box sx={{ mt: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.8)', mb: 1, fontWeight: 600 }}>
Filter by sub-category:
@@ -117,7 +147,7 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
active={selectedSubCategory === null}
theme={theme}
/>
{Object.keys(toolCategories['SEO & Analytics'].subCategories).map((subCategory) => (
{Object.keys(toolCategories['SEO Tools'].subCategories).map((subCategory) => (
<CategoryChip
key={subCategory}
label={subCategory}

View File

@@ -10,7 +10,8 @@ import {
} from '@mui/material';
import {
Star as StarIcon,
StarBorder as StarBorderIcon
StarBorder as StarBorderIcon,
LockOutlined as LockIcon
} from '@mui/icons-material';
import { ToolCardProps } from './types';
import { getStatusConfig } from './utils';
@@ -22,6 +23,7 @@ const ToolCard: React.FC<ToolCardProps> = ({
onToggleFavorite
}) => {
const config = getStatusConfig(tool.status);
const isLocked = tool.status === 'premium' || tool.status === 'pro';
return (
<Card
@@ -30,17 +32,17 @@ const ToolCard: React.FC<ToolCardProps> = ({
backdropFilter: 'blur(24px)',
border: '1px solid rgba(255, 255, 255, 0.12)',
borderRadius: 3,
cursor: 'pointer',
cursor: isLocked ? 'not-allowed' : 'pointer',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden',
'&:hover': {
transform: 'translateY(-8px) scale(1.02)',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
border: '1px solid rgba(255, 255, 255, 0.2)',
transform: isLocked ? 'none' : 'translateY(-8px) scale(1.02)',
boxShadow: isLocked ? 'none' : '0 20px 40px rgba(0, 0, 0, 0.3)',
border: isLocked ? '1px solid rgba(255, 255, 255, 0.12)' : '1px solid rgba(255, 255, 255, 0.2)',
},
}}
onClick={() => onToolClick(tool)}
onClick={() => { if (!isLocked) onToolClick(tool); }}
>
<CardContent sx={{ p: 3 }}>
{/* Header with Icon and Status */}
@@ -59,8 +61,9 @@ const ToolCard: React.FC<ToolCardProps> = ({
background: `${config.color}20`,
color: config.color,
border: `1px solid ${config.color}40`,
fontWeight: 600,
fontWeight: 700,
fontSize: '0.75rem',
textTransform: 'capitalize',
}}
/>
</Box>
@@ -102,28 +105,48 @@ const ToolCard: React.FC<ToolCardProps> = ({
Features:
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{tool.features.slice(0, 3).map((feature, index) => (
<Chip
key={index}
label={feature}
size="small"
sx={{
background: 'rgba(255, 255, 255, 0.1)',
color: 'rgba(255, 255, 255, 0.8)',
fontSize: '0.7rem',
height: '20px',
}}
/>
))}
{tool.features.slice(0, 3).map((feature, index) => {
const isDashboard = tool.name.toLowerCase().includes('dashboard');
return (
<Chip
key={index}
label={feature}
size="small"
sx={{
background: isDashboard
? 'linear-gradient(135deg, rgba(156, 39, 176, 0.3) 0%, rgba(123, 31, 162, 0.2) 100%)'
: 'rgba(255, 255, 255, 0.1)',
color: isDashboard ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 255, 255, 0.8)',
fontSize: '0.7rem',
height: '22px',
border: isDashboard ? '1px solid rgba(156, 39, 176, 0.4)' : 'none',
fontWeight: isDashboard ? 600 : 400,
boxShadow: isDashboard ? '0 2px 8px rgba(156, 39, 176, 0.2)' : 'none',
transition: 'all 0.2s ease',
'&:hover': isDashboard ? {
background: 'linear-gradient(135deg, rgba(156, 39, 176, 0.4) 0%, rgba(123, 31, 162, 0.3) 100%)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(156, 39, 176, 0.3)',
} : {},
}}
/>
);
})}
{tool.features.length > 3 && (
<Chip
label={`+${tool.features.length - 3} more`}
size="small"
sx={{
background: 'rgba(255, 255, 255, 0.1)',
color: 'rgba(255, 255, 255, 0.6)',
background: tool.name.toLowerCase().includes('dashboard')
? 'linear-gradient(135deg, rgba(156, 39, 176, 0.2) 0%, rgba(123, 31, 162, 0.1) 100%)'
: 'rgba(255, 255, 255, 0.1)',
color: tool.name.toLowerCase().includes('dashboard')
? 'rgba(255, 255, 255, 0.8)'
: 'rgba(255, 255, 255, 0.6)',
fontSize: '0.7rem',
height: '20px',
height: '22px',
border: tool.name.toLowerCase().includes('dashboard') ? '1px solid rgba(156, 39, 176, 0.3)' : 'none',
fontWeight: tool.name.toLowerCase().includes('dashboard') ? 600 : 400,
}}
/>
)}
@@ -131,6 +154,40 @@ const ToolCard: React.FC<ToolCardProps> = ({
</Box>
)}
</CardContent>
{/* Locked overlay for Premium/Pro */}
{isLocked && (
<Box
sx={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(180deg, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0.65) 100%)',
backdropFilter: 'blur(2px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
}}
>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
color: 'rgba(255,255,255,0.95)',
background: 'rgba(255,255,255,0.08)',
border: '1px solid rgba(255,255,255,0.25)',
px: 1.5,
py: 0.75,
borderRadius: 2,
boxShadow: '0 8px 24px rgba(0,0,0,0.35)'
}}>
<LockIcon fontSize="small" />
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{(config.label || 'Pro') + ' • Locked'}
</Typography>
</Box>
</Box>
)}
</Card>
);
};

View File

@@ -96,28 +96,35 @@ export const SearchContainer = styled(Box)(({ theme }) => ({
}));
export const CategoryChip = styled(Chip, {
shouldForwardProp: (prop) => prop !== 'active' && prop !== 'toolCount',
})<{ active?: boolean; toolCount?: number }>(({ theme, active, toolCount }) => ({
shouldForwardProp: (prop) => prop !== 'active' && prop !== 'toolCount' && prop !== 'gradient',
})<{ active?: boolean; toolCount?: number; gradient?: string }>(({ theme, active, toolCount, gradient }) => ({
background: active
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.2) 100%)'
: 'rgba(255, 255, 255, 0.1)',
? (gradient || 'linear-gradient(135deg, rgba(76, 175, 80, 0.4) 0%, rgba(139, 195, 74, 0.3) 50%, rgba(255, 255, 255, 0.2) 100%)')
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.05) 100%)',
color: 'white',
fontWeight: active ? 700 : 600,
fontSize: '0.9rem',
padding: theme.spacing(1, 2),
border: active
? '2px solid rgba(255, 255, 255, 0.6)'
: '1px solid rgba(255, 255, 255, 0.2)',
? '2px solid rgba(255, 255, 255, 0.6)'
: '1px solid rgba(255, 255, 255, 0.25)',
boxShadow: active
? '0 6px 20px rgba(255, 255, 255, 0.2), 0 0 0 1px rgba(255,255,255,0.1)'
: 'none',
? '0 6px 20px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.2)'
: '0 2px 8px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1)',
transform: active ? 'translateY(-2px) scale(1.05)' : 'none',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
position: 'relative',
'&:hover': {
background: 'rgba(255, 255, 255, 0.25)',
background: active
? (gradient || 'linear-gradient(135deg, rgba(76, 175, 80, 0.5) 0%, rgba(139, 195, 74, 0.4) 50%, rgba(255, 255, 255, 0.25) 100%)')
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.1) 100%)',
transform: 'translateY(-2px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
boxShadow: active
? '0 8px 25px rgba(76, 175, 80, 0.4), 0 0 0 1px rgba(76, 175, 80, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.3)'
: '0 4px 15px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2)',
border: active
? '2px solid rgba(76, 175, 80, 0.8)'
: '1px solid rgba(255, 255, 255, 0.4)',
},
'& .MuiChip-label': {
padding: theme.spacing(0.5, 1),

View File

@@ -6,7 +6,12 @@ export const getToolsForCategory = (category: Category, selectedSubCategory: str
if (selectedSubCategory && category.subCategories[selectedSubCategory]) {
return category.subCategories[selectedSubCategory].tools;
}
return [];
// When no subcategory is selected, return all tools from all subcategories
const allTools: Tool[] = [];
Object.values(category.subCategories).forEach(subCategory => {
allTools.push(...subCategory.tools);
});
return allTools;
}
return category.tools;
};
@@ -19,7 +24,9 @@ export const getFilteredCategories = (
const filtered: ToolCategories = {};
Object.entries(toolCategories).forEach(([categoryName, category]) => {
if (selectedCategory && categoryName !== selectedCategory) {
// If there's a search query, search across ALL categories regardless of selected category
// If no search query, respect the selected category filter
if (!searchQuery && selectedCategory && categoryName !== selectedCategory) {
return;
}
@@ -60,6 +67,14 @@ export const getStatusConfig = (status: string) => {
return { color: '#FF9800', icon: '⚠', label: 'Good' };
case 'needs_action':
return { color: '#F44336', icon: '✗', label: 'Needs Action' };
case 'premium':
return { color: '#9C27B0', icon: '⭐', label: 'Premium' };
case 'beta':
return { color: '#FF9800', icon: '🧪', label: 'Beta' };
case 'pro':
return { color: '#2196F3', icon: '💎', label: 'Pro' };
case 'active':
return { color: '#4CAF50', icon: '✓', label: 'Active' };
default:
return { color: '#9E9E9E', icon: '', label: 'Unknown' };
}
@@ -74,6 +89,14 @@ export const getStatusColor = (status: string) => {
return '#FF9800';
case 'needs_action':
return '#F44336';
case 'premium':
return '#9C27B0';
case 'beta':
return '#FF9800';
case 'pro':
return '#2196F3';
case 'active':
return '#4CAF50';
default:
return '#9E9E9E';
}
@@ -88,6 +111,14 @@ export const getStatusIcon = (status: string) => {
return '⚠';
case 'needs_action':
return '✗';
case 'premium':
return '⭐';
case 'beta':
return '🧪';
case 'pro':
return '💎';
case 'active':
return '✓';
default:
return '';
}

View File

@@ -9,67 +9,66 @@ import {
Speed as SpeedIcon,
Business as BusinessIcon,
SocialDistance as SocialIcon,
Create as CreateIcon
Create as CreateIcon,
Dashboard as DashboardIcon,
Facebook as FacebookIcon,
LinkedIn as LinkedInIcon,
Twitter as TwitterIcon,
Instagram as InstagramIcon,
Web as WebIcon,
Timeline as StrategyIcon,
CalendarMonth as CalendarIcon,
Image as ImageIcon,
Audiotrack as AudioIcon,
VideoLibrary as VideoIcon
} from '@mui/icons-material';
import { ToolCategories } from '../components/shared/types';
export const toolCategories: ToolCategories = {
'AI Content Writers': {
icon: React.createElement(ArticleIcon),
'Generate Content': {
icon: React.createElement(AutoAwesomeIcon),
color: '#4CAF50',
gradient: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)',
tools: [
{
name: 'AI Blog Writer',
description: 'Generate engaging blog posts with AI',
name: 'Blog Writer',
description: 'AI-powered blog post generation with SEO optimization',
icon: React.createElement(ArticleIcon),
status: 'active',
path: '/ai-blog-writer',
features: ['SEO Optimized', 'Multiple Formats', 'Custom Tone']
status: 'beta',
path: '/blog-writer',
features: ['SEO Optimized', 'Multiple Formats', 'Custom Tone', 'Research Integration', 'Plagiarism Free'],
isHighlighted: true
},
{
name: 'AI Essay Writer',
description: 'Academic and professional essay writing',
icon: React.createElement(CreateIcon),
status: 'active',
path: '/ai-essay-writer',
features: ['Academic Style', 'Citation Support', 'Plagiarism Free']
name: 'Image Generator',
description: 'AI image creation and visual content generation',
icon: React.createElement(ImageIcon),
status: 'beta',
path: '/image-generator',
features: ['AI Art Generation', 'Style Customization', 'High Resolution', 'Brand Consistency', 'Multiple Formats'],
isHighlighted: true
},
{
name: 'AI News Article Writer',
description: 'Professional news and article writing',
icon: React.createElement(ArticleIcon),
status: 'active',
path: '/ai-news-writer',
features: ['Fact-Checked', 'Journalistic Style', 'Breaking News']
name: 'Audio Generator',
description: 'AI voice synthesis and audio content creation',
icon: React.createElement(AudioIcon),
status: 'premium',
path: '/audio-generator',
features: ['Voice Synthesis', 'Multiple Languages', 'Custom Voices', 'Audio Editing', 'Export Options'],
isHighlighted: true
},
{
name: 'AI Story Writer',
description: 'Creative storytelling and fiction writing',
icon: React.createElement(CreateIcon),
status: 'active',
path: '/ai-story-writer',
features: ['Creative Writing', 'Character Development', 'Plot Generation']
},
{
name: 'AI Copywriter',
description: 'Marketing copy and advertising content',
icon: React.createElement(CampaignIcon),
status: 'active',
path: '/ai-copywriter',
features: ['Persuasive Writing', 'Brand Voice', 'Call-to-Action']
},
{
name: 'AI Product Description Writer',
description: 'Compelling product descriptions',
icon: React.createElement(BusinessIcon),
status: 'active',
path: '/ai-product-writer',
features: ['E-commerce Optimized', 'Feature Highlighting', 'Conversion Focused']
name: 'Video Generator',
description: 'AI video creation and multimedia content generation',
icon: React.createElement(VideoIcon),
status: 'premium',
path: '/video-generator',
features: ['AI Video Creation', 'Scene Generation', 'Voice Integration', 'Custom Branding', 'Export Formats'],
isHighlighted: true
}
]
},
'SEO & Analytics': {
'SEO Tools': {
icon: React.createElement(SearchIcon),
color: '#2196F3',
gradient: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)',
@@ -249,7 +248,7 @@ export const toolCategories: ToolCategories = {
name: 'Facebook Content Writer',
description: 'Engaging Facebook posts and ads with AI persona optimization',
icon: React.createElement(SocialIcon),
status: 'premium',
status: 'beta',
path: '/facebook-writer',
features: ['Persona-Aware AI', 'Engagement Focused', 'Ad Copy', 'Post Scheduling', 'Platform Optimization'],
isHighlighted: true
@@ -258,7 +257,7 @@ export const toolCategories: ToolCategories = {
name: 'LinkedIn Content Writer',
description: 'Professional LinkedIn content with AI persona optimization',
icon: React.createElement(BusinessIcon),
status: 'premium',
status: 'beta',
path: '/linkedin-writer',
features: ['Persona-Aware AI', 'Professional Tone', 'Thought Leadership', 'B2B Focus', 'Platform Optimization'],
isHighlighted: true
@@ -267,7 +266,7 @@ export const toolCategories: ToolCategories = {
name: 'Twitter Content Writer',
description: 'Viral Twitter threads and tweets',
icon: React.createElement(SocialIcon),
status: 'active',
status: 'premium',
path: '/twitter-writer',
features: ['Viral Potential', 'Thread Creation', 'Hashtag Optimization']
},
@@ -275,7 +274,7 @@ export const toolCategories: ToolCategories = {
name: 'Instagram Content Writer',
description: 'Visual and engaging Instagram content',
icon: React.createElement(SocialIcon),
status: 'active',
status: 'premium',
path: '/instagram-writer',
features: ['Visual Descriptions', 'Hashtag Strategy', 'Story Content']
},
@@ -283,95 +282,84 @@ export const toolCategories: ToolCategories = {
name: 'YouTube Content Writer',
description: 'Video scripts and descriptions',
icon: React.createElement(SocialIcon),
status: 'active',
status: 'premium',
path: '/youtube-writer',
features: ['Video Scripts', 'SEO Descriptions', 'Engagement Hooks']
}
]
},
'Business & Marketing': {
icon: React.createElement(BusinessIcon),
'Dashboards': {
icon: React.createElement(DashboardIcon),
color: '#9C27B0',
gradient: 'linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%)',
tools: [
{
name: 'Financial Report Generator',
description: 'Professional financial analysis and reports',
icon: React.createElement(AnalyticsIcon),
status: 'active',
path: '/financial-reports',
features: ['Data Analysis', 'Professional Reports', 'Insights Generation']
},
{
name: 'Email Templates',
description: 'Professional email templates and campaigns',
icon: React.createElement(CampaignIcon),
status: 'active',
path: '/email-templates',
features: ['Professional Templates', 'A/B Testing', 'Automation']
},
{
name: 'Press Release Writer',
description: 'Newsworthy press releases',
icon: React.createElement(ArticleIcon),
status: 'active',
path: '/press-releases',
features: ['Newsworthy Content', 'Media Ready', 'Distribution Ready']
},
{
name: 'Landing Page Copy',
description: 'High-converting landing page content',
icon: React.createElement(BusinessIcon),
status: 'active',
path: '/landing-page-copy',
features: ['Conversion Focused', 'A/B Testing', 'UX Optimized']
},
{
name: 'Competitive Intelligence',
description: 'Analyze competitors and market trends',
icon: React.createElement(PsychologyIcon),
status: 'premium',
path: '/competitive-intelligence',
features: ['Market Analysis', 'Competitor Tracking', 'Strategy Insights']
}
]
},
'Creative & Advanced': {
icon: React.createElement(AutoAwesomeIcon),
color: '#E91E63',
gradient: 'linear-gradient(135deg, #E91E63 0%, #C2185B 100%)',
tools: [
{
name: 'AI Agents Crew',
description: 'Multi-agent AI content creation team',
icon: React.createElement(AutoAwesomeIcon),
status: 'premium',
path: '/ai-agents-crew',
features: ['Multi-Agent System', 'Collaborative Writing', 'Advanced AI']
},
{
name: 'Content Performance Predictor',
description: 'Predict content performance and engagement',
icon: React.createElement(AnalyticsIcon),
status: 'premium',
path: '/content-predictor',
features: ['Performance Prediction', 'Engagement Analysis', 'ROI Forecasting']
},
{
name: 'Web Researcher',
description: 'AI-powered web research and analysis',
name: 'SEO Dashboard',
description: 'Comprehensive SEO analytics and performance tracking',
icon: React.createElement(SearchIcon),
status: 'active',
path: '/web-researcher',
features: ['Real-time Research', 'Data Analysis', 'Insight Generation']
status: 'beta',
path: '/seo-dashboard',
features: ['Keyword Rankings', 'Traffic Analytics', 'Backlink Monitoring', 'Site Health', 'Competitor Analysis'],
isHighlighted: true
},
{
name: 'Content Scheduler',
description: 'Intelligent content scheduling and planning',
icon: React.createElement(CampaignIcon),
status: 'active',
path: '/content-scheduler',
features: ['Smart Scheduling', 'Calendar Integration', 'Performance Tracking']
name: 'Facebook Dashboard',
description: 'Facebook page insights and content performance analytics',
icon: React.createElement(FacebookIcon),
status: 'beta',
path: '/facebook-dashboard',
features: ['Page Insights', 'Post Performance', 'Audience Analytics', 'Engagement Metrics', 'Ad Performance'],
isHighlighted: true
},
{
name: 'LinkedIn Dashboard',
description: 'LinkedIn company page and content analytics',
icon: React.createElement(LinkedInIcon),
status: 'beta',
path: '/linkedin-dashboard',
features: ['Company Analytics', 'Content Performance', 'Lead Generation', 'B2B Insights', 'Network Growth'],
isHighlighted: true
},
{
name: 'Twitter Dashboard',
description: 'Twitter analytics and engagement tracking',
icon: React.createElement(TwitterIcon),
status: 'pro',
path: '/twitter-dashboard',
features: ['Tweet Analytics', 'Follower Growth', 'Engagement Rates', 'Hashtag Performance', 'Mention Tracking']
},
{
name: 'Instagram Dashboard',
description: 'Instagram insights and visual content analytics',
icon: React.createElement(InstagramIcon),
status: 'pro',
path: '/instagram-dashboard',
features: ['Story Analytics', 'Post Performance', 'Reach & Impressions', 'Hashtag Insights', 'Audience Demographics']
},
{
name: 'Website Dashboard',
description: 'Website performance and visitor analytics',
icon: React.createElement(WebIcon),
status: 'pro',
path: '/website-dashboard',
features: ['Traffic Analysis', 'Page Performance', 'User Behavior', 'Conversion Tracking', 'Site Speed']
},
{
name: 'Strategy Dashboard',
description: 'Content strategy planning and performance overview',
icon: React.createElement(StrategyIcon),
status: 'beta',
path: '/strategy-dashboard',
features: ['Content Planning', 'Performance Overview', 'Goal Tracking', 'ROI Analysis', 'Strategic Insights'],
isHighlighted: true
},
{
name: 'Calendar Dashboard',
description: 'Content calendar management and scheduling analytics',
icon: React.createElement(CalendarIcon),
status: 'beta',
path: '/calendar-dashboard',
features: ['Content Scheduling', 'Publishing Calendar', 'Performance Tracking', 'Team Collaboration', 'Content Planning']
}
]
}