feature(dashboard): add trial ended popup
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user