feat: onboarding for existing users (#160)

* feat: onboarding for existing users

* fix: escape character

* chore: update msg
This commit is contained in:
Aslam
2024-09-10 22:05:28 +07:00
committed by GitHub
parent 00a2a53781
commit 0668dd5625
5 changed files with 219 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
"use server"
import { authedProcedure } from "@/lib/utils/auth-procedure"
import { currentUser } from "@clerk/nextjs/server"
import { get } from "ronin"
import { create } from "ronin"
import { z } from "zod"
import { ZSAError } from "zsa"
@@ -68,3 +70,9 @@ export const storeImage = authedProcedure
throw new ZSAError("ERROR", "Failed to store image")
}
})
export const isExistingUser = async () => {
const clerkUser = await currentUser()
const roninUser = await get.existingStripeSubscriber.with({ email: clerkUser?.emailAddresses[0].emailAddress })
return clerkUser?.emailAddresses[0].emailAddress === roninUser?.email
}

View File

@@ -10,6 +10,7 @@ import { DeepLinkProvider } from "@/lib/providers/deep-link-provider"
import { GeistMono, GeistSans } from "./fonts"
import { JazzAndAuth } from "@/lib/providers/jazz-provider"
import { TooltipProvider } from "@/components/ui/tooltip"
import { LearnAnythingOnboarding } from "@/components/custom/learn-anything-onboarding"
export const metadata: Metadata = {
title: "Learn Anything",
@@ -42,6 +43,7 @@ export default function RootLayout({
<body className={cn("h-full w-full font-sans antialiased", GeistSans.variable, GeistMono.variable)}>
<Providers>
{children}
<LearnAnythingOnboarding />
<Toaster expand={false} />
</Providers>
</body>

View File

@@ -0,0 +1,102 @@
"use client"
import React, { useEffect, useState } from "react"
import { atom, useAtom } from "jotai"
import { atomWithStorage } from "jotai/utils"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from "@/components/ui/alert-dialog"
import { isExistingUser } from "@/app/actions"
import { usePathname } from "next/navigation"
const hasVisitedAtom = atomWithStorage("hasVisitedLearnAnything", false)
const isDialogOpenAtom = atom(true)
export function LearnAnythingOnboarding() {
const pathname = usePathname()
const [hasVisited, setHasVisited] = useAtom(hasVisitedAtom)
const [isOpen, setIsOpen] = useAtom(isDialogOpenAtom)
const [isFetching, setIsFetching] = useState(true)
const [isExisting, setIsExisting] = useState(false)
if (pathname === "/") return null
useEffect(() => {
const loadUser = async () => {
try {
const existingUser = await isExistingUser()
setIsExisting(existingUser)
setIsOpen(true)
} catch (error) {
console.error("Error loading user:", error)
} finally {
setIsFetching(false)
}
}
if (!hasVisited) {
loadUser()
}
}, [hasVisited, setIsOpen])
const handleClose = () => {
setIsOpen(false)
setHasVisited(true)
}
if (hasVisited || isFetching) return null
return (
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogContent className="max-w-xl">
<AlertDialogHeader>
<AlertDialogTitle>
<h1 className="text-2xl font-bold">Welcome to Learn Anything!</h1>
</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogDescription className="text-foreground/70 space-y-4 text-base leading-5">
{isExisting && (
<>
<p className="font-medium">Existing Customer Notice</p>
<p>
We noticed you are an existing Learn Anything customer. We sincerely apologize for any broken experience
you may have encountered on the old website. We've been working hard on this new version, which
addresses previous issues and offers more features. As an early customer, you're locked in at the{" "}
<strong>$3</strong> price for our upcoming pro version. Thank you for your support!
</p>
</>
)}
<p>
Learn Anything is a learning platform that organizes knowledge in a social way. You can create pages, add
links, track learning status of any topic, and more things in the future.
</p>
<p>Try do these quick onboarding steps to get a feel for the product:</p>
<ul className="list-inside list-disc">
<li>Create your first page</li>
<li>Add a link to a resource</li>
<li>Update your learning status on a topic</li>
</ul>
<p>
If you have any questions, don't hesitate to reach out. Click on question mark button in the bottom right
corner and enter your message.
</p>
</AlertDialogDescription>
<AlertDialogFooter className="mt-4">
<AlertDialogCancel onClick={handleClose}>Close</AlertDialogCancel>
<AlertDialogAction onClick={handleClose}>Get Started</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
export default LearnAnythingOnboarding

View File

@@ -0,0 +1,106 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description ref={ref} className={cn("text-muted-foreground text-sm", className)} {...props} />
))
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel
}

View File

@@ -17,6 +17,7 @@
"@nothing-but/utils": "^0.16.0",
"@omit/react-confirm-dialog": "^1.1.5",
"@omit/react-fancy-switch": "^0.1.3",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.1",