mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
add back onboarding
This commit is contained in:
parent
093949f447
commit
e405fb80f6
12 changed files with 96 additions and 53 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<ModalManager>
|
||||
<UserAccountContextProvider>
|
||||
<AccountContextProvider>
|
||||
{children}
|
||||
<OnboardingGuard>
|
||||
<ModalManager>
|
||||
<UserAccountContextProvider>
|
||||
<AccountContextProvider>
|
||||
{children}
|
||||
|
||||
{/* Add, edit, delete connections and manual accounts */}
|
||||
<AccountsManager />
|
||||
</AccountContextProvider>
|
||||
</UserAccountContextProvider>
|
||||
</ModalManager>
|
||||
{/* Add, edit, delete connections and manual accounts */}
|
||||
<AccountsManager />
|
||||
</AccountContextProvider>
|
||||
</UserAccountContextProvider>
|
||||
</ModalManager>
|
||||
</OnboardingGuard>
|
||||
)
|
||||
}
|
||||
return null
|
||||
|
@ -84,20 +85,18 @@ export default function App({
|
|||
<Meta />
|
||||
<Analytics />
|
||||
<QueryProvider>
|
||||
<AuthProvider>
|
||||
<SessionProvider>
|
||||
<AxiosProvider>
|
||||
<>
|
||||
<APM />
|
||||
{Page.isPublic === true ? (
|
||||
getLayout(<Page {...pageProps} />)
|
||||
) : (
|
||||
<WithAuth>{getLayout(<Page {...pageProps} />)}</WithAuth>
|
||||
)}
|
||||
</>
|
||||
</AxiosProvider>
|
||||
</SessionProvider>
|
||||
</AuthProvider>
|
||||
<SessionProvider>
|
||||
<AxiosProvider>
|
||||
<>
|
||||
<APM />
|
||||
{Page.isPublic === true ? (
|
||||
getLayout(<Page {...pageProps} />)
|
||||
) : (
|
||||
<WithAuth>{getLayout(<Page {...pageProps} />)}</WithAuth>
|
||||
)}
|
||||
</>
|
||||
</AxiosProvider>
|
||||
</SessionProvider>
|
||||
</QueryProvider>
|
||||
</ErrorBoundary>
|
||||
</LogProvider>
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useQueryParam } from '@maybe-finance/client/shared'
|
|||
import {
|
||||
AccountSidebar,
|
||||
BillingPreferences,
|
||||
GeneralPreferences,
|
||||
SecurityPreferences,
|
||||
UserDetails,
|
||||
WithSidebarLayout,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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) {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
}
|
||||
name={user?.name}
|
||||
email={user?.email}
|
||||
name={user?.name ?? ''}
|
||||
email={user?.email ?? ''}
|
||||
>
|
||||
{sidebar}
|
||||
</DefaultContent>
|
||||
|
|
|
@ -237,7 +237,7 @@ export function SidebarOnboarding({ onClose, onHide }: Props) {
|
|||
const description = getDescriptionComponent(step.key)
|
||||
|
||||
return (
|
||||
<Disclosure>
|
||||
<Disclosure key={idx}>
|
||||
{({ open }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
|
|
|
@ -8,14 +8,14 @@ import { useUserApi } from '@maybe-finance/client/shared'
|
|||
import type { StepProps } from '../StepProps'
|
||||
|
||||
export function EmailVerification({ title, onNext }: StepProps) {
|
||||
const { useAuth0Profile, useResendEmailVerification } = useUserApi()
|
||||
const { useAuthProfile, useResendEmailVerification } = useUserApi()
|
||||
|
||||
const emailVerified = useRef(false)
|
||||
|
||||
const profile = useAuth0Profile({
|
||||
const profile = useAuthProfile({
|
||||
refetchInterval: emailVerified.current ? false : 5_000,
|
||||
onSuccess: (data) => {
|
||||
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 ? (
|
||||
<RiMailCheckLine className="w-6 h-6" />
|
||||
) : (
|
||||
<RiMailSendLine className="w-6 h-6" />
|
||||
|
@ -78,10 +78,10 @@ export function EmailVerification({ title, onNext }: StepProps) {
|
|||
</div>
|
||||
</div>
|
||||
<h3 className="mt-12 text-center">
|
||||
{profile.data?.email_verified ? 'Email verified' : title}
|
||||
{profile.data?.emailVerified ? 'Email verified' : title}
|
||||
</h3>
|
||||
<div className="text-base text-center">
|
||||
{profile.data?.email_verified ? (
|
||||
{profile.data?.emailVerified ? (
|
||||
<p className="mt-4 text-gray-50">
|
||||
You have successfully verified{' '}
|
||||
<span className="text-gray-25">{profile.data?.email ?? 'your email'}</span>
|
||||
|
@ -130,7 +130,7 @@ export function EmailVerification({ title, onNext }: StepProps) {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
{profile.data?.email_verified && (
|
||||
{profile.data?.emailVerified && (
|
||||
<Button className="mt-5" fullWidth onClick={onNext}>
|
||||
Continue setup <RiArrowRightLine className="ml-2 w-5 h-5" />
|
||||
</Button>
|
||||
|
|
|
@ -65,6 +65,11 @@ const UserApi = (
|
|||
return data
|
||||
},
|
||||
|
||||
async getAuthProfile() {
|
||||
const { data } = await axios.get<SharedType.AuthUser>('/users/auth-profile')
|
||||
return data
|
||||
},
|
||||
|
||||
async getAuth0Profile() {
|
||||
const { data } = await axios.get<SharedType.Auth0Profile>('/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<SharedType.AuthUser, unknown, SharedType.AuthUser, any[]>,
|
||||
'queryKey' | 'queryFn'
|
||||
>
|
||||
) =>
|
||||
useQuery(['auth-profile'], api.getAuthProfile, {
|
||||
staleTime: staleTimes.user,
|
||||
...options,
|
||||
})
|
||||
|
||||
const useAuth0Profile = (
|
||||
options?: Omit<UseQueryOptions<SharedType.Auth0Profile>, 'queryKey' | 'queryFn'>
|
||||
) => useQuery(['users', 'auth0-profile'], api.getAuth0Profile, options)
|
||||
|
@ -403,6 +419,7 @@ export function useUserApi() {
|
|||
useCurrentNetWorth,
|
||||
useProfile,
|
||||
useUpdateProfile,
|
||||
useAuthProfile,
|
||||
useAuth0Profile,
|
||||
useUpdateAuth0Profile,
|
||||
useSubscription,
|
||||
|
|
|
@ -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<HTMLDivElement | null>(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
|
||||
}}
|
||||
</HeadlessListbox.Options>
|
||||
|
|
|
@ -46,6 +46,8 @@ function Items({
|
|||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(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
|
||||
}}
|
||||
</HeadlessMenu.Items>
|
||||
|
|
|
@ -65,6 +65,13 @@ export class UserService implements IUserService {
|
|||
})
|
||||
}
|
||||
|
||||
async getAuthProfile(id: User['id']): Promise<SharedType.AuthUser> {
|
||||
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<SharedType.Auth0Profile> {
|
||||
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')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue