-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Brief my meeting onboarding #1138
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
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
73298f8
add brief onboarding page
elie222 165de80
add core page
elie222 acb4215
add brief my meeting onboarding steps
elie222 ae28f6d
extract pricing toggle
elie222 1399fe5
stripe for bmm
elie222 f2aafdf
undo pricing edit
elie222 ef0f8bd
fix
elie222 cfc4d7c
remove comments
elie222 e1c788d
fix comment
elie222 38460cd
remove use effect
elie222 e47f628
fix styling
elie222 86ba4a7
make sure we continue onboarding flow after calendar connect
elie222 bc0e756
fixes
elie222 4e8527f
removed unused import
elie222 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
apps/web/app/(app)/[emailAccountId]/onboarding-brief/MeetingBriefsOnboardingContent.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| "use client"; | ||
|
|
||
| import { useCallback } from "react"; | ||
| import { useRouter } from "next/navigation"; | ||
| import { StepConnectCalendar } from "./StepConnectCalendar"; | ||
| import { StepSendTestBrief } from "./StepSendTestBrief"; | ||
| import { StepReady } from "./StepReady"; | ||
| import { prefixPath } from "@/utils/path"; | ||
| import { useAccount } from "@/providers/EmailAccountProvider"; | ||
| import { OnboardingWrapper } from "@/app/(app)/[emailAccountId]/onboarding/OnboardingWrapper"; | ||
|
|
||
| const TOTAL_STEPS = 3; | ||
|
|
||
| interface MeetingBriefsOnboardingContentProps { | ||
| step: number; | ||
| } | ||
|
|
||
| export function MeetingBriefsOnboardingContent({ | ||
| step, | ||
| }: MeetingBriefsOnboardingContentProps) { | ||
| const { emailAccountId } = useAccount(); | ||
| const router = useRouter(); | ||
|
|
||
| const clampedStep = Math.min(Math.max(step, 1), TOTAL_STEPS); | ||
|
elie222 marked this conversation as resolved.
|
||
|
|
||
| const onNext = useCallback(async () => { | ||
| if (clampedStep < TOTAL_STEPS) { | ||
| const nextStep = clampedStep + 1; | ||
| router.push( | ||
| prefixPath(emailAccountId, `/onboarding-brief?step=${nextStep}`), | ||
| ); | ||
| } | ||
| }, [router, emailAccountId, clampedStep]); | ||
|
|
||
| return ( | ||
| <OnboardingWrapper> | ||
| {clampedStep === 1 && <StepConnectCalendar onNext={onNext} />} | ||
| {clampedStep === 2 && <StepSendTestBrief onNext={onNext} />} | ||
| {clampedStep === 3 && <StepReady />} | ||
| </OnboardingWrapper> | ||
| ); | ||
| } | ||
57 changes: 57 additions & 0 deletions
57
apps/web/app/(app)/[emailAccountId]/onboarding-brief/StepConnectCalendar.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| "use client"; | ||
|
|
||
| import { Calendar, CheckIcon } from "lucide-react"; | ||
| import { PageHeading, TypographyP } from "@/components/Typography"; | ||
| import { useCalendars } from "@/hooks/useCalendars"; | ||
| import { IconCircle } from "@/app/(app)/[emailAccountId]/onboarding/IconCircle"; | ||
| import { ConnectCalendar } from "@/app/(app)/[emailAccountId]/calendars/ConnectCalendar"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { useAccount } from "@/providers/EmailAccountProvider"; | ||
| import { prefixPath } from "@/utils/path"; | ||
|
|
||
| export function StepConnectCalendar({ onNext }: { onNext: () => void }) { | ||
| const { emailAccountId } = useAccount(); | ||
| const { data: calendarsData } = useCalendars(); | ||
|
|
||
| const hasCalendarConnected = | ||
|
elie222 marked this conversation as resolved.
|
||
| calendarsData?.connections && calendarsData.connections.length > 0; | ||
|
|
||
| return ( | ||
| <> | ||
| <div className="flex justify-center"> | ||
| <IconCircle size="lg"> | ||
| <Calendar className="size-6" /> | ||
| </IconCircle> | ||
| </div> | ||
|
|
||
| <div className="text-center"> | ||
| <PageHeading className="mt-4">Connect Your Calendar</PageHeading> | ||
| <TypographyP className="mt-2 max-w-lg mx-auto"> | ||
| We'll automatically detect your upcoming meetings with external guests | ||
| and prepare personalized briefings. | ||
| </TypographyP> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col items-center justify-center mt-8 gap-4"> | ||
| {hasCalendarConnected ? ( | ||
| <> | ||
| <div className="flex items-center gap-2 text-green-600 font-medium animate-in fade-in zoom-in duration-300"> | ||
| <CheckIcon className="h-5 w-5" /> | ||
| Calendar Connected! | ||
| </div> | ||
| <Button onClick={onNext} className="mt-2"> | ||
| Continue | ||
| </Button> | ||
| </> | ||
| ) : ( | ||
| <ConnectCalendar | ||
| onboardingReturnPath={prefixPath( | ||
| emailAccountId, | ||
| "/onboarding-brief?step=2", | ||
| )} | ||
| /> | ||
| )} | ||
| </div> | ||
| </> | ||
| ); | ||
| } | ||
163 changes: 163 additions & 0 deletions
163
apps/web/app/(app)/[emailAccountId]/onboarding-brief/StepReady.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
| import Link from "next/link"; | ||
| import { | ||
| Sparkles, | ||
| CheckIcon, | ||
| ChevronRightIcon, | ||
| ExternalLinkIcon, | ||
| } from "lucide-react"; | ||
| import { PageHeading, TypographyP } from "@/components/Typography"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { CardBasic } from "@/components/ui/card"; | ||
| import { IconCircle } from "@/app/(app)/[emailAccountId]/onboarding/IconCircle"; | ||
| import { getGmailBasicSearchUrl } from "@/utils/url"; | ||
| import { useAccount } from "@/providers/EmailAccountProvider"; | ||
| import { isGoogleProvider } from "@/utils/email/provider-types"; | ||
| import { | ||
| PricingFrequencyToggle, | ||
| frequencies, | ||
| DiscountBadge, | ||
| } from "@/app/(app)/premium/PricingFrequencyToggle"; | ||
| import { | ||
| BRIEF_MY_MEETING_PRICE_ID_MONTHLY, | ||
| BRIEF_MY_MEETING_PRICE_ID_ANNUALLY, | ||
| } from "@/app/(app)/premium/config"; | ||
| import { generateCheckoutSessionAction } from "@/utils/actions/premium"; | ||
| import { toastError } from "@/components/Toast"; | ||
|
|
||
| const PRICING_FEATURES = [ | ||
| "Briefs for every external meeting", | ||
| "Google Calendar & Outlook", | ||
| "LinkedIn & web research", | ||
| "Sent 1-24 hours before (you choose)", | ||
| ]; | ||
|
|
||
| export function StepReady() { | ||
| const { emailAccount } = useAccount(); | ||
| const [frequency, setFrequency] = useState(frequencies[1]); | ||
| const [loading, setLoading] = useState(false); | ||
|
|
||
| async function handleCheckout() { | ||
| setLoading(true); | ||
| try { | ||
| const tier = | ||
| frequency.value === "annually" | ||
| ? "BUSINESS_ANNUALLY" | ||
| : "BUSINESS_MONTHLY"; | ||
| const priceId = | ||
| frequency.value === "annually" | ||
| ? BRIEF_MY_MEETING_PRICE_ID_ANNUALLY | ||
| : BRIEF_MY_MEETING_PRICE_ID_MONTHLY; | ||
|
|
||
| const result = await generateCheckoutSessionAction({ tier, priceId }); | ||
|
|
||
| if (!result?.data?.url) { | ||
| toastError({ description: "Error creating checkout session" }); | ||
| return; | ||
| } | ||
|
|
||
| window.location.href = result.data.url; | ||
| } catch { | ||
| toastError({ description: "Error creating checkout session" }); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <div className="flex justify-center"> | ||
| <IconCircle size="lg"> | ||
| <Sparkles className="size-6" /> | ||
| </IconCircle> | ||
| </div> | ||
|
|
||
| <div className="text-center"> | ||
| <PageHeading className="mt-4"> | ||
| Ready to walk into every meeting prepared? | ||
| </PageHeading> | ||
| <TypographyP className="mt-2 max-w-lg mx-auto"> | ||
| You'll get a brief like this before every external meeting, | ||
| automatically. | ||
| </TypographyP> | ||
| </div> | ||
|
|
||
| <div className="mt-8 flex flex-col items-center"> | ||
| <PricingFrequencyToggle | ||
| frequency={frequency} | ||
| setFrequency={setFrequency} | ||
| > | ||
| <div className="ml-1"> | ||
| <DiscountBadge>2 months free!</DiscountBadge> | ||
| </div> | ||
| </PricingFrequencyToggle> | ||
|
|
||
| <CardBasic className="mt-4 p-6 w-full"> | ||
| <div className="flex items-center justify-between mb-5"> | ||
| <div> | ||
| <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground"> | ||
| Meeting Briefs Pro | ||
| </p> | ||
| <p className="text-3xl font-bold text-foreground mt-1"> | ||
| ${frequency.value === "annually" ? "7.50" : "9"} | ||
| <span className="text-base font-normal text-muted-foreground"> | ||
| /month | ||
| </span> | ||
| </p> | ||
| <p className="text-sm text-muted-foreground mt-1"> | ||
| {frequency.value === "annually" | ||
| ? "billed annually ($90/year)" | ||
| : "billed monthly"} | ||
| </p> | ||
| </div> | ||
| <div className="rounded-full border border-green-200 bg-green-50 px-3 py-1.5 text-sm font-semibold text-green-700"> | ||
| 7-day free trial | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-2.5"> | ||
| {PRICING_FEATURES.map((feature) => ( | ||
| <div key={feature} className="flex items-center gap-2.5"> | ||
| <div className="flex h-5 w-5 items-center justify-center rounded-full bg-green-50"> | ||
| <CheckIcon className="h-3 w-3 text-green-600" /> | ||
| </div> | ||
| <span className="text-foreground">{feature}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </CardBasic> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-3 mt-8"> | ||
| <Button | ||
| size="lg" | ||
| className="w-full" | ||
| onClick={handleCheckout} | ||
| loading={loading} | ||
| > | ||
| Start Free Trial | ||
| <ChevronRightIcon className="ml-2 h-4 w-4" /> | ||
| </Button> | ||
|
elie222 marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| {emailAccount?.email && | ||
| isGoogleProvider(emailAccount?.account?.provider) && ( | ||
| <Button variant="outline" size="lg" className="w-full" asChild> | ||
| <Link | ||
| href={getGmailBasicSearchUrl( | ||
| emailAccount.email, | ||
| "from:(getinboxzero.com) subject:(Briefing for)", | ||
| )} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| <ExternalLinkIcon className="mr-2 h-4 w-4" /> | ||
| View test brief in Gmail | ||
| </Link> | ||
| </Button> | ||
| )} | ||
| </div> | ||
| </> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.