public: updates of content
This commit is contained in:
@@ -34,13 +34,13 @@ const questions = [
|
||||
{
|
||||
question: 'How do I change my billing information?',
|
||||
answer: [
|
||||
'You can change your billing information by clicking the "Manage your subscription" button in the billing section.',
|
||||
'You can change your billing information by clicking the "Customer portal" button in the billing section.',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'We need a custom plan, can you help us?',
|
||||
answer: [
|
||||
'Yes, we can help you with that. Please contact us at hello@openpanel.com to request a quote.',
|
||||
'Yes, we can help you with that. Please contact us at hello@openpanel.dev to request a quote.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -52,13 +52,13 @@ export function BillingFaq() {
|
||||
<span className="title">Frequently asked questions</span>
|
||||
</WidgetHead>
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full max-w-screen-md self-center"
|
||||
collapsible
|
||||
type="single"
|
||||
>
|
||||
{questions.map((q) => (
|
||||
<AccordionItem value={q.question} key={q.question}>
|
||||
<AccordionTrigger className="text-left px-4">
|
||||
<AccordionItem key={q.question} value={q.question}>
|
||||
<AccordionTrigger className="px-4 text-left">
|
||||
{q.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { Button, LinkButton } from '@/components/ui/button';
|
||||
import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { useTRPC } from '@/integrations/trpc/react';
|
||||
import { op } from '@/utils/op';
|
||||
import type { IServiceOrganization } from '@openpanel/db';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
@@ -11,11 +6,17 @@ import {
|
||||
InfinityIcon,
|
||||
type LucideIcon,
|
||||
MapIcon,
|
||||
SearchIcon,
|
||||
ShieldCheckIcon,
|
||||
TrendingUpIcon,
|
||||
} from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { Button, LinkButton } from '@/components/ui/button';
|
||||
import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { useTRPC } from '@/integrations/trpc/react';
|
||||
import { op } from '@/utils/op';
|
||||
|
||||
const COPY = {
|
||||
expired: {
|
||||
@@ -59,7 +60,7 @@ export default function BillingPrompt({
|
||||
const { data: products, isLoading: isLoadingProducts } = useQuery(
|
||||
trpc.subscription.products.queryOptions({
|
||||
organizationId: organization.id,
|
||||
}),
|
||||
})
|
||||
);
|
||||
const checkout = useMutation(
|
||||
trpc.subscription.checkout.mutationOptions({
|
||||
@@ -72,15 +73,14 @@ export default function BillingPrompt({
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
const { title, description, body } = COPY[type];
|
||||
|
||||
const bestProductFit = products?.find(
|
||||
(product) =>
|
||||
typeof product.metadata.eventsLimit === 'number' &&
|
||||
product.metadata.eventsLimit >=
|
||||
organization.subscriptionPeriodEventsCount,
|
||||
product.metadata.eventsLimit >= organization.subscriptionPeriodEventsCount
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -98,32 +98,30 @@ export default function BillingPrompt({
|
||||
}).format(
|
||||
bestProductFit.prices[0] && 'priceAmount' in bestProductFit.prices[0]
|
||||
? bestProductFit.prices[0].priceAmount / 100
|
||||
: 0,
|
||||
: 0
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-20 max-w-7xl mx-auto">
|
||||
<div className="border rounded-lg overflow-hidden bg-def-200 p-2 items-center">
|
||||
<div className="mx-auto max-w-7xl p-4 md:p-20">
|
||||
<div className="items-center overflow-hidden rounded-lg border bg-def-200 p-2">
|
||||
<div className="md:row">
|
||||
<div className="p-6 bg-background rounded-md border col gap-4 flex-1">
|
||||
<PageHeader title={title} description={description} />
|
||||
<div className="col flex-1 gap-4 rounded-md border bg-background p-6">
|
||||
<PageHeader description={description} title={title} />
|
||||
{body.map((paragraph) => (
|
||||
<p key={paragraph}>
|
||||
{paragraph.replace(
|
||||
'{{events}}',
|
||||
number.format(
|
||||
organization.subscriptionPeriodEventsCount ?? 0,
|
||||
),
|
||||
number.format(organization.subscriptionPeriodEventsCount ?? 0)
|
||||
)}
|
||||
</p>
|
||||
))}
|
||||
<div className="col gap-2 mt-auto">
|
||||
<div className="col mt-auto gap-2">
|
||||
{bestProductFit && (
|
||||
<div className="text-sm text-muted-foreground leading-normal">
|
||||
<div className="text-muted-foreground text-sm leading-normal">
|
||||
Based on your usage (
|
||||
{number.format(
|
||||
organization.subscriptionPeriodEventsCount ?? 0,
|
||||
organization.subscriptionPeriodEventsCount ?? 0
|
||||
)}{' '}
|
||||
events) we recommend upgrading <br />
|
||||
to the <strong>{bestProductFit.name}</strong> plan for{' '}
|
||||
@@ -132,9 +130,8 @@ export default function BillingPrompt({
|
||||
)}
|
||||
<div className="col md:row gap-2">
|
||||
<Button
|
||||
size="lg"
|
||||
loading={isLoadingProducts}
|
||||
disabled={!bestProductFit}
|
||||
loading={isLoadingProducts}
|
||||
onClick={() => {
|
||||
if (bestProductFit) {
|
||||
op.track('billing_prompt_upgrade_clicked', {
|
||||
@@ -152,33 +149,34 @@ export default function BillingPrompt({
|
||||
});
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
Upgrade to {price}
|
||||
</Button>
|
||||
<LinkButton
|
||||
size="lg"
|
||||
variant="outline"
|
||||
to="/$organizationId/billing"
|
||||
params={{ organizationId: organization.id }}
|
||||
size="lg"
|
||||
to="/$organizationId/billing"
|
||||
variant="outline"
|
||||
>
|
||||
View pricing
|
||||
</LinkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 flex-1 p-6 gap-4 col min-w-[200px] max-w-[300px]">
|
||||
<div className="col min-w-[200px] max-w-[300px] flex-1 shrink-0 gap-4 p-6">
|
||||
<Point icon={DollarSignIcon}>Plans start at just $2.5/month</Point>
|
||||
<Point icon={InfinityIcon}>
|
||||
Unlimited reports, members and projects
|
||||
</Point>
|
||||
<Point icon={BarChart3Icon}>Advanced funnels and conversions</Point>
|
||||
<Point icon={MapIcon}>Real-time analytics</Point>
|
||||
<Point icon={TrendingUpIcon}>
|
||||
Track KPIs and custom events (revenue soon)
|
||||
</Point>
|
||||
<Point icon={TrendingUpIcon}>Track KPIs and custom events</Point>
|
||||
<Point icon={ShieldCheckIcon}>
|
||||
Privacy-focused and GDPR compliant
|
||||
</Point>
|
||||
<Point icon={DollarSignIcon}>Revenue tracking</Point>
|
||||
<Point icon={SearchIcon}>Google Search Console integration</Point>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,13 +187,16 @@ export default function BillingPrompt({
|
||||
function Point({
|
||||
icon: Icon,
|
||||
children,
|
||||
}: { icon: LucideIcon; children: React.ReactNode }) {
|
||||
}: {
|
||||
icon: LucideIcon;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="row gap-2">
|
||||
<div className="size-6 shrink-0 center-center rounded-full bg-amber-500 text-white">
|
||||
<div className="center-center size-6 shrink-0 rounded-full bg-amber-500 text-white">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
<h3 className="font-medium mt-[1.5px]">{children}</h3>
|
||||
<h3 className="mt-[1.5px] font-medium">{children}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user