diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout.tsx index aa3cd085..c4e14f6d 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout.tsx @@ -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 ( The organization you were looking for could not be found. @@ -58,7 +60,7 @@ export default async function AppLayout({ }} /> {children} - + ); } diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/settings/organization/organization/billing.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/settings/organization/organization/billing.tsx index 7ecbe299..466cbd86 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/settings/organization/organization/billing.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/settings/organization/organization/billing.tsx @@ -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(); } }} diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/side-effects.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/side-effects.tsx index 9ca10148..972341d4 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/side-effects.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/side-effects.tsx @@ -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( + willEndInHours !== null && + organization.subscriptionStatus === 'trialing' && + organization.subscriptionEndsAt !== null && + willEndInHours <= 48, + ); + + useEffect(() => { + if (isTrialDialogOpen) { + op.track('trial_expires_soon'); + } + }, [isTrialDialogOpen]); + + return ( + <> + + + 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{' '} + + manage billing + + + } + /> + + + + + ); }