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

+
+
+
+ +
+ +
) }