Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Checkout components #4862

Merged
merged 19 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ea4eb0b
server/checkout: output Stripe publishable key in payment processor m…
frankie567 Jan 16, 2025
7c4e587
clients: move checkout components to a dedicated package
frankie567 Jan 21, 2025
da5196b
clients/apps/web: implement a context provider to ease mocking
frankie567 Jan 21, 2025
19a6916
clients/web: re-enable Checkout preview in Storefront
frankie567 Jan 22, 2025
0362b28
clients/packages/checkout: fix @polar-sh/ui imports and tweak build
frankie567 Jan 23, 2025
ef306da
clients/examples: add a Next.js example for checkout components
frankie567 Jan 23, 2025
2722c27
clients/packages/checkout: add SSE hooks
frankie567 Jan 24, 2025
047067b
clients/web: simplify and fix checkout pages using Checkout SDK
frankie567 Jan 24, 2025
26ff4a8
clients/packages/checkout: remove onCheckoutConfirmed prop
frankie567 Jan 24, 2025
4330657
clients/web: tweak loading status of checkout submit button
frankie567 Jan 24, 2025
122b768
clients/packages/checkout: fix import casing
frankie567 Jan 24, 2025
1414cad
clients/web: fix import casing
frankie567 Jan 24, 2025
cb62096
clients/web: fix component usage
frankie567 Jan 24, 2025
55c8a00
clients/web: re-add embed confirmed event on checkout
frankie567 Jan 24, 2025
8aaea92
clients/packages/checkout: fix typing in debounce hook
frankie567 Jan 24, 2025
84b73e8
clients/packages/checkout: tweak imports
frankie567 Jan 24, 2025
4d2421b
clients: upgrade @polar-sh/sdk
frankie567 Jan 24, 2025
8d497fb
clients/web: remove Product media slideshow on Checkout info to reduc…
frankie567 Jan 27, 2025
0083cd8
clients/web: chase some Material UI icons to reduce bundle size
frankie567 Jan 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clients/.changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"ignore": [
"web",
"@examples/checkout-embed",
"@examples/checkout-components",
"@polar-sh/api"
]
}
}
6 changes: 6 additions & 0 deletions clients/apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ const nextConfig = {

images: {
remotePatterns: [
{
protocol: process.env.S3_PUBLIC_IMAGES_BUCKET_PROTOCOL || 'http',
hostname: process.env.S3_PUBLIC_IMAGES_BUCKET_HOSTNAME || '127.0.0.1',
port: process.env.S3_PUBLIC_IMAGES_BUCKET_PORT || '9000',
pathname: process.env.S3_PUBLIC_IMAGES_BUCKET_PATHNAME || '**',
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
Expand Down
1 change: 1 addition & 0 deletions clients/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@polar-sh/api": "workspace:*",
"@polar-sh/checkout": "workspace:^",
"@polar-sh/mdx": "workspace:*",
"@polar-sh/sdk": "^0.22.0",
"@polar-sh/ui": "workspace:*",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-toast": "^1.2.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
'use client'

import { Checkout } from '@/components/Checkout/Checkout'
import { CheckoutPublic, Organization } from '@polar-sh/api'
import { useTheme } from 'next-themes'
import Checkout from '@/components/Checkout/Checkout'

export default function ClientPage({
organization,
checkout,
}: {
organization: Organization
checkout: CheckoutPublic
}) {
const { resolvedTheme } = useTheme()
return (
<Checkout
checkout={checkout}
organization={organization}
theme={resolvedTheme as 'light' | 'dark'}
/>
)
const ClientPage = () => {
return <Checkout />
}

export default ClientPage
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import CheckoutProductInfo from '@/components/Checkout/CheckoutProductInfo'
import { getServerURL } from '@/utils/api'
import { getServerSideAPI } from '@/utils/api/serverside'
import { isCrawler } from '@/utils/crawlers'
import { getStorefrontOrNotFound } from '@/utils/storefront'
import { CheckoutPublic } from '@polar-sh/api'
import {
CheckoutFormProvider,
CheckoutProvider,
} from '@polar-sh/checkout/providers'
import type { Metadata } from 'next'
import { headers } from 'next/headers'
import { notFound } from 'next/navigation'
Expand Down Expand Up @@ -65,21 +69,18 @@ export default async function Page({
params: { organization: string; productId: string }
}) {
const api = getServerSideAPI()
const { organization, products } = await getStorefrontOrNotFound(
api,
params.organization,
)
const { products } = await getStorefrontOrNotFound(api, params.organization)
const product = products.find((p) => p.id === params.productId)

if (!product) {
notFound()
}

/* Avoid creating a checkout for crawlers, just render a simple product info page */
/* Avoid creating a checkout for crawlers */
const headersList = headers()
const userAgent = headersList.get('user-agent')
if (userAgent && isCrawler(userAgent)) {
return <CheckoutProductInfo organization={organization} product={product} />
return <></>
}

let checkout: CheckoutPublic
Expand All @@ -93,5 +94,14 @@ export default async function Page({
throw err
}

return <ClientPage checkout={checkout} organization={organization} />
return (
<CheckoutProvider
clientSecret={checkout.client_secret}
serverURL={getServerURL()}
>
<CheckoutFormProvider>
<ClientPage />
</CheckoutFormProvider>
</CheckoutProvider>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const BenefitRow = ({ benefit, organization }: BenefitRowProps) => {
className="flex flex-row items-start gap-x-3 align-middle"
>
<span className="dark:bg-polar-700 flex h-6 w-6 shrink-0 flex-row items-center justify-center rounded-full bg-blue-50 text-2xl text-blue-500 dark:text-white">
{resolveBenefitIcon(benefit, 'inherit', 'h-3 w-3')}
{resolveBenefitIcon(benefit.type, 'h-3 w-3')}
</span>
<span className="text-sm">{benefit.description}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ const BenefitRow = ({ benefit, organization }: BenefitRowProps) => {
className="flex flex-row items-start gap-x-3 align-middle"
>
<span className="dark:bg-polar-700 flex h-6 w-6 shrink-0 flex-row items-center justify-center rounded-full bg-blue-50 text-2xl text-blue-500 dark:text-white">
{resolveBenefitIcon(benefit, 'inherit', 'h-3 w-3')}
{resolveBenefitIcon(benefit.type, 'h-3 w-3')}
</span>
<span className="text-sm">{benefit.description}</span>
</div>
Expand Down
22 changes: 22 additions & 0 deletions clients/apps/web/src/app/checkout/[clientSecret]/ClientPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import Checkout from '@/components/Checkout/Checkout'
import CheckoutLayout from '@/components/Checkout/CheckoutLayout'
import { useCheckout } from '@polar-sh/checkout/providers'

const ClientPage = ({
embed,
theme,
}: {
embed: boolean
theme?: 'light' | 'dark'
}) => {
const { checkout } = useCheckout()
return (
<CheckoutLayout checkout={checkout} embed={embed} theme={theme}>
<Checkout embed={embed} theme={theme} />
</CheckoutLayout>
)
}

export default ClientPage
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CheckoutConfirmation } from '@/components/Checkout/CheckoutConfirmation'
import CheckoutLayout from '@/components/Checkout/CheckoutLayout'
import { getServerSideAPI } from '@/utils/api/serverside'
import { getCheckoutByClientSecret } from '@/utils/checkout'
import { CheckoutStatus } from '@polar-sh/api'
import { redirect } from 'next/navigation'
import { getServerURL } from '@/utils/api'
import { PolarCore } from '@polar-sh/sdk/core'
import { checkoutsCustomClientGet } from '@polar-sh/sdk/funcs/checkoutsCustomClientGet'
import { ResourceNotFound } from '@polar-sh/sdk/models/errors/resourcenotfound'
import { notFound, redirect } from 'next/navigation'

export default async function Page({
params: { clientSecret },
Expand All @@ -16,19 +17,29 @@ export default async function Page({
customer_session_token?: string
}
}) {
const api = getServerSideAPI()
const client = new PolarCore({ serverURL: getServerURL() })
const {
ok,
value: checkout,
error,
} = await checkoutsCustomClientGet(client, { clientSecret })

const checkout = await getCheckoutByClientSecret(api, clientSecret)
if (!ok) {
if (error instanceof ResourceNotFound) {
notFound()
} else {
throw error
}
}

if (checkout.status === CheckoutStatus.OPEN) {
if (checkout.status === 'open') {
redirect(checkout.url)
}

return (
<CheckoutLayout checkout={checkout} embed={embed === 'true'} theme={theme}>
<CheckoutConfirmation
checkout={checkout}
organization={checkout.organization}
customerSessionToken={customer_session_token}
/>
</CheckoutLayout>
Expand Down
53 changes: 34 additions & 19 deletions clients/apps/web/src/app/checkout/[clientSecret]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Checkout } from '@/components/Checkout/Checkout'
import CheckoutLayout from '@/components/Checkout/CheckoutLayout'
import { getServerSideAPI } from '@/utils/api/serverside'
import { getCheckoutByClientSecret } from '@/utils/checkout'
import { CheckoutStatus } from '@polar-sh/api'
import { redirect } from 'next/navigation'
import { getServerURL } from '@/utils/api'
import {
CheckoutFormProvider,
CheckoutProvider,
} from '@polar-sh/checkout/providers'
import { PolarCore } from '@polar-sh/sdk/core'
import { checkoutsCustomClientGet } from '@polar-sh/sdk/funcs/checkoutsCustomClientGet'
import { ResourceNotFound } from '@polar-sh/sdk/models/errors/resourcenotfound'
import { notFound, redirect } from 'next/navigation'
import ClientPage from './ClientPage'

export default async function Page({
params: { clientSecret },
Expand All @@ -16,23 +20,34 @@ export default async function Page({
>
}) {
const embed = _embed === 'true'
const api = getServerSideAPI()
const client = new PolarCore({ serverURL: getServerURL() })

const checkout = await getCheckoutByClientSecret(api, clientSecret)
const {
ok,
value: checkout,
error,
} = await checkoutsCustomClientGet(client, { clientSecret })

if (checkout.status !== CheckoutStatus.OPEN) {
redirect(checkout.success_url)
if (!ok) {
if (error instanceof ResourceNotFound) {
notFound()
} else {
throw error
}
}

if (checkout.status !== 'open') {
redirect(checkout.successUrl)
}

return (
<CheckoutLayout checkout={checkout} embed={embed} theme={theme}>
<Checkout
organization={checkout.organization}
checkout={checkout}
theme={theme}
embed={embed}
prefilledParameters={prefilledParameters}
/>
</CheckoutLayout>
<CheckoutProvider
clientSecret={checkout.clientSecret}
serverURL={getServerURL()}
>
<CheckoutFormProvider prefilledParameters={prefilledParameters}>
<ClientPage theme={theme} embed={embed} />
</CheckoutFormProvider>
</CheckoutProvider>
)
}
10 changes: 7 additions & 3 deletions clients/apps/web/src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import { ThemeProvider } from 'next-themes'
import { usePathname, useSearchParams } from 'next/navigation'
import { NuqsAdapter } from 'nuqs/adapters/next/app'
import PostHog from 'posthog-js-lite'
import { PropsWithChildren, useEffect, useMemo, useState } from 'react'

import { createContext } from 'react'
import {
createContext,
PropsWithChildren,
useEffect,
useMemo,
useState,
} from 'react'

const stub = (): never => {
throw new Error(
Expand Down
2 changes: 1 addition & 1 deletion clients/apps/web/src/components/Benefit/BenefitGrant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export const BenefitGrant = ({ api, benefitGrant }: BenefitGrantProps) => {
<div className="flex flex-row items-center gap-x-4">
<div className="flex flex-row items-center gap-x-2 text-xs text-blue-500 dark:text-white">
<span className="dark:bg-polar-700 flex h-8 w-8 flex-row items-center justify-center rounded-full bg-blue-50 text-sm">
{resolveBenefitIcon(benefit, 'small')}
{resolveBenefitIcon(benefit.type, 'h-3 w-3')}
</span>
</div>
<div className="flex flex-col">
Expand Down
30 changes: 10 additions & 20 deletions clients/apps/web/src/components/Benefit/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,33 @@
import {
CheckOutlined,
FileDownloadOutlined,
GitHub,
KeyOutlined,
WebOutlined,
} from '@mui/icons-material'
import { BenefitBase, BenefitType } from '@polar-sh/api'
import { BenefitType } from '@polar-sh/api'
import { Check, Download, Globe, Key } from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import GitHubIcon from '../Icons/GitHubIcon'

export type CreatableBenefit = BenefitType

export const resolveBenefitCategoryIcon = (
type?: BenefitType,
fontSize: 'small' | 'inherit' | 'large' | 'medium' = 'small',
className?: string,
) => {
const cn = twMerge('h-4 w-4', className)
switch (type) {
case BenefitType.ADS:
return <WebOutlined className={cn} fontSize={fontSize} />
return <Globe className={cn} />
case BenefitType.DISCORD:
return <DiscordIcon className={cn} />
case BenefitType.GITHUB_REPOSITORY:
return <GitHub className={cn} fontSize={fontSize} />
return <GitHubIcon className={cn} />
case BenefitType.DOWNLOADABLES:
return <FileDownloadOutlined className={cn} fontSize={fontSize} />
return <Download className={cn} />
case BenefitType.LICENSE_KEYS:
return <KeyOutlined className={cn} fontSize={fontSize} />
return <Key className={cn} />
default:
return <CheckOutlined className={cn} fontSize={fontSize} />
return <Check className={cn} />
}
}

export const resolveBenefitIcon = (
benefit: BenefitBase,
fontSize: 'small' | 'inherit' | 'large' | 'medium' = 'small',
className?: string,
) => {
return resolveBenefitCategoryIcon(benefit?.type, fontSize, className)
export const resolveBenefitIcon = (type: BenefitType, className?: string) => {
return resolveBenefitCategoryIcon(type, className)
}

export const resolveBenefitTypeDisplayName = (type: BenefitType | 'usage') => {
Expand Down
Loading
Loading