Pricing Page and Subscription Guard
This commit is contained in:
166
frontend/src/components/SubscriptionGuard.tsx
Normal file
166
frontend/src/components/SubscriptionGuard.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
Alert,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Card,
|
||||
CardContent,
|
||||
CardActions,
|
||||
Chip,
|
||||
LinearProgress
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSubscriptionGuard, SubscriptionGuardOptions } from '../hooks/useSubscriptionGuard';
|
||||
import { Lock as LockIcon, Upgrade as UpgradeIcon } from '@mui/icons-material';
|
||||
|
||||
interface SubscriptionGuardProps extends SubscriptionGuardOptions {
|
||||
children: ReactNode;
|
||||
feature?: string;
|
||||
fallbackMessage?: string;
|
||||
showUpgradeButton?: boolean;
|
||||
showUsageProgress?: boolean;
|
||||
}
|
||||
|
||||
export const SubscriptionGuard: React.FC<SubscriptionGuardProps> = ({
|
||||
children,
|
||||
feature,
|
||||
fallbackMessage,
|
||||
showUpgradeButton = true,
|
||||
showUsageProgress = false,
|
||||
...guardOptions
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
subscription,
|
||||
loading,
|
||||
isGuarded,
|
||||
checkFeatureAccess,
|
||||
getRemainingUsage,
|
||||
checkSubscription
|
||||
} = useSubscriptionGuard(guardOptions);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<LinearProgress />
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
Checking subscription...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (isGuarded) {
|
||||
if (fallbackMessage) {
|
||||
return (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{fallbackMessage}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{ maxWidth: 400, mx: 'auto', mt: 2 }}>
|
||||
<CardContent>
|
||||
<Box sx={{ textAlign: 'center', mb: 2 }}>
|
||||
<LockIcon sx={{ fontSize: 48, color: 'warning.main', mb: 1 }} />
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Feature Locked
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
This feature requires an active subscription.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{subscription && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Current Plan: <Chip label={subscription.plan} size="small" />
|
||||
</Typography>
|
||||
{subscription.reason && (
|
||||
<Typography variant="body2" color="error">
|
||||
{subscription.reason}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
{showUpgradeButton && (
|
||||
<CardActions sx={{ justifyContent: 'center', pb: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<UpgradeIcon />}
|
||||
onClick={() => {
|
||||
navigate('/pricing');
|
||||
}}
|
||||
>
|
||||
Upgrade Plan
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (feature && !checkFeatureAccess(feature)) {
|
||||
const remaining = getRemainingUsage(feature);
|
||||
|
||||
return (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
<Typography variant="body2">
|
||||
{fallbackMessage || `You've reached your limit for ${feature}. Upgrade to continue using this feature.`}
|
||||
</Typography>
|
||||
{showUpgradeButton && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ mt: 1 }}
|
||||
onClick={() => {
|
||||
navigate('/pricing');
|
||||
}}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
// Convenience component for protecting entire sections
|
||||
export const ProtectedSection: React.FC<{
|
||||
children: ReactNode;
|
||||
feature?: string;
|
||||
title?: string;
|
||||
}> = ({ children, feature, title }) => {
|
||||
return (
|
||||
<SubscriptionGuard feature={feature}>
|
||||
<Box>
|
||||
{title && (
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{children}
|
||||
</Box>
|
||||
</SubscriptionGuard>
|
||||
);
|
||||
};
|
||||
|
||||
// Hook for checking if user can perform an action
|
||||
export const useCanPerformAction = (action: string) => {
|
||||
const { subscription, isFeatureAvailable } = useSubscriptionGuard();
|
||||
|
||||
return {
|
||||
canPerform: subscription?.active && isFeatureAvailable(action),
|
||||
subscription,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user