From d270a989ae2e08d3efc735e46a3e83d4793a11b2 Mon Sep 17 00:00:00 2001 From: d-ivashchuk Date: Mon, 15 Apr 2024 14:01:47 +0200 Subject: [PATCH] add onboarding --- package.json | 1 + pnpm-lock.yaml | 70 +++++--- .../(onboarding)/layout.tsx | 31 ++++ .../(onboarding)/onboarding/settings/page.tsx | 30 ++++ .../(onboarding)/onboarding/user/page.tsx | 73 +++++++++ src/app/layout.tsx | 4 +- .../patterns/{layout.tsx => app-shell.tsx} | 13 +- src/components/patterns/avatar-selection.tsx | 152 ++++++++++++++++++ src/server/api/routers/user.ts | 11 +- src/stories/Layout.stories.ts | 6 +- 10 files changed, 358 insertions(+), 33 deletions(-) create mode 100644 src/app/app/(authenticated-routes)/(onboarding)/layout.tsx create mode 100644 src/app/app/(authenticated-routes)/(onboarding)/onboarding/settings/page.tsx create mode 100644 src/app/app/(authenticated-routes)/(onboarding)/onboarding/user/page.tsx rename src/components/patterns/{layout.tsx => app-shell.tsx} (93%) create mode 100644 src/components/patterns/avatar-selection.tsx diff --git a/package.json b/package.json index 8fbfef7..313e38b 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "cmdk": "^1.0.0", "contentlayer": "^0.3.4", "date-fns": "^3.6.0", + "framer-motion": "^11.0.28", "loops": "^1.0.0", "lost-pixel": "^3.16.0", "lucide-react": "^0.363.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6bbc679..5351511 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,16 +91,16 @@ dependencies: version: 2.3.18 '@trpc/client': specifier: next - version: 11.0.0-rc.332(@trpc/server@11.0.0-rc.332) + version: 11.0.0-rc.340(@trpc/server@11.0.0-rc.340) '@trpc/next': specifier: next - version: 11.0.0-rc.332(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.332)(@trpc/react-query@11.0.0-rc.332)(@trpc/server@11.0.0-rc.332)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0) + version: 11.0.0-rc.340(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.340)(@trpc/react-query@11.0.0-rc.340)(@trpc/server@11.0.0-rc.340)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0) '@trpc/react-query': specifier: next - version: 11.0.0-rc.332(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.332)(@trpc/server@11.0.0-rc.332)(react-dom@18.2.0)(react@18.2.0) + version: 11.0.0-rc.340(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.340)(@trpc/server@11.0.0-rc.340)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': specifier: next - version: 11.0.0-rc.332 + version: 11.0.0-rc.340 '@uploadthing/react': specifier: ^6.4.1 version: 6.4.1(next@14.1.4)(react@18.2.0)(solid-js@1.8.16)(uploadthing@6.7.0) @@ -122,6 +122,9 @@ dependencies: date-fns: specifier: ^3.6.0 version: 3.6.0 + framer-motion: + specifier: ^11.0.28 + version: 11.0.28(react-dom@18.2.0)(react@18.2.0) loops: specifier: ^1.0.0 version: 1.0.0 @@ -5168,21 +5171,21 @@ packages: - utf-8-validate dev: false - /@trpc/client@11.0.0-rc.332(@trpc/server@11.0.0-rc.332): - resolution: {integrity: sha512-f9vpTwAZWI5VvbLDq22nzQI94Pe7nlt/SJH2PC/KzOheNoSxm+CoQ1PjriMPUePr69GjxG0OOGH8RDWG+vseKw==} + /@trpc/client@11.0.0-rc.340(@trpc/server@11.0.0-rc.340): + resolution: {integrity: sha512-q2ecZW58LM9pKpzRt+um9Jh33Nmjt7qDZu3xsjMFD61QLE6M/Tply7lC02iqZzrXzTGxR0w2+dLdkkrSlITOKQ==} peerDependencies: - '@trpc/server': 11.0.0-rc.332+67c093749 + '@trpc/server': 11.0.0-rc.340+d1652d1bc dependencies: - '@trpc/server': 11.0.0-rc.332 + '@trpc/server': 11.0.0-rc.340 dev: false - /@trpc/next@11.0.0-rc.332(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.332)(@trpc/react-query@11.0.0-rc.332)(@trpc/server@11.0.0-rc.332)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-TvnPDCNravfskMh3LTgVVonvKdwFkMYCp8ltaiDEMHroNYZKlf5A7nAdKlXG7hk8UYhvTJb7iG2/IRJCy8n3pw==} + /@trpc/next@11.0.0-rc.340(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.340)(@trpc/react-query@11.0.0-rc.340)(@trpc/server@11.0.0-rc.340)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-eMjVxNruE0ZdpoSIuOyjuM38Mf9CgXVgpnbFt+mp+hasJQpUlHIUj+EVuoAs+KqYo+9V/LBqIZNs774vgNf9MQ==} peerDependencies: '@tanstack/react-query': ^5.25.0 - '@trpc/client': 11.0.0-rc.332+67c093749 - '@trpc/react-query': 11.0.0-rc.332+67c093749 - '@trpc/server': 11.0.0-rc.332+67c093749 + '@trpc/client': 11.0.0-rc.340+d1652d1bc + '@trpc/react-query': 11.0.0-rc.340+d1652d1bc + '@trpc/server': 11.0.0-rc.340+d1652d1bc next: '*' react: '>=16.8.0' react-dom: '>=16.8.0' @@ -5193,32 +5196,32 @@ packages: optional: true dependencies: '@tanstack/react-query': 5.28.6(react@18.2.0) - '@trpc/client': 11.0.0-rc.332(@trpc/server@11.0.0-rc.332) - '@trpc/react-query': 11.0.0-rc.332(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.332)(@trpc/server@11.0.0-rc.332)(react-dom@18.2.0)(react@18.2.0) - '@trpc/server': 11.0.0-rc.332 + '@trpc/client': 11.0.0-rc.340(@trpc/server@11.0.0-rc.340) + '@trpc/react-query': 11.0.0-rc.340(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.340)(@trpc/server@11.0.0-rc.340)(react-dom@18.2.0)(react@18.2.0) + '@trpc/server': 11.0.0-rc.340 next: 14.1.4(@babel/core@7.24.3)(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@trpc/react-query@11.0.0-rc.332(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.332)(@trpc/server@11.0.0-rc.332)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-9+jhHg2xLJo3ig3YkgQvHmS1VgcQOXqOQRI23gpXxEfEkuObi0lELowiKFoFI6aua2Tm04tzfD5AWFctJwroDQ==} + /@trpc/react-query@11.0.0-rc.340(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-rc.340)(@trpc/server@11.0.0-rc.340)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3c+KWh3+KN+T0gBKSnxzGfrHlX1NRK5u+v4BqFWLTBg0ADi2eVOgSpTaToF/WEea616kacmPh5ptJeK8SmyBKQ==} peerDependencies: '@tanstack/react-query': ^5.25.0 - '@trpc/client': 11.0.0-rc.332+67c093749 - '@trpc/server': 11.0.0-rc.332+67c093749 + '@trpc/client': 11.0.0-rc.340+d1652d1bc + '@trpc/server': 11.0.0-rc.340+d1652d1bc react: '>=18.2.0' react-dom: '>=18.2.0' dependencies: '@tanstack/react-query': 5.28.6(react@18.2.0) - '@trpc/client': 11.0.0-rc.332(@trpc/server@11.0.0-rc.332) - '@trpc/server': 11.0.0-rc.332 + '@trpc/client': 11.0.0-rc.340(@trpc/server@11.0.0-rc.340) + '@trpc/server': 11.0.0-rc.340 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@trpc/server@11.0.0-rc.332: - resolution: {integrity: sha512-K+cyJm2MmD4uxhfcMH0oHl/KNy8J23h/Bl9nisWOjjtYjNB6fTiO35uTuN8+CkKfXjc+873snT2QyaQXwjp1SA==} + /@trpc/server@11.0.0-rc.340: + resolution: {integrity: sha512-fKzFeAzFbsezC8Jx6gIy4AjlzNvQy4aFc2LT0qIq7hNWYCkuv3gQ7ROgcIF4dUI0rfPC3Gt/y8PQjE+GayAhvA==} dev: false /@tsconfig/node10@1.0.10: @@ -8989,6 +8992,25 @@ packages: engines: {node: '>= 0.6'} dev: true + /framer-motion@11.0.28(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-j/vNYTCH5MX5sY/3dwMs00z1+qAqKX3iIHF762bwqlU814ooD5dDbuj3pA0LmIT5YqyryCkXEb/q+zRblin0lw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.2 + dev: false + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} diff --git a/src/app/app/(authenticated-routes)/(onboarding)/layout.tsx b/src/app/app/(authenticated-routes)/(onboarding)/layout.tsx new file mode 100644 index 0000000..3beecc0 --- /dev/null +++ b/src/app/app/(authenticated-routes)/(onboarding)/layout.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { LogOut } from "lucide-react"; +import React from "react"; +import { Zenitho } from "uvcanvas"; +import { AvatarSelection } from "~/components/patterns/avatar-selection"; +import { Button } from "~/components/ui/button"; +import { signOut } from "next-auth/react"; + +const Layout = ({ children }: { children: React.ReactNode }) => { + return ( +
+ +
+
+ +
+ +
+
{children}
+
+ ); +}; + +export default Layout; diff --git a/src/app/app/(authenticated-routes)/(onboarding)/onboarding/settings/page.tsx b/src/app/app/(authenticated-routes)/(onboarding)/onboarding/settings/page.tsx new file mode 100644 index 0000000..e966501 --- /dev/null +++ b/src/app/app/(authenticated-routes)/(onboarding)/onboarding/settings/page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useSession } from "next-auth/react"; + +import React from "react"; + +import { api } from "~/trpc/react"; + +const Onboarding = () => { + const session = useSession(); + + const getUserQuery = api.user.getUser.useQuery(); + + return ( +
+
+

+ {session.status === "authenticated" + ? `Welcome, ${getUserQuery.data?.name}` + : "Welcome to Pullout.so"} +

+

+ Change your name or avatar so we can personalize your experience +

+
+
+ ); +}; + +export default Onboarding; diff --git a/src/app/app/(authenticated-routes)/(onboarding)/onboarding/user/page.tsx b/src/app/app/(authenticated-routes)/(onboarding)/onboarding/user/page.tsx new file mode 100644 index 0000000..6c1a478 --- /dev/null +++ b/src/app/app/(authenticated-routes)/(onboarding)/onboarding/user/page.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { ChevronRight, Loader2 } from "lucide-react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; + +import React from "react"; + +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { api } from "~/trpc/react"; +import { motion } from "framer-motion"; + +const Onboarding = () => { + const session = useSession(); + const [name, setName] = React.useState(session.data?.user.name ?? ""); + const utils = api.useUtils(); + const getUserQuery = api.user.getUser.useQuery(); + const updateUserMutation = api.user.updateUser.useMutation({ + onSuccess: (data) => { + utils.user.invalidate(); + router.push("/app/onboarding/settings"); + }, + }); + const router = useRouter(); + + return ( +
+ +

