From cf49cbcfa3ab411950a3353470cbc5c483034255 Mon Sep 17 00:00:00 2001
From: Emil Widlund
Date: Mon, 5 Feb 2024 15:56:12 +0100
Subject: [PATCH] clients/onboarding: update creator onboarding layout
---
.../(topbar)/overview/CreatorUpsell.tsx | 293 +++++++++---------
1 file changed, 143 insertions(+), 150 deletions(-)
diff --git a/clients/apps/web/src/app/maintainer/[organization]/(topbar)/overview/CreatorUpsell.tsx b/clients/apps/web/src/app/maintainer/[organization]/(topbar)/overview/CreatorUpsell.tsx
index dd3011fa0c..97485a668d 100644
--- a/clients/apps/web/src/app/maintainer/[organization]/(topbar)/overview/CreatorUpsell.tsx
+++ b/clients/apps/web/src/app/maintainer/[organization]/(topbar)/overview/CreatorUpsell.tsx
@@ -1,39 +1,31 @@
-import { AnimatedIconButton } from '@/components/Feed/Posts/Post'
import SubscriptionGroupIcon from '@/components/Subscriptions/SubscriptionGroupIcon'
-import { useAuth, useCurrentOrgAndRepoFromURL } from '@/hooks'
+import { useCurrentOrgAndRepoFromURL } from '@/hooks'
import {
- ArrowForward,
- AttachMoneyOutlined,
- DiamondOutlined,
+ CloseOutlined,
ViewDayOutlined,
+ WifiTetheringOutlined,
} from '@mui/icons-material'
import { Platforms } from '@polar-sh/sdk'
import Link from 'next/link'
import { LogoIcon } from 'polarkit/components/brand'
-import {
- Card,
- CardContent,
- CardFooter,
- CardHeader,
-} from 'polarkit/components/ui/atoms'
-import {
- useOrganizationArticles,
- useSubscriptionBenefits,
- useSubscriptionTiers,
-} from 'polarkit/hooks'
-import { useRef } from 'react'
-import { useHoverDirty } from 'react-use'
-
-import Icon from '@/components/Icons/Icon'
-import { Status } from '@polar-sh/sdk'
-import { ACCOUNT_TYPE_DISPLAY_NAMES, ACCOUNT_TYPE_ICON } from 'polarkit/account'
-import { useAccount } from 'polarkit/hooks'
-
-const useUpsellCards = () => {
- const { org: currentOrg } = useCurrentOrgAndRepoFromURL()
- const { currentUser } = useAuth()
+import { ShadowBoxOnMd } from 'polarkit/components/ui/atoms'
+import { useOrganizationArticles, useSubscriptionTiers } from 'polarkit/hooks'
+import { useCallback, useEffect, useRef, useState } from 'react'
+
+const ONBOARDING_MAP_KEY = 'creator_onboarding'
- const isPersonal = currentOrg?.name === currentUser?.username
+interface OnboardingMap {
+ postCreated: boolean
+ subscriptionTierCreated: boolean
+ polarPageShared: boolean
+}
+
+const useUpsellSteps = () => {
+ const { org: currentOrg } = useCurrentOrgAndRepoFromURL()
+ const [upsellSteps, setUpsellSteps] = useState([])
+ const [onboardingCompletedMap, setOnboardingCompletedMap] = useState<
+ Partial
+ >(JSON.parse(localStorage.getItem(ONBOARDING_MAP_KEY) ?? '{}'))
const { data: tiers, isPending: tiersPending } = useSubscriptionTiers(
currentOrg?.name ?? '',
@@ -43,113 +35,78 @@ const useUpsellCards = () => {
platform: Platforms.GITHUB,
showUnpublished: false,
})
- const { data: benefits, isPending: benefitsPending } =
- useSubscriptionBenefits(currentOrg?.name ?? '')
-
- const { data: organizationAccount, isPending: organizationAccountPending } =
- useAccount(currentOrg?.account_id)
- const { data: personalAccount, isPending: personalAccountPending } =
- useAccount(currentUser?.account_id)
-
- const setupLink = isPersonal
- ? '/finance/account'
- : `/maintainer/${currentOrg?.name}/finance/account`
-
- const currentAccount = isPersonal
- ? organizationAccount || personalAccount
- : organizationAccount
- const isAccountActive =
- currentAccount?.status === Status.UNREVIEWED ||
- currentAccount?.status === Status.ACTIVE
- const isAccountUnderReview = currentAccount?.status === Status.UNDER_REVIEW
-
- const upsellCards: UpsellCardProps[] = []
-
- if (tiersPending || articlesPending || benefitsPending) {
- return upsellCards
- }
-
- if (!currentAccount) {
- upsellCards.push({
- icon: (
-
- ),
- title: 'Setup your payout account',
- description:
- 'Setup your Stripe or Open Collective account to receive payments',
- href: setupLink,
- })
- }
-
- if (currentAccount && !isAccountActive && !isAccountUnderReview) {
- const AccountTypeIcon = ACCOUNT_TYPE_ICON[currentAccount.account_type]
- upsellCards.push({
- icon: } />,
- title: `Continue setting up your ${
- ACCOUNT_TYPE_DISPLAY_NAMES[currentAccount.account_type]
- } account`,
- description: `Continue the setup of your ${
- ACCOUNT_TYPE_DISPLAY_NAMES[currentAccount.account_type]
- } account to receive transfers`,
- href: setupLink,
- })
- }
-
- if (posts?.items?.length === 0) {
- upsellCards.push({
- icon: ,
- title: 'Write your first post',
- description: 'Start engaging with your community by writing a post',
- href: `/maintainer/${currentOrg?.name}/posts/new`,
- })
- }
-
- const individualTiers =
- tiers?.items?.filter((tier) => tier.type === 'individual') ?? []
- const businessTiers =
- tiers?.items?.filter((tier) => tier.type === 'business') ?? []
-
- if (individualTiers.length === 0) {
- upsellCards.push({
- icon: ,
- title: 'Setup an Individual Subscription',
- description:
- 'Allow individuals to obtain a subscription, and give them benefits in return',
- href: `/maintainer/${currentOrg?.name}/subscriptions/tiers/new?type=individual`,
- })
- }
- if (businessTiers.length === 0) {
- upsellCards.push({
- icon: ,
- title: 'Offer a Business Subscription',
- description:
- 'Make it possible for companies to obtain a subscription, and offer benefits in return',
- href: `/maintainer/${currentOrg?.name}/subscriptions/tiers/new?type=business`,
- })
- }
-
- const nonBuiltInBenefits = benefits?.items?.filter(
- (benefit) => benefit.deletable,
+ const handleDismiss = useCallback(
+ (onboardingKey: keyof OnboardingMap) => {
+ setOnboardingCompletedMap((prev) => ({
+ ...prev,
+ [onboardingKey]: true,
+ }))
+ },
+ [setOnboardingCompletedMap],
)
- if (nonBuiltInBenefits?.length === 0) {
- upsellCards.push({
- icon: ,
- title: 'Create a custom Benefit',
- description:
- 'Create a custom benefit like Discord invites, consulting or private access to your repositories',
- href: `/maintainer/${currentOrg?.name}/subscriptions/benefits`,
- })
+ useEffect(() => {
+ const steps: UpsellStepProps[] = []
+
+ if (posts?.items?.length === 0 && !onboardingCompletedMap.postCreated) {
+ steps.push({
+ icon: ,
+ title: 'Write your first post',
+ description:
+ 'Start building a community & newsletter by writing your first post – your hello world on Polar',
+ href: `/maintainer/${currentOrg?.name}/posts/new`,
+ onboardingKey: 'postCreated',
+ onDismiss: handleDismiss,
+ })
+ }
+
+ const nonFreeTiers =
+ tiers?.items?.filter((tier) => tier.type !== 'free') ?? []
+
+ if (
+ nonFreeTiers.length === 0 &&
+ !onboardingCompletedMap.subscriptionTierCreated
+ ) {
+ steps.push({
+ icon: ,
+ title: 'Setup paid subscriptions & membership benefits',
+ description:
+ 'Offer built-in benefits like premium posts, Discord invites, sponsor ads & private GitHub repository access',
+ href: `/maintainer/${currentOrg?.name}/subscriptions/tiers`,
+ onboardingKey: 'subscriptionTierCreated',
+ onDismiss: handleDismiss,
+ })
+ }
+
+ if (!onboardingCompletedMap.polarPageShared) {
+ steps.push({
+ icon: (
+
+ ),
+ title: 'Review & Share your Polar page',
+ description:
+ 'Promote it on social media & GitHub to build free- and paid subscribers',
+ href: `/${currentOrg?.name}`,
+ onboardingKey: 'polarPageShared',
+ onDismiss: handleDismiss,
+ })
+ }
+
+ setUpsellSteps(steps)
+ }, [currentOrg, onboardingCompletedMap, posts, tiers, handleDismiss])
+
+ if (tiersPending || articlesPending) {
+ return []
+ } else {
+ return upsellSteps
}
-
- return upsellCards.slice(0, 3)
}
export const CreatorUpsell = () => {
- const cards = useUpsellCards()
+ const steps = useUpsellSteps()
- if (cards.length < 1) {
+ if (steps.length < 1) {
return null
}
@@ -167,9 +124,9 @@ export const CreatorUpsell = () => {
Polar.
-
- {cards.map((card) => (
-
+
+ {steps.map((card) => (
+
))}
@@ -177,34 +134,70 @@ export const CreatorUpsell = () => {
)
}
-export interface UpsellCardProps {
+export interface UpsellStepProps {
icon: React.ReactNode
title: string
description: string
href: string
+ onboardingKey: keyof OnboardingMap
+ onDismiss: (onboardingKey: keyof OnboardingMap) => void
}
-const UpsellCard = ({ icon, title, description, href }: UpsellCardProps) => {
+const UpsellStep = ({
+ icon,
+ title,
+ description,
+ href,
+ onboardingKey,
+ onDismiss,
+}: UpsellStepProps) => {
const ref = useRef(null)
- const isHovered = useHoverDirty(ref)
+ const [dismissed, setDismissed] = useState(false)
+
+ const handleDismiss = (e: React.MouseEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+
+ const onboardingCompletedMap: Partial = JSON.parse(
+ localStorage.getItem(ONBOARDING_MAP_KEY) ?? '{}',
+ )
+ onboardingCompletedMap[onboardingKey] = true
+ localStorage.setItem(
+ ONBOARDING_MAP_KEY,
+ JSON.stringify(onboardingCompletedMap),
+ )
+
+ setDismissed(true)
+
+ onDismiss(onboardingKey)
+ }
+
+ if (dismissed) {
+ return null
+ }
+
return (
-
-
- {icon}
-
- {title}
- {description}
-
-
-
-
-
-
-
+
+
+
+
{icon}
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+
+
+
)
}