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; +}