Pro/business
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
BarChart3,
|
||||
Copy,
|
||||
CreditCard,
|
||||
Download,
|
||||
Filter,
|
||||
Loader2,
|
||||
@@ -68,6 +69,12 @@ type SegmentRow = {
|
||||
qrCount: number;
|
||||
dynamicQrCount: number;
|
||||
scanCount: number;
|
||||
lifecycleLogs: Array<{
|
||||
fromStage: string | null;
|
||||
toStage: string;
|
||||
reason: string | null;
|
||||
createdAt: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type DashboardData = {
|
||||
@@ -76,6 +83,7 @@ type DashboardData = {
|
||||
mismatchCount: number;
|
||||
activatedUsers: number;
|
||||
paidUsers: number;
|
||||
recentBillingEvents: number;
|
||||
};
|
||||
acquisition: {
|
||||
bySource: Array<{
|
||||
@@ -119,6 +127,16 @@ type DashboardData = {
|
||||
byTeamSize: Array<any>;
|
||||
};
|
||||
lifecycleSummary: Record<string, number>;
|
||||
recentBillingActivity: Array<{
|
||||
userId: string;
|
||||
name: string | null;
|
||||
email: string;
|
||||
plan: string;
|
||||
reason: string | null;
|
||||
fromStage: string | null;
|
||||
toStage: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
campaignSourceQuality: Array<any>;
|
||||
upgradeCandidates: Array<SegmentRow>;
|
||||
filterOptions: {
|
||||
@@ -184,6 +202,27 @@ function buildQuery(filters: Filters) {
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
function formatStage(value: string | null) {
|
||||
return value ? value.replace(/_/g, ' ') : 'none';
|
||||
}
|
||||
|
||||
function formatBillingReason(value: string | null) {
|
||||
switch (value) {
|
||||
case 'subscription_created':
|
||||
return 'Started paid plan';
|
||||
case 'subscription_updated':
|
||||
return 'Updated subscription';
|
||||
case 'subscription_canceled_at_period_end':
|
||||
return 'Cancels at period end';
|
||||
case 'subscription_deleted':
|
||||
return 'Ended subscription';
|
||||
case 'subscription_synced':
|
||||
return 'Synced subscription';
|
||||
default:
|
||||
return 'Subscription changed';
|
||||
}
|
||||
}
|
||||
|
||||
function LifecycleCard({
|
||||
label,
|
||||
value,
|
||||
@@ -475,6 +514,50 @@ export default function NewsletterClient() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="mb-8">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<CreditCard className="h-5 w-5 text-slate-500" />
|
||||
<CardTitle>Recent Billing Activity</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{data.recentBillingActivity.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>User</TableHead>
|
||||
<TableHead>Plan</TableHead>
|
||||
<TableHead>Action</TableHead>
|
||||
<TableHead>Lifecycle change</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.recentBillingActivity.map((event) => (
|
||||
<TableRow key={`${event.userId}-${event.createdAt}`}>
|
||||
<TableCell>
|
||||
<div>
|
||||
<div className="font-medium text-slate-900">{event.name || event.email}</div>
|
||||
<div className="text-xs text-slate-500">{event.email}</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{event.plan}</TableCell>
|
||||
<TableCell>{formatBillingReason(event.reason)}</TableCell>
|
||||
<TableCell>
|
||||
{formatStage(event.fromStage)} -> {formatStage(event.toStage)}
|
||||
</TableCell>
|
||||
<TableCell>{new Date(event.createdAt).toLocaleString()}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">No billing activity logged yet.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="mb-8 grid gap-6 xl:grid-cols-[1.4fr_1fr]">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@@ -77,7 +77,7 @@ export default function PricingPage() {
|
||||
const handleDowngrade = async () => {
|
||||
// Show confirmation dialog
|
||||
const confirmed = window.confirm(
|
||||
'Are you sure you want to downgrade to the Free plan? Your subscription will be canceled immediately and you will lose access to premium features.'
|
||||
'Are you sure you want to cancel your paid plan? You will keep premium features until the end of your current billing period.'
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -99,7 +99,7 @@ export default function PricingPage() {
|
||||
throw new Error(error.error || 'Failed to cancel subscription');
|
||||
}
|
||||
|
||||
showToast('Successfully downgraded to Free plan', 'success');
|
||||
showToast('Subscription will end at the end of your current billing period.', 'success');
|
||||
|
||||
// Refresh to update the plan
|
||||
setTimeout(() => {
|
||||
@@ -148,7 +148,7 @@ export default function PricingPage() {
|
||||
'Standard QR design templates',
|
||||
'Download as SVG/PNG',
|
||||
],
|
||||
buttonText: currentPlan === 'FREE' ? 'Current Plan' : 'Downgrade to Free',
|
||||
buttonText: currentPlan === 'FREE' ? 'Current Plan' : 'Cancel paid plan',
|
||||
buttonVariant: 'outline' as const,
|
||||
disabled: currentPlan === 'FREE',
|
||||
popular: false,
|
||||
|
||||
Reference in New Issue
Block a user