Skip to content

Commit

Permalink
feat: add onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
sadmann7 committed May 22, 2024
1 parent ac4e946 commit 2923f4a
Show file tree
Hide file tree
Showing 20 changed files with 505 additions and 137 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"react-medium-image-zoom": "^5.2.4",
"react-syntax-highlighter": "^15.5.0",
"react-textarea-autosize": "^8.5.3",
"react-use-measure": "^2.1.1",
"remark-gfm": "^4.0.0",
"resend": "^3.2.0",
"server-only": "^0.0.1",
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app/(dashboard)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default async function DashboardLayout({
/>
</DashboardSidebar>
<div className="flex flex-col">
<DashboardHeader user={user}>
<DashboardHeader user={user} storeId="storeId">
<DashboardSidebarSheet className="lg:hidden">
<DashboardSidebar storeId="storeId">
<StoreSwitcher
Expand Down
70 changes: 70 additions & 0 deletions src/app/(dashboard)/onboarding/_components/connect-stripe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client"

import React from "react"
import { useRouter } from "next/navigation"
import { motion } from "framer-motion"

import { ConnectStoreToStripeButton } from "@/components/connect-store-to-stripe-button"

interface ConnectStripeProps {
storeId: string | null
}

export function ConnectStripe({ storeId }: ConnectStripeProps) {
const router = useRouter()

React.useEffect(() => {
if (!storeId) {
router.push("/onboarding")
}
}, [router, storeId])

return (
<motion.div
className="flex size-full flex-col items-center justify-center"
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3, type: "spring" }}
>
<motion.div
variants={{
show: {
transition: {
staggerChildren: 0.2,
},
},
}}
initial="hidden"
animate="show"
className="flex flex-col rounded-xl bg-background/60 p-8"
>
<motion.h1
className="mb-4 text-balance text-2xl font-bold transition-colors sm:text-3xl"
variants={{
hidden: { opacity: 0, x: 250 },
show: {
opacity: 1,
x: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
Now connect your store to Stripe
</motion.h1>
{storeId && (
<motion.div
variants={{
hidden: { opacity: 0, x: 100 },
show: {
opacity: 1,
x: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
<ConnectStoreToStripeButton storeId={storeId} />
</motion.div>
)}
</motion.div>
</motion.div>
)
}
110 changes: 110 additions & 0 deletions src/app/(dashboard)/onboarding/_components/create-store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"use client"

import * as React from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { zodResolver } from "@hookform/resolvers/zod"
import { motion } from "framer-motion"
import { useForm } from "react-hook-form"
import { toast } from "sonner"

import { createStore } from "@/lib/actions/store"
import {
createStoreSchema,
type CreateStoreSchema,
} from "@/lib/validations/store"
import { Button } from "@/components/ui/button"
import { Icons } from "@/components/icons"

import { CreateStoreForm } from "../../store/[storeId]/_components/create-store-form"

interface CreateStoreProps {
userId: string
}

export function CreateStore({ userId }: CreateStoreProps) {
const router = useRouter()
const searchParams = useSearchParams()
const [isCreatePending, startCreateTransaction] = React.useTransition()

const form = useForm<CreateStoreSchema>({
resolver: zodResolver(createStoreSchema),
defaultValues: {
name: "",
description: "",
},
})

function onSubmit(input: CreateStoreSchema) {
startCreateTransaction(async () => {
const { data, error } = await createStore({ ...input, userId })

if (error) {
toast.error(error)
return
}

if (data) {
const newSearchParams = new URLSearchParams(searchParams)
newSearchParams.set("step", "connect")
newSearchParams.set("store", data.id)
router.push(`/onboarding?${newSearchParams.toString()}`)
}

form.reset()
})
}

return (
<motion.div
className="my-auto flex size-full flex-col items-center justify-center"
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3, type: "spring" }}
>
<motion.div
variants={{
show: {
transition: {
staggerChildren: 0.2,
},
},
}}
initial="hidden"
animate="show"
className="flex flex-col rounded-xl bg-background/60 p-8"
>
<motion.h1
className="mb-4 text-balance text-2xl font-bold transition-colors sm:text-3xl"
variants={{
hidden: { opacity: 0, x: 250 },
show: {
opacity: 1,
x: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
Let&apos;s start by creating your store
</motion.h1>
<motion.div
variants={{
hidden: { opacity: 0, x: 100 },
show: {
opacity: 1,
x: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
<CreateStoreForm form={form} onSubmit={onSubmit}>
<Button type="submit" disabled={isCreatePending}>
{isCreatePending && (
<Icons.spinner className="mr-2 size-4 animate-spin" />
)}
Create store
</Button>
</CreateStoreForm>
</motion.div>
</motion.div>
</motion.div>
)
}
79 changes: 79 additions & 0 deletions src/app/(dashboard)/onboarding/_components/intro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client"

import { useRouter } from "next/navigation"
import { motion } from "framer-motion"

import { useDebounce } from "@/hooks/use-debounce"
import { Button } from "@/components/ui/button"

export function Intro() {
const router = useRouter()

const showText = useDebounce(true, 800)

return (
<motion.div
className="flex size-full flex-col items-center justify-center"
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3, type: "spring" }}
>
{showText && (
<motion.div
variants={{
show: {
transition: {
staggerChildren: 0.2,
},
},
}}
initial="hidden"
animate="show"
className="mx-5 flex flex-col items-center space-y-2.5 text-center sm:mx-auto"
>
<motion.h1
className="text-balance text-4xl font-bold transition-colors sm:text-5xl"
variants={{
hidden: { opacity: 0, y: 50 },
show: {
opacity: 1,
y: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
Welcome to Skateshop
</motion.h1>
<motion.p
className="max-w-md text-muted-foreground transition-colors sm:text-lg"
variants={{
hidden: { opacity: 0, y: 50 },
show: {
opacity: 1,
y: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
Get started with your new store in just a few steps and start
selling your products online.
</motion.p>
<motion.div
className="pt-4"
variants={{
hidden: { opacity: 0, y: 50 },
show: {
opacity: 1,
y: 0,
transition: { duration: 0.4, type: "spring" },
},
}}
>
<Button onClick={() => router.push("/onboarding?step=create")}>
Get started
</Button>
</motion.div>
</motion.div>
)}
</motion.div>
)
}
30 changes: 30 additions & 0 deletions src/app/(dashboard)/onboarding/_components/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @see https://github.com/juliusmarminge/acme-corp/blob/main/apps/nextjs/src/app/(dashboard)/onboarding/multi-step-form.tsx

"use client"

import { useSearchParams } from "next/navigation"
import { AnimatePresence } from "framer-motion"

import { ConnectStripe } from "./connect-stripe"
import { CreateStore } from "./create-store"
import { Intro } from "./intro"

interface OnboardingProps {
userId: string
}

export function Onboarding({ userId }: OnboardingProps) {
const search = useSearchParams()
const step = search.get("step")
const storeId = search.get("store")

return (
<div className="mx-auto flex h-[calc(100vh-14rem)] w-full max-w-screen-sm flex-col items-center">
<AnimatePresence mode="wait">
{!step && <Intro key="intro" />}
{step === "create" && <CreateStore userId={userId} />}
{step === "connect" && <ConnectStripe storeId={storeId} />}
</AnimatePresence>
</div>
)
}
26 changes: 26 additions & 0 deletions src/app/(dashboard)/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type Metadata } from "next"
import { redirect } from "next/navigation"
import { auth } from "@clerk/nextjs/server"

import { Shell } from "@/components/shell"

import { Onboarding } from "./_components/onboarding"

export const metadata: Metadata = {
title: "Onboarding",
description: "Get started with your new store",
}

export default function OnboardingPage() {
const { userId } = auth()

if (!userId) {
redirect("/signin")
}

return (
<Shell>
<Onboarding userId={userId} />
</Shell>
)
}
Loading

0 comments on commit 2923f4a

Please sign in to comment.