+ {session.status === "authenticated" + ? `Welcome, ${name.length > 0 ? name : getUserQuery.data?.name}` + : "Welcome to Pullout.so"} +

+

+ Change your name or avatar so we can personalize your experience +

+
+ + setName(e.target.value)} + /> + + +
+ ); +}; + +export default Onboarding; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4c2cc25..a6d1788 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,7 +4,7 @@ import "~/styles/globals.css"; import { Inter } from "next/font/google"; -import { Layout } from "~/components/patterns/layout"; +import { AppShell } from "~/components/patterns/app-shell"; import Providers from "~/components/providers"; import { Suspense } from "react"; import Script from "next/script"; @@ -31,7 +31,7 @@ export default function RootLayout({ /> - {children} + {children} diff --git a/src/components/patterns/layout.tsx b/src/components/patterns/app-shell.tsx similarity index 93% rename from src/components/patterns/layout.tsx rename to src/components/patterns/app-shell.tsx index 0d00e80..9066190 100644 --- a/src/components/patterns/layout.tsx +++ b/src/components/patterns/app-shell.tsx @@ -18,12 +18,15 @@ import { ColorModeSwitch } from "./color-mode-switch"; import Image from "next/image"; import { usePathname } from "next/navigation"; -export async function Layout({ children }: { children: React.ReactNode }) { +export async function AppShell({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const isInApplicationRoute = pathname.includes("/app"); + const isInOnboardingRoute = pathname.includes("/app/onboarding"); - return ( + return isInOnboardingRoute ? ( +
{children}
+ ) : (
@@ -38,6 +41,12 @@ export async function Layout({ children }: { children: React.ReactNode }) {