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

@@ -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 '';
}