AI Story Writer Backend Migration Complete, Frontend UI Components Added
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Avatar, Box, Menu, MenuItem, Typography, Tooltip, Chip } from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Avatar, Box, Menu, MenuItem, Typography, Tooltip, Chip, Divider } from '@mui/material';
|
||||
import { useUser, useClerk } from '@clerk/clerk-react';
|
||||
import { useSubscription } from '../../contexts/SubscriptionContext';
|
||||
import SystemStatusIndicator from '../ContentPlanningDashboard/components/SystemStatusIndicator';
|
||||
import UsageDashboard from './UsageDashboard';
|
||||
import { apiClient } from '../../api/client';
|
||||
|
||||
interface UserBadgeProps {
|
||||
colorMode?: 'light' | 'dark';
|
||||
@@ -12,6 +15,7 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
|
||||
const { signOut } = useClerk();
|
||||
const { subscription } = useSubscription();
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [systemStatus, setSystemStatus] = useState<'healthy' | 'warning' | 'critical' | 'unknown'>('unknown');
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const initials = React.useMemo(() => {
|
||||
@@ -20,8 +24,43 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
|
||||
return (first + last || user?.username?.[0] || user?.primaryEmailAddress?.emailAddress?.[0] || '?').toUpperCase();
|
||||
}, [user]);
|
||||
|
||||
// Fetch system status for status bulb
|
||||
useEffect(() => {
|
||||
const fetchSystemStatus = async () => {
|
||||
try {
|
||||
const response = await apiClient.get('/api/content-planning/monitoring/lightweight-stats');
|
||||
const result = response.data;
|
||||
if (result.status === 'success' && result.data) {
|
||||
setSystemStatus(result.data.status || 'unknown');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching system status:', err);
|
||||
setSystemStatus('unknown');
|
||||
}
|
||||
};
|
||||
|
||||
fetchSystemStatus();
|
||||
// Refresh every 30 seconds
|
||||
const interval = setInterval(fetchSystemStatus, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!isSignedIn) return null;
|
||||
|
||||
// Get status bulb color
|
||||
const getStatusBulbColor = () => {
|
||||
switch (systemStatus) {
|
||||
case 'healthy':
|
||||
return '#4caf50'; // Green
|
||||
case 'warning':
|
||||
return '#ff9800'; // Orange
|
||||
case 'critical':
|
||||
return '#f44336'; // Red
|
||||
default:
|
||||
return '#757575'; // Gray for unknown
|
||||
}
|
||||
};
|
||||
|
||||
// Get plan display info
|
||||
const getPlanColor = () => {
|
||||
switch (subscription?.plan) {
|
||||
@@ -65,24 +104,65 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tooltip title={`${user?.fullName || user?.username || user?.primaryEmailAddress?.emailAddress || 'User'}`}>
|
||||
<Avatar
|
||||
onClick={handleOpen}
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
cursor: 'pointer',
|
||||
bgcolor: colorMode === 'dark' ? 'rgba(255,255,255,0.2)' : 'primary.main',
|
||||
color: colorMode === 'dark' ? 'white' : 'white',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
src={user?.imageUrl || undefined}
|
||||
>
|
||||
{initials}
|
||||
</Avatar>
|
||||
<Tooltip title={`${user?.fullName || user?.username || user?.primaryEmailAddress?.emailAddress || 'User'} - System: ${systemStatus.toUpperCase()}`}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<Avatar
|
||||
onClick={handleOpen}
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
cursor: 'pointer',
|
||||
bgcolor: colorMode === 'dark' ? 'rgba(255,255,255,0.2)' : 'primary.main',
|
||||
color: colorMode === 'dark' ? 'white' : 'white',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
src={user?.imageUrl || undefined}
|
||||
>
|
||||
{initials}
|
||||
</Avatar>
|
||||
{/* Status Bulb */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: '50%',
|
||||
bgcolor: getStatusBulbColor(),
|
||||
border: `2px solid ${colorMode === 'dark' ? '#1a1a1a' : 'white'}`,
|
||||
boxShadow: `0 0 8px ${getStatusBulbColor()}80`,
|
||||
animation: systemStatus === 'healthy' ? 'pulse 2s ease-in-out infinite' : 'none',
|
||||
'@keyframes pulse': {
|
||||
'0%, 100%': {
|
||||
opacity: 1,
|
||||
transform: 'scale(1)',
|
||||
},
|
||||
'50%': {
|
||||
opacity: 0.8,
|
||||
transform: 'scale(1.1)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Menu anchorEl={anchorEl} open={open} onClose={handleClose} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }}>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
minWidth: 320,
|
||||
maxWidth: 400,
|
||||
maxHeight: '80vh',
|
||||
overflow: 'auto'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ px: 2, py: 1, borderBottom: '1px solid rgba(0,0,0,0.1)' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
|
||||
{user?.fullName || user?.username || 'User'}
|
||||
@@ -110,6 +190,50 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
{/* System Status Indicator */}
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
py: 1.5,
|
||||
bgcolor: 'rgba(0,0,0,0.02)',
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1, fontWeight: 600 }}>
|
||||
System Health
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', '& > *': { transform: 'scale(0.85)' } }}>
|
||||
<SystemStatusIndicator />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
{/* Usage Dashboard */}
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
py: 1.5,
|
||||
bgcolor: 'rgba(0,0,0,0.02)',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1, fontWeight: 600 }}>
|
||||
Usage Statistics
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
<UsageDashboard compact={true} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
<MenuItem onClick={() => { handleClose(); window.location.href = '/pricing'; }}>
|
||||
Manage Subscription
|
||||
</MenuItem>
|
||||
|
||||
Reference in New Issue
Block a user