feature(dashboard): add trial ended popup
This commit is contained in:
@@ -30,7 +30,9 @@ export default async function AppLayout({
|
|||||||
getDashboardsByProjectId(projectId),
|
getDashboardsByProjectId(projectId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!organizations.find((item) => item.id === organizationId)) {
|
const organization = organizations.find((item) => item.id === organizationId);
|
||||||
|
|
||||||
|
if (!organization) {
|
||||||
return (
|
return (
|
||||||
<FullPageEmptyState title="Not found" className="min-h-screen">
|
<FullPageEmptyState title="Not found" className="min-h-screen">
|
||||||
The organization you were looking for could not be found.
|
The organization you were looking for could not be found.
|
||||||
@@ -58,7 +60,7 @@ export default async function AppLayout({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LayoutContent>{children}</LayoutContent>
|
<LayoutContent>{children}</LayoutContent>
|
||||||
<SideEffects />
|
<SideEffects organization={organization} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import useWS from '@/hooks/useWS';
|
|||||||
import { showConfirm } from '@/modals';
|
import { showConfirm } from '@/modals';
|
||||||
import { api } from '@/trpc/client';
|
import { api } from '@/trpc/client';
|
||||||
import type { IServiceOrganization } from '@openpanel/db';
|
import type { IServiceOrganization } from '@openpanel/db';
|
||||||
|
import { useOpenPanel } from '@openpanel/nextjs';
|
||||||
import type { IPolarPrice } from '@openpanel/payments';
|
import type { IPolarPrice } from '@openpanel/payments';
|
||||||
import { Loader2Icon } from 'lucide-react';
|
import { Loader2Icon } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -32,6 +33,7 @@ type Props = {
|
|||||||
export default function Billing({ organization }: Props) {
|
export default function Billing({ organization }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId } = useAppParams();
|
const { projectId } = useAppParams();
|
||||||
|
const op = useOpenPanel();
|
||||||
const [customerSessionToken, setCustomerSessionToken] = useQueryState(
|
const [customerSessionToken, setCustomerSessionToken] = useQueryState(
|
||||||
'customer_session_token',
|
'customer_session_token',
|
||||||
);
|
);
|
||||||
@@ -61,6 +63,12 @@ export default function Billing({ organization }: Props) {
|
|||||||
}
|
}
|
||||||
}, [organization.subscriptionInterval]);
|
}, [organization.subscriptionInterval]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (customerSessionToken) {
|
||||||
|
op.track('subscription_created');
|
||||||
|
}
|
||||||
|
}, [customerSessionToken]);
|
||||||
|
|
||||||
function renderBillingTable() {
|
function renderBillingTable() {
|
||||||
if (productsQuery.isLoading) {
|
if (productsQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -227,6 +235,7 @@ function CheckoutButton({
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
disabled?: string | null;
|
disabled?: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
const op = useOpenPanel();
|
||||||
const isCurrentPrice = organization.subscriptionPriceId === price.id;
|
const isCurrentPrice = organization.subscriptionPriceId === price.id;
|
||||||
const checkout = api.subscription.checkout.useMutation({
|
const checkout = api.subscription.checkout.useMutation({
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
@@ -270,9 +279,15 @@ function CheckoutButton({
|
|||||||
showConfirm({
|
showConfirm({
|
||||||
title: 'Are you sure?',
|
title: 'Are you sure?',
|
||||||
text: `You're about the change your subscription.`,
|
text: `You're about the change your subscription.`,
|
||||||
onConfirm: () => createCheckout(),
|
onConfirm: () => {
|
||||||
|
op.track('subscription_change');
|
||||||
|
createCheckout();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
op.track('subscription_checkout', {
|
||||||
|
product: price.productId,
|
||||||
|
});
|
||||||
createCheckout();
|
createCheckout();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,11 +1,65 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { pushModal, useOnPushModal } from '@/modals';
|
import { pushModal, useOnPushModal } from '@/modals';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays, differenceInHours } from 'date-fns';
|
||||||
import { useEffect } from 'react';
|
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 { useOpenPanel } from '@openpanel/nextjs';
|
||||||
|
import Billing from './settings/organization/organization/billing';
|
||||||
|
|
||||||
export default function SideEffects() {
|
interface SideEffectsProps {
|
||||||
return null;
|
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