diff --git a/apps/public/components/sections/faq.tsx b/apps/public/components/sections/faq.tsx index 54e6d666..1bdc2d88 100644 --- a/apps/public/components/sections/faq.tsx +++ b/apps/public/components/sections/faq.tsx @@ -13,7 +13,7 @@ const questions = [ { question: 'Does OpenPanel have a free tier?', answer: [ - 'For our Cloud plan we offer a 14 days free trial, this is mostly for you to be able to try out OpenPanel before committing to a paid plan.', + 'For our Cloud plan we offer a 30 days free trial, this is mostly for you to be able to try out OpenPanel before committing to a paid plan.', 'OpenPanel is also open-source and you can self-host it for free!', '', 'Why does OpenPanel not have a free tier?', diff --git a/apps/start/src/components/feedback-button.tsx b/apps/start/src/components/feedback-button.tsx index d7745261..0bc70e97 100644 --- a/apps/start/src/components/feedback-button.tsx +++ b/apps/start/src/components/feedback-button.tsx @@ -8,7 +8,7 @@ export function FeedbackButton() { return ( + + View pricing + + + + +
+ Plans start at just $2.5/month + + Unlimited reports, members and projects + + Advanced funnels and conversions + Real-time analytics + + Track KPIs and custom events (revenue soon) + + + Privacy-focused and GDPR compliant + +
+ + + + ); +} + +function Point({ + icon: Icon, + children, +}: { icon: LucideIcon; children: React.ReactNode }) { + return ( +
+
+ +
+

{children}

+
+ ); +} diff --git a/apps/start/src/components/organization/usage.tsx b/apps/start/src/components/organization/billing-usage.tsx similarity index 70% rename from apps/start/src/components/organization/usage.tsx rename to apps/start/src/components/organization/billing-usage.tsx index 778ed624..5cb7dae1 100644 --- a/apps/start/src/components/organization/usage.tsx +++ b/apps/start/src/components/organization/billing-usage.tsx @@ -18,7 +18,6 @@ import { BarChart, CartesianGrid, Tooltip as RechartTooltip, - ReferenceLine, ResponsiveContainer, XAxis, YAxis, @@ -38,7 +37,7 @@ function Card({ title, value }: { title: string; value: string }) { ); } -export default function Usage({ organization }: Props) { +export default function BillingUsage({ organization }: Props) { const number = useNumber(); const trpc = useTRPC(); const usageQuery = useQuery( @@ -82,6 +81,7 @@ export default function Usage({ organization }: Props) { , ); } + if (usageQuery.isError) { return wrapper(
@@ -90,6 +90,12 @@ export default function Usage({ organization }: Props) { ); } + if (usageQuery.data?.length === 0) { + return wrapper( +
No usage data yet
, + ); + } + const subscriptionPeriodEventsLimit = organization.hasSubscription ? organization.subscriptionPeriodEventsLimit : 0; @@ -159,7 +165,7 @@ export default function Usage({ organization }: Props) { return wrapper( <> -
+
{organization.hasSubscription ? ( <> - - + + ) : ( <> @@ -209,95 +215,36 @@ export default function Usage({ organization }: Props) { )}
-
- {/* Events Chart */} -
-

- {useWeeklyIntervals ? 'Weekly Events' : 'Daily Events'} -

-
- - - } - /> - - - - - - -
-
- - {/* Total Events vs Limit Chart */} -
-

- Total Events vs Limit -

-
- - - } cursor={false} /> - {organization.hasSubscription && - subscriptionPeriodEventsLimit > 0 && ( - - )} - - - - - - -
+ {/* Events Chart */} +
+

+ {useWeeklyIntervals ? 'Weekly Events' : 'Daily Events'} +

+
+ + + } + cursor={{ + fill: 'var(--def-200)', + stroke: 'var(--def-200)', + }} + /> + + + + + +
, diff --git a/apps/start/src/components/organization/billing.tsx b/apps/start/src/components/organization/billing.tsx index f25b314f..6c8fd041 100644 --- a/apps/start/src/components/organization/billing.tsx +++ b/apps/start/src/components/organization/billing.tsx @@ -1,45 +1,55 @@ import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogTitle, -} from '@/components/ui/dialog'; -import { Slider } from '@/components/ui/slider'; -import { Switch } from '@/components/ui/switch'; -import { Tooltiper } from '@/components/ui/tooltip'; -import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; -import { useAppParams } from '@/hooks/use-app-params'; +import { useNumber } from '@/hooks/use-numer-formatter'; import useWS from '@/hooks/use-ws'; import { useTRPC } from '@/integrations/trpc/react'; -import { showConfirm } from '@/modals'; -import { op } from '@/utils/op'; +import { pushModal, useOnPushModal } from '@/modals'; +import { formatDate } from '@/utils/date'; import type { IServiceOrganization } from '@openpanel/db'; -import type { IPolarPrice } from '@openpanel/payments'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { Loader2Icon } from 'lucide-react'; +import { differenceInDays } from 'date-fns'; import { useQueryState } from 'nuqs'; import { useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; +import { Progress } from '../ui/progress'; +import { Widget, WidgetBody, WidgetHead } from '../widget'; +import { BillingFaq } from './billing-faq'; +import BillingUsage from './billing-usage'; type Props = { organization: IServiceOrganization; }; export default function Billing({ organization }: Props) { + const [success, setSuccess] = useQueryState('customer_session_token'); const queryClient = useQueryClient(); const trpc = useTRPC(); - const [customerSessionToken, setCustomerSessionToken] = useQueryState( - 'customer_session_token', - ); + const number = useNumber(); + const productsQuery = useQuery( trpc.subscription.products.queryOptions({ organizationId: organization.id, }), ); + const currentProductQuery = useQuery( + trpc.subscription.getCurrent.queryOptions({ + organizationId: organization.id, + }), + ); + + const portalMutation = useMutation( + trpc.subscription.portal.mutationOptions({ + onSuccess(data) { + if (data?.url) { + window.location.href = data.url; + } + }, + onError(error) { + toast.error(error.message); + }, + }), + ); + useWS(`/live/organization/${organization.id}`, () => { queryClient.invalidateQueries(trpc.organization.pathFilter()); }); @@ -54,378 +64,228 @@ export default function Billing({ organization }: Props) { .filter((product) => product.prices.some((p) => p.amountType !== 'free')); }, [productsQuery.data, recurringInterval]); - useEffect(() => { - if (organization.subscriptionInterval) { - setRecurringInterval( - organization.subscriptionInterval as 'year' | 'month', + const currentProduct = currentProductQuery.data ?? null; + const currentPrice = currentProduct?.prices.flatMap((p) => + p.type === 'recurring' && p.amountType === 'fixed' ? [p] : [], + )[0]; + + const renderStatus = () => { + if (organization.isActive && organization.subscriptionCurrentPeriodEnd) { + return ( +

+ Your subscription will be renewed on{' '} + {formatDate(organization.subscriptionCurrentPeriodEnd)} +

); } - }, [organization.subscriptionInterval]); + + if (organization.isCanceled && organization.subscriptionCanceledAt) { + return ( +

+ Your subscription was canceled on{' '} + {formatDate(organization.subscriptionCanceledAt)} +

+ ); + } + if ( + organization.isWillBeCanceled && + organization.subscriptionCurrentPeriodEnd + ) { + return ( +

+ Your subscription will be canceled on{' '} + {formatDate(organization.subscriptionCurrentPeriodEnd)} +

+ ); + } + + if ( + organization.subscriptionStatus === 'expired' && + organization.subscriptionCurrentPeriodEnd + ) { + return ( +

+ Your subscription expired on{' '} + {formatDate(organization.subscriptionCurrentPeriodEnd)} +

+ ); + } + if ( + organization.subscriptionStatus === 'trialing' && + organization.subscriptionEndsAt + ) { + return ( +

+ Your trial will end on {formatDate(organization.subscriptionEndsAt)} +

+ ); + } + + return null; + }; useEffect(() => { - if (customerSessionToken) { - op.track('subscription_created'); + if (success) { + pushModal('BillingSuccess'); } - }, [customerSessionToken]); + }, [success]); - const [selectedProductIndex, setSelectedProductIndex] = useState(0); - - // Check if organization has a custom product - const hasCustomProduct = useMemo(() => { - return products.some((product) => product.metadata?.custom === true); - }, [products]); - - // Preferred default selection when there is no active subscription - const defaultSelectedIndex = useMemo(() => { - const defaultIndex = products.findIndex( - (product) => product.metadata?.eventsLimit === 100_000, - ); - return defaultIndex >= 0 ? defaultIndex : 0; - }, [products]); - - // Find current subscription index (-1 when no subscription) - const currentSubscriptionIndex = useMemo(() => { - if (!organization.subscriptionProductId) { - return -1; + // Clear query state when modal is closed + useOnPushModal('BillingSuccess', (open) => { + if (!open) { + setSuccess(null); } - return products.findIndex( - (product) => product.id === organization.subscriptionProductId, - ); - }, [products, organization.subscriptionProductId]); + }); - // Check if selected index is the "custom" option (beyond available products) - const isCustomOption = selectedProductIndex >= products.length; - - // Find the highest event limit to make the custom option dynamic - const highestEventLimit = useMemo(() => { - const limits = products - .map((product) => product.metadata?.eventsLimit) - .filter((limit): limit is number => typeof limit === 'number'); - return Math.max(...limits, 0); - }, [products]); - - // Format the custom option label dynamically - const customOptionLabel = useMemo(() => { - if (highestEventLimit >= 1_000_000) { - return `+${(highestEventLimit / 1_000_000).toFixed(0)}M`; - } - if (highestEventLimit >= 1_000) { - return `+${(highestEventLimit / 1_000).toFixed(0)}K`; - } - return `+${highestEventLimit}`; - }, [highestEventLimit]); - - // Set initial slider position to current subscription or default plan when none - useEffect(() => { - if (currentSubscriptionIndex >= 0) { - setSelectedProductIndex(currentSubscriptionIndex); - } else { - setSelectedProductIndex(defaultSelectedIndex); - } - }, [currentSubscriptionIndex, defaultSelectedIndex]); - - const selectedProduct = products[selectedProductIndex]; - const isUpgrade = selectedProductIndex > currentSubscriptionIndex; - const isDowngrade = selectedProductIndex < currentSubscriptionIndex; - const isCurrentPlan = selectedProductIndex === currentSubscriptionIndex; - - function renderBillingSlider() { - if (productsQuery.isLoading) { - return ( -
- -
- ); - } - if (productsQuery.isError) { - return ( -
- Issues loading all tiers -
- ); - } - - if (hasCustomProduct) { - return ( -
-
- Not applicable since custom product -
-
- ); - } - - return ( -
-
-
- Select your plan - - {selectedProduct?.name || 'No plan selected'} - -
- - setSelectedProductIndex(value)} - min={0} - max={products.length} // +1 for the custom option - step={1} - className="w-full" - disabled={hasCustomProduct} - /> - -
- {products.map((product, index) => { - const eventsLimit = product.metadata?.eventsLimit; - return ( -
-
- {eventsLimit && typeof eventsLimit === 'number' - ? `${(eventsLimit / 1000).toFixed(0)}K` - : 'Free'} -
-
events
+ return ( +
+
+ {currentProduct && currentPrice ? ( + + +
{currentProduct.name}
+
+ + {number.currency(currentPrice.priceAmount / 100)} + + + {' / '} + {recurringInterval === 'year' ? 'year' : 'month'} + +
+
+ + {renderStatus()} +
+
+ {number.format(organization.subscriptionPeriodEventsCount)} /{' '} + {number.format(Number(currentProduct.metadata.eventsLimit))}
- ); - })} - {/* Add the custom option label */} -
-
{customOptionLabel}
-
events
-
-
-
- - {(selectedProduct || isCustomOption) && ( -
- {isCustomOption ? ( - // Custom option content - <> -
-
-

Custom Plan

-

- {customOptionLabel} events per {recurringInterval} -

-
-
- - Custom Pricing - -
-
-
-

- Need higher limits? -

-

- Reach out to{' '} - +

+ +
- - ) : ( - // Regular product content - <> -
-
-

{selectedProduct.name}

-

- {selectedProduct.metadata?.eventsLimit - ? `${selectedProduct.metadata.eventsLimit.toLocaleString()} events per ${recurringInterval}` - : 'Free tier'} -

-
-
- {selectedProduct.prices[0]?.amountType === 'free' ? ( - Free - ) : ( - - {new Intl.NumberFormat('en-US', { - style: 'currency', - currency: - selectedProduct.prices[0]?.priceCurrency || 'USD', - minimumFractionDigits: 0, - maximumFractionDigits: 1, - }).format( - (selectedProduct.prices[0] && - 'priceAmount' in selectedProduct.prices[0] - ? selectedProduct.prices[0].priceAmount - : 0) / 100, - )} - - {' / '} - {recurringInterval === 'year' ? 'year' : 'month'} - - - )} -
+
+ + + ) : ( + + +
+ {organization.isTrial + ? 'Get started' + : 'No active subscription'} +
+
+ + {organization.isTrial ? '30 days free trial' : ''} + +
+
+ + {organization.isTrial && organization.subscriptionEndsAt ? ( +

+ Your trial will end on{' '} + {formatDate(organization.subscriptionEndsAt)} ( + {differenceInDays( + organization.subscriptionEndsAt, + new Date(), + ) + 1}{' '} + days left) +

+ ) : ( +

+ Your trial has expired. Please upgrade your account to + continue using Openpanel. +

+ )} +
+
+ {number.format(organization.subscriptionPeriodEventsCount)} /{' '} + {number.format( + Number(organization.subscriptionPeriodEventsLimit), + )}
- - {!isCurrentPlan && selectedProduct.prices[0] && ( -
- -
- )} - - {isCurrentPlan && ( -
- -
- )} - - )} -
+ +
+ +
+
+ + )} + +
- ); - } - return ( - <> - - - Billing -
- - {recurringInterval === 'year' - ? 'Yearly (2 months free)' - : 'Monthly'} - - - setRecurringInterval(checked ? 'year' : 'month') - } - /> -
-
- -
{renderBillingSlider()}
-
-
- { - setCustomerSessionToken(null); - if (!open) { - queryClient.invalidateQueries(trpc.organization.pathFilter()); - } - }} - > - - Subscription created - - We have registered your subscription. It'll be activated within a - couple of seconds. - - - - - - - - - - ); -} - -function CheckoutButton({ - price, - organization, - disabled, - buttonText, -}: { - price: IPolarPrice; - organization: IServiceOrganization; - disabled?: string | null; - buttonText?: string; -}) { - const trpc = useTRPC(); - const isCurrentPrice = organization.subscriptionPriceId === price.id; - const checkout = useMutation( - trpc.subscription.checkout.mutationOptions({ - onSuccess(data) { - if (data?.url) { - window.location.href = data.url; - } else { - toast.success('Subscription updated', { - description: 'It might take a few seconds to update', - }); - } - }, - }), - ); - - const isCanceled = - organization.subscriptionStatus === 'active' && - isCurrentPrice && - organization.subscriptionCanceledAt; - const isActive = - organization.subscriptionStatus === 'active' && isCurrentPrice; - - return ( - - - + +
); } diff --git a/apps/start/src/components/organization/current-subscription.tsx b/apps/start/src/components/organization/current-subscription.tsx deleted file mode 100644 index 2ae28f78..00000000 --- a/apps/start/src/components/organization/current-subscription.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { Button } from '@/components/ui/button'; -import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; -import { useAppParams } from '@/hooks/use-app-params'; -import { useNumber } from '@/hooks/use-numer-formatter'; -import useWS from '@/hooks/use-ws'; -import { useTRPC } from '@/integrations/trpc/react'; -import { showConfirm } from '@/modals'; -import { cn } from '@/utils/cn'; -import type { IServiceOrganization } from '@openpanel/db'; -import { FREE_PRODUCT_IDS } from '@openpanel/payments'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { format } from 'date-fns'; -import { Loader2Icon } from 'lucide-react'; -import { toast } from 'sonner'; - -type Props = { - organization: IServiceOrganization; -}; - -export default function CurrentSubscription({ organization }: Props) { - const { projectId } = useAppParams(); - const queryClient = useQueryClient(); - const number = useNumber(); - const trpc = useTRPC(); - const productQuery = useQuery( - trpc.subscription.getCurrent.queryOptions({ - organizationId: organization.id, - }), - ); - const cancelSubscription = useMutation( - trpc.subscription.cancelSubscription.mutationOptions({ - onSuccess() { - toast.success('Subscription cancelled', { - description: 'It might take a few seconds to update', - }); - }, - onError(error) { - toast.error(error.message); - }, - }), - ); - const portalMutation = useMutation( - trpc.subscription.portal.mutationOptions({ - onSuccess(data) { - if (data?.url) { - window.location.href = data.url; - } - }, - }), - ); - const checkout = useMutation( - trpc.subscription.checkout.mutationOptions({ - onSuccess(data) { - if (data?.url) { - window.location.href = data.url; - } else { - toast.success('Subscription updated', { - description: 'It might take a few seconds to update', - }); - } - }, - }), - ); - - useWS(`/live/organization/${organization.id}`, () => { - queryClient.invalidateQueries( - trpc.subscription.getCurrent.queryOptions({ - organizationId: organization.id, - }), - ); - }); - - function render() { - if (productQuery.isLoading) { - return ( -
- -
- ); - } - if (productQuery.isError) { - return ( -
- Issues loading all tiers -
- ); - } - - if (!productQuery.data) { - return ( -
- No subscription found -
- ); - } - - const product = productQuery.data; - const price = product.prices[0]!; - return ( - <> -
- {price.amountType === 'free' && ( - - Free plan is removed - - We've removed the free plan. You can upgrade to a paid plan to - continue using OpenPanel. - - - )} -
-
Name
-
{product.name}
-
- {price.amountType === 'fixed' ? ( - <> -
-
Price
-
- {number.currency(price.priceAmount / 100)} -
-
- - ) : ( - <> -
-
Price
-
FREE
-
- - )} -
-
Billing Cycle
-
- {price.recurringInterval === 'month' ? 'Monthly' : 'Yearly'} -
-
- {typeof product.metadata.eventsLimit === 'number' && ( -
-
Events per mount
-
- {number.format(product.metadata.eventsLimit)} -
-
- )} -
- {organization.subscriptionProductId && - !FREE_PRODUCT_IDS.includes(organization.subscriptionProductId) && ( -
- {organization.isWillBeCanceled || organization.isCanceled ? ( - - ) : ( - - )} -
- )} - - ); - } - - return ( -
- - - Current Subscription -
-
-
-
-
-
- - - {organization.isTrial && organization.subscriptionEndsAt && ( - - Free trial - - Your organization is on a free trial. It ends on{' '} - {format(organization.subscriptionEndsAt, 'PPP')} - - - )} - {organization.isExpired && organization.subscriptionEndsAt && ( - - Subscription expired - - Your subscription has expired. You can reactivate it by choosing - a new plan below. - - - It expired on {format(organization.subscriptionEndsAt, 'PPP')} - - - )} - {organization.isWillBeCanceled && ( - - Subscription canceled - - You have canceled your subscription. You can reactivate it by - choosing a new plan below. - - - It'll expire on{' '} - {format(organization.subscriptionEndsAt!, 'PPP')} - - - )} - {organization.isCanceled && ( - - Subscription canceled - - Your subscription was canceled on{' '} - {format(organization.subscriptionCanceledAt!, 'PPP')} - - - )} - {render()} - - - {organization.hasSubscription && ( - - )} -
- ); -} diff --git a/apps/start/src/components/organization/supporter-prompt.tsx b/apps/start/src/components/organization/supporter-prompt.tsx new file mode 100644 index 00000000..4d942b49 --- /dev/null +++ b/apps/start/src/components/organization/supporter-prompt.tsx @@ -0,0 +1,147 @@ +import { Button, LinkButton } from '@/components/ui/button'; +import { useAppContext } from '@/hooks/use-app-context'; +import { useCookieStore } from '@/hooks/use-cookie-store'; +import { AnimatePresence, motion } from 'framer-motion'; +import { + AwardIcon, + HeartIcon, + type LucideIcon, + MessageCircleIcon, + RocketIcon, + SparklesIcon, + XIcon, + ZapIcon, +} from 'lucide-react'; + +const PERKS = [ + { + icon: RocketIcon, + text: 'Latest Docker Images', + description: 'Bleeding-edge builds on every commit', + }, + { + icon: MessageCircleIcon, + text: 'Prioritized Support', + description: 'Get help faster with priority Discord support', + }, + { + icon: SparklesIcon, + text: 'Feature Requests', + description: 'Your ideas get prioritized in our roadmap', + }, + { + icon: AwardIcon, + text: 'Exclusive Discord Role', + description: 'Special badge and recognition in our community', + }, + { + icon: ZapIcon, + text: 'Early Access', + description: 'Try new features before public release', + }, + { + icon: HeartIcon, + text: 'Direct Impact', + description: 'Your support directly funds development', + }, +] as const; + +function PerkPoint({ + icon: Icon, + text, + description, +}: { + icon: LucideIcon; + text: string; + description: string; +}) { + return ( +
+ +
+

{text}

+

{description}

+
+
+ ); +} + +export default function SupporterPrompt() { + const { isSelfHosted } = useAppContext(); + const [supporterPromptClosed, setSupporterPromptClosed] = useCookieStore( + 'supporter-prompt-closed', + false, + ); + + if (!isSelfHosted) { + return null; + } + + return ( + + {!supporterPromptClosed && ( + +
+
+
+

Support OpenPanel

+ +
+

+ Help us build the future of open analytics +

+
+ +
+ {PERKS.map((perk) => ( + + ))} +
+ +
+ + Become a Supporter + +

+ Starting at $20/month • Cancel anytime •{' '} + + Learn more + +

+
+
+
+ )} +
+ ); +} diff --git a/apps/start/src/components/sidebar.tsx b/apps/start/src/components/sidebar.tsx index e816acd4..5562247d 100644 --- a/apps/start/src/components/sidebar.tsx +++ b/apps/start/src/components/sidebar.tsx @@ -135,9 +135,12 @@ export function SidebarContainer({
{isSelfHosted && ( -
- Self-hosted instance -
+ + Self-hosted instance, support us! + )}
diff --git a/apps/start/src/hooks/use-cookie-store.tsx b/apps/start/src/hooks/use-cookie-store.tsx index ec775a1b..e3717b0e 100644 --- a/apps/start/src/hooks/use-cookie-store.tsx +++ b/apps/start/src/hooks/use-cookie-store.tsx @@ -5,12 +5,20 @@ import { pick } from 'ramda'; import { useEffect, useMemo, useRef, useState } from 'react'; import { z } from 'zod'; -const VALID_COOKIES = ['ui-theme', 'chartType', 'range'] as const; +const VALID_COOKIES = [ + 'ui-theme', + 'chartType', + 'range', + 'supporter-prompt-closed', +] as const; const COOKIE_EVENT_NAME = '__cookie-change'; const setCookieFn = createServerFn({ method: 'POST' }) .inputValidator(z.object({ key: z.enum(VALID_COOKIES), value: z.string() })) .handler(({ data: { key, value } }) => { + if (!VALID_COOKIES.includes(key)) { + return; + } setCookie(key, value); }); diff --git a/apps/start/src/modals/billing-success.tsx b/apps/start/src/modals/billing-success.tsx new file mode 100644 index 00000000..748717f6 --- /dev/null +++ b/apps/start/src/modals/billing-success.tsx @@ -0,0 +1,47 @@ +import { Check, X } from 'lucide-react'; +import { popModal } from '.'; +import { ModalContent } from './Modal/Container'; + +export default function BillingSuccess() { + return ( + + + +
+ {/* Success Icon with animated rings */} +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + {/* Success Message */} +

+ Subscription updated successfully +

+

+ Thank you for your purchase! You have now full access to OpenPanel. If + you have any questions or feedback, please don't hesitate to contact + us. +

+
+ + ); +} diff --git a/apps/start/src/modals/index.tsx b/apps/start/src/modals/index.tsx index 709951d8..d616746b 100644 --- a/apps/start/src/modals/index.tsx +++ b/apps/start/src/modals/index.tsx @@ -2,6 +2,7 @@ import { createPushModal } from 'pushmodal'; import OverviewTopGenericModal from '@/components/overview/overview-top-generic-modal'; import OverviewTopPagesModal from '@/components/overview/overview-top-pages-modal'; +import { op } from '@/utils/op'; import Instructions from './Instructions'; import AddClient from './add-client'; import AddDashboard from './add-dashboard'; @@ -10,6 +11,7 @@ import AddIntegration from './add-integration'; import AddNotificationRule from './add-notification-rule'; import AddProject from './add-project'; import AddReference from './add-reference'; +import BillingSuccess from './billing-success'; import Confirm from './confirm'; import type { ConfirmProps } from './confirm'; import CreateInvite from './create-invite'; @@ -27,6 +29,7 @@ import OverviewChartDetails from './overview-chart-details'; import OverviewFilters from './overview-filters'; import RequestPasswordReset from './request-reset-password'; import SaveReport from './save-report'; +import SelectBillingPlan from './select-billing-plan'; import ShareOverviewModal from './share-overview-modal'; const modals = { @@ -57,6 +60,8 @@ const modals = { AddNotificationRule: AddNotificationRule, OverviewFilters: OverviewFilters, CreateInvite: CreateInvite, + SelectBillingPlan: SelectBillingPlan, + BillingSuccess: BillingSuccess, }; export const { @@ -66,8 +71,13 @@ export const { popAllModals, ModalProvider, useOnPushModal, + onPushModal, } = createPushModal({ modals, }); +onPushModal('*', (open, props, name) => { + op.screenView(`modal:${name}`, props as Record); +}); + export const showConfirm = (props: ConfirmProps) => pushModal('Confirm', props); diff --git a/apps/start/src/modals/save-report.tsx b/apps/start/src/modals/save-report.tsx index d99aec2d..eb8eb3fb 100644 --- a/apps/start/src/modals/save-report.tsx +++ b/apps/start/src/modals/save-report.tsx @@ -40,7 +40,7 @@ export default function SaveReport({ const queryClient = useQueryClient(); const { organizationId, projectId } = useAppParams(); const searchParams = useSearch({ - from: '/_app/$organizationId/$projectId_/reports', + from: '/_app/$organizationId/$projectId/reports', shouldThrow: false, }); const dashboardId = searchParams?.dashboardId; diff --git a/apps/start/src/modals/select-billing-plan.tsx b/apps/start/src/modals/select-billing-plan.tsx new file mode 100644 index 00000000..3f0c6460 --- /dev/null +++ b/apps/start/src/modals/select-billing-plan.tsx @@ -0,0 +1,309 @@ +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { useNumber } from '@/hooks/use-numer-formatter'; +import { useTRPC } from '@/integrations/trpc/react'; +import { cn } from '@/utils/cn'; +import { op } from '@/utils/op'; +import type { IServiceOrganization } from '@openpanel/db'; +import type { IPolarProduct } from '@openpanel/payments'; +import { current } from '@reduxjs/toolkit'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { CheckIcon, ShuffleIcon } from 'lucide-react'; +import { Fragment, useEffect, useState } from 'react'; +import { toast } from 'sonner'; +import { popModal } from '.'; +import { ModalContent, ModalHeader } from './Modal/Container'; + +interface Props { + organization: IServiceOrganization; + currentProduct: IPolarProduct | null; +} + +const getPrice = (product: IPolarProduct) => { + return product.prices[0] && 'priceAmount' in product.prices[0] + ? product.prices[0].priceAmount / 100 + : 0; +}; + +export default function SelectBillingPlan({ + organization, + currentProduct, +}: Props) { + const number = useNumber(); + const trpc = useTRPC(); + const queryClient = useQueryClient(); + const productsQuery = useQuery( + trpc.subscription.products.queryOptions({ + organizationId: organization.id, + }), + ); + const [recurringInterval, setRecurringInterval] = useState<'year' | 'month'>( + (organization.subscriptionInterval as 'year' | 'month') || 'month', + ); + const [selectedProductId, setSelectedProductId] = useState( + organization.subscriptionProductId || null, + ); + const products = productsQuery.data || []; + const selectedProduct = products.find( + (product) => product.id === selectedProductId, + ); + + const checkoutMutation = useMutation( + trpc.subscription.checkout.mutationOptions({ + onSuccess(data) { + if (data?.url) { + window.location.href = data.url; + } else { + queryClient.invalidateQueries( + trpc.organization.get.queryOptions({ + organizationId: organization.id, + }), + ); + + queryClient.invalidateQueries( + trpc.subscription.getCurrent.queryOptions({ + organizationId: organization.id, + }), + ); + toast.success('Subscription updated', { + description: 'It might take a few seconds to update', + }); + popModal(); + } + }, + onError(error) { + toast.error(error.message); + }, + }), + ); + + const cancelSubscription = useMutation( + trpc.subscription.cancelSubscription.mutationOptions({ + onSuccess() { + queryClient.invalidateQueries( + trpc.organization.get.queryOptions({ + organizationId: organization.id, + }), + ); + queryClient.invalidateQueries( + trpc.subscription.getCurrent.queryOptions({ + organizationId: organization.id, + }), + ); + toast.success('Subscription canceled', { + description: 'It might take a few seconds to update', + }); + popModal(); + }, + onError(error) { + toast.error(error.message); + }, + }), + ); + + const handleCheckout = () => { + if (!selectedProduct) return; + op.track('subscription_checkout_started', { + organizationId: organization.id, + limit: selectedProduct.metadata.eventsLimit, + price: getPrice(selectedProduct), + }); + checkoutMutation.mutate({ + organizationId: organization.id, + productPriceId: selectedProduct.prices[0].id, + productId: selectedProduct.id, + }); + }; + + const handleCancelSubscription = () => { + if (!selectedProduct) return; + op.track('subscription_canceled', { + organizationId: organization.id, + limit: selectedProduct.metadata.eventsLimit, + price: getPrice(selectedProduct), + }); + cancelSubscription.mutate({ + organizationId: organization.id, + }); + }; + + const renderAction = () => { + if (!selectedProduct) { + return null; + } + + const isCurrentProduct = selectedProduct.id === currentProduct?.id; + + if (isCurrentProduct && organization.isActive) { + return ( + + ); + } + + const payLabel = (() => { + if ( + organization.isCanceled || + organization.isWillBeCanceled || + organization.isExpired + ) { + return isCurrentProduct + ? 'Reactivate subscription' + : 'Change subscription'; + } + + if (currentProduct) { + return 'Change subscription'; + } + + return 'Pay with Polar'; + })(); + + return ( + + ); + }; + + return ( + + +
+ {currentProduct && ( +
+ Your current usage is{' '} + {number.format(organization.subscriptionPeriodEventsCount)} out of{' '} + {number.format(Number(currentProduct?.metadata.eventsLimit))}{' '} + events.{' '} + + You cannot downgrade if your usage exceeds the limit of the new + plan. + +
+ )} +
+
+ {recurringInterval === 'year' ? ( + 'Switch to monthly' + ) : ( + <> + Switch to yearly and get{' '} + + 2 months for free + + + )} +
+ + +
+
+
+ {products + .filter((product) => + product.prices.some((p) => p.amountType !== 'free'), + ) + .filter((product) => product.metadata.eventsLimit) + .filter((product) => product.recurringInterval === recurringInterval) + .map((product) => { + const price = getPrice(product); + + const limit = product.metadata.eventsLimit + ? Number(product.metadata.eventsLimit) + : 0; + + const isProductDisabled = + (limit > 0 && + organization.subscriptionPeriodEventsCount >= limit) || + !!product.disabled; + + return ( + + ); + })} +
+ {renderAction()} +
+ ); +} diff --git a/apps/start/src/routeTree.gen.ts b/apps/start/src/routeTree.gen.ts index e221cf0e..735a873a 100644 --- a/apps/start/src/routeTree.gen.ts +++ b/apps/start/src/routeTree.gen.ts @@ -28,49 +28,50 @@ import { Route as StepsOnboardingProjectRouteImport } from './routes/_steps.onbo import { Route as AppOrganizationIdSettingsRouteImport } from './routes/_app.$organizationId.settings' import { Route as AppOrganizationIdBillingRouteImport } from './routes/_app.$organizationId.billing' import { Route as AppOrganizationIdProjectIdRouteImport } from './routes/_app.$organizationId.$projectId' +import { Route as AppOrganizationIdProjectIdIndexRouteImport } from './routes/_app.$organizationId.$projectId.index' import { Route as StepsOnboardingProjectIdVerifyRouteImport } from './routes/_steps.onboarding.$projectId.verify' import { Route as StepsOnboardingProjectIdConnectRouteImport } from './routes/_steps.onboarding.$projectId.connect' import { Route as AppOrganizationIdMembersTabsRouteImport } from './routes/_app.$organizationId.members._tabs' import { Route as AppOrganizationIdIntegrationsTabsRouteImport } from './routes/_app.$organizationId.integrations._tabs' -import { Route as AppOrganizationIdProjectIdSessionsRouteImport } from './routes/_app.$organizationId.$projectId_.sessions' -import { Route as AppOrganizationIdProjectIdReportsRouteImport } from './routes/_app.$organizationId.$projectId_.reports' -import { Route as AppOrganizationIdProjectIdReferencesRouteImport } from './routes/_app.$organizationId.$projectId_.references' -import { Route as AppOrganizationIdProjectIdRealtimeRouteImport } from './routes/_app.$organizationId.$projectId_.realtime' -import { Route as AppOrganizationIdProjectIdPagesRouteImport } from './routes/_app.$organizationId.$projectId_.pages' -import { Route as AppOrganizationIdProjectIdDashboardsRouteImport } from './routes/_app.$organizationId.$projectId_.dashboards' -import { Route as AppOrganizationIdProjectIdChatRouteImport } from './routes/_app.$organizationId.$projectId_.chat' +import { Route as AppOrganizationIdProjectIdSessionsRouteImport } from './routes/_app.$organizationId.$projectId.sessions' +import { Route as AppOrganizationIdProjectIdReportsRouteImport } from './routes/_app.$organizationId.$projectId.reports' +import { Route as AppOrganizationIdProjectIdReferencesRouteImport } from './routes/_app.$organizationId.$projectId.references' +import { Route as AppOrganizationIdProjectIdRealtimeRouteImport } from './routes/_app.$organizationId.$projectId.realtime' +import { Route as AppOrganizationIdProjectIdPagesRouteImport } from './routes/_app.$organizationId.$projectId.pages' +import { Route as AppOrganizationIdProjectIdDashboardsRouteImport } from './routes/_app.$organizationId.$projectId.dashboards' +import { Route as AppOrganizationIdProjectIdChatRouteImport } from './routes/_app.$organizationId.$projectId.chat' import { Route as AppOrganizationIdMembersTabsIndexRouteImport } from './routes/_app.$organizationId.members._tabs.index' import { Route as AppOrganizationIdIntegrationsTabsIndexRouteImport } from './routes/_app.$organizationId.integrations._tabs.index' import { Route as AppOrganizationIdMembersTabsMembersRouteImport } from './routes/_app.$organizationId.members._tabs.members' import { Route as AppOrganizationIdMembersTabsInvitationsRouteImport } from './routes/_app.$organizationId.members._tabs.invitations' import { Route as AppOrganizationIdIntegrationsTabsInstalledRouteImport } from './routes/_app.$organizationId.integrations._tabs.installed' import { Route as AppOrganizationIdIntegrationsTabsAvailableRouteImport } from './routes/_app.$organizationId.integrations._tabs.available' -import { Route as AppOrganizationIdProjectIdSettingsTabsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs' -import { Route as AppOrganizationIdProjectIdSessionsSessionIdRouteImport } from './routes/_app.$organizationId.$projectId_.sessions_.$sessionId' -import { Route as AppOrganizationIdProjectIdReportsReportIdRouteImport } from './routes/_app.$organizationId.$projectId_.reports_.$reportId' -import { Route as AppOrganizationIdProjectIdProfilesTabsRouteImport } from './routes/_app.$organizationId.$projectId_.profiles._tabs' -import { Route as AppOrganizationIdProjectIdNotificationsTabsRouteImport } from './routes/_app.$organizationId.$projectId_.notifications._tabs' -import { Route as AppOrganizationIdProjectIdEventsTabsRouteImport } from './routes/_app.$organizationId.$projectId_.events._tabs' -import { Route as AppOrganizationIdProjectIdDashboardsDashboardIdRouteImport } from './routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId' -import { Route as AppOrganizationIdProjectIdSettingsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.index' -import { Route as AppOrganizationIdProjectIdProfilesTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId_.profiles._tabs.index' -import { Route as AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId_.notifications._tabs.index' -import { Route as AppOrganizationIdProjectIdEventsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId_.events._tabs.index' -import { Route as AppOrganizationIdProjectIdSettingsTabsImportsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.imports' -import { Route as AppOrganizationIdProjectIdSettingsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.events' -import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.details' -import { Route as AppOrganizationIdProjectIdSettingsTabsClientsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.clients' -import { Route as AppOrganizationIdProjectIdProfilesTabsPowerUsersRouteImport } from './routes/_app.$organizationId.$projectId_.profiles._tabs.power-users' -import { Route as AppOrganizationIdProjectIdProfilesTabsIdentifiedRouteImport } from './routes/_app.$organizationId.$projectId_.profiles._tabs.identified' -import { Route as AppOrganizationIdProjectIdProfilesTabsAnonymousRouteImport } from './routes/_app.$organizationId.$projectId_.profiles._tabs.anonymous' -import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsRouteImport } from './routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs' -import { Route as AppOrganizationIdProjectIdNotificationsTabsRulesRouteImport } from './routes/_app.$organizationId.$projectId_.notifications._tabs.rules' -import { Route as AppOrganizationIdProjectIdNotificationsTabsNotificationsRouteImport } from './routes/_app.$organizationId.$projectId_.notifications._tabs.notifications' -import { Route as AppOrganizationIdProjectIdEventsTabsStatsRouteImport } from './routes/_app.$organizationId.$projectId_.events._tabs.stats' -import { Route as AppOrganizationIdProjectIdEventsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId_.events._tabs.events' -import { Route as AppOrganizationIdProjectIdEventsTabsConversionsRouteImport } from './routes/_app.$organizationId.$projectId_.events._tabs.conversions' -import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.index' -import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.events' +import { Route as AppOrganizationIdProjectIdSettingsTabsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs' +import { Route as AppOrganizationIdProjectIdSessionsSessionIdRouteImport } from './routes/_app.$organizationId.$projectId.sessions_.$sessionId' +import { Route as AppOrganizationIdProjectIdReportsReportIdRouteImport } from './routes/_app.$organizationId.$projectId.reports_.$reportId' +import { Route as AppOrganizationIdProjectIdProfilesTabsRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs' +import { Route as AppOrganizationIdProjectIdNotificationsTabsRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs' +import { Route as AppOrganizationIdProjectIdEventsTabsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs' +import { Route as AppOrganizationIdProjectIdDashboardsDashboardIdRouteImport } from './routes/_app.$organizationId.$projectId.dashboards_.$dashboardId' +import { Route as AppOrganizationIdProjectIdSettingsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.index' +import { Route as AppOrganizationIdProjectIdProfilesTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.index' +import { Route as AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.index' +import { Route as AppOrganizationIdProjectIdEventsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.index' +import { Route as AppOrganizationIdProjectIdSettingsTabsImportsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.imports' +import { Route as AppOrganizationIdProjectIdSettingsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.events' +import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.details' +import { Route as AppOrganizationIdProjectIdSettingsTabsClientsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.clients' +import { Route as AppOrganizationIdProjectIdProfilesTabsPowerUsersRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.power-users' +import { Route as AppOrganizationIdProjectIdProfilesTabsIdentifiedRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.identified' +import { Route as AppOrganizationIdProjectIdProfilesTabsAnonymousRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.anonymous' +import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs' +import { Route as AppOrganizationIdProjectIdNotificationsTabsRulesRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.rules' +import { Route as AppOrganizationIdProjectIdNotificationsTabsNotificationsRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.notifications' +import { Route as AppOrganizationIdProjectIdEventsTabsStatsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.stats' +import { Route as AppOrganizationIdProjectIdEventsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.events' +import { Route as AppOrganizationIdProjectIdEventsTabsConversionsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.conversions' +import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.index' +import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.events' const AppOrganizationIdMembersRouteImport = createFileRoute( '/_app/$organizationId/members', @@ -79,19 +80,19 @@ const AppOrganizationIdIntegrationsRouteImport = createFileRoute( '/_app/$organizationId/integrations', )() const AppOrganizationIdProjectIdSettingsRouteImport = createFileRoute( - '/_app/$organizationId/$projectId_/settings', + '/_app/$organizationId/$projectId/settings', )() const AppOrganizationIdProjectIdProfilesRouteImport = createFileRoute( - '/_app/$organizationId/$projectId_/profiles', + '/_app/$organizationId/$projectId/profiles', )() const AppOrganizationIdProjectIdNotificationsRouteImport = createFileRoute( - '/_app/$organizationId/$projectId_/notifications', + '/_app/$organizationId/$projectId/notifications', )() const AppOrganizationIdProjectIdEventsRouteImport = createFileRoute( - '/_app/$organizationId/$projectId_/events', + '/_app/$organizationId/$projectId/events', )() const AppOrganizationIdProjectIdProfilesProfileIdRouteImport = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/$profileId', + '/_app/$organizationId/$projectId/profiles/$profileId', )() const StepsRoute = StepsRouteImport.update({ @@ -192,27 +193,33 @@ const AppOrganizationIdProjectIdRoute = } as any) const AppOrganizationIdProjectIdSettingsRoute = AppOrganizationIdProjectIdSettingsRouteImport.update({ - id: '/$projectId_/settings', - path: '/$projectId/settings', - getParentRoute: () => AppOrganizationIdRoute, + id: '/settings', + path: '/settings', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdProfilesRoute = AppOrganizationIdProjectIdProfilesRouteImport.update({ - id: '/$projectId_/profiles', - path: '/$projectId/profiles', - getParentRoute: () => AppOrganizationIdRoute, + id: '/profiles', + path: '/profiles', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdNotificationsRoute = AppOrganizationIdProjectIdNotificationsRouteImport.update({ - id: '/$projectId_/notifications', - path: '/$projectId/notifications', - getParentRoute: () => AppOrganizationIdRoute, + id: '/notifications', + path: '/notifications', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdEventsRoute = AppOrganizationIdProjectIdEventsRouteImport.update({ - id: '/$projectId_/events', - path: '/$projectId/events', - getParentRoute: () => AppOrganizationIdRoute, + id: '/events', + path: '/events', + getParentRoute: () => AppOrganizationIdProjectIdRoute, + } as any) +const AppOrganizationIdProjectIdIndexRoute = + AppOrganizationIdProjectIdIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const StepsOnboardingProjectIdVerifyRoute = StepsOnboardingProjectIdVerifyRouteImport.update({ @@ -238,45 +245,45 @@ const AppOrganizationIdIntegrationsTabsRoute = } as any) const AppOrganizationIdProjectIdSessionsRoute = AppOrganizationIdProjectIdSessionsRouteImport.update({ - id: '/$projectId_/sessions', - path: '/$projectId/sessions', - getParentRoute: () => AppOrganizationIdRoute, + id: '/sessions', + path: '/sessions', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdReportsRoute = AppOrganizationIdProjectIdReportsRouteImport.update({ - id: '/$projectId_/reports', - path: '/$projectId/reports', - getParentRoute: () => AppOrganizationIdRoute, + id: '/reports', + path: '/reports', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdReferencesRoute = AppOrganizationIdProjectIdReferencesRouteImport.update({ - id: '/$projectId_/references', - path: '/$projectId/references', - getParentRoute: () => AppOrganizationIdRoute, + id: '/references', + path: '/references', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdRealtimeRoute = AppOrganizationIdProjectIdRealtimeRouteImport.update({ - id: '/$projectId_/realtime', - path: '/$projectId/realtime', - getParentRoute: () => AppOrganizationIdRoute, + id: '/realtime', + path: '/realtime', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdPagesRoute = AppOrganizationIdProjectIdPagesRouteImport.update({ - id: '/$projectId_/pages', - path: '/$projectId/pages', - getParentRoute: () => AppOrganizationIdRoute, + id: '/pages', + path: '/pages', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdDashboardsRoute = AppOrganizationIdProjectIdDashboardsRouteImport.update({ - id: '/$projectId_/dashboards', - path: '/$projectId/dashboards', - getParentRoute: () => AppOrganizationIdRoute, + id: '/dashboards', + path: '/dashboards', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdChatRoute = AppOrganizationIdProjectIdChatRouteImport.update({ - id: '/$projectId_/chat', - path: '/$projectId/chat', - getParentRoute: () => AppOrganizationIdRoute, + id: '/chat', + path: '/chat', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdProfilesProfileIdRoute = AppOrganizationIdProjectIdProfilesProfileIdRouteImport.update({ @@ -327,15 +334,15 @@ const AppOrganizationIdProjectIdSettingsTabsRoute = } as any) const AppOrganizationIdProjectIdSessionsSessionIdRoute = AppOrganizationIdProjectIdSessionsSessionIdRouteImport.update({ - id: '/$projectId_/sessions_/$sessionId', - path: '/$projectId/sessions/$sessionId', - getParentRoute: () => AppOrganizationIdRoute, + id: '/sessions_/$sessionId', + path: '/sessions/$sessionId', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdReportsReportIdRoute = AppOrganizationIdProjectIdReportsReportIdRouteImport.update({ - id: '/$projectId_/reports_/$reportId', - path: '/$projectId/reports/$reportId', - getParentRoute: () => AppOrganizationIdRoute, + id: '/reports_/$reportId', + path: '/reports/$reportId', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdProfilesTabsRoute = AppOrganizationIdProjectIdProfilesTabsRouteImport.update({ @@ -354,9 +361,9 @@ const AppOrganizationIdProjectIdEventsTabsRoute = } as any) const AppOrganizationIdProjectIdDashboardsDashboardIdRoute = AppOrganizationIdProjectIdDashboardsDashboardIdRouteImport.update({ - id: '/$projectId_/dashboards_/$dashboardId', - path: '/$projectId/dashboards/$dashboardId', - getParentRoute: () => AppOrganizationIdRoute, + id: '/dashboards_/$dashboardId', + path: '/dashboards/$dashboardId', + getParentRoute: () => AppOrganizationIdProjectIdRoute, } as any) const AppOrganizationIdProjectIdSettingsTabsIndexRoute = AppOrganizationIdProjectIdSettingsTabsIndexRouteImport.update({ @@ -480,7 +487,7 @@ export interface FileRoutesByFullPath { '/onboarding': typeof PublicOnboardingRoute '/api/config': typeof ApiConfigRoute '/api/healthcheck': typeof ApiHealthcheckRoute - '/$organizationId/$projectId': typeof AppOrganizationIdProjectIdRoute + '/$organizationId/$projectId': typeof AppOrganizationIdProjectIdRouteWithChildren '/$organizationId/billing': typeof AppOrganizationIdBillingRoute '/$organizationId/settings': typeof AppOrganizationIdSettingsRoute '/onboarding/project': typeof StepsOnboardingProjectRoute @@ -497,6 +504,7 @@ export interface FileRoutesByFullPath { '/$organizationId/members': typeof AppOrganizationIdMembersTabsRouteWithChildren '/onboarding/$projectId/connect': typeof StepsOnboardingProjectIdConnectRoute '/onboarding/$projectId/verify': typeof StepsOnboardingProjectIdVerifyRoute + '/$organizationId/$projectId/': typeof AppOrganizationIdProjectIdIndexRoute '/$organizationId/$projectId/dashboards/$dashboardId': typeof AppOrganizationIdProjectIdDashboardsDashboardIdRoute '/$organizationId/$projectId/events': typeof AppOrganizationIdProjectIdEventsTabsRouteWithChildren '/$organizationId/$projectId/notifications': typeof AppOrganizationIdProjectIdNotificationsTabsRouteWithChildren @@ -537,7 +545,6 @@ export interface FileRoutesByTo { '/onboarding': typeof PublicOnboardingRoute '/api/config': typeof ApiConfigRoute '/api/healthcheck': typeof ApiHealthcheckRoute - '/$organizationId/$projectId': typeof AppOrganizationIdProjectIdRoute '/$organizationId/billing': typeof AppOrganizationIdBillingRoute '/$organizationId/settings': typeof AppOrganizationIdSettingsRoute '/onboarding/project': typeof StepsOnboardingProjectRoute @@ -554,6 +561,7 @@ export interface FileRoutesByTo { '/$organizationId/members': typeof AppOrganizationIdMembersTabsIndexRoute '/onboarding/$projectId/connect': typeof StepsOnboardingProjectIdConnectRoute '/onboarding/$projectId/verify': typeof StepsOnboardingProjectIdVerifyRoute + '/$organizationId/$projectId': typeof AppOrganizationIdProjectIdIndexRoute '/$organizationId/$projectId/dashboards/$dashboardId': typeof AppOrganizationIdProjectIdDashboardsDashboardIdRoute '/$organizationId/$projectId/events': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute '/$organizationId/$projectId/notifications': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute @@ -593,62 +601,63 @@ export interface FileRoutesById { '/_public/onboarding': typeof PublicOnboardingRoute '/api/config': typeof ApiConfigRoute '/api/healthcheck': typeof ApiHealthcheckRoute - '/_app/$organizationId/$projectId': typeof AppOrganizationIdProjectIdRoute + '/_app/$organizationId/$projectId': typeof AppOrganizationIdProjectIdRouteWithChildren '/_app/$organizationId/billing': typeof AppOrganizationIdBillingRoute '/_app/$organizationId/settings': typeof AppOrganizationIdSettingsRoute '/_steps/onboarding/project': typeof StepsOnboardingProjectRoute '/share/overview/$shareId': typeof ShareOverviewShareIdRoute '/_app/$organizationId/': typeof AppOrganizationIdIndexRoute - '/_app/$organizationId/$projectId_/chat': typeof AppOrganizationIdProjectIdChatRoute - '/_app/$organizationId/$projectId_/dashboards': typeof AppOrganizationIdProjectIdDashboardsRoute - '/_app/$organizationId/$projectId_/pages': typeof AppOrganizationIdProjectIdPagesRoute - '/_app/$organizationId/$projectId_/realtime': typeof AppOrganizationIdProjectIdRealtimeRoute - '/_app/$organizationId/$projectId_/references': typeof AppOrganizationIdProjectIdReferencesRoute - '/_app/$organizationId/$projectId_/reports': typeof AppOrganizationIdProjectIdReportsRoute - '/_app/$organizationId/$projectId_/sessions': typeof AppOrganizationIdProjectIdSessionsRoute + '/_app/$organizationId/$projectId/chat': typeof AppOrganizationIdProjectIdChatRoute + '/_app/$organizationId/$projectId/dashboards': typeof AppOrganizationIdProjectIdDashboardsRoute + '/_app/$organizationId/$projectId/pages': typeof AppOrganizationIdProjectIdPagesRoute + '/_app/$organizationId/$projectId/realtime': typeof AppOrganizationIdProjectIdRealtimeRoute + '/_app/$organizationId/$projectId/references': typeof AppOrganizationIdProjectIdReferencesRoute + '/_app/$organizationId/$projectId/reports': typeof AppOrganizationIdProjectIdReportsRoute + '/_app/$organizationId/$projectId/sessions': typeof AppOrganizationIdProjectIdSessionsRoute '/_app/$organizationId/integrations': typeof AppOrganizationIdIntegrationsRouteWithChildren '/_app/$organizationId/integrations/_tabs': typeof AppOrganizationIdIntegrationsTabsRouteWithChildren '/_app/$organizationId/members': typeof AppOrganizationIdMembersRouteWithChildren '/_app/$organizationId/members/_tabs': typeof AppOrganizationIdMembersTabsRouteWithChildren '/_steps/onboarding/$projectId/connect': typeof StepsOnboardingProjectIdConnectRoute '/_steps/onboarding/$projectId/verify': typeof StepsOnboardingProjectIdVerifyRoute - '/_app/$organizationId/$projectId_/dashboards_/$dashboardId': typeof AppOrganizationIdProjectIdDashboardsDashboardIdRoute - '/_app/$organizationId/$projectId_/events': typeof AppOrganizationIdProjectIdEventsRouteWithChildren - '/_app/$organizationId/$projectId_/events/_tabs': typeof AppOrganizationIdProjectIdEventsTabsRouteWithChildren - '/_app/$organizationId/$projectId_/notifications': typeof AppOrganizationIdProjectIdNotificationsRouteWithChildren - '/_app/$organizationId/$projectId_/notifications/_tabs': typeof AppOrganizationIdProjectIdNotificationsTabsRouteWithChildren - '/_app/$organizationId/$projectId_/profiles': typeof AppOrganizationIdProjectIdProfilesRouteWithChildren - '/_app/$organizationId/$projectId_/profiles/_tabs': typeof AppOrganizationIdProjectIdProfilesTabsRouteWithChildren - '/_app/$organizationId/$projectId_/reports_/$reportId': typeof AppOrganizationIdProjectIdReportsReportIdRoute - '/_app/$organizationId/$projectId_/sessions_/$sessionId': typeof AppOrganizationIdProjectIdSessionsSessionIdRoute - '/_app/$organizationId/$projectId_/settings': typeof AppOrganizationIdProjectIdSettingsRouteWithChildren - '/_app/$organizationId/$projectId_/settings/_tabs': typeof AppOrganizationIdProjectIdSettingsTabsRouteWithChildren + '/_app/$organizationId/$projectId/': typeof AppOrganizationIdProjectIdIndexRoute + '/_app/$organizationId/$projectId/dashboards_/$dashboardId': typeof AppOrganizationIdProjectIdDashboardsDashboardIdRoute + '/_app/$organizationId/$projectId/events': typeof AppOrganizationIdProjectIdEventsRouteWithChildren + '/_app/$organizationId/$projectId/events/_tabs': typeof AppOrganizationIdProjectIdEventsTabsRouteWithChildren + '/_app/$organizationId/$projectId/notifications': typeof AppOrganizationIdProjectIdNotificationsRouteWithChildren + '/_app/$organizationId/$projectId/notifications/_tabs': typeof AppOrganizationIdProjectIdNotificationsTabsRouteWithChildren + '/_app/$organizationId/$projectId/profiles': typeof AppOrganizationIdProjectIdProfilesRouteWithChildren + '/_app/$organizationId/$projectId/profiles/_tabs': typeof AppOrganizationIdProjectIdProfilesTabsRouteWithChildren + '/_app/$organizationId/$projectId/reports_/$reportId': typeof AppOrganizationIdProjectIdReportsReportIdRoute + '/_app/$organizationId/$projectId/sessions_/$sessionId': typeof AppOrganizationIdProjectIdSessionsSessionIdRoute + '/_app/$organizationId/$projectId/settings': typeof AppOrganizationIdProjectIdSettingsRouteWithChildren + '/_app/$organizationId/$projectId/settings/_tabs': typeof AppOrganizationIdProjectIdSettingsTabsRouteWithChildren '/_app/$organizationId/integrations/_tabs/available': typeof AppOrganizationIdIntegrationsTabsAvailableRoute '/_app/$organizationId/integrations/_tabs/installed': typeof AppOrganizationIdIntegrationsTabsInstalledRoute '/_app/$organizationId/members/_tabs/invitations': typeof AppOrganizationIdMembersTabsInvitationsRoute '/_app/$organizationId/members/_tabs/members': typeof AppOrganizationIdMembersTabsMembersRoute '/_app/$organizationId/integrations/_tabs/': typeof AppOrganizationIdIntegrationsTabsIndexRoute '/_app/$organizationId/members/_tabs/': typeof AppOrganizationIdMembersTabsIndexRoute - '/_app/$organizationId/$projectId_/events/_tabs/conversions': typeof AppOrganizationIdProjectIdEventsTabsConversionsRoute - '/_app/$organizationId/$projectId_/events/_tabs/events': typeof AppOrganizationIdProjectIdEventsTabsEventsRoute - '/_app/$organizationId/$projectId_/events/_tabs/stats': typeof AppOrganizationIdProjectIdEventsTabsStatsRoute - '/_app/$organizationId/$projectId_/notifications/_tabs/notifications': typeof AppOrganizationIdProjectIdNotificationsTabsNotificationsRoute - '/_app/$organizationId/$projectId_/notifications/_tabs/rules': typeof AppOrganizationIdProjectIdNotificationsTabsRulesRoute - '/_app/$organizationId/$projectId_/profiles/$profileId': typeof AppOrganizationIdProjectIdProfilesProfileIdRouteWithChildren - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRouteWithChildren - '/_app/$organizationId/$projectId_/profiles/_tabs/anonymous': typeof AppOrganizationIdProjectIdProfilesTabsAnonymousRoute - '/_app/$organizationId/$projectId_/profiles/_tabs/identified': typeof AppOrganizationIdProjectIdProfilesTabsIdentifiedRoute - '/_app/$organizationId/$projectId_/profiles/_tabs/power-users': typeof AppOrganizationIdProjectIdProfilesTabsPowerUsersRoute - '/_app/$organizationId/$projectId_/settings/_tabs/clients': typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute - '/_app/$organizationId/$projectId_/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute - '/_app/$organizationId/$projectId_/settings/_tabs/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute - '/_app/$organizationId/$projectId_/settings/_tabs/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute - '/_app/$organizationId/$projectId_/events/_tabs/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute - '/_app/$organizationId/$projectId_/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute - '/_app/$organizationId/$projectId_/profiles/_tabs/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute - '/_app/$organizationId/$projectId_/settings/_tabs/': typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute + '/_app/$organizationId/$projectId/events/_tabs/conversions': typeof AppOrganizationIdProjectIdEventsTabsConversionsRoute + '/_app/$organizationId/$projectId/events/_tabs/events': typeof AppOrganizationIdProjectIdEventsTabsEventsRoute + '/_app/$organizationId/$projectId/events/_tabs/stats': typeof AppOrganizationIdProjectIdEventsTabsStatsRoute + '/_app/$organizationId/$projectId/notifications/_tabs/notifications': typeof AppOrganizationIdProjectIdNotificationsTabsNotificationsRoute + '/_app/$organizationId/$projectId/notifications/_tabs/rules': typeof AppOrganizationIdProjectIdNotificationsTabsRulesRoute + '/_app/$organizationId/$projectId/profiles/$profileId': typeof AppOrganizationIdProjectIdProfilesProfileIdRouteWithChildren + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRouteWithChildren + '/_app/$organizationId/$projectId/profiles/_tabs/anonymous': typeof AppOrganizationIdProjectIdProfilesTabsAnonymousRoute + '/_app/$organizationId/$projectId/profiles/_tabs/identified': typeof AppOrganizationIdProjectIdProfilesTabsIdentifiedRoute + '/_app/$organizationId/$projectId/profiles/_tabs/power-users': typeof AppOrganizationIdProjectIdProfilesTabsPowerUsersRoute + '/_app/$organizationId/$projectId/settings/_tabs/clients': typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute + '/_app/$organizationId/$projectId/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute + '/_app/$organizationId/$projectId/settings/_tabs/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute + '/_app/$organizationId/$projectId/settings/_tabs/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + '/_app/$organizationId/$projectId/events/_tabs/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute + '/_app/$organizationId/$projectId/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute + '/_app/$organizationId/$projectId/profiles/_tabs/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute + '/_app/$organizationId/$projectId/settings/_tabs/': typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -677,6 +686,7 @@ export interface FileRouteTypes { | '/$organizationId/members' | '/onboarding/$projectId/connect' | '/onboarding/$projectId/verify' + | '/$organizationId/$projectId/' | '/$organizationId/$projectId/dashboards/$dashboardId' | '/$organizationId/$projectId/events' | '/$organizationId/$projectId/notifications' @@ -717,7 +727,6 @@ export interface FileRouteTypes { | '/onboarding' | '/api/config' | '/api/healthcheck' - | '/$organizationId/$projectId' | '/$organizationId/billing' | '/$organizationId/settings' | '/onboarding/project' @@ -734,6 +743,7 @@ export interface FileRouteTypes { | '/$organizationId/members' | '/onboarding/$projectId/connect' | '/onboarding/$projectId/verify' + | '/$organizationId/$projectId' | '/$organizationId/$projectId/dashboards/$dashboardId' | '/$organizationId/$projectId/events' | '/$organizationId/$projectId/notifications' @@ -778,56 +788,57 @@ export interface FileRouteTypes { | '/_steps/onboarding/project' | '/share/overview/$shareId' | '/_app/$organizationId/' - | '/_app/$organizationId/$projectId_/chat' - | '/_app/$organizationId/$projectId_/dashboards' - | '/_app/$organizationId/$projectId_/pages' - | '/_app/$organizationId/$projectId_/realtime' - | '/_app/$organizationId/$projectId_/references' - | '/_app/$organizationId/$projectId_/reports' - | '/_app/$organizationId/$projectId_/sessions' + | '/_app/$organizationId/$projectId/chat' + | '/_app/$organizationId/$projectId/dashboards' + | '/_app/$organizationId/$projectId/pages' + | '/_app/$organizationId/$projectId/realtime' + | '/_app/$organizationId/$projectId/references' + | '/_app/$organizationId/$projectId/reports' + | '/_app/$organizationId/$projectId/sessions' | '/_app/$organizationId/integrations' | '/_app/$organizationId/integrations/_tabs' | '/_app/$organizationId/members' | '/_app/$organizationId/members/_tabs' | '/_steps/onboarding/$projectId/connect' | '/_steps/onboarding/$projectId/verify' - | '/_app/$organizationId/$projectId_/dashboards_/$dashboardId' - | '/_app/$organizationId/$projectId_/events' - | '/_app/$organizationId/$projectId_/events/_tabs' - | '/_app/$organizationId/$projectId_/notifications' - | '/_app/$organizationId/$projectId_/notifications/_tabs' - | '/_app/$organizationId/$projectId_/profiles' - | '/_app/$organizationId/$projectId_/profiles/_tabs' - | '/_app/$organizationId/$projectId_/reports_/$reportId' - | '/_app/$organizationId/$projectId_/sessions_/$sessionId' - | '/_app/$organizationId/$projectId_/settings' - | '/_app/$organizationId/$projectId_/settings/_tabs' + | '/_app/$organizationId/$projectId/' + | '/_app/$organizationId/$projectId/dashboards_/$dashboardId' + | '/_app/$organizationId/$projectId/events' + | '/_app/$organizationId/$projectId/events/_tabs' + | '/_app/$organizationId/$projectId/notifications' + | '/_app/$organizationId/$projectId/notifications/_tabs' + | '/_app/$organizationId/$projectId/profiles' + | '/_app/$organizationId/$projectId/profiles/_tabs' + | '/_app/$organizationId/$projectId/reports_/$reportId' + | '/_app/$organizationId/$projectId/sessions_/$sessionId' + | '/_app/$organizationId/$projectId/settings' + | '/_app/$organizationId/$projectId/settings/_tabs' | '/_app/$organizationId/integrations/_tabs/available' | '/_app/$organizationId/integrations/_tabs/installed' | '/_app/$organizationId/members/_tabs/invitations' | '/_app/$organizationId/members/_tabs/members' | '/_app/$organizationId/integrations/_tabs/' | '/_app/$organizationId/members/_tabs/' - | '/_app/$organizationId/$projectId_/events/_tabs/conversions' - | '/_app/$organizationId/$projectId_/events/_tabs/events' - | '/_app/$organizationId/$projectId_/events/_tabs/stats' - | '/_app/$organizationId/$projectId_/notifications/_tabs/notifications' - | '/_app/$organizationId/$projectId_/notifications/_tabs/rules' - | '/_app/$organizationId/$projectId_/profiles/$profileId' - | '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs' - | '/_app/$organizationId/$projectId_/profiles/_tabs/anonymous' - | '/_app/$organizationId/$projectId_/profiles/_tabs/identified' - | '/_app/$organizationId/$projectId_/profiles/_tabs/power-users' - | '/_app/$organizationId/$projectId_/settings/_tabs/clients' - | '/_app/$organizationId/$projectId_/settings/_tabs/details' - | '/_app/$organizationId/$projectId_/settings/_tabs/events' - | '/_app/$organizationId/$projectId_/settings/_tabs/imports' - | '/_app/$organizationId/$projectId_/events/_tabs/' - | '/_app/$organizationId/$projectId_/notifications/_tabs/' - | '/_app/$organizationId/$projectId_/profiles/_tabs/' - | '/_app/$organizationId/$projectId_/settings/_tabs/' - | '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/events' - | '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/' + | '/_app/$organizationId/$projectId/events/_tabs/conversions' + | '/_app/$organizationId/$projectId/events/_tabs/events' + | '/_app/$organizationId/$projectId/events/_tabs/stats' + | '/_app/$organizationId/$projectId/notifications/_tabs/notifications' + | '/_app/$organizationId/$projectId/notifications/_tabs/rules' + | '/_app/$organizationId/$projectId/profiles/$profileId' + | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs' + | '/_app/$organizationId/$projectId/profiles/_tabs/anonymous' + | '/_app/$organizationId/$projectId/profiles/_tabs/identified' + | '/_app/$organizationId/$projectId/profiles/_tabs/power-users' + | '/_app/$organizationId/$projectId/settings/_tabs/clients' + | '/_app/$organizationId/$projectId/settings/_tabs/details' + | '/_app/$organizationId/$projectId/settings/_tabs/events' + | '/_app/$organizationId/$projectId/settings/_tabs/imports' + | '/_app/$organizationId/$projectId/events/_tabs/' + | '/_app/$organizationId/$projectId/notifications/_tabs/' + | '/_app/$organizationId/$projectId/profiles/_tabs/' + | '/_app/$organizationId/$projectId/settings/_tabs/' + | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events' + | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -976,33 +987,40 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdProjectIdRouteImport parentRoute: typeof AppOrganizationIdRoute } - '/_app/$organizationId/$projectId_/settings': { - id: '/_app/$organizationId/$projectId_/settings' - path: '/$projectId/settings' + '/_app/$organizationId/$projectId/settings': { + id: '/_app/$organizationId/$projectId/settings' + path: '/settings' fullPath: '/$organizationId/$projectId/settings' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/profiles': { - id: '/_app/$organizationId/$projectId_/profiles' - path: '/$projectId/profiles' + '/_app/$organizationId/$projectId/profiles': { + id: '/_app/$organizationId/$projectId/profiles' + path: '/profiles' fullPath: '/$organizationId/$projectId/profiles' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/notifications': { - id: '/_app/$organizationId/$projectId_/notifications' - path: '/$projectId/notifications' + '/_app/$organizationId/$projectId/notifications': { + id: '/_app/$organizationId/$projectId/notifications' + path: '/notifications' fullPath: '/$organizationId/$projectId/notifications' preLoaderRoute: typeof AppOrganizationIdProjectIdNotificationsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/events': { - id: '/_app/$organizationId/$projectId_/events' - path: '/$projectId/events' + '/_app/$organizationId/$projectId/events': { + id: '/_app/$organizationId/$projectId/events' + path: '/events' fullPath: '/$organizationId/$projectId/events' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute + } + '/_app/$organizationId/$projectId/': { + id: '/_app/$organizationId/$projectId/' + path: '/' + fullPath: '/$organizationId/$projectId/' + preLoaderRoute: typeof AppOrganizationIdProjectIdIndexRouteImport + parentRoute: typeof AppOrganizationIdProjectIdRoute } '/_steps/onboarding/$projectId/verify': { id: '/_steps/onboarding/$projectId/verify' @@ -1032,57 +1050,57 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdIntegrationsTabsRouteImport parentRoute: typeof AppOrganizationIdIntegrationsRoute } - '/_app/$organizationId/$projectId_/sessions': { - id: '/_app/$organizationId/$projectId_/sessions' - path: '/$projectId/sessions' + '/_app/$organizationId/$projectId/sessions': { + id: '/_app/$organizationId/$projectId/sessions' + path: '/sessions' fullPath: '/$organizationId/$projectId/sessions' preLoaderRoute: typeof AppOrganizationIdProjectIdSessionsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/reports': { - id: '/_app/$organizationId/$projectId_/reports' - path: '/$projectId/reports' + '/_app/$organizationId/$projectId/reports': { + id: '/_app/$organizationId/$projectId/reports' + path: '/reports' fullPath: '/$organizationId/$projectId/reports' preLoaderRoute: typeof AppOrganizationIdProjectIdReportsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/references': { - id: '/_app/$organizationId/$projectId_/references' - path: '/$projectId/references' + '/_app/$organizationId/$projectId/references': { + id: '/_app/$organizationId/$projectId/references' + path: '/references' fullPath: '/$organizationId/$projectId/references' preLoaderRoute: typeof AppOrganizationIdProjectIdReferencesRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/realtime': { - id: '/_app/$organizationId/$projectId_/realtime' - path: '/$projectId/realtime' + '/_app/$organizationId/$projectId/realtime': { + id: '/_app/$organizationId/$projectId/realtime' + path: '/realtime' fullPath: '/$organizationId/$projectId/realtime' preLoaderRoute: typeof AppOrganizationIdProjectIdRealtimeRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/pages': { - id: '/_app/$organizationId/$projectId_/pages' - path: '/$projectId/pages' + '/_app/$organizationId/$projectId/pages': { + id: '/_app/$organizationId/$projectId/pages' + path: '/pages' fullPath: '/$organizationId/$projectId/pages' preLoaderRoute: typeof AppOrganizationIdProjectIdPagesRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/dashboards': { - id: '/_app/$organizationId/$projectId_/dashboards' - path: '/$projectId/dashboards' + '/_app/$organizationId/$projectId/dashboards': { + id: '/_app/$organizationId/$projectId/dashboards' + path: '/dashboards' fullPath: '/$organizationId/$projectId/dashboards' preLoaderRoute: typeof AppOrganizationIdProjectIdDashboardsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/chat': { - id: '/_app/$organizationId/$projectId_/chat' - path: '/$projectId/chat' + '/_app/$organizationId/$projectId/chat': { + id: '/_app/$organizationId/$projectId/chat' + path: '/chat' fullPath: '/$organizationId/$projectId/chat' preLoaderRoute: typeof AppOrganizationIdProjectIdChatRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/profiles/$profileId': { - id: '/_app/$organizationId/$projectId_/profiles/$profileId' + '/_app/$organizationId/$projectId/profiles/$profileId': { + id: '/_app/$organizationId/$projectId/profiles/$profileId' path: '/$profileId' fullPath: '/$organizationId/$projectId/profiles/$profileId' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdRouteImport @@ -1130,183 +1148,183 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdIntegrationsTabsAvailableRouteImport parentRoute: typeof AppOrganizationIdIntegrationsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs': { - id: '/_app/$organizationId/$projectId_/settings/_tabs' - path: '/$projectId/settings' + '/_app/$organizationId/$projectId/settings/_tabs': { + id: '/_app/$organizationId/$projectId/settings/_tabs' + path: '/settings' fullPath: '/$organizationId/$projectId/settings' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsRoute } - '/_app/$organizationId/$projectId_/sessions_/$sessionId': { - id: '/_app/$organizationId/$projectId_/sessions_/$sessionId' - path: '/$projectId/sessions/$sessionId' + '/_app/$organizationId/$projectId/sessions_/$sessionId': { + id: '/_app/$organizationId/$projectId/sessions_/$sessionId' + path: '/sessions/$sessionId' fullPath: '/$organizationId/$projectId/sessions/$sessionId' preLoaderRoute: typeof AppOrganizationIdProjectIdSessionsSessionIdRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/reports_/$reportId': { - id: '/_app/$organizationId/$projectId_/reports_/$reportId' - path: '/$projectId/reports/$reportId' + '/_app/$organizationId/$projectId/reports_/$reportId': { + id: '/_app/$organizationId/$projectId/reports_/$reportId' + path: '/reports/$reportId' fullPath: '/$organizationId/$projectId/reports/$reportId' preLoaderRoute: typeof AppOrganizationIdProjectIdReportsReportIdRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/profiles/_tabs': { - id: '/_app/$organizationId/$projectId_/profiles/_tabs' - path: '/$projectId/profiles' + '/_app/$organizationId/$projectId/profiles/_tabs': { + id: '/_app/$organizationId/$projectId/profiles/_tabs' + path: '/profiles' fullPath: '/$organizationId/$projectId/profiles' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesTabsRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesRoute } - '/_app/$organizationId/$projectId_/notifications/_tabs': { - id: '/_app/$organizationId/$projectId_/notifications/_tabs' - path: '/$projectId/notifications' + '/_app/$organizationId/$projectId/notifications/_tabs': { + id: '/_app/$organizationId/$projectId/notifications/_tabs' + path: '/notifications' fullPath: '/$organizationId/$projectId/notifications' preLoaderRoute: typeof AppOrganizationIdProjectIdNotificationsTabsRouteImport parentRoute: typeof AppOrganizationIdProjectIdNotificationsRoute } - '/_app/$organizationId/$projectId_/events/_tabs': { - id: '/_app/$organizationId/$projectId_/events/_tabs' - path: '/$projectId/events' + '/_app/$organizationId/$projectId/events/_tabs': { + id: '/_app/$organizationId/$projectId/events/_tabs' + path: '/events' fullPath: '/$organizationId/$projectId/events' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsRoute } - '/_app/$organizationId/$projectId_/dashboards_/$dashboardId': { - id: '/_app/$organizationId/$projectId_/dashboards_/$dashboardId' - path: '/$projectId/dashboards/$dashboardId' + '/_app/$organizationId/$projectId/dashboards_/$dashboardId': { + id: '/_app/$organizationId/$projectId/dashboards_/$dashboardId' + path: '/dashboards/$dashboardId' fullPath: '/$organizationId/$projectId/dashboards/$dashboardId' preLoaderRoute: typeof AppOrganizationIdProjectIdDashboardsDashboardIdRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/' + '/_app/$organizationId/$projectId/settings/_tabs/': { + id: '/_app/$organizationId/$projectId/settings/_tabs/' path: '/' fullPath: '/$organizationId/$projectId/settings/' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } - '/_app/$organizationId/$projectId_/profiles/_tabs/': { - id: '/_app/$organizationId/$projectId_/profiles/_tabs/' + '/_app/$organizationId/$projectId/profiles/_tabs/': { + id: '/_app/$organizationId/$projectId/profiles/_tabs/' path: '/' fullPath: '/$organizationId/$projectId/profiles/' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesTabsRoute } - '/_app/$organizationId/$projectId_/notifications/_tabs/': { - id: '/_app/$organizationId/$projectId_/notifications/_tabs/' + '/_app/$organizationId/$projectId/notifications/_tabs/': { + id: '/_app/$organizationId/$projectId/notifications/_tabs/' path: '/' fullPath: '/$organizationId/$projectId/notifications/' preLoaderRoute: typeof AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdNotificationsTabsRoute } - '/_app/$organizationId/$projectId_/events/_tabs/': { - id: '/_app/$organizationId/$projectId_/events/_tabs/' + '/_app/$organizationId/$projectId/events/_tabs/': { + id: '/_app/$organizationId/$projectId/events/_tabs/' path: '/' fullPath: '/$organizationId/$projectId/events/' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/imports': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/imports' + '/_app/$organizationId/$projectId/settings/_tabs/imports': { + id: '/_app/$organizationId/$projectId/settings/_tabs/imports' path: '/imports' fullPath: '/$organizationId/$projectId/settings/imports' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/events': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/events' + '/_app/$organizationId/$projectId/settings/_tabs/events': { + id: '/_app/$organizationId/$projectId/settings/_tabs/events' path: '/events' fullPath: '/$organizationId/$projectId/settings/events' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsEventsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/details': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/details' + '/_app/$organizationId/$projectId/settings/_tabs/details': { + id: '/_app/$organizationId/$projectId/settings/_tabs/details' path: '/details' fullPath: '/$organizationId/$projectId/settings/details' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/clients': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/clients' + '/_app/$organizationId/$projectId/settings/_tabs/clients': { + id: '/_app/$organizationId/$projectId/settings/_tabs/clients' path: '/clients' fullPath: '/$organizationId/$projectId/settings/clients' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsClientsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } - '/_app/$organizationId/$projectId_/profiles/_tabs/power-users': { - id: '/_app/$organizationId/$projectId_/profiles/_tabs/power-users' + '/_app/$organizationId/$projectId/profiles/_tabs/power-users': { + id: '/_app/$organizationId/$projectId/profiles/_tabs/power-users' path: '/power-users' fullPath: '/$organizationId/$projectId/profiles/power-users' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesTabsPowerUsersRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesTabsRoute } - '/_app/$organizationId/$projectId_/profiles/_tabs/identified': { - id: '/_app/$organizationId/$projectId_/profiles/_tabs/identified' + '/_app/$organizationId/$projectId/profiles/_tabs/identified': { + id: '/_app/$organizationId/$projectId/profiles/_tabs/identified' path: '/identified' fullPath: '/$organizationId/$projectId/profiles/identified' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesTabsIdentifiedRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesTabsRoute } - '/_app/$organizationId/$projectId_/profiles/_tabs/anonymous': { - id: '/_app/$organizationId/$projectId_/profiles/_tabs/anonymous' + '/_app/$organizationId/$projectId/profiles/_tabs/anonymous': { + id: '/_app/$organizationId/$projectId/profiles/_tabs/anonymous' path: '/anonymous' fullPath: '/$organizationId/$projectId/profiles/anonymous' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesTabsAnonymousRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesTabsRoute } - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs': { - id: '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs' + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs': { + id: '/_app/$organizationId/$projectId/profiles/$profileId/_tabs' path: '/$profileId' fullPath: '/$organizationId/$projectId/profiles/$profileId' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdRoute } - '/_app/$organizationId/$projectId_/notifications/_tabs/rules': { - id: '/_app/$organizationId/$projectId_/notifications/_tabs/rules' + '/_app/$organizationId/$projectId/notifications/_tabs/rules': { + id: '/_app/$organizationId/$projectId/notifications/_tabs/rules' path: '/rules' fullPath: '/$organizationId/$projectId/notifications/rules' preLoaderRoute: typeof AppOrganizationIdProjectIdNotificationsTabsRulesRouteImport parentRoute: typeof AppOrganizationIdProjectIdNotificationsTabsRoute } - '/_app/$organizationId/$projectId_/notifications/_tabs/notifications': { - id: '/_app/$organizationId/$projectId_/notifications/_tabs/notifications' + '/_app/$organizationId/$projectId/notifications/_tabs/notifications': { + id: '/_app/$organizationId/$projectId/notifications/_tabs/notifications' path: '/notifications' fullPath: '/$organizationId/$projectId/notifications/notifications' preLoaderRoute: typeof AppOrganizationIdProjectIdNotificationsTabsNotificationsRouteImport parentRoute: typeof AppOrganizationIdProjectIdNotificationsTabsRoute } - '/_app/$organizationId/$projectId_/events/_tabs/stats': { - id: '/_app/$organizationId/$projectId_/events/_tabs/stats' + '/_app/$organizationId/$projectId/events/_tabs/stats': { + id: '/_app/$organizationId/$projectId/events/_tabs/stats' path: '/stats' fullPath: '/$organizationId/$projectId/events/stats' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsStatsRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute } - '/_app/$organizationId/$projectId_/events/_tabs/events': { - id: '/_app/$organizationId/$projectId_/events/_tabs/events' + '/_app/$organizationId/$projectId/events/_tabs/events': { + id: '/_app/$organizationId/$projectId/events/_tabs/events' path: '/events' fullPath: '/$organizationId/$projectId/events/events' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsEventsRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute } - '/_app/$organizationId/$projectId_/events/_tabs/conversions': { - id: '/_app/$organizationId/$projectId_/events/_tabs/conversions' + '/_app/$organizationId/$projectId/events/_tabs/conversions': { + id: '/_app/$organizationId/$projectId/events/_tabs/conversions' path: '/conversions' fullPath: '/$organizationId/$projectId/events/conversions' preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsConversionsRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute } - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/': { - id: '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/' + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/': { + id: '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/' path: '/' fullPath: '/$organizationId/$projectId/profiles/$profileId/' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRoute } - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/events': { - id: '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/events' + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events': { + id: '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events' path: '/events' fullPath: '/$organizationId/$projectId/profiles/$profileId/events' preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRouteImport @@ -1315,78 +1333,6 @@ declare module '@tanstack/react-router' { } } -interface AppOrganizationIdIntegrationsTabsRouteChildren { - AppOrganizationIdIntegrationsTabsAvailableRoute: typeof AppOrganizationIdIntegrationsTabsAvailableRoute - AppOrganizationIdIntegrationsTabsInstalledRoute: typeof AppOrganizationIdIntegrationsTabsInstalledRoute - AppOrganizationIdIntegrationsTabsIndexRoute: typeof AppOrganizationIdIntegrationsTabsIndexRoute -} - -const AppOrganizationIdIntegrationsTabsRouteChildren: AppOrganizationIdIntegrationsTabsRouteChildren = - { - AppOrganizationIdIntegrationsTabsAvailableRoute: - AppOrganizationIdIntegrationsTabsAvailableRoute, - AppOrganizationIdIntegrationsTabsInstalledRoute: - AppOrganizationIdIntegrationsTabsInstalledRoute, - AppOrganizationIdIntegrationsTabsIndexRoute: - AppOrganizationIdIntegrationsTabsIndexRoute, - } - -const AppOrganizationIdIntegrationsTabsRouteWithChildren = - AppOrganizationIdIntegrationsTabsRoute._addFileChildren( - AppOrganizationIdIntegrationsTabsRouteChildren, - ) - -interface AppOrganizationIdIntegrationsRouteChildren { - AppOrganizationIdIntegrationsTabsRoute: typeof AppOrganizationIdIntegrationsTabsRouteWithChildren -} - -const AppOrganizationIdIntegrationsRouteChildren: AppOrganizationIdIntegrationsRouteChildren = - { - AppOrganizationIdIntegrationsTabsRoute: - AppOrganizationIdIntegrationsTabsRouteWithChildren, - } - -const AppOrganizationIdIntegrationsRouteWithChildren = - AppOrganizationIdIntegrationsRoute._addFileChildren( - AppOrganizationIdIntegrationsRouteChildren, - ) - -interface AppOrganizationIdMembersTabsRouteChildren { - AppOrganizationIdMembersTabsInvitationsRoute: typeof AppOrganizationIdMembersTabsInvitationsRoute - AppOrganizationIdMembersTabsMembersRoute: typeof AppOrganizationIdMembersTabsMembersRoute - AppOrganizationIdMembersTabsIndexRoute: typeof AppOrganizationIdMembersTabsIndexRoute -} - -const AppOrganizationIdMembersTabsRouteChildren: AppOrganizationIdMembersTabsRouteChildren = - { - AppOrganizationIdMembersTabsInvitationsRoute: - AppOrganizationIdMembersTabsInvitationsRoute, - AppOrganizationIdMembersTabsMembersRoute: - AppOrganizationIdMembersTabsMembersRoute, - AppOrganizationIdMembersTabsIndexRoute: - AppOrganizationIdMembersTabsIndexRoute, - } - -const AppOrganizationIdMembersTabsRouteWithChildren = - AppOrganizationIdMembersTabsRoute._addFileChildren( - AppOrganizationIdMembersTabsRouteChildren, - ) - -interface AppOrganizationIdMembersRouteChildren { - AppOrganizationIdMembersTabsRoute: typeof AppOrganizationIdMembersTabsRouteWithChildren -} - -const AppOrganizationIdMembersRouteChildren: AppOrganizationIdMembersRouteChildren = - { - AppOrganizationIdMembersTabsRoute: - AppOrganizationIdMembersTabsRouteWithChildren, - } - -const AppOrganizationIdMembersRouteWithChildren = - AppOrganizationIdMembersRoute._addFileChildren( - AppOrganizationIdMembersRouteChildren, - ) - interface AppOrganizationIdProjectIdEventsTabsRouteChildren { AppOrganizationIdProjectIdEventsTabsConversionsRoute: typeof AppOrganizationIdProjectIdEventsTabsConversionsRoute AppOrganizationIdProjectIdEventsTabsEventsRoute: typeof AppOrganizationIdProjectIdEventsTabsEventsRoute @@ -1579,11 +1525,7 @@ const AppOrganizationIdProjectIdSettingsRouteWithChildren = AppOrganizationIdProjectIdSettingsRouteChildren, ) -interface AppOrganizationIdRouteChildren { - AppOrganizationIdProjectIdRoute: typeof AppOrganizationIdProjectIdRoute - AppOrganizationIdBillingRoute: typeof AppOrganizationIdBillingRoute - AppOrganizationIdSettingsRoute: typeof AppOrganizationIdSettingsRoute - AppOrganizationIdIndexRoute: typeof AppOrganizationIdIndexRoute +interface AppOrganizationIdProjectIdRouteChildren { AppOrganizationIdProjectIdChatRoute: typeof AppOrganizationIdProjectIdChatRoute AppOrganizationIdProjectIdDashboardsRoute: typeof AppOrganizationIdProjectIdDashboardsRoute AppOrganizationIdProjectIdPagesRoute: typeof AppOrganizationIdProjectIdPagesRoute @@ -1591,8 +1533,7 @@ interface AppOrganizationIdRouteChildren { AppOrganizationIdProjectIdReferencesRoute: typeof AppOrganizationIdProjectIdReferencesRoute AppOrganizationIdProjectIdReportsRoute: typeof AppOrganizationIdProjectIdReportsRoute AppOrganizationIdProjectIdSessionsRoute: typeof AppOrganizationIdProjectIdSessionsRoute - AppOrganizationIdIntegrationsRoute: typeof AppOrganizationIdIntegrationsRouteWithChildren - AppOrganizationIdMembersRoute: typeof AppOrganizationIdMembersRouteWithChildren + AppOrganizationIdProjectIdIndexRoute: typeof AppOrganizationIdProjectIdIndexRoute AppOrganizationIdProjectIdDashboardsDashboardIdRoute: typeof AppOrganizationIdProjectIdDashboardsDashboardIdRoute AppOrganizationIdProjectIdEventsRoute: typeof AppOrganizationIdProjectIdEventsRouteWithChildren AppOrganizationIdProjectIdNotificationsRoute: typeof AppOrganizationIdProjectIdNotificationsRouteWithChildren @@ -1602,40 +1543,131 @@ interface AppOrganizationIdRouteChildren { AppOrganizationIdProjectIdSettingsRoute: typeof AppOrganizationIdProjectIdSettingsRouteWithChildren } +const AppOrganizationIdProjectIdRouteChildren: AppOrganizationIdProjectIdRouteChildren = + { + AppOrganizationIdProjectIdChatRoute: AppOrganizationIdProjectIdChatRoute, + AppOrganizationIdProjectIdDashboardsRoute: + AppOrganizationIdProjectIdDashboardsRoute, + AppOrganizationIdProjectIdPagesRoute: AppOrganizationIdProjectIdPagesRoute, + AppOrganizationIdProjectIdRealtimeRoute: + AppOrganizationIdProjectIdRealtimeRoute, + AppOrganizationIdProjectIdReferencesRoute: + AppOrganizationIdProjectIdReferencesRoute, + AppOrganizationIdProjectIdReportsRoute: + AppOrganizationIdProjectIdReportsRoute, + AppOrganizationIdProjectIdSessionsRoute: + AppOrganizationIdProjectIdSessionsRoute, + AppOrganizationIdProjectIdIndexRoute: AppOrganizationIdProjectIdIndexRoute, + AppOrganizationIdProjectIdDashboardsDashboardIdRoute: + AppOrganizationIdProjectIdDashboardsDashboardIdRoute, + AppOrganizationIdProjectIdEventsRoute: + AppOrganizationIdProjectIdEventsRouteWithChildren, + AppOrganizationIdProjectIdNotificationsRoute: + AppOrganizationIdProjectIdNotificationsRouteWithChildren, + AppOrganizationIdProjectIdProfilesRoute: + AppOrganizationIdProjectIdProfilesRouteWithChildren, + AppOrganizationIdProjectIdReportsReportIdRoute: + AppOrganizationIdProjectIdReportsReportIdRoute, + AppOrganizationIdProjectIdSessionsSessionIdRoute: + AppOrganizationIdProjectIdSessionsSessionIdRoute, + AppOrganizationIdProjectIdSettingsRoute: + AppOrganizationIdProjectIdSettingsRouteWithChildren, + } + +const AppOrganizationIdProjectIdRouteWithChildren = + AppOrganizationIdProjectIdRoute._addFileChildren( + AppOrganizationIdProjectIdRouteChildren, + ) + +interface AppOrganizationIdIntegrationsTabsRouteChildren { + AppOrganizationIdIntegrationsTabsAvailableRoute: typeof AppOrganizationIdIntegrationsTabsAvailableRoute + AppOrganizationIdIntegrationsTabsInstalledRoute: typeof AppOrganizationIdIntegrationsTabsInstalledRoute + AppOrganizationIdIntegrationsTabsIndexRoute: typeof AppOrganizationIdIntegrationsTabsIndexRoute +} + +const AppOrganizationIdIntegrationsTabsRouteChildren: AppOrganizationIdIntegrationsTabsRouteChildren = + { + AppOrganizationIdIntegrationsTabsAvailableRoute: + AppOrganizationIdIntegrationsTabsAvailableRoute, + AppOrganizationIdIntegrationsTabsInstalledRoute: + AppOrganizationIdIntegrationsTabsInstalledRoute, + AppOrganizationIdIntegrationsTabsIndexRoute: + AppOrganizationIdIntegrationsTabsIndexRoute, + } + +const AppOrganizationIdIntegrationsTabsRouteWithChildren = + AppOrganizationIdIntegrationsTabsRoute._addFileChildren( + AppOrganizationIdIntegrationsTabsRouteChildren, + ) + +interface AppOrganizationIdIntegrationsRouteChildren { + AppOrganizationIdIntegrationsTabsRoute: typeof AppOrganizationIdIntegrationsTabsRouteWithChildren +} + +const AppOrganizationIdIntegrationsRouteChildren: AppOrganizationIdIntegrationsRouteChildren = + { + AppOrganizationIdIntegrationsTabsRoute: + AppOrganizationIdIntegrationsTabsRouteWithChildren, + } + +const AppOrganizationIdIntegrationsRouteWithChildren = + AppOrganizationIdIntegrationsRoute._addFileChildren( + AppOrganizationIdIntegrationsRouteChildren, + ) + +interface AppOrganizationIdMembersTabsRouteChildren { + AppOrganizationIdMembersTabsInvitationsRoute: typeof AppOrganizationIdMembersTabsInvitationsRoute + AppOrganizationIdMembersTabsMembersRoute: typeof AppOrganizationIdMembersTabsMembersRoute + AppOrganizationIdMembersTabsIndexRoute: typeof AppOrganizationIdMembersTabsIndexRoute +} + +const AppOrganizationIdMembersTabsRouteChildren: AppOrganizationIdMembersTabsRouteChildren = + { + AppOrganizationIdMembersTabsInvitationsRoute: + AppOrganizationIdMembersTabsInvitationsRoute, + AppOrganizationIdMembersTabsMembersRoute: + AppOrganizationIdMembersTabsMembersRoute, + AppOrganizationIdMembersTabsIndexRoute: + AppOrganizationIdMembersTabsIndexRoute, + } + +const AppOrganizationIdMembersTabsRouteWithChildren = + AppOrganizationIdMembersTabsRoute._addFileChildren( + AppOrganizationIdMembersTabsRouteChildren, + ) + +interface AppOrganizationIdMembersRouteChildren { + AppOrganizationIdMembersTabsRoute: typeof AppOrganizationIdMembersTabsRouteWithChildren +} + +const AppOrganizationIdMembersRouteChildren: AppOrganizationIdMembersRouteChildren = + { + AppOrganizationIdMembersTabsRoute: + AppOrganizationIdMembersTabsRouteWithChildren, + } + +const AppOrganizationIdMembersRouteWithChildren = + AppOrganizationIdMembersRoute._addFileChildren( + AppOrganizationIdMembersRouteChildren, + ) + +interface AppOrganizationIdRouteChildren { + AppOrganizationIdProjectIdRoute: typeof AppOrganizationIdProjectIdRouteWithChildren + AppOrganizationIdBillingRoute: typeof AppOrganizationIdBillingRoute + AppOrganizationIdSettingsRoute: typeof AppOrganizationIdSettingsRoute + AppOrganizationIdIndexRoute: typeof AppOrganizationIdIndexRoute + AppOrganizationIdIntegrationsRoute: typeof AppOrganizationIdIntegrationsRouteWithChildren + AppOrganizationIdMembersRoute: typeof AppOrganizationIdMembersRouteWithChildren +} + const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = { - AppOrganizationIdProjectIdRoute: AppOrganizationIdProjectIdRoute, + AppOrganizationIdProjectIdRoute: AppOrganizationIdProjectIdRouteWithChildren, AppOrganizationIdBillingRoute: AppOrganizationIdBillingRoute, AppOrganizationIdSettingsRoute: AppOrganizationIdSettingsRoute, AppOrganizationIdIndexRoute: AppOrganizationIdIndexRoute, - AppOrganizationIdProjectIdChatRoute: AppOrganizationIdProjectIdChatRoute, - AppOrganizationIdProjectIdDashboardsRoute: - AppOrganizationIdProjectIdDashboardsRoute, - AppOrganizationIdProjectIdPagesRoute: AppOrganizationIdProjectIdPagesRoute, - AppOrganizationIdProjectIdRealtimeRoute: - AppOrganizationIdProjectIdRealtimeRoute, - AppOrganizationIdProjectIdReferencesRoute: - AppOrganizationIdProjectIdReferencesRoute, - AppOrganizationIdProjectIdReportsRoute: - AppOrganizationIdProjectIdReportsRoute, - AppOrganizationIdProjectIdSessionsRoute: - AppOrganizationIdProjectIdSessionsRoute, AppOrganizationIdIntegrationsRoute: AppOrganizationIdIntegrationsRouteWithChildren, AppOrganizationIdMembersRoute: AppOrganizationIdMembersRouteWithChildren, - AppOrganizationIdProjectIdDashboardsDashboardIdRoute: - AppOrganizationIdProjectIdDashboardsDashboardIdRoute, - AppOrganizationIdProjectIdEventsRoute: - AppOrganizationIdProjectIdEventsRouteWithChildren, - AppOrganizationIdProjectIdNotificationsRoute: - AppOrganizationIdProjectIdNotificationsRouteWithChildren, - AppOrganizationIdProjectIdProfilesRoute: - AppOrganizationIdProjectIdProfilesRouteWithChildren, - AppOrganizationIdProjectIdReportsReportIdRoute: - AppOrganizationIdProjectIdReportsReportIdRoute, - AppOrganizationIdProjectIdSessionsSessionIdRoute: - AppOrganizationIdProjectIdSessionsSessionIdRoute, - AppOrganizationIdProjectIdSettingsRoute: - AppOrganizationIdProjectIdSettingsRouteWithChildren, } const AppOrganizationIdRouteWithChildren = diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.chat.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.chat.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.chat.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.chat.tsx index f14a5c84..b63e2cbe 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.chat.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.chat.tsx @@ -6,7 +6,7 @@ import { keepPreviousData, useSuspenseQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import type { UIMessage } from 'ai'; -export const Route = createFileRoute('/_app/$organizationId/$projectId_/chat')({ +export const Route = createFileRoute('/_app/$organizationId/$projectId/chat')({ component: Component, pendingComponent: FullPageLoadingState, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.dashboards.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.dashboards.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.dashboards.tsx index bab898b8..641ce539 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.dashboards.tsx @@ -32,7 +32,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { Link, createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/dashboards', + '/_app/$organizationId/$projectId/dashboards', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.dashboards_.$dashboardId.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.dashboards_.$dashboardId.tsx index 64d51c37..503cb1e8 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.dashboards_.$dashboardId.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.dashboards_.$dashboardId.tsx @@ -53,7 +53,7 @@ type Layout = { }; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/dashboards_/$dashboardId', + '/_app/$organizationId/$projectId/dashboards_/$dashboardId', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.conversions.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.conversions.tsx similarity index 94% rename from apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.conversions.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.conversions.tsx index 49781aa4..2a1926cf 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.conversions.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.conversions.tsx @@ -6,7 +6,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsIsoDateTime, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/events/_tabs/conversions', + '/_app/$organizationId/$projectId/events/_tabs/conversions', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.events.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.events.tsx similarity index 95% rename from apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.events.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.events.tsx index 095f3f4e..10a4b095 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.events.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.events.tsx @@ -9,7 +9,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsIsoDateTime, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/events/_tabs/events', + '/_app/$organizationId/$projectId/events/_tabs/events', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.index.tsx similarity index 85% rename from apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.index.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.index.tsx index 97cda125..a2c1203f 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.index.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.index.tsx @@ -1,7 +1,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/events/_tabs/', + '/_app/$organizationId/$projectId/events/_tabs/', )({ component: Component, beforeLoad({ params }) { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.stats.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.stats.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.stats.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.stats.tsx index 928e52c5..8b745631 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.stats.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.stats.tsx @@ -14,7 +14,7 @@ import type { IChartEvent } from '@openpanel/validation'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/events/_tabs/stats', + '/_app/$organizationId/$projectId/events/_tabs/stats', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.tsx similarity index 96% rename from apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.tsx index de0ff39d..206e4cbd 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.events._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.events._tabs.tsx @@ -5,7 +5,7 @@ import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; import { Outlet, createFileRoute, useRouter } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/events/_tabs', + '/_app/$organizationId/$projectId/events/_tabs', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.index.tsx new file mode 100644 index 00000000..a9e71c78 --- /dev/null +++ b/apps/start/src/routes/_app.$organizationId.$projectId.index.tsx @@ -0,0 +1,59 @@ +import { + OverviewFilterButton, + OverviewFiltersButtons, +} from '@/components/overview/filters/overview-filters-buttons'; +import { LiveCounter } from '@/components/overview/live-counter'; +import { OverviewInterval } from '@/components/overview/overview-interval'; +import OverviewMetrics from '@/components/overview/overview-metrics'; +import { OverviewRange } from '@/components/overview/overview-range'; +import { OverviewShare } from '@/components/overview/overview-share'; +import OverviewTopDevices from '@/components/overview/overview-top-devices'; +import OverviewTopEvents from '@/components/overview/overview-top-events'; +import OverviewTopGeo from '@/components/overview/overview-top-geo'; +import OverviewTopPages from '@/components/overview/overview-top-pages'; +import OverviewTopSources from '@/components/overview/overview-top-sources'; +import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_app/$organizationId/$projectId/')({ + component: ProjectDashboard, + head: () => { + return { + meta: [ + { + title: createProjectTitle(PAGE_TITLES.DASHBOARD), + }, + ], + }; + }, +}); + +function ProjectDashboard() { + const { projectId } = Route.useParams(); + return ( +
+
+
+
+ + + +
+
+ + +
+
+ +
+
+ + + + + + +
+
+ ); +} diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.index.tsx similarity index 84% rename from apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.index.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.index.tsx index 2e4603a6..6642d1ef 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.index.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.index.tsx @@ -1,7 +1,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/notifications/_tabs/', + '/_app/$organizationId/$projectId/notifications/_tabs/', )({ component: Component, beforeLoad({ params }) { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.notifications.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.notifications.tsx similarity index 92% rename from apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.notifications.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.notifications.tsx index f8acebff..510ae0b9 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.notifications.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.notifications.tsx @@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/notifications/_tabs/notifications', + '/_app/$organizationId/$projectId/notifications/_tabs/notifications', )({ component: Component, loader: async ({ context, params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.rules.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.rules.tsx similarity index 97% rename from apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.rules.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.rules.tsx index 6a7ffff9..df1788a0 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.rules.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.rules.tsx @@ -12,7 +12,7 @@ import { PencilRulerIcon, PlusIcon } from 'lucide-react'; import { useMemo } from 'react'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/notifications/_tabs/rules', + '/_app/$organizationId/$projectId/notifications/_tabs/rules', )({ component: Component, loader: async ({ context, params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.tsx similarity index 96% rename from apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.tsx index 2e360dd1..a3973b26 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.notifications._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.notifications._tabs.tsx @@ -5,7 +5,7 @@ import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; import { Outlet, createFileRoute, useRouter } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/notifications/_tabs', + '/_app/$organizationId/$projectId/notifications/_tabs', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.pages.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.pages.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.pages.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.pages.tsx index 11b6e3e7..aeb0aeb8 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.pages.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.pages.tsx @@ -24,7 +24,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsInteger, useQueryState } from 'nuqs'; import { memo } from 'react'; -export const Route = createFileRoute('/_app/$organizationId/$projectId_/pages')( +export const Route = createFileRoute('/_app/$organizationId/$projectId/pages')( { component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.events.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.events.tsx similarity index 94% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.events.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.events.tsx index 2343b520..2d0f6e13 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.events.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.events.tsx @@ -9,7 +9,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsIsoDateTime, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/events', + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.index.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.index.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.index.tsx index f1fa52a2..086bdfc1 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.index.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.index.tsx @@ -11,7 +11,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs/', + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/', )({ component: Component, loader: async ({ context, params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx index 78492879..24f2a563 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles.$profileId._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx @@ -11,7 +11,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { Outlet, createFileRoute, useRouter } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/$profileId/_tabs', + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs', )({ component: Component, loader: async ({ context, params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.anonymous.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.anonymous.tsx similarity index 95% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.anonymous.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.anonymous.tsx index 2fbff3e2..de74ab35 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.anonymous.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.anonymous.tsx @@ -7,7 +7,7 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/_tabs/anonymous', + '/_app/$organizationId/$projectId/profiles/_tabs/anonymous', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.identified.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx similarity index 94% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.identified.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx index 5ce82831..569ef42f 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.identified.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx @@ -7,7 +7,7 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/_tabs/identified', + '/_app/$organizationId/$projectId/profiles/_tabs/identified', )({ head: () => { return { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.index.tsx similarity index 85% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.index.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.index.tsx index a8d8046b..4be908ad 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.index.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.index.tsx @@ -1,7 +1,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/_tabs/', + '/_app/$organizationId/$projectId/profiles/_tabs/', )({ component: Component, beforeLoad({ params }) { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.power-users.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.power-users.tsx similarity index 94% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.power-users.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.power-users.tsx index 80e96057..32595ea6 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.power-users.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.power-users.tsx @@ -6,7 +6,7 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/_tabs/power-users', + '/_app/$organizationId/$projectId/profiles/_tabs/power-users', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.tsx similarity index 96% rename from apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.tsx index fc397c37..7aa3df7c 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.profiles._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.tsx @@ -5,7 +5,7 @@ import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; import { Outlet, createFileRoute, useRouter } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/profiles/_tabs', + '/_app/$organizationId/$projectId/profiles/_tabs', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.realtime.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.realtime.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.realtime.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.realtime.tsx index 20a038f3..62bf3c62 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.realtime.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.realtime.tsx @@ -12,7 +12,7 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/realtime', + '/_app/$organizationId/$projectId/realtime', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.references.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.references.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.references.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.references.tsx index 1c4b6e7f..7e3b8e64 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.references.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.references.tsx @@ -33,7 +33,7 @@ import { PlusIcon } from 'lucide-react'; import { toast } from 'sonner'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/references', + '/_app/$organizationId/$projectId/references', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.reports.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.reports.tsx similarity index 92% rename from apps/start/src/routes/_app.$organizationId.$projectId_.reports.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.reports.tsx index 5a7a7845..43c6d545 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.reports.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.reports.tsx @@ -4,7 +4,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/reports', + '/_app/$organizationId/$projectId/reports', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.reports_.$reportId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.reports_.$reportId.tsx similarity index 95% rename from apps/start/src/routes/_app.$organizationId.$projectId_.reports_.$reportId.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.reports_.$reportId.tsx index 317cc1bd..714ec89f 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.reports_.$reportId.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.reports_.$reportId.tsx @@ -7,7 +7,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/reports_/$reportId', + '/_app/$organizationId/$projectId/reports_/$reportId', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.sessions.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.sessions.tsx similarity index 96% rename from apps/start/src/routes/_app.$organizationId.$projectId_.sessions.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.sessions.tsx index 610f9422..7fb9e506 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.sessions.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.sessions.tsx @@ -13,7 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsString, parseAsStringEnum, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/sessions', + '/_app/$organizationId/$projectId/sessions', )({ component: Component, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.sessions_.$sessionId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx similarity index 98% rename from apps/start/src/routes/_app.$organizationId.$projectId_.sessions_.$sessionId.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx index f795cbf5..9bc18003 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.sessions_.$sessionId.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx @@ -16,7 +16,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { parseAsIsoDateTime, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/sessions_/$sessionId', + '/_app/$organizationId/$projectId/sessions_/$sessionId', )({ component: Component, loader: async ({ context, params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.clients.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.clients.tsx similarity index 89% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.clients.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.clients.tsx index c899b09d..5c46a85d 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.clients.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.clients.tsx @@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/clients', + '/_app/$organizationId/$projectId/settings/_tabs/clients', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.details.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.details.tsx similarity index 94% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.details.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.details.tsx index d42c0a3d..535a03e0 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.details.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.details.tsx @@ -7,7 +7,7 @@ import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/details', + '/_app/$organizationId/$projectId/settings/_tabs/details', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.events.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.events.tsx similarity index 93% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.events.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.events.tsx index 7f04c53c..41be9109 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.events.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.events.tsx @@ -6,7 +6,7 @@ import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/events', + '/_app/$organizationId/$projectId/settings/_tabs/events', )({ component: Component, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx index 123e7875..35b0c101 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx @@ -33,7 +33,7 @@ import { import { toast } from 'sonner'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/imports', + '/_app/$organizationId/$projectId/settings/_tabs/imports', )({ component: ImportsSettings, }); diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.index.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.index.tsx similarity index 85% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.index.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.index.tsx index daf22f3a..04f1c0cc 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.index.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.index.tsx @@ -1,7 +1,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/', + '/_app/$organizationId/$projectId/settings/_tabs/', )({ component: Component, beforeLoad: ({ params }) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx similarity index 97% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx index 0c7f217a..620894cd 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx @@ -10,7 +10,7 @@ import { } from '@tanstack/react-router'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs', + '/_app/$organizationId/$projectId/settings/_tabs', )({ component: ProjectDashboard, head: () => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.tsx index 25837b31..b00437ec 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.tsx @@ -1,19 +1,9 @@ -import { - OverviewFilterButton, - OverviewFiltersButtons, -} from '@/components/overview/filters/overview-filters-buttons'; -import { LiveCounter } from '@/components/overview/live-counter'; -import { OverviewInterval } from '@/components/overview/overview-interval'; -import OverviewMetrics from '@/components/overview/overview-metrics'; -import { OverviewRange } from '@/components/overview/overview-range'; -import { OverviewShare } from '@/components/overview/overview-share'; -import OverviewTopDevices from '@/components/overview/overview-top-devices'; -import OverviewTopEvents from '@/components/overview/overview-top-events'; -import OverviewTopGeo from '@/components/overview/overview-top-geo'; -import OverviewTopPages from '@/components/overview/overview-top-pages'; -import OverviewTopSources from '@/components/overview/overview-top-sources'; +import BillingPrompt from '@/components/organization/billing-prompt'; +import { useTRPC } from '@/integrations/trpc/react'; import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; -import { createFileRoute } from '@tanstack/react-router'; +import { FREE_PRODUCT_IDS } from '@openpanel/payments'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import { Outlet, createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/_app/$organizationId/$projectId')({ component: ProjectDashboard, @@ -26,34 +16,43 @@ export const Route = createFileRoute('/_app/$organizationId/$projectId')({ ], }; }, + loader: async ({ context, params }) => { + await context.queryClient.prefetchQuery( + context.trpc.organization.get.queryOptions({ + organizationId: params.organizationId, + }), + ); + }, }); function ProjectDashboard() { - const { projectId } = Route.useParams(); - return ( -
-
-
-
- - - -
-
- - -
-
- -
-
- - - - - - -
-
+ const { organizationId } = Route.useParams(); + const trpc = useTRPC(); + const { data: organization } = useSuspenseQuery( + trpc.organization.get.queryOptions({ + organizationId, + }), ); + + if ( + organization.subscriptionProductId && + FREE_PRODUCT_IDS.includes(organization.subscriptionProductId) + ) { + return ; + } + + if (organization.isExpired) { + return ( + + ); + } + + return ; } diff --git a/apps/start/src/routes/_app.$organizationId.billing.tsx b/apps/start/src/routes/_app.$organizationId.billing.tsx index 7a815739..0dcff45d 100644 --- a/apps/start/src/routes/_app.$organizationId.billing.tsx +++ b/apps/start/src/routes/_app.$organizationId.billing.tsx @@ -1,9 +1,6 @@ import { FullPageEmptyState } from '@/components/full-page-empty-state'; import FullPageLoadingState from '@/components/full-page-loading-state'; import Billing from '@/components/organization/billing'; -import { BillingFaq } from '@/components/organization/billing-faq'; -import CurrentSubscription from '@/components/organization/current-subscription'; -import Usage from '@/components/organization/usage'; import { PageHeader } from '@/components/page-header'; import { useTRPC } from '@/integrations/trpc/react'; import { PAGE_TITLES, createOrganizationTitle } from '@/utils/title'; @@ -22,6 +19,20 @@ export const Route = createFileRoute('/_app/$organizationId/billing')({ ], }; }, + beforeLoad: async ({ params, context }) => { + await Promise.all([ + context.queryClient.prefetchQuery( + context.trpc.subscription.products.queryOptions({ + organizationId: params.organizationId, + }), + ), + context.queryClient.prefetchQuery( + context.trpc.subscription.getCurrent.queryOptions({ + organizationId: params.organizationId, + }), + ), + ]); + }, }); function OrganizationPage() { @@ -51,14 +62,7 @@ function OrganizationPage() { className="mb-8" /> -
-
- - - -
- -
+
); } diff --git a/apps/start/src/routes/_app.$organizationId.tsx b/apps/start/src/routes/_app.$organizationId.tsx index 1dfc783a..9b7c8c6c 100644 --- a/apps/start/src/routes/_app.$organizationId.tsx +++ b/apps/start/src/routes/_app.$organizationId.tsx @@ -1,8 +1,8 @@ import FullPageLoadingState from '@/components/full-page-loading-state'; +import SupporterPrompt from '@/components/organization/supporter-prompt'; import { LinkButton } from '@/components/ui/button'; import { useTRPC } from '@/integrations/trpc/react'; import { cn } from '@/utils/cn'; -import { FREE_PRODUCT_IDS } from '@openpanel/payments'; import { useSuspenseQuery } from '@tanstack/react-query'; import { Outlet, @@ -106,24 +106,9 @@ function Component() { )} - {organization.subscriptionEndsAt && organization.isExpired && ( - - - Reactivate - - - )} {organization.subscriptionEndsAt && organization.isWillBeCanceled && ( )} - {organization.subscriptionProductId && - FREE_PRODUCT_IDS.includes(organization.subscriptionProductId) && ( - - - Upgrade - - - )} + ); } diff --git a/apps/start/src/styles.css b/apps/start/src/styles.css index ea40cab8..616bf314 100644 --- a/apps/start/src/styles.css +++ b/apps/start/src/styles.css @@ -327,4 +327,16 @@ button { .animate-float-2 { animation: float-2 3.5s ease-in-out infinite; animation-delay: 1s; +} + +/* Slow ping animation for success modal */ +@keyframes ping-slow { + 75%, 100% { + transform: scale(1.2); + opacity: 0; + } +} + +.animate-ping-slow { + animation: ping-slow 2s cubic-bezier(0, 0, 0.2, 1) infinite; } \ No newline at end of file diff --git a/packages/db/src/prisma-client.ts b/packages/db/src/prisma-client.ts index 97a2136c..e5900dd6 100644 --- a/packages/db/src/prisma-client.ts +++ b/packages/db/src/prisma-client.ts @@ -117,6 +117,22 @@ const getPrismaClient = () => { return new Date(Date.now() + 1000 * 60 * 60 * 24); }, }, + isActive: { + needs: { + subscriptionStatus: true, + subscriptionEndsAt: true, + subscriptionCanceledAt: true, + }, + compute(org) { + return ( + org.subscriptionStatus === 'active' && + org.subscriptionEndsAt && + org.subscriptionEndsAt > new Date() && + !isCanceled(org) && + !isWillBeCanceled(org) + ); + }, + }, isTrial: { needs: { subscriptionStatus: true, subscriptionEndsAt: true }, compute(org) { diff --git a/packages/payments/src/prices.ts b/packages/payments/src/prices.ts index c1846ab3..00f3675e 100644 --- a/packages/payments/src/prices.ts +++ b/packages/payments/src/prices.ts @@ -1,3 +1,5 @@ +export type { ProductPrice } from '@polar-sh/sdk/models/components/productprice.js'; + export type IPrice = { price: number; events: number; diff --git a/packages/trpc/src/routers/import.ts b/packages/trpc/src/routers/import.ts index d77effa1..a3e7e253 100644 --- a/packages/trpc/src/routers/import.ts +++ b/packages/trpc/src/routers/import.ts @@ -5,7 +5,11 @@ import { importQueue } from '@openpanel/queue'; import { zCreateImport } from '@openpanel/validation'; import { getProjectAccess } from '../access'; -import { TRPCAccessError } from '../errors'; +import { + TRPCAccessError, + TRPCBadRequestError, + TRPCNotFoundError, +} from '../errors'; import { createTRPCRouter, protectedProcedure } from '../trpc'; export const importRouter = createTRPCRouter({ @@ -69,6 +73,28 @@ export const importRouter = createTRPCRouter({ ); } + const organization = await db.organization.findFirst({ + where: { + projects: { + some: { + id: input.projectId, + }, + }, + }, + }); + + if (!organization) { + throw TRPCNotFoundError( + 'Could not start import, organization not found', + ); + } + + if (!organization.isActive) { + throw TRPCBadRequestError( + 'You cannot start an import without an active subscription!', + ); + } + // Create import record const importRecord = await db.import.create({ data: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b6e6377..28941df1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1364,7 +1364,7 @@ importers: packages/sdks/astro: dependencies: '@openpanel/web': - specifier: workspace:1.0.1-local + specifier: workspace:1.0.2-local version: link:../web devDependencies: astro: @@ -1402,10 +1402,10 @@ importers: packages/sdks/nextjs: dependencies: '@openpanel/web': - specifier: workspace:1.0.1-local + specifier: workspace:1.0.2-local version: link:../web next: - specifier: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + specifier: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 version: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -30999,7 +30999,7 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1