mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
Small refactors after testing org team settings
This commit is contained in:
1 parent
439b356f0c
commit
4e1887472d
6 files changed
+246
-228
No files matched your search
@@ -5,7 +5,8 @@ import { Fragment, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common/hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query'
|
||||
import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation'
|
||||
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
|
||||
@@ -35,8 +36,19 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
|
||||
const { profile } = useProfile()
|
||||
const { data: permissions } = usePermissionsQuery()
|
||||
const { data: detailData, isLoading: isLoadingOrgDetails } = useOrganizationDetailQuery({ slug })
|
||||
const { data: rolesData, isLoading: isLoadingRoles } = useOrganizationRolesQuery({ slug })
|
||||
const {
|
||||
data: detailData,
|
||||
error: detailError,
|
||||
isLoading: isLoadingOrgDetails,
|
||||
isError: isErrorOrgDetails,
|
||||
isSuccess: isSuccessOrgDetails,
|
||||
} = useOrganizationDetailQuery({ slug })
|
||||
const {
|
||||
data: rolesData,
|
||||
error: rolesError,
|
||||
isLoading: isLoadingRoles,
|
||||
isError: isErrorRoles,
|
||||
} = useOrganizationRolesQuery({ slug })
|
||||
const { mutateAsync: updateOrganizationMember, isLoading } = useOrganizationMemberUpdateMutation({
|
||||
onSuccess() {
|
||||
ui.setNotification({
|
||||
@@ -44,10 +56,12 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
message: `Successfully updated role for ${getUserDisplayName(selectedMember)}`,
|
||||
})
|
||||
},
|
||||
onError() {
|
||||
onError(error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to update role for ${getUserDisplayName(selectedMember)}`,
|
||||
message: `Failed to update role for ${getUserDisplayName(selectedMember)}: ${
|
||||
error.message
|
||||
}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -94,230 +108,233 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
setUserRoleChangeModalVisible(false)
|
||||
}
|
||||
|
||||
if (isLoadingOrgDetails) {
|
||||
return (
|
||||
<div className="py-4 space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rounded w-full">
|
||||
<Loading active={!filteredMembers}>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="header-user">User</Table.th>,
|
||||
<Table.th key="header-status"></Table.th>,
|
||||
<Table.th key="header-role" className="flex items-center space-x-2">
|
||||
<span>Role</span>
|
||||
<RolesHelperModal />
|
||||
</Table.th>,
|
||||
<Table.th key="header-action"></Table.th>,
|
||||
]}
|
||||
body={[
|
||||
...filteredMembers.map((x: Member, i: number) => {
|
||||
const [memberRoleId] = x.role_ids ?? []
|
||||
const role = (roles || []).find((role) => role.id === memberRoleId)
|
||||
const memberIsUser = x.primary_email == profile?.primary_email
|
||||
const memberIsPendingInvite = !!x.invited_id
|
||||
const canRemoveRole = rolesRemovable.includes(memberRoleId)
|
||||
const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite
|
||||
const isEmailUser = x.username === x.primary_email
|
||||
{isLoadingOrgDetails && <GenericSkeletonLoader />}
|
||||
|
||||
const validateSelectedRoleToChange = (roleId: any) => {
|
||||
if (!role || role.id === roleId) return
|
||||
{isErrorOrgDetails && (
|
||||
<AlertError error={detailError} subject="Failed to retrieve organization members" />
|
||||
)}
|
||||
|
||||
const selectedRole = (roles || []).find((role) => role.id === roleId)
|
||||
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
|
||||
{isErrorRoles && (
|
||||
<AlertError error={rolesError} subject="Failed to retrieve organization roles" />
|
||||
)}
|
||||
|
||||
if (!canAddRole) {
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
duration: 4000,
|
||||
message: `You do not have permission to update this team member to ${
|
||||
selectedRole!.name
|
||||
}`,
|
||||
})
|
||||
{isSuccessOrgDetails && (
|
||||
<div className="rounded w-full">
|
||||
<Loading active={!filteredMembers}>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="header-user">User</Table.th>,
|
||||
<Table.th key="header-status"></Table.th>,
|
||||
<Table.th key="header-role" className="flex items-center space-x-2">
|
||||
<span>Role</span>
|
||||
<RolesHelperModal />
|
||||
</Table.th>,
|
||||
<Table.th key="header-action"></Table.th>,
|
||||
]}
|
||||
body={[
|
||||
...filteredMembers.map((x: Member, i: number) => {
|
||||
const [memberRoleId] = x.role_ids ?? []
|
||||
const role = (roles || []).find((role) => role.id === memberRoleId)
|
||||
const memberIsUser = x.primary_email == profile?.primary_email
|
||||
const memberIsPendingInvite = !!x.invited_id
|
||||
const canRemoveRole = rolesRemovable.includes(memberRoleId)
|
||||
const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite
|
||||
const isEmailUser = x.username === x.primary_email
|
||||
|
||||
const validateSelectedRoleToChange = (roleId: any) => {
|
||||
if (!role || role.id === roleId) return
|
||||
|
||||
const selectedRole = (roles || []).find((role) => role.id === roleId)
|
||||
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
|
||||
|
||||
if (!canAddRole) {
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
duration: 4000,
|
||||
message: `You do not have permission to update this team member to ${
|
||||
selectedRole!.name
|
||||
}`,
|
||||
})
|
||||
}
|
||||
|
||||
setUserRoleChangeModalVisible(true)
|
||||
setSelectedMember({ ...x, oldRoleId: role.id, newRoleId: roleId })
|
||||
}
|
||||
|
||||
setUserRoleChangeModalVisible(true)
|
||||
setSelectedMember({ ...x, oldRoleId: role.id, newRoleId: roleId })
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={`member-row-${i}`}>
|
||||
<Table.tr>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
{x.invited_id ? (
|
||||
<span className="flex p-2 border-2 rounded-full border-scale-700">
|
||||
<IconUser size={20} strokeWidth={2} />
|
||||
</span>
|
||||
) : isEmailUser ? (
|
||||
<div className="w-[40px] h-[40px] bg-scale-300 border border-scale-400 rounded-full text-scale-900 flex items-center justify-center">
|
||||
<IconUser strokeWidth={1.5} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
src={`https://github.com/${x.username}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<Fragment key={`member-row-${i}`}>
|
||||
<Table.tr>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
{x.invited_id ? (
|
||||
<span className="flex p-2 border-2 rounded-full border-scale-700">
|
||||
<IconUser size={20} strokeWidth={2} />
|
||||
</span>
|
||||
) : isEmailUser ? (
|
||||
<div className="w-[40px] h-[40px] bg-scale-300 border border-scale-400 rounded-full text-scale-900 flex items-center justify-center">
|
||||
<IconUser strokeWidth={1.5} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
alt={x.username}
|
||||
src={`https://github.com/${x.username}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-scale-1200">{getUserDisplayName(x)}</p>
|
||||
{x.invited_id === undefined && (
|
||||
<p className="text-scale-1100">{x.primary_email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-scale-1200">{getUserDisplayName(x)}</p>
|
||||
{x.invited_id === undefined && (
|
||||
<p className="text-scale-1100">{x.primary_email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.td>
|
||||
|
||||
<Table.td>
|
||||
{x.invited_id && x.invited_at && (
|
||||
<Badge color={isInviteExpired(x.invited_at) ? 'red' : 'yellow'}>
|
||||
{isInviteExpired(x.invited_at) ? 'Expired' : 'Invited'}
|
||||
</Badge>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{x.invited_id && x.invited_at && (
|
||||
<Badge color={isInviteExpired(x.invited_at) ? 'red' : 'yellow'}>
|
||||
{isInviteExpired(x.invited_at) ? 'Expired' : 'Invited'}
|
||||
</Badge>
|
||||
)}
|
||||
</Table.td>
|
||||
|
||||
<Table.td>
|
||||
{isLoadingRoles ? (
|
||||
<div className="w-[140px]">
|
||||
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
) : role !== undefined ? (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger className="w-[140px]">
|
||||
<Listbox
|
||||
className={disableRoleEdit ? 'pointer-events-none' : ''}
|
||||
disabled={disableRoleEdit}
|
||||
value={role.id}
|
||||
onChange={validateSelectedRoleToChange}
|
||||
>
|
||||
{roles.map((r: any) => (
|
||||
<Listbox.Option
|
||||
key={r.id}
|
||||
value={r.id}
|
||||
label={r.name}
|
||||
disabled={disableRoleEdit}
|
||||
className="w-36"
|
||||
>
|
||||
{r.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</Tooltip.Trigger>
|
||||
{memberIsPendingInvite ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Role can only be changed after the user has accepted the
|
||||
invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !memberIsUser && !canRemoveRole ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100">Invalid role</p>
|
||||
<Table.td>
|
||||
{isLoadingRoles ? (
|
||||
<div className="w-[140px]">
|
||||
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
) : role !== undefined ? (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconAlertCircle size={16} strokeWidth={1.5} />
|
||||
<Tooltip.Trigger className="w-[140px]">
|
||||
<Listbox
|
||||
className={disableRoleEdit ? 'pointer-events-none' : ''}
|
||||
disabled={disableRoleEdit}
|
||||
value={role.id}
|
||||
onChange={validateSelectedRoleToChange}
|
||||
>
|
||||
{roles.map((r: any) => (
|
||||
<Listbox.Option
|
||||
key={r.id}
|
||||
value={r.id}
|
||||
label={r.name}
|
||||
disabled={disableRoleEdit}
|
||||
className="w-36"
|
||||
>
|
||||
{r.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
This user has an invalid role, please reach out to us via
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
{memberIsPendingInvite ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Role can only be changed after the user has accepted the
|
||||
invite
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : !memberIsUser && !canRemoveRole ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to manage this team member
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100">Invalid role</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconAlertCircle size={16} strokeWidth={1.5} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
This user has an invalid role, please reach out to us via
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{!memberIsUser && <MemberActions member={x} roles={roles} />}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</Fragment>
|
||||
)
|
||||
}),
|
||||
...(searchString.length > 0 && filteredMembers.length === 0
|
||||
? [
|
||||
<Table.tr
|
||||
key="no-results"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={12}>
|
||||
<div className="flex items-center space-x-3 opacity-75">
|
||||
<IconAlertCircle size={16} strokeWidth={2} />
|
||||
<p className="text-scale-1100">
|
||||
No users matched the search query "{searchString}"
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{!memberIsUser && <MemberActions member={x} roles={roles} />}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</Fragment>
|
||||
)
|
||||
}),
|
||||
...(searchString.length > 0 && filteredMembers.length === 0
|
||||
? [
|
||||
<Table.tr
|
||||
key="no-results"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={12}>
|
||||
<div className="flex items-center space-x-3 opacity-75">
|
||||
<IconAlertCircle size={16} strokeWidth={2} />
|
||||
<p className="text-scale-1100">
|
||||
No users matched the search query "{searchString}"
|
||||
</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
]
|
||||
: []),
|
||||
<Table.tr
|
||||
key="footer"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={4}>
|
||||
<p className="text-scale-1100">
|
||||
{searchString ? `${filteredMembers.length} of ` : ''}
|
||||
{members.length || '0'} {members.length == 1 ? 'user' : 'users'}
|
||||
</p>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
]}
|
||||
/>
|
||||
</Loading>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
]
|
||||
: []),
|
||||
<Table.tr
|
||||
key="footer"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={4}>
|
||||
<p className="text-scale-1100">
|
||||
{searchString ? `${filteredMembers.length} of ` : ''}
|
||||
{members.length || '0'} {members.length == 1 ? 'user' : 'users'}
|
||||
</p>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
]}
|
||||
/>
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
hideFooter
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ScaffoldContainerLegacy,
|
||||
ScaffoldFilterAndContent,
|
||||
ScaffoldActionsContainer,
|
||||
ScaffoldSectionContent,
|
||||
} from 'components/layouts/Scaffold'
|
||||
|
||||
const TeamSettings = () => {
|
||||
@@ -133,7 +134,9 @@ const TeamSettings = () => {
|
||||
</div>
|
||||
</ScaffoldActionsGroup>
|
||||
</ScaffoldActionsContainer>
|
||||
<MembersView searchString={searchString} />
|
||||
<ScaffoldSectionContent className="w-full">
|
||||
<MembersView searchString={searchString} />
|
||||
</ScaffoldSectionContent>
|
||||
</ScaffoldFilterAndContent>
|
||||
</ScaffoldContainerLegacy>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { Member } from 'types'
|
||||
import { Member, ResponseError } from 'types'
|
||||
import { organizationKeys } from './keys'
|
||||
|
||||
export type OrganizationDetailVariables = {
|
||||
@@ -56,7 +56,7 @@ export async function getOrganizationDetail(
|
||||
}
|
||||
|
||||
export type OrganizationDetailData = Awaited<ReturnType<typeof getOrganizationDetail>>
|
||||
export type OrganizationDetailError = unknown
|
||||
export type OrganizationDetailError = ResponseError
|
||||
|
||||
export const useOrganizationDetailQuery = <TData = OrganizationDetailData>(
|
||||
{ slug }: OrganizationDetailVariables,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { delete_ } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { organizationKeys } from './keys'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { ResponseError } from 'types'
|
||||
import { organizationKeys } from './keys'
|
||||
|
||||
export type OrganizationMemberInviteDeleteVariables = {
|
||||
slug: string
|
||||
@@ -18,10 +19,7 @@ export async function deleteOrganizationMemberInvite({
|
||||
`${API_URL}/organizations/${slug}/members/invite?invited_id=${invitedId}`,
|
||||
{}
|
||||
)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
if (response.error) throw response.error
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { Role } from 'types'
|
||||
import { ResponseError, Role } from 'types'
|
||||
import { organizationKeys } from './keys'
|
||||
|
||||
export type OrganizationRolesVariables = {
|
||||
@@ -32,7 +32,7 @@ export async function getOrganizationRoles(
|
||||
}
|
||||
|
||||
export type OrganizationRolesData = Awaited<ReturnType<typeof getOrganizationRoles>>
|
||||
export type OrganizationRolesError = unknown
|
||||
export type OrganizationRolesError = ResponseError
|
||||
|
||||
export const useOrganizationRolesQuery = <TData = OrganizationRolesData>(
|
||||
{ slug }: OrganizationRolesVariables,
|
||||
|
||||
@@ -89,7 +89,7 @@ export type SupaResponse<T> = T & ResponseFailure
|
||||
export type SupaResponseV2<T> = T | ResponseFailure
|
||||
|
||||
export interface ResponseError {
|
||||
code: number
|
||||
code?: number
|
||||
message: string
|
||||
requestId: string
|
||||
requestId?: string
|
||||
}
|
||||
Reference in new issue
Block a user