ALwrity LinkedIn Writer: Billing Dashboard: Compact View, Billing Overview, System Health Indicator, Cost Breakdown, Usage Trends, Usage Alerts, Comprehensive API Breakdown
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Chip, Button, CircularProgress } from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Chip, Button, CircularProgress, Tooltip } from '@mui/material';
|
||||
import { PlayArrow, Pause, Stop } from '@mui/icons-material';
|
||||
import { ShimmerHeader } from './styled';
|
||||
import { DashboardHeaderProps } from './types';
|
||||
@@ -12,6 +12,47 @@ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
customIcon,
|
||||
workflowControls
|
||||
}) => {
|
||||
// State for enhanced start button behavior
|
||||
const [isFirstVisit, setIsFirstVisit] = useState(false);
|
||||
const [showFloatingCTA, setShowFloatingCTA] = useState(false);
|
||||
const [tooltipMessage, setTooltipMessage] = useState("🎯 Start your daily content workflow here!");
|
||||
|
||||
// Check if this is first visit and set up enhanced behavior
|
||||
useEffect(() => {
|
||||
const hasVisited = localStorage.getItem('alwrity-has-visited');
|
||||
if (!hasVisited) {
|
||||
setIsFirstVisit(true);
|
||||
localStorage.setItem('alwrity-has-visited', 'true');
|
||||
|
||||
// Set up floating CTA after 15 seconds
|
||||
const timer = setTimeout(() => {
|
||||
setShowFloatingCTA(true);
|
||||
// Auto-hide after 30 seconds
|
||||
setTimeout(() => setShowFloatingCTA(false), 30000);
|
||||
}, 15000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Progressive tooltip messages
|
||||
useEffect(() => {
|
||||
if (!isFirstVisit) return;
|
||||
|
||||
const messages = [
|
||||
"🎯 Start your daily content workflow here!",
|
||||
"💡 This button launches your personalized content plan",
|
||||
"⚡ Click to begin your digital marketing automation"
|
||||
];
|
||||
|
||||
let messageIndex = 0;
|
||||
const interval = setInterval(() => {
|
||||
messageIndex = (messageIndex + 1) % messages.length;
|
||||
setTooltipMessage(messages[messageIndex]);
|
||||
}, 10000); // Change message every 10 seconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [isFirstVisit]);
|
||||
return (
|
||||
<ShimmerHeader sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||
@@ -59,60 +100,87 @@ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{/* Workflow Control Buttons */}
|
||||
{!workflowControls.isWorkflowActive ? (
|
||||
/* Start Button with Badge and Lightning Glow */
|
||||
/* Enhanced Start Button with Phase 1 Improvements */
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<PlayArrow />}
|
||||
onClick={workflowControls.onStartWorkflow}
|
||||
disabled={workflowControls.isLoading}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(135deg, #4caf50 0%, #388e3c 100%)',
|
||||
border: '2px solid transparent',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #388e3c 0%, #2e7d32 100%)',
|
||||
},
|
||||
minWidth: 'auto',
|
||||
px: 2,
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '-100%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent)',
|
||||
animation: 'shimmer 2.5s infinite',
|
||||
zIndex: 1,
|
||||
},
|
||||
'&::after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: -2,
|
||||
left: -2,
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
background: 'linear-gradient(45deg, #4caf50, #8bc34a, #4caf50, #8bc34a)',
|
||||
backgroundSize: '400% 400%',
|
||||
borderRadius: 'inherit',
|
||||
zIndex: -1,
|
||||
animation: 'borderGlow 3s ease-in-out infinite',
|
||||
},
|
||||
'@keyframes shimmer': {
|
||||
'0%': { left: '-100%' },
|
||||
'100%': { left: '100%' },
|
||||
},
|
||||
'@keyframes borderGlow': {
|
||||
'0%, 100%': { backgroundPosition: '0% 50%' },
|
||||
'50%': { backgroundPosition: '100% 50%' },
|
||||
},
|
||||
}}
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
<Tooltip title={tooltipMessage} arrow placement="bottom">
|
||||
<Button
|
||||
variant="contained"
|
||||
size={isFirstVisit ? "medium" : "small"}
|
||||
startIcon={<PlayArrow />}
|
||||
onClick={workflowControls.onStartWorkflow}
|
||||
disabled={workflowControls.isLoading}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
// Phase 1: Orange/Amber color psychology for action
|
||||
background: 'linear-gradient(135deg, #FF6B35 0%, #E55A2B 100%)',
|
||||
border: '2px solid transparent',
|
||||
// Reduced size by 30% for both first visit and returning users
|
||||
transform: isFirstVisit ? 'scale(0.875)' : 'scale(0.7)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #E55A2B 0%, #D1491F 100%)',
|
||||
transform: isFirstVisit ? 'scale(0.95)' : 'scale(0.75)',
|
||||
},
|
||||
minWidth: 'auto',
|
||||
px: isFirstVisit ? 3 : 2,
|
||||
py: isFirstVisit ? 1.5 : 1,
|
||||
fontSize: isFirstVisit ? '1rem' : '0.875rem',
|
||||
fontWeight: 700,
|
||||
// Phase 1: Enhanced pulsing animation
|
||||
animation: isFirstVisit ? 'pulse 2s ease-in-out infinite' : 'none',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '-100%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent)',
|
||||
animation: 'shimmer 2.5s infinite',
|
||||
zIndex: 1,
|
||||
},
|
||||
// Phase 1: Stronger outer glow effect
|
||||
'&::after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: -4,
|
||||
left: -4,
|
||||
right: -4,
|
||||
bottom: -4,
|
||||
background: 'linear-gradient(45deg, #FF6B35, #FF8C42, #FF6B35, #FF8C42)',
|
||||
backgroundSize: '400% 400%',
|
||||
borderRadius: 'inherit',
|
||||
zIndex: -1,
|
||||
animation: 'borderGlow 3s ease-in-out infinite',
|
||||
// Enhanced glow effect
|
||||
boxShadow: isFirstVisit
|
||||
? '0 0 20px rgba(255, 107, 53, 0.6), 0 0 40px rgba(255, 107, 53, 0.4), 0 0 60px rgba(255, 107, 53, 0.2)'
|
||||
: '0 0 15px rgba(255, 107, 53, 0.4), 0 0 30px rgba(255, 107, 53, 0.2)',
|
||||
},
|
||||
'@keyframes pulse': {
|
||||
'0%, 100%': {
|
||||
transform: isFirstVisit ? 'scale(0.875)' : 'scale(0.7)',
|
||||
boxShadow: '0 0 20px rgba(255, 107, 53, 0.6)'
|
||||
},
|
||||
'50%': {
|
||||
transform: isFirstVisit ? 'scale(0.95)' : 'scale(0.75)',
|
||||
boxShadow: '0 0 30px rgba(255, 107, 53, 0.8)'
|
||||
},
|
||||
},
|
||||
'@keyframes shimmer': {
|
||||
'0%': { left: '-100%' },
|
||||
'100%': { left: '100%' },
|
||||
},
|
||||
'@keyframes borderGlow': {
|
||||
'0%, 100%': { backgroundPosition: '0% 50%' },
|
||||
'50%': { backgroundPosition: '100% 50%' },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isFirstVisit ? '🚀 Start Journey' : 'Start'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
@@ -130,6 +198,90 @@ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
>
|
||||
{`${workflowControls.completedTasks}/${workflowControls.totalTasks}`}
|
||||
</Box>
|
||||
|
||||
{/* Floating CTA for first-time users */}
|
||||
{showFloatingCTA && isFirstVisit && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
right: 0,
|
||||
mt: 2,
|
||||
p: 2,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.95)',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 8px 32px rgba(255, 107, 53, 0.4)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
zIndex: 1000,
|
||||
animation: 'fadeInUp 0.5s ease-out',
|
||||
maxWidth: 280,
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: -8,
|
||||
right: 20,
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeft: '8px solid transparent',
|
||||
borderRight: '8px solid transparent',
|
||||
borderBottom: '8px solid rgba(255, 107, 53, 0.95)',
|
||||
},
|
||||
'@keyframes fadeInUp': {
|
||||
'0%': {
|
||||
opacity: 0,
|
||||
transform: 'translateY(20px)',
|
||||
},
|
||||
'100%': {
|
||||
opacity: 1,
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
🎯 Ready to create amazing content?
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
display: 'block',
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
Click the orange button above to start your personalized content workflow!
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => setShowFloatingCTA(false)}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderColor: 'rgba(255, 255, 255, 0.5)',
|
||||
fontSize: '0.75rem',
|
||||
py: 0.5,
|
||||
px: 1,
|
||||
'&:hover': {
|
||||
borderColor: 'white',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Got it!
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
/* In-Progress/Completed Controls with Enhanced Styling */
|
||||
|
||||
@@ -25,7 +25,8 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
|
||||
onSubCategoryChange,
|
||||
toolCategories,
|
||||
theme,
|
||||
onCategoryClick
|
||||
onCategoryClick,
|
||||
compact = false
|
||||
}) => {
|
||||
// Helper function to get tool count from a category
|
||||
const getToolCount = (category: any): number => {
|
||||
@@ -44,6 +45,87 @@ const SearchFilter: React.FC<SearchFilterProps> = ({
|
||||
'Social Media': 'Platform writers for Facebook, LinkedIn, Twitter, Instagram, YouTube.',
|
||||
'Dashboards': 'Analytics dashboards: SEO, Social, Website, Strategy, and Calendar.'
|
||||
};
|
||||
if (compact) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{/* Compact Search Input */}
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
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
|
||||
size="small"
|
||||
onClick={onClearSearch}
|
||||
sx={{ color: 'rgba(255,255,255,0.7)' }}
|
||||
>
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
sx: {
|
||||
backgroundColor: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: 2,
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: 'rgba(255,255,255,0.3)',
|
||||
},
|
||||
},
|
||||
'& .MuiInputBase-input': {
|
||||
color: '#ffffff',
|
||||
'&::placeholder': {
|
||||
color: 'rgba(255,255,255,0.5)',
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Compact Category Filters */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{Object.entries(toolCategories).map(([categoryId, category]) => (
|
||||
<CategoryChip
|
||||
key={categoryId}
|
||||
label={`${categoryId} (${getToolCount(category)})`}
|
||||
onClick={() => onCategoryClick?.(categoryId, category)}
|
||||
sx={{
|
||||
fontSize: '0.75rem',
|
||||
height: 24,
|
||||
backgroundColor: selectedCategory === categoryId
|
||||
? 'rgba(255,255,255,0.2)'
|
||||
: 'rgba(255,255,255,0.05)',
|
||||
color: '#ffffff',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
'& .MuiChip-label': {
|
||||
px: 1,
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255,255,255,0.15)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
{/* Single Row Layout: Search Input + Category Filters */}
|
||||
|
||||
@@ -303,7 +303,7 @@ const ContentDistributionPie: React.FC<ContentDistributionPieProps> = ({
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={({ name, percent }) => `${name} ${((percent || 0) * 100).toFixed(0)}%`}
|
||||
label={({ name, value }) => `${name} ${value}`}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface SearchFilterProps {
|
||||
toolCategories: ToolCategories;
|
||||
theme: any;
|
||||
onCategoryClick?: (category: string | null, categoryData?: any) => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardHeaderProps {
|
||||
|
||||
Reference in New Issue
Block a user