mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Refactor error handling for profile mutations
This commit is contained in:
1 parent
ed77f09070
commit
cc320af73d
5 files changed
+111
-55
No files matched your search
@@ -1,6 +1,9 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { ResponseError } from 'types'
|
||||
import { profileKeys } from './keys'
|
||||
import { Profile } from './types'
|
||||
|
||||
@@ -19,16 +22,23 @@ type ProfileCreateData = Awaited<ReturnType<typeof createProfile>>
|
||||
|
||||
export const useProfileCreateMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<UseMutationOptions<ProfileCreateData, unknown, void>, 'mutationFn'> = {}) => {
|
||||
}: Omit<UseMutationOptions<ProfileCreateData, ResponseError, void>, 'mutationFn'> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<ProfileCreateData, unknown, void>(() => createProfile(), {
|
||||
return useMutation<ProfileCreateData, ResponseError, void>(() => createProfile(), {
|
||||
async onSuccess(data, variables, context) {
|
||||
await queryClient.invalidateQueries(profileKeys.profile())
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to create profile: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -4,25 +4,21 @@ import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { profileKeys } from './keys'
|
||||
import { Profile } from './types'
|
||||
import { ResponseError } from 'types'
|
||||
|
||||
export type ProfileResponse = Profile
|
||||
|
||||
export async function getProfile(signal?: AbortSignal) {
|
||||
const response = await get(`${API_URL}/profile`, {
|
||||
signal,
|
||||
headers: {
|
||||
Version: '2',
|
||||
},
|
||||
headers: { Version: '2' },
|
||||
})
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
if (response.error) throw response.error
|
||||
return response as ProfileResponse
|
||||
}
|
||||
|
||||
export type ProfileData = Awaited<ReturnType<typeof getProfile>>
|
||||
export type ProfileError = unknown
|
||||
export type ProfileError = ResponseError
|
||||
|
||||
export const useProfileQuery = <TData = ProfileData>({
|
||||
enabled = true,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { patch } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { ResponseError } from 'types'
|
||||
import { profileKeys } from './keys'
|
||||
|
||||
export type ProfileUpdateVariables = {
|
||||
@@ -24,21 +27,28 @@ type ProfileUpdateData = Awaited<ReturnType<typeof updateProfile>>
|
||||
|
||||
export const useProfileUpdateMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<ProfileUpdateData, unknown, ProfileUpdateVariables>,
|
||||
UseMutationOptions<ProfileUpdateData, ResponseError, ProfileUpdateVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<ProfileUpdateData, unknown, ProfileUpdateVariables>(
|
||||
return useMutation<ProfileUpdateData, ResponseError, ProfileUpdateVariables>(
|
||||
(vars) => updateProfile(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
await queryClient.invalidateQueries(profileKeys.profile())
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to create profile: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
+27
-3
@@ -10,15 +10,22 @@ import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import { Profile } from 'data/profile/types'
|
||||
import { useStore } from 'hooks'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { ResponseError } from 'types'
|
||||
|
||||
export type ProfileContextType = {
|
||||
profile: Profile | undefined
|
||||
error: ResponseError | null
|
||||
isLoading: boolean
|
||||
isError: boolean
|
||||
isSuccess: boolean
|
||||
}
|
||||
|
||||
export const ProfileContext = createContext<ProfileContextType>({
|
||||
profile: undefined,
|
||||
error: null,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
})
|
||||
|
||||
export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
@@ -37,7 +44,7 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
|
||||
await invalidateOrganizationsQuery(queryClient)
|
||||
},
|
||||
onError(err) {
|
||||
onError() {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: 'Failed to create your profile. Please refresh to try again.',
|
||||
@@ -46,7 +53,13 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
})
|
||||
|
||||
// Track telemetry for the current user
|
||||
const { data: profile, isLoading: isLoadingProfile } = useProfileQuery({
|
||||
const {
|
||||
error,
|
||||
data: profile,
|
||||
isLoading: isLoadingProfile,
|
||||
isError,
|
||||
isSuccess,
|
||||
} = useProfileQuery({
|
||||
onSuccess(profile) {
|
||||
Telemetry.sendIdentify(profile, telemetryProps)
|
||||
},
|
||||
@@ -64,10 +77,21 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const isLoading = isLoadingProfile || isCreatingProfile || isLoadingPermissions
|
||||
|
||||
return {
|
||||
error,
|
||||
profile,
|
||||
isLoading,
|
||||
isError,
|
||||
isSuccess,
|
||||
}
|
||||
}, [isLoadingProfile, isCreatingProfile, isLoadingPermissions, profile])
|
||||
}, [
|
||||
isLoadingProfile,
|
||||
isCreatingProfile,
|
||||
isLoadingPermissions,
|
||||
profile,
|
||||
error,
|
||||
isError,
|
||||
isSuccess,
|
||||
])
|
||||
|
||||
return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
|
||||
}
|
||||
|
||||
+54
-38
@@ -12,6 +12,8 @@ import { useSession } from 'lib/auth'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Button, IconMoon, IconSun, Input, Listbox } from 'ui'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
|
||||
const User: NextPageWithLayout = () => {
|
||||
return (
|
||||
@@ -39,53 +41,67 @@ export default User
|
||||
|
||||
const ProfileCard = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const { mutateAsync } = useProfileUpdateMutation()
|
||||
|
||||
const { profile } = useProfile()
|
||||
// TODO: ^ handle loading state
|
||||
|
||||
const updateUser = async (model: any) => {
|
||||
try {
|
||||
await mutateAsync({
|
||||
firstName: model.first_name,
|
||||
lastName: model.last_name,
|
||||
})
|
||||
|
||||
ui.setNotification({ category: 'success', message: 'Successfully saved profile' })
|
||||
} catch (error) {
|
||||
const { profile, error, isLoading, isError, isSuccess } = useProfile()
|
||||
const { mutateAsync: updateProfile } = useProfileUpdateMutation({
|
||||
onError: (error) => {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: "Couldn't update profile. Please try again later.",
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const updateUser = async (model: any) => {
|
||||
await updateProfile({
|
||||
firstName: model.first_name,
|
||||
lastName: model.last_name,
|
||||
})
|
||||
ui.setNotification({ category: 'success', message: 'Successfully saved profile' })
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="max-w-4xl p-4">
|
||||
<section>
|
||||
<Profile profile={profile} />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
{/* @ts-ignore */}
|
||||
<SchemaFormPanel
|
||||
title="Profile"
|
||||
schema={{
|
||||
type: 'object',
|
||||
required: [],
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
}}
|
||||
model={{
|
||||
first_name: profile?.first_name ?? '',
|
||||
last_name: profile?.last_name ?? '',
|
||||
}}
|
||||
onSubmit={updateUser}
|
||||
/>
|
||||
</section>
|
||||
{isLoading && (
|
||||
<Panel>
|
||||
<div className="p-4">
|
||||
<GenericSkeletonLoader />
|
||||
</div>
|
||||
</Panel>
|
||||
)}
|
||||
{isError && (
|
||||
<Panel>
|
||||
<div className="p-4">
|
||||
<AlertError error={error} subject="Failed to retrieve account information" />
|
||||
</div>
|
||||
</Panel>
|
||||
)}
|
||||
{isSuccess && (
|
||||
<>
|
||||
<section>
|
||||
<Profile profile={profile} />
|
||||
</section>
|
||||
<section>
|
||||
{/* @ts-ignore */}
|
||||
<SchemaFormPanel
|
||||
title="Profile"
|
||||
schema={{
|
||||
type: 'object',
|
||||
required: [],
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
}}
|
||||
model={{
|
||||
first_name: profile?.first_name ?? '',
|
||||
last_name: profile?.last_name ?? '',
|
||||
}}
|
||||
onSubmit={updateUser}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<ThemeSettings />
|
||||
|
||||
Reference in new issue
Block a user