mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge pull request #14361 from supabase/hi/new-profile-apis
feat: new profile apis
This commit is contained in:
19 files changed
+163
-62
No files matched your search
@@ -8,9 +8,9 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { checkPermissions, useFlag, useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { createSqlSnippetSkeleton } from '../SQLEditor/SQLEditor.utils'
|
||||
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import { SqlSnippet } from 'data/content/sql-snippets-query'
|
||||
import { useProjectApiQuery } from 'data/config/project-api-query'
|
||||
import { codeBlock } from 'common-tags'
|
||||
@@ -25,7 +25,7 @@ const CommandMenuWrapper = observer(({ children }: PropsWithChildren<{}>) => {
|
||||
const allowCMDKDataOptIn = useFlag('dashboardCmdkDataOptIn')
|
||||
const isOptedInToAI = opt_in_tags?.includes('AI_SQL_GENERATOR_OPT_IN') ?? false
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const { data: settings } = useProjectApiQuery({ projectRef: ref })
|
||||
const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
resource: { type: 'sql', owner_id: profile?.id },
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, Icon
|
||||
|
||||
import { Member } from 'types'
|
||||
import { useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useParams } from 'common/hooks'
|
||||
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
@@ -14,7 +15,6 @@ import MemberActions from './MemberActions'
|
||||
import RolesHelperModal from './RolesHelperModal/RolesHelperModal'
|
||||
import { getRolesManagementPermissions } from './TeamSettings.utils'
|
||||
import { isInviteExpired, getUserDisplayName } from '../Organization.utils'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
|
||||
import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query'
|
||||
import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation'
|
||||
@@ -32,7 +32,7 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const { data: detailData, isLoading: isLoadingOrgDetails } = useOrganizationDetailQuery({ slug })
|
||||
const { data: rolesData, isLoading: isLoadingRoles } = useOrganizationRolesQuery({ slug })
|
||||
const { mutate: updateOrganizationMember, isLoading } = useOrganizationMemberUpdateMutation({
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Button, Input, IconSearch } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import InviteMemberButton from './InviteMemberButton'
|
||||
import MembersView from './MembersView'
|
||||
import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils'
|
||||
@@ -19,7 +19,7 @@ const TeamSettings = () => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const isOwner = ui.selectedOrganization?.is_owner
|
||||
|
||||
const { data: detailData } = useOrganizationDetailQuery({ slug })
|
||||
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { METRIC_CATEGORIES, METRICS, TIME_PERIODS_REPORTS } from 'lib/constants'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
@@ -33,7 +33,7 @@ const DEFAULT_CHART_ROW_COUNT = 4
|
||||
|
||||
const Reports = () => {
|
||||
const { id, ref } = useParams()
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
|
||||
const [report, setReport] = useState<any>()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants'
|
||||
import SQLCard from './SQLCard'
|
||||
import { createSqlSnippetSkeleton } from '../SQLEditor.utils'
|
||||
@@ -18,7 +18,7 @@ const SQLTemplates = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const { ref } = useParams()
|
||||
const router = useRouter()
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' })
|
||||
|
||||
const telemetryProps = useTelemetryProps()
|
||||
|
||||
@@ -13,7 +13,7 @@ import DatePickers from './Logs.DatePickers'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { checkPermissions } from 'hooks'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
|
||||
export interface LogsQueryPanelProps {
|
||||
@@ -45,7 +45,7 @@ const LogsQueryPanel = ({
|
||||
onDateChange,
|
||||
warnings,
|
||||
}: LogsQueryPanelProps) => {
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const canCreateLogQuery = checkPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
resource: { type: 'log_sql', owner_id: profile?.id },
|
||||
subject: { id: profile?.id },
|
||||
|
||||
@@ -19,10 +19,10 @@ import { CLIENT_LIBRARIES } from 'common/constants'
|
||||
|
||||
import { Project } from 'types'
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { post, get } from 'lib/common/fetch'
|
||||
import { detectBrowser } from 'lib/helpers'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
|
||||
import Divider from 'components/ui/Divider'
|
||||
import Connecting from 'components/ui/Loading'
|
||||
@@ -76,7 +76,7 @@ const SupportForm: FC<Props> = ({ setSentCategory }) => {
|
||||
}
|
||||
}, [uploadedFiles])
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const respondToEmail = profile?.primary_email ?? 'your email'
|
||||
|
||||
if (!isInitialized) {
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useFlag, useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
|
||||
import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem'
|
||||
@@ -33,7 +33,7 @@ const SideBarContent = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const { ref, id } = useParams()
|
||||
const router = useRouter()
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const [filterString, setFilterString] = useState('')
|
||||
const { setPages, setIsOpen } = useCommandMenu()
|
||||
const showCmdkHelper = useFlag('dashboardCmdk')
|
||||
|
||||
@@ -5,24 +5,6 @@ import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC, useEffect } from 'react'
|
||||
|
||||
function sanitizePageViewRoute(_route?: string) {
|
||||
// remove all fragments
|
||||
const noFragments = _route?.split('#')[0]
|
||||
// remove sensitive params
|
||||
const paramsSplits = noFragments?.split('?')
|
||||
const hasParams = paramsSplits && paramsSplits?.length > 1
|
||||
|
||||
if (hasParams) {
|
||||
const urlParams = new URLSearchParams(paramsSplits[1])
|
||||
const sensitiveKeys = [...urlParams.keys()].filter((x) => x.includes('token'))
|
||||
const sensitiveParams = ['code', ...sensitiveKeys]
|
||||
sensitiveParams.forEach((name) => urlParams.delete(name))
|
||||
return `${paramsSplits[0]}?${urlParams?.toString()}`
|
||||
}
|
||||
|
||||
return noFragments
|
||||
}
|
||||
|
||||
const PageTelemetry: FC = ({ children }) => {
|
||||
const router = useRouter()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
@@ -53,11 +35,8 @@ const PageTelemetry: FC = ({ children }) => {
|
||||
*
|
||||
* @param route: the browser url
|
||||
* */
|
||||
const handlePageTelemetry = async (_route?: string) => {
|
||||
const handlePageTelemetry = async (route: string) => {
|
||||
if (IS_PLATFORM) {
|
||||
// filter out sensitive query params
|
||||
const route = sanitizePageViewRoute(_route)
|
||||
|
||||
/**
|
||||
* Get referrer from browser
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { profileKeys } from './keys'
|
||||
import { Profile } from './types'
|
||||
|
||||
export type ProfileResponse = Profile
|
||||
|
||||
export async function createProfile() {
|
||||
const response = await post(`${API_URL}/profile`, {})
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
return response as ProfileResponse
|
||||
}
|
||||
|
||||
type ProfileCreateData = Awaited<ReturnType<typeof createProfile>>
|
||||
|
||||
export const useProfileCreateMutation = ({
|
||||
onSuccess,
|
||||
...options
|
||||
}: Omit<UseMutationOptions<ProfileCreateData, unknown, void>, 'mutationFn'> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<ProfileCreateData, unknown, void>(() => createProfile(), {
|
||||
async onSuccess(data, variables, context) {
|
||||
await queryClient.invalidateQueries(profileKeys.profile())
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -3,25 +3,16 @@ import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { profileKeys } from './keys'
|
||||
|
||||
export type Profile = {
|
||||
id: number
|
||||
auth0_id: string
|
||||
primary_email: string
|
||||
username: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
mobile: string | null
|
||||
is_alpha_user: boolean
|
||||
gotrue_id: string
|
||||
free_project_limit: number
|
||||
}
|
||||
import { Profile } from './types'
|
||||
|
||||
export type ProfileResponse = Profile
|
||||
|
||||
export async function getProfile(signal?: AbortSignal) {
|
||||
const response = await get(`${API_URL}/profile`, {
|
||||
signal,
|
||||
headers: {
|
||||
Version: '2',
|
||||
},
|
||||
})
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
@@ -36,8 +27,8 @@ export type ProfileError = unknown
|
||||
export const useProfileQuery = <TData = ProfileData>({
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<ProfileData, ProfileError, TData> = {}) =>
|
||||
useQuery<ProfileData, ProfileError, TData>(
|
||||
}: UseQueryOptions<ProfileData, ProfileError, TData> = {}) => {
|
||||
return useQuery<ProfileData, ProfileError, TData>(
|
||||
profileKeys.profile(),
|
||||
({ signal }) => getProfile(signal),
|
||||
{
|
||||
@@ -45,6 +36,7 @@ export const useProfileQuery = <TData = ProfileData>({
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const useProfilePrefetch = () => {
|
||||
const client = useQueryClient()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { patch } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { profileKeys } from './keys'
|
||||
|
||||
@@ -9,7 +9,7 @@ export type ProfileUpdateVariables = {
|
||||
}
|
||||
|
||||
export async function updateProfile({ firstName, lastName }: ProfileUpdateVariables) {
|
||||
const response = await post(`${API_URL}/profile/update`, {
|
||||
const response = await patch(`${API_URL}/profile`, {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export type Profile = {
|
||||
id: number
|
||||
auth0_id: string
|
||||
primary_email: string
|
||||
username: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
mobile: string | null
|
||||
is_alpha_user: boolean
|
||||
gotrue_id: string
|
||||
free_project_limit: number
|
||||
}
|
||||
@@ -4,7 +4,31 @@ import { useState } from 'react'
|
||||
let queryClient: QueryClient | undefined
|
||||
|
||||
export function getQueryClient() {
|
||||
const _queryClient = queryClient ?? new QueryClient()
|
||||
const _queryClient =
|
||||
queryClient ??
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: (failureCount, error) => {
|
||||
// Don't retry on 404s
|
||||
if (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'code' in error &&
|
||||
(error as any).code === 404
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (failureCount < 3) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// For SSG and SSR always create a new queryClient
|
||||
if (typeof window === 'undefined') return _queryClient
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useTelemetryProps } from 'common'
|
||||
import { useProfileCreateMutation } from 'data/profile/profile-create-mutation'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
|
||||
/**
|
||||
* Fetches the user's profile, creating one if it does not exist
|
||||
*/
|
||||
function useProfile() {
|
||||
const router = useRouter()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
|
||||
const { mutate: createProfile, isLoading: isCreating } = useProfileCreateMutation({
|
||||
onSuccess() {
|
||||
Telemetry.sendEvent(
|
||||
{ category: 'conversion', action: 'sign_up', label: '' },
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const { isLoading, ...result } = useProfileQuery({
|
||||
onError(err) {
|
||||
// if the user does not yet exist, create a profile for them
|
||||
if (typeof err === 'object' && err !== null && 'code' in err && (err as any).code === 404) {
|
||||
createProfile()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
// Continue the loading state until the profile is created
|
||||
isLoading: isLoading || isCreating,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
export default useProfile
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useRouter } from 'next/router'
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
||||
|
||||
import {
|
||||
@@ -17,6 +18,7 @@ export const AuthContext = AuthContextInternal
|
||||
|
||||
export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const { ui, app } = useStore()
|
||||
const router = useRouter()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
|
||||
// Check for unverified GitHub users after a GitHub sign in
|
||||
@@ -36,6 +38,22 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
handleEmailVerificationError()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = gotrueClient.onAuthStateChange((event) => {
|
||||
if (event === 'SIGNED_IN') {
|
||||
Telemetry.sendEvent(
|
||||
{ category: 'account', action: 'sign_in', label: '' },
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return subscription.unsubscribe
|
||||
}, [])
|
||||
|
||||
// Track telemetry for the current user
|
||||
useProfileQuery({
|
||||
onSuccess(profile) {
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Button, IconMoon, IconSun, Input, Listbox } from 'ui'
|
||||
import { AccountLayout } from 'components/layouts'
|
||||
import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { Profile as ProfileType, useProfileQuery } from 'data/profile/profile-query'
|
||||
import { Profile as ProfileType } from 'data/profile/types'
|
||||
import { useProfileUpdateMutation } from 'data/profile/profile-update-mutation'
|
||||
import { useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useSession } from 'lib/auth'
|
||||
import Link from 'next/link'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
@@ -40,7 +41,7 @@ const ProfileCard = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const { mutateAsync } = useProfileUpdateMutation()
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
// TODO: ^ handle loading state
|
||||
|
||||
const updateUser = async (model: any) => {
|
||||
|
||||
@@ -4,10 +4,10 @@ import { useRouter } from 'next/router'
|
||||
import { Button, IconCheckSquare, Loading } from 'ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useSignOut } from 'lib/auth'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get, post, delete_ } from 'lib/common/fetch'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
|
||||
interface ITokenInfo {
|
||||
organization_name?: string | undefined
|
||||
@@ -24,7 +24,7 @@ const JoinOrganizationPage = () => {
|
||||
const router = useRouter()
|
||||
const { slug, token, name } = router.query
|
||||
const { ui, app } = useStore()
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const signOut = useSignOut()
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
@@ -5,11 +5,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { checkPermissions, useFlag, useStore } from 'hooks'
|
||||
import useProfile from 'hooks/misc/useProfile'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
|
||||
import { createReport } from 'components/to-be-cleaned/Reports/Reports.utils'
|
||||
@@ -21,7 +21,7 @@ export const UserReportPage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
|
||||
const { data: profile } = useProfileQuery()
|
||||
const { data: profile } = useProfile()
|
||||
const { ui } = useStore()
|
||||
const project = ui.selectedProject
|
||||
|
||||
|
||||
Reference in new issue
Block a user