diff --git a/.gitignore b/.gitignore
index 41ccab438..f31f951a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,4 +52,8 @@ yarn-error.log*
!.vscode/extensions.json
# logs
-logs.json
\ No newline at end of file
+logs.json
+
+# claude
+.claude
+CLAUDE.md
\ No newline at end of file
diff --git a/apps/remix/app/components/general/billing-plans.tsx b/apps/remix/app/components/general/billing-plans.tsx
index e1c92cc9f..fabfbf6b5 100644
--- a/apps/remix/app/components/general/billing-plans.tsx
+++ b/apps/remix/app/components/general/billing-plans.tsx
@@ -44,12 +44,22 @@ const MotionCard = motion(Card);
export type BillingPlansProps = {
plans: InternalClaimPlans;
+ selectedPlan?: string | null;
+ selectedCycle?: 'monthly' | 'yearly' | null;
+ isFromPricingPage?: boolean;
};
-export const BillingPlans = ({ plans }: BillingPlansProps) => {
+export const BillingPlans = ({
+ plans,
+ selectedPlan,
+ selectedCycle,
+ isFromPricingPage,
+}: BillingPlansProps) => {
const isMounted = useIsMounted();
- const [interval, setInterval] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice');
+ const [interval, setInterval] = useState<'monthlyPrice' | 'yearlyPrice'>(
+ selectedCycle === 'monthly' ? 'monthlyPrice' : 'yearlyPrice',
+ );
const pricesToDisplay = useMemo(() => {
const prices = [];
@@ -85,56 +95,65 @@ export const BillingPlans = ({ plans }: BillingPlansProps) => {
- {pricesToDisplay.map((price) => (
-
-
- {price.product.name}
+ {pricesToDisplay.map((price) => {
+ const planId = price.claim.toLowerCase().replace('claim_', '');
+ const isSelected = selectedPlan && planId === selectedPlan;
-
- {price.friendlyPrice + ' '}
-
- {interval === 'monthlyPrice' ? (
- per month
- ) : (
- per year
- )}
-
-
+ return (
+
+
+ {price.product.name}
-
- {price.product.description}
-
-
- {price.product.features && price.product.features.length > 0 && (
-
-
Includes:
-
-
- {price.product.features.map((feature, index) => (
- -
- {feature.name}
-
- ))}
-
+
+ {price.friendlyPrice + ' '}
+
+ {interval === 'monthlyPrice' ? (
+ per month
+ ) : (
+ per year
+ )}
+
- )}
-
+
+ {price.product.description}
+
-
-
-
- ))}
+ {price.product.features && price.product.features.length > 0 && (
+
+
Includes:
+
+
+ {price.product.features.map((feature, index) => (
+ -
+ {feature.name}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ );
+ })}
@@ -145,13 +164,19 @@ const BillingDialog = ({
priceId,
planName,
claim,
+ isSelected,
+ isFromPricingPage,
+ interval,
}: {
priceId: string;
planName: string;
memberCount: number;
claim: string;
+ isSelected?: boolean;
+ isFromPricingPage?: boolean;
+ interval: 'monthlyPrice' | 'yearlyPrice';
}) => {
- const [isOpen, setIsOpen] = useState(false);
+ const [isOpen, setIsOpen] = useState(isSelected && isFromPricingPage);
const { t } = useLingui();
const { toast } = useToast();
@@ -227,11 +252,13 @@ const BillingDialog = ({
- Subscribe
+
+ Subscribe to {planName} {interval === 'monthlyPrice' ? '(Monthly)' : '(Yearly)'}
+
- You are about to subscribe to the {planName}
+ Choose how to proceed with your subscription
diff --git a/apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx b/apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
index 7820e20a3..ea78bcc1f 100644
--- a/apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
+++ b/apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
@@ -1,6 +1,7 @@
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Loader } from 'lucide-react';
+import { useSearchParams } from 'react-router';
import type Stripe from 'stripe';
import { match } from 'ts-pattern';
@@ -19,9 +20,14 @@ export function meta() {
export default function TeamsSettingBillingPage() {
const { _, i18n } = useLingui();
+ const [searchParams] = useSearchParams();
const organisation = useCurrentOrganisation();
+ const selectedPlan = searchParams.get('plan');
+ const selectedCycle = searchParams.get('cycle') as 'monthly' | 'yearly' | null;
+ const source = searchParams.get('source');
+
const { data: subscriptionQuery, isLoading: isLoadingSubscription } =
trpc.billing.subscription.get.useQuery({
organisationId: organisation.id,
@@ -48,8 +54,21 @@ export default function TeamsSettingBillingPage() {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(stripeSubscription?.items.data[0].price.product as Stripe.Product | undefined)?.name;
+ const isFromPricingPage = source === 'pricing';
+
return (
+ {isFromPricingPage && selectedPlan && !subscription && (
+
+
+
+ Select a plan below to upgrade {organisation.name} to the{' '}
+ {selectedPlan} plan
+
+
+
+ )}
+
@@ -134,7 +153,14 @@ export default function TeamsSettingBillingPage() {
- {!subscription && canManageBilling && }
+ {!subscription && canManageBilling && (
+
+ )}
org.type === 'PERSONAL') || organisations[0];
+ if (personalOrg) {
+ return redirect(`/o/${personalOrg.url}/settings/billing${queryString}`);
+ }
+
+ return redirect('/settings/profile');
+}
+
+export default function BillingRedirect() {
+ return null;
+}