From 927ec86116b36f9e77e24ff54dddcd79c3bbe386 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 18 Jun 2025 06:21:38 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../marketplace-project-index.tsx | 52 ++++++++++++++++--- readme.md | 7 ++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/apps/web/src/app/marketplace/projects/[namespace]/[project-name]/marketplace-project-index.tsx b/apps/web/src/app/marketplace/projects/[namespace]/[project-name]/marketplace-project-index.tsx index 72cac1e3..ad1b1db7 100644 --- a/apps/web/src/app/marketplace/projects/[namespace]/[project-name]/marketplace-project-index.tsx +++ b/apps/web/src/app/marketplace/projects/[namespace]/[project-name]/marketplace-project-index.tsx @@ -1,9 +1,8 @@ 'use client' -import { assert, omit } from '@agentic/platform-core' -import { redirect } from 'next/navigation' -import { useCallback, useEffect } from 'react' -import { useSearchParam } from 'react-use' +import { assert, omit, sanitizeSearchParams } from '@agentic/platform-core' +import { redirect, useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useRef } from 'react' import { useAgentic } from '@/components/agentic-provider' import { LoadingIndicator } from '@/components/loading-indicator' @@ -17,7 +16,10 @@ export function MarketplaceProjectIndex({ projectIdentifier: string }) { const ctx = useAgentic() - const checkoutStatus = useSearchParam('checkout') + const searchParams = useSearchParams() + const checkout = searchParams.get('checkout') + const plan = searchParams.get('plan') + const { data: project, isLoading, @@ -34,6 +36,7 @@ export function MarketplaceProjectIndex({ const onSubscribe = useCallback( async (pricingPlanSlug: string) => { + assert(ctx, 500, 'Agentic context is required') assert(project, 500, 'Project is required') const { lastPublishedDeploymentId } = project assert( @@ -42,6 +45,14 @@ export function MarketplaceProjectIndex({ `Public project "${projectIdentifier}" expected to have a last published deployment, but none found.` ) + if (!ctx.isAuthenticated) { + return redirect( + `/signup?${sanitizeSearchParams({ + next: `/marketplace/projects/${projectIdentifier}?checkout=true&plan=${pricingPlanSlug}` + }).toString()}` + ) + } + let checkoutSession: { url: string; id: string } | undefined try { @@ -61,11 +72,38 @@ export function MarketplaceProjectIndex({ [ctx, projectIdentifier, project] ) + const hasInitializedCheckoutFromSearchParams = useRef(false) + useEffect(() => { - if (ctx && checkoutStatus === 'canceled') { + if (!ctx) return + + if (checkout === 'canceled') { toast('Checkout canceled') + } else if ( + checkout === 'true' && + plan && + project && + !hasInitializedCheckoutFromSearchParams.current + ) { + hasInitializedCheckoutFromSearchParams.current = true + + // Start checkout flow if search params have `?checkout=true&plan={plan}` + // This is to allow unauthenticated users to subscribe to a plan by first + // visiting `/login` or `/signup` and then being redirected to this page + // with the target checkout search params already pre-filled. + // Another use case for this functionality is providing a single link to + // subscribe to a specific project and pricing plan – with the checkout + // details pre-filled. + void onSubscribe(checkout) } - }, [checkoutStatus, ctx]) + }, [ + checkout, + plan, + ctx, + project, + onSubscribe, + hasInitializedCheckoutFromSearchParams + ]) return (
diff --git a/readme.md b/readme.md index d8eb0fb5..a5e26433 100644 --- a/readme.md +++ b/readme.md @@ -19,10 +19,9 @@ - if user is subscribed to a plan, show that plan as selected - handle unauthenticated checkout flow => auth and then redirect to create a checkout session - will need a `redirect` url for `/login` and `/signup` - - `/marketplace/projects/@{projectIdentifier}/checkout?plan={plan}` -- stripe - - stripe checkout - - stripe billing portal + - `/marketplace/projects/@{projectIdentifier}?checkout=true&plan={plan}` + - stripe checkout + - stripe billing portal - **API gateway** - oauth flow - https://docs.scalekit.com/guides/mcp/oauth