feature(dashboard): add trial ended popup

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-03-27 00:13:23 +01:00
parent ee80b47b0d
commit d4c1c15174
3 changed files with 78 additions and 7 deletions

View File

@@ -30,7 +30,9 @@ export default async function AppLayout({
getDashboardsByProjectId(projectId),
]);
if (!organizations.find((item) => item.id === organizationId)) {
const organization = organizations.find((item) => item.id === organizationId);
if (!organization) {
return (
<FullPageEmptyState title="Not found" className="min-h-screen">
The organization you were looking for could not be found.
@@ -58,7 +60,7 @@ export default async function AppLayout({
}}
/>
<LayoutContent>{children}</LayoutContent>
<SideEffects />
<SideEffects organization={organization} />
</div>
);
}

View File

@@ -18,6 +18,7 @@ import useWS from '@/hooks/useWS';
import { showConfirm } from '@/modals';
import { api } from '@/trpc/client';
import type { IServiceOrganization } from '@openpanel/db';
import { useOpenPanel } from '@openpanel/nextjs';
import type { IPolarPrice } from '@openpanel/payments';
import { Loader2Icon } from 'lucide-react';
import { useRouter } from 'next/navigation';
@@ -32,6 +33,7 @@ type Props = {
export default function Billing({ organization }: Props) {
const router = useRouter();
const { projectId } = useAppParams();
const op = useOpenPanel();
const [customerSessionToken, setCustomerSessionToken] = useQueryState(
'customer_session_token',
);
@@ -61,6 +63,12 @@ export default function Billing({ organization }: Props) {
}
}, [organization.subscriptionInterval]);
useEffect(() => {
if (customerSessionToken) {
op.track('subscription_created');
}
}, [customerSessionToken]);
function renderBillingTable() {
if (productsQuery.isLoading) {
return (
@@ -227,6 +235,7 @@ function CheckoutButton({
projectId: string;
disabled?: string | null;
}) {
const op = useOpenPanel();
const isCurrentPrice = organization.subscriptionPriceId === price.id;
const checkout = api.subscription.checkout.useMutation({
onSuccess(data) {
@@ -270,9 +279,15 @@ function CheckoutButton({
showConfirm({
title: 'Are you sure?',
text: `You're about the change your subscription.`,
onConfirm: () => createCheckout(),
onConfirm: () => {
op.track('subscription_change');
createCheckout();
},
});
} else {
op.track('subscription_checkout', {
product: price.productId,
});
createCheckout();
}
}}

View File

@@ -1,11 +1,65 @@
'use client';
import { pushModal, useOnPushModal } from '@/modals';
import { differenceInDays } from 'date-fns';
import { useEffect } from 'react';
import { differenceInDays, differenceInHours } from 'date-fns';
import { useEffect, useState } from 'react';
import { ProjectLink } from '@/components/links';
import { Dialog, DialogContent, DialogHeader } from '@/components/ui/dialog';
import { ModalHeader } from '@/modals/Modal/Container';
import type { IServiceOrganization } from '@openpanel/db';
import { useOpenPanel } from '@openpanel/nextjs';
import Billing from './settings/organization/organization/billing';
export default function SideEffects() {
return null;
interface SideEffectsProps {
organization: IServiceOrganization;
}
export default function SideEffects({ organization }: SideEffectsProps) {
const op = useOpenPanel();
const willEndInHours = organization.subscriptionEndsAt
? differenceInHours(organization.subscriptionEndsAt, new Date())
: null;
const [isTrialDialogOpen, setIsTrialDialogOpen] = useState<boolean>(
willEndInHours !== null &&
organization.subscriptionStatus === 'trialing' &&
organization.subscriptionEndsAt !== null &&
willEndInHours <= 48,
);
useEffect(() => {
if (isTrialDialogOpen) {
op.track('trial_expires_soon');
}
}, [isTrialDialogOpen]);
return (
<>
<Dialog open={isTrialDialogOpen} onOpenChange={setIsTrialDialogOpen}>
<DialogContent className="max-w-xl">
<ModalHeader
onClose={() => setIsTrialDialogOpen(false)}
title={
willEndInHours !== null && willEndInHours > 0
? `Your trial is ending in ${willEndInHours} hours`
: 'Your trial has ended'
}
text={
<>
Please upgrade your plan to continue using OpenPanel. Select a
tier which is appropriate for your needs or{' '}
<ProjectLink
href="/settings/organization/billing"
className="underline text-foreground"
>
manage billing
</ProjectLink>
</>
}
/>
<Billing organization={organization} />
</DialogContent>
</Dialog>
</>
);
}