Files
medusa-store/packages/admin-ui/ui/src/providers/analytics-provider.tsx
Juan Pablo Orsay 87e3a7d06a fix(admin-ui): disabling analytics when opted out (#4939)
* fixes #4423 by either using the `useAnalytics` hook or using an ErrorBoundary specific instance

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
2023-09-11 12:40:15 +02:00

224 lines
5.3 KiB
TypeScript

import { AnalyticsBrowser } from "@segment/analytics-next"
import { useAdminGetSession, useAdminStore, useAdminUsers } from "medusa-react"
import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import { useLocation } from "react-router-dom"
import Fade from "../components/atoms/fade-wrapper"
import AnalyticsPreferencesModal from "../components/organisms/analytics-preferences"
import { useDebounce } from "../hooks/use-debounce"
import { useFeatureFlag } from "../providers/feature-flag-provider"
import { useAdminAnalyticsConfig } from "../services/analytics"
type Props = {
children?: React.ReactNode
writeKey: string
}
type Event =
| "numProducts"
| "numOrders"
| "numDiscounts"
| "numUsers"
| "regions"
| "currencies"
| "storeName"
| "userEmail"
type AnalyticsContextType = {
trackCurrencies: (properties: TrackCurrenciesPayload) => void
trackNumberOfOrders: (properties: TrackCountPayload) => void
trackNumberOfDiscounts: (properties: TrackCountPayload) => void
trackNumberOfProducts: (properties: TrackCountPayload) => void
trackRegions: (properties: TrackRegionsPayload) => void
trackUserEmail: (properties: TrackUserEmailPayload) => void
setSubmittingConfig: (status: boolean) => void
}
const AnalyticsContext = createContext<AnalyticsContextType | null>(null)
export const AnalyticsProvider = ({ writeKey, children }: Props) => {
const [submittingConfig, setSubmittingConfig] = useState(false)
const { analytics_config: config, isLoading } = useAdminAnalyticsConfig()
const location = useLocation()
const { user } = useAdminGetSession()
const { users } = useAdminUsers()
const { store } = useAdminStore()
const { isFeatureEnabled } = useFeatureFlag()
const isEnabled = useMemo(() => {
return isFeatureEnabled("analytics")
}, [isFeatureEnabled])
const analytics = useMemo(() => {
if (!config || !isEnabled) {
return null // Don't initialize analytics if not enabled or the user's preferences are not loaded yet
}
if (config.opt_out) {
return null // Don't initialize if user has opted out
}
return AnalyticsBrowser.load({ writeKey })
}, [config, writeKey, isEnabled])
useEffect(() => {
if (!analytics || !config || !user || !store) {
return
}
analytics.identify(user.id, {
store: store.name,
})
}, [config, analytics, user, store])
const askPermission = useMemo(() => {
if (submittingConfig) {
return true
}
if (!isEnabled || !user) {
return false // Don't ask for permission if feature is not enabled
}
return !config && !isLoading
}, [config, isLoading, isEnabled, user, submittingConfig])
/**
* Ensure that the focus modal is animated smoothly.
*/
const animateIn = useDebounce(askPermission, 1000)
const track = useCallback(
(event: Event, properties?: Record<string, unknown>) => {
if (!analytics) {
// If analytics is not initialized, then we return early
return
}
analytics.track(event, properties)
},
[analytics]
)
const trackNumberOfUsers = useCallback(
(properties: TrackCountPayload) => {
track("numUsers", properties)
},
[track]
)
const trackStoreName = useCallback(
(properties: TrackStoreNamePayload) => {
track("storeName", properties)
},
[track]
)
const trackNumberOfProducts = (properties: TrackCountPayload) => {
track("numProducts", properties)
}
const trackNumberOfOrders = (properties: TrackCountPayload) => {
track("numOrders", properties)
}
const trackRegions = (properties: TrackRegionsPayload) => {
track("regions", properties)
}
const trackCurrencies = (properties: TrackCurrenciesPayload) => {
track("currencies", properties)
}
const trackNumberOfDiscounts = (properties: TrackCountPayload) => {
track("numDiscounts", properties)
}
const trackUserEmail = (properties: TrackUserEmailPayload) => {
track("userEmail", properties)
}
// Track number of users
useEffect(() => {
if (users) {
trackNumberOfUsers({ count: users.length })
}
}, [users, trackNumberOfUsers])
// Track store name
useEffect(() => {
if (store) {
trackStoreName({ name: store.name })
}
}, [store, trackStoreName])
// Track pages visited when location changes
useEffect(() => {
if (!analytics) {
return
}
analytics.page()
}, [location])
return (
<AnalyticsContext.Provider
value={{
trackRegions,
trackCurrencies,
trackNumberOfOrders,
trackNumberOfProducts,
trackNumberOfDiscounts,
trackUserEmail,
setSubmittingConfig,
}}
>
{askPermission && (
<Fade isVisible={animateIn} isFullScreen={true}>
<AnalyticsPreferencesModal />
</Fade>
)}
{children}
</AnalyticsContext.Provider>
)
}
type TrackCurrenciesPayload = {
used_currencies: string[]
}
type TrackStoreNamePayload = {
name: string
}
type TrackCountPayload = {
count: number
}
type TrackRegionsPayload = {
regions: string[]
count: number
}
type TrackUserEmailPayload = {
email: string | undefined
}
export const useAnalytics = () => {
const context = useContext(AnalyticsContext)
if (!context) {
throw new Error("useAnalytics must be used within a AnalyticsProvider")
}
return context
}