From 108edf4dfeeeede08181eea78adeef1f318971f5 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Thu, 4 May 2023 12:56:24 +0700 Subject: [PATCH 1/9] fix: create new user profile --- studio/data/profile/profile-query.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/studio/data/profile/profile-query.ts b/studio/data/profile/profile-query.ts index 479cb7233aa..63d16a441d7 100644 --- a/studio/data/profile/profile-query.ts +++ b/studio/data/profile/profile-query.ts @@ -1,8 +1,9 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' -import { get } from 'lib/common/fetch' +import { get, post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { profileKeys } from './keys' +import Telemetry from 'lib/telemetry' export type Profile = { id: number @@ -19,11 +20,36 @@ export type Profile = { export type ProfileResponse = Profile +/** + * Create profile for new user + * This will also need to send a sign_up event + */ +async function createProfile() { + const response = await post(`${API_URL}/profile`, {}) + if (response.error) { + throw response.error + } + + Telemetry.sendEvent( + { category: 'conversion', action: 'sign_up', label: '' }, + { + screenResolution: + typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined, + language: 'en-US', + } + ) + + return response +} + export async function getProfile(signal?: AbortSignal) { const response = await get(`${API_URL}/profile`, { signal, }) if (response.error) { + // if response.error is not found + // we need to send request to create new user profile + if (response.error.code === 404) return await createProfile() throw response.error } From cec5d78881c332e2a974b8cb5518aa7ce45a2983 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 15 May 2023 11:14:50 +0700 Subject: [PATCH 2/9] fix: update update profile api --- studio/data/profile/profile-update-mutation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/data/profile/profile-update-mutation.ts b/studio/data/profile/profile-update-mutation.ts index b1b9ba67252..d4633f184bb 100644 --- a/studio/data/profile/profile-update-mutation.ts +++ b/studio/data/profile/profile-update-mutation.ts @@ -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, }) From e83ddb92aee25b55b7f817661df40e41c19e33a8 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 15 May 2023 11:15:21 +0700 Subject: [PATCH 3/9] fix: create profile --- studio/data/profile/profile-query.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/studio/data/profile/profile-query.ts b/studio/data/profile/profile-query.ts index 63d16a441d7..bd06b0965a9 100644 --- a/studio/data/profile/profile-query.ts +++ b/studio/data/profile/profile-query.ts @@ -4,6 +4,8 @@ import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { profileKeys } from './keys' import Telemetry from 'lib/telemetry' +import { useRouter } from 'next/router' +import { useTelemetryProps } from 'common' export type Profile = { id: number @@ -25,18 +27,19 @@ export type ProfileResponse = Profile * This will also need to send a sign_up event */ async function createProfile() { + const router = useRouter() + const telemetryProps = useTelemetryProps() + const response = await post(`${API_URL}/profile`, {}) if (response.error) { throw response.error } + // send conversion event Telemetry.sendEvent( { category: 'conversion', action: 'sign_up', label: '' }, - { - screenResolution: - typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined, - language: 'en-US', - } + telemetryProps, + router ) return response From eb057cb19b3d0d1e9b3a52b673d50bd277c05df1 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 15 May 2023 14:32:55 +0700 Subject: [PATCH 4/9] fix: use get profile api v2 --- studio/data/profile/profile-query.ts | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/studio/data/profile/profile-query.ts b/studio/data/profile/profile-query.ts index bd06b0965a9..7cbddd3e13c 100644 --- a/studio/data/profile/profile-query.ts +++ b/studio/data/profile/profile-query.ts @@ -4,7 +4,7 @@ import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { profileKeys } from './keys' import Telemetry from 'lib/telemetry' -import { useRouter } from 'next/router' +import { NextRouter, useRouter } from 'next/router' import { useTelemetryProps } from 'common' export type Profile = { @@ -26,10 +26,7 @@ export type ProfileResponse = Profile * Create profile for new user * This will also need to send a sign_up event */ -async function createProfile() { - const router = useRouter() - const telemetryProps = useTelemetryProps() - +async function createProfile(router: NextRouter, telemetryProps: any) { const response = await post(`${API_URL}/profile`, {}) if (response.error) { throw response.error @@ -45,14 +42,17 @@ async function createProfile() { return response } -export async function getProfile(signal?: AbortSignal) { +export async function getProfile(router: NextRouter, telemetryProps: any, signal?: AbortSignal) { const response = await get(`${API_URL}/profile`, { signal, + headers: { + Version: '2', + }, }) if (response.error) { // if response.error is not found // we need to send request to create new user profile - if (response.error.code === 404) return await createProfile() + if (response.error.code === 404) return await createProfile(router, telemetryProps) throw response.error } @@ -65,20 +65,28 @@ export type ProfileError = unknown export const useProfileQuery = ({ enabled = true, ...options -}: UseQueryOptions = {}) => - useQuery( +}: UseQueryOptions = {}) => { + const router = useRouter() + const telemetryProps = useTelemetryProps() + + return useQuery( profileKeys.profile(), - ({ signal }) => getProfile(signal), + ({ signal }) => getProfile(router, telemetryProps, signal), { staleTime: 1000 * 60 * 30, // default good for 30 mins ...options, } ) +} export const useProfilePrefetch = () => { const client = useQueryClient() + const router = useRouter() + const telemetryProps = useTelemetryProps() return useCallback(() => { - client.prefetchQuery(profileKeys.profile(), ({ signal }) => getProfile(signal)) + client.prefetchQuery(profileKeys.profile(), ({ signal }) => + getProfile(router, telemetryProps, signal) + ) }, []) } From 4158fb9c6f09a8b4af38ed5213304211bf960040 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 15 May 2023 14:58:21 +0700 Subject: [PATCH 5/9] fix: page location is sanitized server side --- studio/components/ui/PageTelemetry.tsx | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx index f6de24e69b6..6d9d469ef9a 100644 --- a/studio/components/ui/PageTelemetry.tsx +++ b/studio/components/ui/PageTelemetry.tsx @@ -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 */ From 9c4d82c0218679d07dec84d9fb095e75a20a4b20 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 15 May 2023 21:37:38 +1000 Subject: [PATCH 6/9] adds new useProfile hook --- .../interfaces/App/CommandMenuWrapper.tsx | 4 +- .../Organization/TeamSettings/MembersView.tsx | 4 +- .../TeamSettings/TeamSettings.tsx | 4 +- .../components/interfaces/Reports/Reports.tsx | 4 +- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 4 +- .../Settings/Logs/LogsQueryPanel.tsx | 4 +- .../interfaces/Support/SupportForm.tsx | 4 +- .../layouts/SQLEditorLayout/SQLEditorMenu.tsx | 4 +- .../data/profile/profile-create-mutation.ts | 34 ++++++++++++ studio/data/profile/profile-query.ts | 55 ++----------------- studio/data/profile/types.ts | 12 ++++ studio/data/query-client.ts | 26 ++++++++- studio/hooks/misc/useProfile.tsx | 41 ++++++++++++++ studio/pages/account/me.tsx | 5 +- studio/pages/join/index.tsx | 4 +- studio/pages/project/[ref]/reports/index.tsx | 4 +- 16 files changed, 140 insertions(+), 73 deletions(-) create mode 100644 studio/data/profile/profile-create-mutation.ts create mode 100644 studio/data/profile/types.ts create mode 100644 studio/hooks/misc/useProfile.tsx diff --git a/studio/components/interfaces/App/CommandMenuWrapper.tsx b/studio/components/interfaces/App/CommandMenuWrapper.tsx index 748c2f0a0cc..c4498e11db1 100644 --- a/studio/components/interfaces/App/CommandMenuWrapper.tsx +++ b/studio/components/interfaces/App/CommandMenuWrapper.tsx @@ -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 }, diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index 3dca2403e09..5e40307f65b 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -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({ diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index bb5f155102d..04cb1a953a2 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -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 }) diff --git a/studio/components/interfaces/Reports/Reports.tsx b/studio/components/interfaces/Reports/Reports.tsx index 4b41c46826b..f4215efde7f 100644 --- a/studio/components/interfaces/Reports/Reports.tsx +++ b/studio/components/interfaces/Reports/Reports.tsx @@ -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() diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index 86f850fbce4..d1e950982a9 100644 --- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -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() diff --git a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx index 7258a5e9a2a..1a3b564ad34 100644 --- a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx @@ -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 }, diff --git a/studio/components/interfaces/Support/SupportForm.tsx b/studio/components/interfaces/Support/SupportForm.tsx index f927cd86096..2247e68f821 100644 --- a/studio/components/interfaces/Support/SupportForm.tsx +++ b/studio/components/interfaces/Support/SupportForm.tsx @@ -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 = ({ setSentCategory }) => { } }, [uploadedFiles]) - const { data: profile } = useProfileQuery() + const { data: profile } = useProfile() const respondToEmail = profile?.primary_email ?? 'your email' if (!isInitialized) { diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index bcbcb7e54fd..7bc09bfc106 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -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') diff --git a/studio/data/profile/profile-create-mutation.ts b/studio/data/profile/profile-create-mutation.ts new file mode 100644 index 00000000000..01f1ffff75c --- /dev/null +++ b/studio/data/profile/profile-create-mutation.ts @@ -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> + +export const useProfileCreateMutation = ({ + onSuccess, + ...options +}: Omit, 'mutationFn'> = {}) => { + const queryClient = useQueryClient() + + return useMutation(() => createProfile(), { + async onSuccess(data, variables, context) { + await queryClient.invalidateQueries(profileKeys.profile()) + + await onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/studio/data/profile/profile-query.ts b/studio/data/profile/profile-query.ts index 7cbddd3e13c..a6e990485a4 100644 --- a/studio/data/profile/profile-query.ts +++ b/studio/data/profile/profile-query.ts @@ -1,48 +1,13 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' -import { get, post } from 'lib/common/fetch' +import { get } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { profileKeys } from './keys' -import Telemetry from 'lib/telemetry' -import { NextRouter, useRouter } from 'next/router' -import { useTelemetryProps } from 'common' - -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 -/** - * Create profile for new user - * This will also need to send a sign_up event - */ -async function createProfile(router: NextRouter, telemetryProps: any) { - const response = await post(`${API_URL}/profile`, {}) - if (response.error) { - throw response.error - } - - // send conversion event - Telemetry.sendEvent( - { category: 'conversion', action: 'sign_up', label: '' }, - telemetryProps, - router - ) - - return response -} - -export async function getProfile(router: NextRouter, telemetryProps: any, signal?: AbortSignal) { +export async function getProfile(signal?: AbortSignal) { const response = await get(`${API_URL}/profile`, { signal, headers: { @@ -50,9 +15,6 @@ export async function getProfile(router: NextRouter, telemetryProps: any, signal }, }) if (response.error) { - // if response.error is not found - // we need to send request to create new user profile - if (response.error.code === 404) return await createProfile(router, telemetryProps) throw response.error } @@ -66,12 +28,9 @@ export const useProfileQuery = ({ enabled = true, ...options }: UseQueryOptions = {}) => { - const router = useRouter() - const telemetryProps = useTelemetryProps() - return useQuery( profileKeys.profile(), - ({ signal }) => getProfile(router, telemetryProps, signal), + ({ signal }) => getProfile(signal), { staleTime: 1000 * 60 * 30, // default good for 30 mins ...options, @@ -81,12 +40,8 @@ export const useProfileQuery = ({ export const useProfilePrefetch = () => { const client = useQueryClient() - const router = useRouter() - const telemetryProps = useTelemetryProps() return useCallback(() => { - client.prefetchQuery(profileKeys.profile(), ({ signal }) => - getProfile(router, telemetryProps, signal) - ) + client.prefetchQuery(profileKeys.profile(), ({ signal }) => getProfile(signal)) }, []) } diff --git a/studio/data/profile/types.ts b/studio/data/profile/types.ts new file mode 100644 index 00000000000..4af9a4d09e9 --- /dev/null +++ b/studio/data/profile/types.ts @@ -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 +} diff --git a/studio/data/query-client.ts b/studio/data/query-client.ts index ee9ce6f2522..80dee15c3c3 100644 --- a/studio/data/query-client.ts +++ b/studio/data/query-client.ts @@ -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.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 diff --git a/studio/hooks/misc/useProfile.tsx b/studio/hooks/misc/useProfile.tsx new file mode 100644 index 00000000000..08f4dbaca4c --- /dev/null +++ b/studio/hooks/misc/useProfile.tsx @@ -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.code === 404) { + createProfile() + } + }, + }) + + return { + // Continue the loading state until the profile is created + isLoading: isLoading || isCreating, + ...result, + } +} + +export default useProfile diff --git a/studio/pages/account/me.tsx b/studio/pages/account/me.tsx index bc98dd9c7c9..cbe90639743 100644 --- a/studio/pages/account/me.tsx +++ b/studio/pages/account/me.tsx @@ -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) => { diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index a87cffe2e10..ce5174adbce 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -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) diff --git a/studio/pages/project/[ref]/reports/index.tsx b/studio/pages/project/[ref]/reports/index.tsx index dfda77f3db6..a533b467837 100644 --- a/studio/pages/project/[ref]/reports/index.tsx +++ b/studio/pages/project/[ref]/reports/index.tsx @@ -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 From b49968d977d4e3047b20fc28e231a367d458442e Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 15 May 2023 21:43:48 +1000 Subject: [PATCH 7/9] send sign_in telemetry event --- studio/hooks/misc/useProfile.tsx | 4 ++-- studio/lib/auth.tsx | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/studio/hooks/misc/useProfile.tsx b/studio/hooks/misc/useProfile.tsx index 08f4dbaca4c..93901e82cd3 100644 --- a/studio/hooks/misc/useProfile.tsx +++ b/studio/hooks/misc/useProfile.tsx @@ -3,7 +3,7 @@ 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' +import Telemetry from 'lib/telemetry' /** * Fetches the user's profile, creating one if it does not exist @@ -14,7 +14,7 @@ function useProfile() { const { mutate: createProfile, isLoading: isCreating } = useProfileCreateMutation({ onSuccess() { - telemetry.sendEvent( + Telemetry.sendEvent( { category: 'conversion', action: 'sign_up', label: '' }, telemetryProps, router diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx index 973530e3afd..815387f5c08 100644 --- a/studio/lib/auth.tsx +++ b/studio/lib/auth.tsx @@ -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: 'conversion', action: 'sign_in', label: '' }, + telemetryProps, + router + ) + } + }) + + return subscription.unsubscribe + }, []) + // Track telemetry for the current user useProfileQuery({ onSuccess(profile) { From da0e196bf9e239817d7607f6ca4f678c8acef58f Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 15 May 2023 21:46:35 +1000 Subject: [PATCH 8/9] fix ts build error --- studio/data/query-client.ts | 2 +- studio/hooks/misc/useProfile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/data/query-client.ts b/studio/data/query-client.ts index 80dee15c3c3..73cbb94e64b 100644 --- a/studio/data/query-client.ts +++ b/studio/data/query-client.ts @@ -15,7 +15,7 @@ export function getQueryClient() { typeof error === 'object' && error !== null && 'code' in error && - error.code === 404 + (error as any).code === 404 ) { return false } diff --git a/studio/hooks/misc/useProfile.tsx b/studio/hooks/misc/useProfile.tsx index 93901e82cd3..a41439114b3 100644 --- a/studio/hooks/misc/useProfile.tsx +++ b/studio/hooks/misc/useProfile.tsx @@ -25,7 +25,7 @@ function useProfile() { 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.code === 404) { + if (typeof err === 'object' && err !== null && 'code' in err && (err as any).code === 404) { createProfile() } }, From 5131409215bdd9bbc2b9473369c3f194ba1275a7 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 16 May 2023 10:50:46 +1000 Subject: [PATCH 9/9] changes sign_in telemetry category --- studio/lib/auth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx index 815387f5c08..b42ac9c84bc 100644 --- a/studio/lib/auth.tsx +++ b/studio/lib/auth.tsx @@ -44,7 +44,7 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { } = gotrueClient.onAuthStateChange((event) => { if (event === 'SIGNED_IN') { Telemetry.sendEvent( - { category: 'conversion', action: 'sign_in', label: '' }, + { category: 'account', action: 'sign_in', label: '' }, telemetryProps, router )