diff --git a/web/app/actions.ts b/web/app/actions.ts index 7431453f..14795825 100644 --- a/web/app/actions.ts +++ b/web/app/actions.ts @@ -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 +} diff --git a/web/app/layout.tsx b/web/app/layout.tsx index b9dd669c..785a5550 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -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({ {children} + diff --git a/web/components/custom/learn-anything-onboarding.tsx b/web/components/custom/learn-anything-onboarding.tsx new file mode 100644 index 00000000..619d9a5a --- /dev/null +++ b/web/components/custom/learn-anything-onboarding.tsx @@ -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 ( + + + + +

Welcome to Learn Anything!

+
+
+ + + {isExisting && ( + <> +

Existing Customer Notice

+

+ 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{" "} + $3 price for our upcoming pro version. Thank you for your support! +

+ + )} +

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

+

Try do these quick onboarding steps to get a feel for the product:

+
    +
  • Create your first page
  • +
  • Add a link to a resource
  • +
  • Update your learning status on a topic
  • +
+

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

+
+ + + Close + Get Started + +
+
+ ) +} + +export default LearnAnythingOnboarding diff --git a/web/components/ui/alert-dialog.tsx b/web/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..10a573a4 --- /dev/null +++ b/web/components/ui/alert-dialog.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel +} diff --git a/web/package.json b/web/package.json index c431e0cc..4aeca69c 100644 --- a/web/package.json +++ b/web/package.json @@ -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",