From e405fb80f6277f9ccd017edbd06f5f534f3c9187 Mon Sep 17 00:00:00 2001 From: Tyler Myracle Date: Fri, 12 Jan 2024 23:46:54 -0600 Subject: [PATCH] add back onboarding --- apps/client/components/APM.tsx | 12 ++--- apps/client/pages/_app.tsx | 49 +++++++++---------- apps/client/pages/settings.tsx | 1 - apps/server/src/app/routes/users.router.ts | 9 ++++ .../features/src/account/AccountMenu.tsx | 11 +++-- .../features/src/layout/DesktopLayout.tsx | 9 ++-- .../onboarding/sidebar/SidebarOnboarding.tsx | 2 +- .../steps/setup/EmailVerification.tsx | 14 +++--- libs/client/shared/src/api/useUserApi.ts | 21 +++++++- .../design-system/src/lib/Listbox/Listbox.tsx | 7 ++- libs/design-system/src/lib/Menu/Menu.tsx | 5 +- libs/server/features/src/user/user.service.ts | 9 +++- 12 files changed, 96 insertions(+), 53 deletions(-) diff --git a/apps/client/components/APM.tsx b/apps/client/components/APM.tsx index 94e858c3..f043261d 100644 --- a/apps/client/components/APM.tsx +++ b/apps/client/components/APM.tsx @@ -1,19 +1,19 @@ -import { useAuth0 } from '@auth0/auth0-react' import { useEffect } from 'react' import * as Sentry from '@sentry/react' +import { useSession } from 'next-auth/react' export default function APM() { - const { user } = useAuth0() + const { data: session } = useSession() // Identify Sentry user useEffect(() => { - if (user) { + if (session && session.user) { Sentry.setUser({ - id: user.sub, - email: user.email, + id: session.user['sub'] ?? undefined, + email: session.user['https://maybe.co'] ?? undefined, }) } - }, [user]) + }, [session]) return null } diff --git a/apps/client/pages/_app.tsx b/apps/client/pages/_app.tsx index bdac22af..9b608036 100644 --- a/apps/client/pages/_app.tsx +++ b/apps/client/pages/_app.tsx @@ -8,9 +8,8 @@ import { ErrorFallback, LogProvider, UserAccountContextProvider, - AuthProvider, } from '@maybe-finance/client/shared' -import { AccountsManager } from '@maybe-finance/client/features' +import { AccountsManager, OnboardingGuard } from '@maybe-finance/client/features' import { AccountContextProvider } from '@maybe-finance/client/shared' import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' @@ -46,16 +45,18 @@ const WithAuth = function ({ children }: PropsWithChildren) { if (session) { return ( - - - - {children} + + + + + {children} - {/* Add, edit, delete connections and manual accounts */} - - - - + {/* Add, edit, delete connections and manual accounts */} + + + + + ) } return null @@ -84,20 +85,18 @@ export default function App({ - - - - <> - - {Page.isPublic === true ? ( - getLayout() - ) : ( - {getLayout()} - )} - - - - + + + <> + + {Page.isPublic === true ? ( + getLayout() + ) : ( + {getLayout()} + )} + + + diff --git a/apps/client/pages/settings.tsx b/apps/client/pages/settings.tsx index e3e8e2de..581a937b 100644 --- a/apps/client/pages/settings.tsx +++ b/apps/client/pages/settings.tsx @@ -3,7 +3,6 @@ import { useQueryParam } from '@maybe-finance/client/shared' import { AccountSidebar, BillingPreferences, - GeneralPreferences, SecurityPreferences, UserDetails, WithSidebarLayout, diff --git a/apps/server/src/app/routes/users.router.ts b/apps/server/src/app/routes/users.router.ts index f009fea2..ef9bf4bc 100644 --- a/apps/server/src/app/routes/users.router.ts +++ b/apps/server/src/app/routes/users.router.ts @@ -105,6 +105,15 @@ router.put( }) ) +router.get( + '/auth-profile', + endpoint.create({ + resolve: async ({ ctx }) => { + return ctx.userService.getAuthProfile(ctx.user!.id) + }, + }) +) + // TODO: Remove this endpoint router.get( '/auth0-profile', diff --git a/libs/client/features/src/account/AccountMenu.tsx b/libs/client/features/src/account/AccountMenu.tsx index a7b88e44..358b95a0 100644 --- a/libs/client/features/src/account/AccountMenu.tsx +++ b/libs/client/features/src/account/AccountMenu.tsx @@ -1,16 +1,21 @@ import type { SharedType } from '@maybe-finance/shared' -import { BrowserUtil, useAccountApi, useAccountContext } from '@maybe-finance/client/shared' +import { + BrowserUtil, + useAccountApi, + useAccountContext, + useUserApi, +} from '@maybe-finance/client/shared' import { Menu } from '@maybe-finance/design-system' import { RiDeleteBin5Line, RiPencilLine, RiRefreshLine } from 'react-icons/ri' import { useRouter } from 'next/router' -import { useAuth0 } from '@auth0/auth0-react' type Props = { account?: SharedType.AccountDetail } export function AccountMenu({ account }: Props) { - const { user } = useAuth0() + const { useProfile } = useUserApi() + const user = useProfile() const { editAccount, deleteAccount } = useAccountContext() const { useSyncAccount } = useAccountApi() diff --git a/libs/client/features/src/layout/DesktopLayout.tsx b/libs/client/features/src/layout/DesktopLayout.tsx index cee54604..e61fbdac 100644 --- a/libs/client/features/src/layout/DesktopLayout.tsx +++ b/libs/client/features/src/layout/DesktopLayout.tsx @@ -22,9 +22,9 @@ import { RiArrowRightSLine, } from 'react-icons/ri' import { Button, Tooltip } from '@maybe-finance/design-system' -import { useAuth0 } from '@auth0/auth0-react' import { MenuPopover } from './MenuPopover' import { SidebarOnboarding } from '../onboarding' +import { useSession } from 'next-auth/react' export interface DesktopLayoutProps { sidebar: React.ReactNode @@ -93,7 +93,8 @@ export function DesktopLayout({ children, sidebar }: DesktopLayoutProps) { const [onboardingExpanded, setOnboardingExpanded] = useState(false) const { popoutContents, close: closePopout } = usePopoutContext() - const { user } = useAuth0() + const { data: session } = useSession() + const user = session!.user const { useOnboarding, useUpdateOnboarding } = useUserApi() const onboarding = useOnboarding('sidebar') const updateOnboarding = useUpdateOnboarding() @@ -268,8 +269,8 @@ export function DesktopLayout({ children, sidebar }: DesktopLayoutProps) { )} } - name={user?.name} - email={user?.email} + name={user?.name ?? ''} + email={user?.email ?? ''} > {sidebar} diff --git a/libs/client/features/src/onboarding/sidebar/SidebarOnboarding.tsx b/libs/client/features/src/onboarding/sidebar/SidebarOnboarding.tsx index 07ddc24a..5d8b8e75 100644 --- a/libs/client/features/src/onboarding/sidebar/SidebarOnboarding.tsx +++ b/libs/client/features/src/onboarding/sidebar/SidebarOnboarding.tsx @@ -237,7 +237,7 @@ export function SidebarOnboarding({ onClose, onHide }: Props) { const description = getDescriptionComponent(step.key) return ( - + {({ open }) => (
{ - if (data.email_verified) { + if (data.emailVerified) { emailVerified.current = true } }, @@ -70,7 +70,7 @@ export function EmailVerification({ title, onNext }: StepProps) { 'linear-gradient(180deg, rgba(35, 36, 40, 0.2) 0%, rgba(68, 71, 76, 0.2) 100%)', }} > - {profile.data?.email_verified ? ( + {profile.data?.emailVerified ? ( ) : ( @@ -78,10 +78,10 @@ export function EmailVerification({ title, onNext }: StepProps) {

- {profile.data?.email_verified ? 'Email verified' : title} + {profile.data?.emailVerified ? 'Email verified' : title}

- {profile.data?.email_verified ? ( + {profile.data?.emailVerified ? (

You have successfully verified{' '} {profile.data?.email ?? 'your email'} @@ -130,7 +130,7 @@ export function EmailVerification({ title, onNext }: StepProps) { )}

- {profile.data?.email_verified && ( + {profile.data?.emailVerified && ( diff --git a/libs/client/shared/src/api/useUserApi.ts b/libs/client/shared/src/api/useUserApi.ts index 83148bcd..356f789a 100644 --- a/libs/client/shared/src/api/useUserApi.ts +++ b/libs/client/shared/src/api/useUserApi.ts @@ -65,6 +65,11 @@ const UserApi = ( return data }, + async getAuthProfile() { + const { data } = await axios.get('/users/auth-profile') + return data + }, + async getAuth0Profile() { const { data } = await axios.get('/users/auth0-profile') return data @@ -160,10 +165,10 @@ const UserApi = ( return data }, - async resendEmailVerification(auth0Id?: string) { + async resendEmailVerification(authId?: string) { const { data } = await axios.post<{ success: boolean }>( '/users/resend-verification-email', - { auth0Id } + { authId } ) return data @@ -288,6 +293,17 @@ export function useUserApi() { ...options, }) + const useAuthProfile = ( + options?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' + > + ) => + useQuery(['auth-profile'], api.getAuthProfile, { + staleTime: staleTimes.user, + ...options, + }) + const useAuth0Profile = ( options?: Omit, 'queryKey' | 'queryFn'> ) => useQuery(['users', 'auth0-profile'], api.getAuth0Profile, options) @@ -403,6 +419,7 @@ export function useUserApi() { useCurrentNetWorth, useProfile, useUpdateProfile, + useAuthProfile, useAuth0Profile, useUpdateAuth0Profile, useSubscription, diff --git a/libs/design-system/src/lib/Listbox/Listbox.tsx b/libs/design-system/src/lib/Listbox/Listbox.tsx index 32cffe85..baf5c421 100644 --- a/libs/design-system/src/lib/Listbox/Listbox.tsx +++ b/libs/design-system/src/lib/Listbox/Listbox.tsx @@ -1,7 +1,7 @@ import type { Dispatch, MouseEventHandler, PropsWithChildren, SetStateAction } from 'react' import type { IconType } from 'react-icons' import type { PopperProps } from 'react-popper' -import React, { createContext, useContext, useState, useEffect } from 'react' +import React, { createContext, useContext, useState, useEffect, useRef } from 'react' import { Listbox as HeadlessListbox } from '@headlessui/react' import classNames from 'classnames' import { RiArrowDownSFill, RiCheckFill } from 'react-icons/ri' @@ -166,6 +166,8 @@ function Options({ const [popperElement, setPopperElement] = useState(null) const [isOpen, setIsOpen] = useState(false) + const isOpenRef = useRef(false) + const { styles, attributes, update } = usePopper(referenceElement, popperElement, { placement, modifiers: [ @@ -180,6 +182,7 @@ function Options({ useEffect(() => { if (isOpen && update) update() + if (isOpenRef.current !== isOpen) setIsOpen(isOpenRef.current) }, [isOpen, update]) return ( @@ -198,7 +201,7 @@ function Options({ {...rest} > {({ open }) => { - setIsOpen(open) + isOpenRef.current = open return children }} diff --git a/libs/design-system/src/lib/Menu/Menu.tsx b/libs/design-system/src/lib/Menu/Menu.tsx index eac82024..7d625883 100644 --- a/libs/design-system/src/lib/Menu/Menu.tsx +++ b/libs/design-system/src/lib/Menu/Menu.tsx @@ -46,6 +46,8 @@ function Items({ const [popperElement, setPopperElement] = useState(null) const [isOpen, setIsOpen] = useState(false) + const isOpenRef = useRef(false) + const { styles, attributes, update } = usePopper(referenceElement?.current, popperElement, { placement, modifiers: [ @@ -60,6 +62,7 @@ function Items({ useEffect(() => { if (isOpen && update) update() + if (isOpenRef.current !== isOpen) setIsOpen(isOpenRef.current) }, [isOpen, update]) return ( @@ -75,7 +78,7 @@ function Items({ {...rest} > {(renderProps) => { - setIsOpen(renderProps.open) + isOpenRef.current = renderProps.open return typeof children === 'function' ? children(renderProps) : children }} diff --git a/libs/server/features/src/user/user.service.ts b/libs/server/features/src/user/user.service.ts index 6b18246c..5f21ac1f 100644 --- a/libs/server/features/src/user/user.service.ts +++ b/libs/server/features/src/user/user.service.ts @@ -65,6 +65,13 @@ export class UserService implements IUserService { }) } + async getAuthProfile(id: User['id']): Promise { + const user = await this.get(id) + return this.prisma.authUser.findUniqueOrThrow({ + where: { id: user.authId }, + }) + } + // TODO: Update this to use new Auth async getAuth0Profile(user: User): Promise { if (!user.email) throw new Error('No email found for user') @@ -371,7 +378,7 @@ export class UserService implements IUserService { .setTitle((_) => "Before we start, let's verify your email") .addToGroup('setup') .completeIf((user) => user.emailVerified) - .excludeIf((user) => user.isAppleIdentity) // Auth0 auto-verifies Apple identities. + .excludeIf((user) => user.isAppleIdentity || true) // TODO: Needs email service to send, skip for now onboarding .addStep('firstAccount')