feat: add some tracker
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
549d76285a
commit
b5fc2f6a70
|
|
@ -49,7 +49,6 @@ const FeedFoundCanBeFollowErrorFallback: FC<AppErrorFallbackProps> = ({ resetErr
|
|||
title: t("feed_form.add_feed"),
|
||||
content: ({ dismiss }) => (
|
||||
<FeedForm
|
||||
asWidget
|
||||
url={feed.url}
|
||||
defaultValues={{
|
||||
view: getRouteParams().view.toString(),
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ export const useFeedActions = ({
|
|||
click: () => {
|
||||
present({
|
||||
title: t("sidebar.feed_actions.edit_feed"),
|
||||
content: ({ dismiss }) => <FeedForm asWidget id={feedId} onSuccess={dismiss} />,
|
||||
content: ({ dismiss }) => <FeedForm id={feedId} onSuccess={dismiss} />,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
@ -424,7 +424,7 @@ export const useListActions = ({ listId, view }: { listId: string; view?: FeedVi
|
|||
click: () => {
|
||||
present({
|
||||
title: t("sidebar.feed_actions.edit_list"),
|
||||
content: ({ dismiss }) => <ListForm asWidget id={listId} onSuccess={dismiss} />,
|
||||
content: ({ dismiss }) => <ListForm id={listId} onSuccess={dismiss} />,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ export const usePresentFeedFormModal = () => {
|
|||
title: isList ? t("sidebar.feed_actions.edit_list") : t("sidebar.feed_actions.edit_feed"),
|
||||
content: ({ dismiss }) =>
|
||||
isList ? (
|
||||
<ListForm asWidget id={params.listId} onSuccess={dismiss} />
|
||||
<ListForm id={params.listId} onSuccess={dismiss} />
|
||||
) : (
|
||||
<FeedForm asWidget id={params.feedId} onSuccess={dismiss} />
|
||||
<FeedForm id={params.feedId} onSuccess={dismiss} />
|
||||
),
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -87,14 +87,12 @@ export const useFollow = () => {
|
|||
}
|
||||
return options?.isList ? (
|
||||
<ListForm
|
||||
asWidget
|
||||
id={options?.id}
|
||||
defaultValues={options?.defaultValues as ListFormDataValuesType}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
) : (
|
||||
<FeedForm
|
||||
asWidget
|
||||
id={options?.id}
|
||||
url={options?.url}
|
||||
defaultValues={options?.defaultValues as FeedFormDataValuesType}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import type { UserModel } from "@follow/models"
|
||||
import { identifyUserOpenPanel } from "@follow/tracker"
|
||||
|
||||
import { op } from "./op"
|
||||
|
||||
export const setIntegrationIdentify = async (user: UserModel) => {
|
||||
op.identify({
|
||||
profileId: user.id,
|
||||
email: user.email,
|
||||
avatar: user.image ?? undefined,
|
||||
lastName: user.name ?? undefined,
|
||||
properties: {
|
||||
handle: user.handle,
|
||||
name: user.name,
|
||||
},
|
||||
})
|
||||
identifyUserOpenPanel(user, op.identify.bind(op))
|
||||
op.track("identify", {
|
||||
user_id: user.id,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const useRegisterListCommands = () => {
|
|||
if (!listId) return
|
||||
present({
|
||||
title: t("sidebar.feed_actions.edit_list"),
|
||||
content: ({ dismiss }) => <ListForm asWidget id={listId} onSuccess={dismiss} />,
|
||||
content: ({ dismiss }) => <ListForm id={listId} onSuccess={dismiss} />,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -203,7 +203,6 @@ export const DiscoverFeedForm = ({
|
|||
title: t("feed_form.add_feed"),
|
||||
content: () => (
|
||||
<FeedForm
|
||||
asWidget
|
||||
url={finalUrl}
|
||||
defaultValues={{
|
||||
view: defaultView.toString(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Logo } from "@follow/components/icons/logo.jsx"
|
||||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Card, CardHeader } from "@follow/components/ui/card/index.jsx"
|
||||
import {
|
||||
|
|
@ -15,6 +14,7 @@ import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
|
|||
import { Switch } from "@follow/components/ui/switch/index.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import type { EntryModelSimple, FeedModel } from "@follow/models/types"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
|
|
@ -54,9 +54,9 @@ export const FeedForm: Component<{
|
|||
url?: string
|
||||
id?: string
|
||||
defaultValues?: FeedFormDataValuesType
|
||||
asWidget?: boolean
|
||||
|
||||
onSuccess?: () => void
|
||||
}> = ({ id: _id, defaultValues = defaultValue, url, asWidget, onSuccess }) => {
|
||||
}> = ({ id: _id, defaultValues = defaultValue, url, onSuccess }) => {
|
||||
const queryParams = { id: _id, url }
|
||||
|
||||
const feedQuery = useFeed(queryParams)
|
||||
|
|
@ -67,34 +67,22 @@ export const FeedForm: Component<{
|
|||
url,
|
||||
}) as FeedModel
|
||||
|
||||
const hasSub = useSubscriptionByFeedId(feed?.id || "")
|
||||
const isSubscribed = !!feedQuery.data?.subscription || hasSub
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full flex-col",
|
||||
asWidget
|
||||
? "mx-auto min-h-[420px] w-full max-w-[550px] lg:min-w-[550px]"
|
||||
: "px-[18px] pb-[18px] pt-12",
|
||||
"mx-auto min-h-[420px] w-full max-w-[550px] lg:min-w-[550px]",
|
||||
)}
|
||||
>
|
||||
{!asWidget && (
|
||||
<div className="mb-4 mt-2 flex items-center gap-2 text-[22px] font-bold">
|
||||
<Logo className="size-8" />
|
||||
{isSubscribed ? t("feed_form.update_follow") : t("feed_form.add_follow")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{feed ? (
|
||||
<FeedInnerForm
|
||||
{...{
|
||||
defaultValues,
|
||||
id,
|
||||
url,
|
||||
asWidget,
|
||||
|
||||
onSuccess,
|
||||
subscriptionData: feedQuery.data?.subscription,
|
||||
entries: feedQuery.data?.entries,
|
||||
|
|
@ -167,7 +155,7 @@ export const FeedForm: Component<{
|
|||
const FeedInnerForm = ({
|
||||
defaultValues,
|
||||
id,
|
||||
asWidget,
|
||||
|
||||
onSuccess,
|
||||
subscriptionData,
|
||||
feed,
|
||||
|
|
@ -175,7 +163,7 @@ const FeedInnerForm = ({
|
|||
}: {
|
||||
defaultValues?: z.infer<typeof formSchema>
|
||||
id?: string
|
||||
asWidget?: boolean
|
||||
|
||||
onSuccess?: () => void
|
||||
subscriptionData?: {
|
||||
view?: number
|
||||
|
|
@ -244,8 +232,8 @@ const FeedInnerForm = ({
|
|||
duration: 1000,
|
||||
})
|
||||
|
||||
if (!asWidget && !isSubscribed) {
|
||||
window.close()
|
||||
if (!isSubscribed) {
|
||||
tracker.subscribe({ feedId: feed.id, view: Number.parseInt(variables.view) })
|
||||
}
|
||||
|
||||
onSuccess?.()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Logo } from "@follow/components/icons/logo.jsx"
|
||||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Card, CardHeader } from "@follow/components/ui/card/index.jsx"
|
||||
import {
|
||||
|
|
@ -15,6 +14,7 @@ import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
|
|||
import { Switch } from "@follow/components/ui/switch/index.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import type { ListModel } from "@follow/models/types"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
|
|
@ -55,10 +55,8 @@ export const ListForm: Component<{
|
|||
|
||||
defaultValues?: ListFormDataValuesType
|
||||
|
||||
asWidget?: boolean
|
||||
|
||||
onSuccess?: () => void
|
||||
}> = ({ id: _id, defaultValues = defaultValue, asWidget, onSuccess }) => {
|
||||
}> = ({ id: _id, defaultValues = defaultValue, onSuccess }) => {
|
||||
const queryParams = { id: _id }
|
||||
|
||||
const feedQuery = useList(queryParams)
|
||||
|
|
@ -66,33 +64,21 @@ export const ListForm: Component<{
|
|||
const id = feedQuery.data?.list.id || _id
|
||||
const list = useListById(id)
|
||||
|
||||
const hasSub = useSubscriptionByFeedId(list?.id || "")
|
||||
const isSubscribed = !!feedQuery.data?.subscription || hasSub
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full flex-col",
|
||||
asWidget
|
||||
? "mx-auto min-h-[420px] w-full max-w-[550px] lg:min-w-[550px]"
|
||||
: "px-[18px] pb-[18px] pt-12",
|
||||
"mx-auto min-h-[420px] w-full max-w-[550px] lg:min-w-[550px]",
|
||||
)}
|
||||
>
|
||||
{!asWidget && (
|
||||
<div className="mb-4 mt-2 flex items-center gap-2 text-[22px] font-bold">
|
||||
<Logo className="size-8" />
|
||||
{isSubscribed ? t("feed_form.update_follow") : t("feed_form.add_follow")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{list ? (
|
||||
<ListInnerForm
|
||||
{...{
|
||||
defaultValues,
|
||||
id,
|
||||
asWidget,
|
||||
|
||||
onSuccess,
|
||||
subscriptionData: feedQuery.data?.subscription,
|
||||
list,
|
||||
|
|
@ -164,14 +150,14 @@ export const ListForm: Component<{
|
|||
const ListInnerForm = ({
|
||||
defaultValues,
|
||||
id,
|
||||
asWidget,
|
||||
|
||||
onSuccess,
|
||||
subscriptionData,
|
||||
list,
|
||||
}: {
|
||||
defaultValues?: z.infer<typeof formSchema>
|
||||
id?: string
|
||||
asWidget?: boolean
|
||||
|
||||
onSuccess?: () => void
|
||||
subscriptionData?: {
|
||||
view?: number
|
||||
|
|
@ -241,8 +227,8 @@ const ListInnerForm = ({
|
|||
duration: 1000,
|
||||
})
|
||||
|
||||
if (!asWidget && !isSubscribed) {
|
||||
window.close()
|
||||
if (!isSubscribed) {
|
||||
tracker.subscribe({ listId: list.id, view: Number.parseInt(variables.view) })
|
||||
}
|
||||
|
||||
onSuccess?.()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { Logo } from "@follow/components/icons/logo.jsx"
|
||||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Kbd } from "@follow/components/ui/kbd/Kbd.js"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import type { ComponentProps, FunctionComponentElement } from "react"
|
||||
import { createElement, useCallback, useMemo, useState } from "react"
|
||||
import { createElement, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
|
|
@ -156,6 +157,12 @@ export function GuideModalContent({ onClose }: { onClose: () => void }) {
|
|||
[step, totalSteps],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
tracker.onBoarding({
|
||||
step,
|
||||
done: status === "complete",
|
||||
})
|
||||
}, [status, step])
|
||||
const title = useMemo(() => guideSteps[step - 1]?.title, [guideSteps, step])
|
||||
|
||||
const [isLottieAnimating, setIsLottieAnimating] = useState(false)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ const CmdNPanel = () => {
|
|||
title: t("feed_form.add_feed"),
|
||||
content: () => (
|
||||
<FeedForm
|
||||
asWidget
|
||||
url={url}
|
||||
defaultValues={{
|
||||
view: defaultView.toString(),
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { useLayoutEffect } from "react"
|
||||
import { useSearchParams } from "react-router"
|
||||
|
||||
import { FeedForm } from "~/modules/discover/feed-form"
|
||||
|
||||
export function Component() {
|
||||
const [urlSearchParams] = useSearchParams()
|
||||
|
||||
const paramUrl = urlSearchParams.get("url")
|
||||
const url = paramUrl ? decodeURIComponent(paramUrl) : undefined
|
||||
const id = urlSearchParams.get("id") || undefined
|
||||
const defaultView = urlSearchParams.get("view") || FeedViewType.Articles
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.body.style.overflow = "hidden"
|
||||
return () => {
|
||||
document.body.style.overflow = ""
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className="bg-theme-background h-full overflow-hidden">
|
||||
<FeedForm
|
||||
url={url}
|
||||
id={id}
|
||||
defaultValues={{
|
||||
view: defaultView.toString(),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
"@follow/hooks": "workspace:*",
|
||||
"@follow/models": "workspace:*",
|
||||
"@follow/shared": "workspace:*",
|
||||
"@follow/tracker": "workspace:*",
|
||||
"@follow/utils": "workspace:*",
|
||||
"@gorhom/portal": "1.0.14",
|
||||
"@hookform/resolvers": "4.1.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { identifyUserOpenPanel, setOpenPanelTracker } from "@follow/tracker"
|
||||
import { nativeApplicationVersion, nativeBuildVersion } from "expo-application"
|
||||
|
||||
import { op } from "../lib/op"
|
||||
import { whoami } from "../store/user/getters"
|
||||
|
||||
export const initAnalytics = () => {
|
||||
setOpenPanelTracker(op.track.bind(op))
|
||||
const user = whoami()
|
||||
if (user) {
|
||||
identifyUserOpenPanel(user, op.identify.bind(op))
|
||||
}
|
||||
|
||||
op.setGlobalProperties({
|
||||
build: "rn",
|
||||
version: nativeApplicationVersion,
|
||||
buildId: nativeBuildVersion,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
import { tracker } from "@follow/tracker"
|
||||
import { nativeApplicationVersion } from "expo-application"
|
||||
|
||||
import { initializeDb } from "../database"
|
||||
import { initAnalytics } from "./analytics"
|
||||
import { initializeAppCheck } from "./app-check"
|
||||
import { initializeDayjs } from "./dayjs"
|
||||
import { hydrateDatabaseToStore, hydrateQueryClient, hydrateSettings } from "./hydrate"
|
||||
|
|
@ -11,18 +15,30 @@ export const initializeApp = async () => {
|
|||
|
||||
const now = Date.now()
|
||||
initializeDb()
|
||||
initAnalytics()
|
||||
|
||||
await apm("migrateDatabase", migrateDatabase)
|
||||
initializeDayjs()
|
||||
|
||||
await apm("hydrateSettings", hydrateSettings)
|
||||
let dataHydratedTime = Date.now()
|
||||
await apm("hydrateDatabaseToStore", hydrateDatabaseToStore)
|
||||
|
||||
dataHydratedTime = Date.now() - dataHydratedTime
|
||||
await apm("hydrateQueryClient", hydrateQueryClient)
|
||||
await apm("initializeAppCheck", initializeAppCheck)
|
||||
|
||||
const loadingTime = Date.now() - now
|
||||
console.log(`Initialize done,`, `${loadingTime}ms`)
|
||||
|
||||
tracker.appInit({
|
||||
rn: true,
|
||||
loading_time: loadingTime,
|
||||
version: nativeApplicationVersion!,
|
||||
data_hydrated_time: dataHydratedTime,
|
||||
electron: false,
|
||||
using_indexed_db: true,
|
||||
})
|
||||
await apm("initializePlayer", initializePlayer)
|
||||
console.log(`Initialize done,`, `${loadingTime}ms`)
|
||||
}
|
||||
|
||||
const apm = async (label: string, fn: () => Promise<any> | any) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { OpenPanel } from "@openpanel/sdk"
|
||||
import { OpenPanel } from "@follow/tracker/src/op"
|
||||
import { nativeApplicationVersion, nativeBuildVersion } from "expo-application"
|
||||
|
||||
import { proxyEnv } from "./proxy-env"
|
||||
|
||||
export const op = new OpenPanel({
|
||||
clientId: proxyEnv.OPENPANEL_CLIENT_ID ?? "",
|
||||
|
||||
apiUrl: proxyEnv.OPENPANEL_API_URL,
|
||||
// waitForProfile: true,
|
||||
headers: {
|
||||
Host: "app.follow.is",
|
||||
Origin: "https://app.follow.is",
|
||||
"User-Agent": `Folo/${nativeApplicationVersion}(${nativeBuildVersion})`,
|
||||
},
|
||||
sdkVersion: "1.0.0",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { cn, formatEstimatedMins, formatTimeToSeconds } from "@follow/utils"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from "react-native"
|
||||
|
|
@ -37,6 +38,11 @@ export function EntryNormalItem({ entryId, extraData }: { entryId: string; extra
|
|||
const isHorizontalScrolling = getHorizontalScrolling()
|
||||
if (entry && !isHorizontalScrolling) {
|
||||
preloadWebViewEntry(entry)
|
||||
tracker.navigateEntry({
|
||||
feedId: entry.feedId!,
|
||||
entryId: entry.id,
|
||||
})
|
||||
|
||||
navigation.pushControllerView(EntryDetailScreen, {
|
||||
entryId,
|
||||
view,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { tracker } from "@follow/tracker"
|
||||
import { uniqBy } from "es-toolkit/compat"
|
||||
import { useMemo } from "react"
|
||||
import { Text, View } from "react-native"
|
||||
|
|
@ -38,6 +39,10 @@ export function EntryPictureItem({ id }: { id: string }) {
|
|||
if (!feed) {
|
||||
return
|
||||
}
|
||||
tracker.navigateEntry({
|
||||
feedId: item.feedId!,
|
||||
entryId: id,
|
||||
})
|
||||
|
||||
showEntryGaleriaAccessory({
|
||||
author: item.author || "",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { useCallback, useEffect, useMemo } from "react"
|
||||
import { Pressable, Text, View } from "react-native"
|
||||
import ReAnimated, { useAnimatedStyle, useSharedValue, withSpring } from "react-native-reanimated"
|
||||
|
|
@ -33,12 +34,16 @@ export function EntrySocialItem({ entryId }: { entryId: string }) {
|
|||
const isHorizontalScrolling = getHorizontalScrolling()
|
||||
if (!isHorizontalScrolling) {
|
||||
unreadSyncService.markEntryAsRead(entryId)
|
||||
tracker.navigateEntry({
|
||||
feedId: entry?.feedId!,
|
||||
entryId,
|
||||
})
|
||||
navigation.pushControllerView(EntryDetailScreen, {
|
||||
entryId,
|
||||
view: FeedViewType.SocialMedia,
|
||||
})
|
||||
}
|
||||
}, [entryId, navigation])
|
||||
}, [entry?.feedId, entryId, navigation])
|
||||
|
||||
const unreadZoomSharedValue = useSharedValue(entry?.read ? 0 : 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { tracker } from "@follow/tracker"
|
||||
import { transformVideoUrl } from "@follow/utils"
|
||||
import { Linking } from "react-native"
|
||||
|
||||
|
|
@ -25,10 +26,15 @@ export function EntryVideoItem({ id }: { id: string }) {
|
|||
className="m-1"
|
||||
onPress={() => {
|
||||
unreadSyncService.markEntryAsRead(id)
|
||||
tracker.navigateEntry({
|
||||
feedId: item.feedId!,
|
||||
entryId: id,
|
||||
})
|
||||
if (!item.url) {
|
||||
toast.error("No video URL found")
|
||||
return
|
||||
}
|
||||
|
||||
openVideo(item.url)
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { tracker } from "@follow/tracker"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import type { Control } from "react-hook-form"
|
||||
|
|
@ -49,6 +50,7 @@ async function onSubmit(values: FormValue) {
|
|||
toast.error(res.error.message)
|
||||
} else {
|
||||
toast.success("Sign up successful")
|
||||
tracker.register({ type: "email" })
|
||||
Navigation.rootNavigation.back()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { tracker } from "@follow/tracker"
|
||||
import { useCallback, useState } from "react"
|
||||
import { SafeAreaView, Text, TouchableOpacity, View } from "react-native"
|
||||
import { SheetScreen } from "react-native-sheet-transitions"
|
||||
|
|
@ -20,8 +21,10 @@ export const OnboardingScreen: NavigationControllerView = () => {
|
|||
const handleNext = useCallback(() => {
|
||||
if (currentStep < totalSteps) {
|
||||
setCurrentStep(currentStep + 1)
|
||||
tracker.onBoarding({ step: currentStep, done: false })
|
||||
} else {
|
||||
// Complete onboarding
|
||||
tracker.onBoarding({ step: currentStep, done: true })
|
||||
kv.set(isOnboardingFinishedStorageKey, "true")
|
||||
queryClient.invalidateQueries({ queryKey: isNewUserQueryKey }).then(() => {
|
||||
navigation.back()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { tracker } from "@follow/tracker"
|
||||
|
||||
import type { SubscriptionSchema } from "@/src/database/schemas/types"
|
||||
import { apiClient } from "@/src/lib/api-fetch"
|
||||
|
|
@ -223,6 +224,7 @@ class SubscriptionSyncService {
|
|||
|
||||
if (data.feed) {
|
||||
feedActions.upsertMany([data.feed])
|
||||
tracker.subscribe({ feedId: data.feed.id, view: subscription.view })
|
||||
}
|
||||
|
||||
if (data.list) {
|
||||
|
|
@ -232,8 +234,8 @@ class SubscriptionSyncService {
|
|||
userId: data.list.ownerUserId,
|
||||
},
|
||||
])
|
||||
tracker.subscribe({ listId: data.list.id, view: subscription.view })
|
||||
}
|
||||
|
||||
// Insert to subscription
|
||||
subscriptionActions.upsertMany([
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { identifyUserOpenPanel, tracker } from "@follow/tracker"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { useEffect } from "react"
|
||||
|
||||
|
|
@ -21,19 +22,8 @@ export const usePrefetchSessionUser = () => {
|
|||
useEffect(() => {
|
||||
if (query.data) {
|
||||
const user = query.data
|
||||
op.identify({
|
||||
profileId: user.id,
|
||||
email: user.email!,
|
||||
avatar: user.image ?? undefined,
|
||||
lastName: user.name!,
|
||||
properties: {
|
||||
handle: user.handle,
|
||||
name: user.name,
|
||||
},
|
||||
})
|
||||
op.track("user_login", {
|
||||
userId: query.data.id,
|
||||
})
|
||||
identifyUserOpenPanel(user, op.identify.bind(op))
|
||||
tracker.identify(user.id)
|
||||
}
|
||||
}, [query.data])
|
||||
return query
|
||||
|
|
|
|||
|
|
@ -1,32 +1,14 @@
|
|||
import { env } from "@follow/shared/env"
|
||||
import type { TrackProperties } from "@openpanel/web"
|
||||
import { setOpenPanelTracker } from "@follow/tracker"
|
||||
|
||||
import { op } from "./op"
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
analytics?: {
|
||||
capture: (event_name: string, properties?: TrackProperties | null) => void
|
||||
reset: () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
export const initAnalytics = () => {
|
||||
if (env.VITE_OPENPANEL_CLIENT_ID === undefined) return
|
||||
|
||||
setOpenPanelTracker(op.track.bind(op))
|
||||
op.setGlobalProperties({
|
||||
build: "external-web",
|
||||
hash: GIT_COMMIT_SHA,
|
||||
})
|
||||
|
||||
window.analytics = {
|
||||
reset: () => {
|
||||
// op.clear()
|
||||
},
|
||||
capture(event_name: string, properties?: TrackProperties | null) {
|
||||
if (import.meta.env.DEV) return
|
||||
|
||||
op.track(event_name, properties as TrackProperties)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
import type { AuthUser } from "@follow/shared/hono"
|
||||
import { identifyUserOpenPanel, tracker } from "@follow/tracker"
|
||||
|
||||
import { op } from "./op"
|
||||
|
||||
export const setIntegrationIdentify = async (user: AuthUser) => {
|
||||
op.identify({
|
||||
profileId: user.id,
|
||||
email: user.email,
|
||||
avatar: user.image ?? undefined,
|
||||
lastName: user.name,
|
||||
properties: {
|
||||
handle: user.handle,
|
||||
name: user.name,
|
||||
},
|
||||
})
|
||||
identifyUserOpenPanel(user, op.identify.bind(op))
|
||||
tracker.identify(user.id)
|
||||
|
||||
await import("@sentry/react").then(({ setTag }) => {
|
||||
setTag("user_id", user.id)
|
||||
setTag("user_name", user.name)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@follow/components/ui/form/index.jsx"
|
||||
import { Input } from "@follow/components/ui/input/index.js"
|
||||
import { env } from "@follow/shared/env"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useRef } from "react"
|
||||
import ReCAPTCHA from "react-google-recaptcha"
|
||||
|
|
@ -67,6 +68,9 @@ function RegisterForm() {
|
|||
callbackURL: "/",
|
||||
fetchOptions: {
|
||||
onSuccess() {
|
||||
tracker.register({
|
||||
type: "email",
|
||||
})
|
||||
navigate("/login")
|
||||
},
|
||||
onError(context) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@fastify/middie": "9.0.3",
|
||||
"@fastify/request-context": "6.1.0",
|
||||
"@follow/tracker": "workspace:*",
|
||||
"@fontsource/sn-pro": "5.2.5",
|
||||
"@openpanel/web": "1.0.1",
|
||||
"@resvg/resvg-js": "2.6.2",
|
||||
|
|
|
|||
|
|
@ -6,3 +6,29 @@ export const tracker = new TrackerPoints()
|
|||
|
||||
export { type TrackerPoints } from "./points"
|
||||
export { TrackerMapper } from "./points"
|
||||
|
||||
///// OpenPanel Utils
|
||||
|
||||
type Nullable<T> = T | null | undefined
|
||||
|
||||
export const identifyUserOpenPanel = (
|
||||
user: {
|
||||
id: string
|
||||
email?: Nullable<string>
|
||||
name?: Nullable<string>
|
||||
handle?: Nullable<string>
|
||||
image?: Nullable<string>
|
||||
},
|
||||
identifyFn: (...args: any[]) => any,
|
||||
) => {
|
||||
identifyFn({
|
||||
profileId: user.id,
|
||||
email: user.email!,
|
||||
avatar: user.image ?? undefined,
|
||||
lastName: user.name!,
|
||||
properties: {
|
||||
handle: user.handle,
|
||||
name: user.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ interface ApiConfig {
|
|||
defaultHeaders?: Record<string, string | Promise<string | null>>
|
||||
maxRetries?: number
|
||||
initialRetryDelay?: number
|
||||
|
||||
headers?: Record<string, string | Promise<string | null>>
|
||||
}
|
||||
|
||||
interface FetchOptions extends RequestInit {
|
||||
|
|
@ -22,7 +20,6 @@ export class Api {
|
|||
this.headers = {
|
||||
"Content-Type": "application/json",
|
||||
...config.defaultHeaders,
|
||||
...config.headers,
|
||||
}
|
||||
this.maxRetries = config.maxRetries ?? 3
|
||||
this.initialRetryDelay = config.initialRetryDelay ?? 500
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export type OpenPanelOptions = {
|
|||
waitForProfile?: boolean
|
||||
filter?: (payload: TrackHandlerPayload) => boolean
|
||||
disabled?: boolean
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
export class OpenPanel {
|
||||
|
|
@ -79,6 +80,7 @@ export class OpenPanel {
|
|||
constructor(public options: OpenPanelOptions) {
|
||||
const defaultHeaders: Record<string, string> = {
|
||||
"openpanel-client-id": options.clientId,
|
||||
...options.headers,
|
||||
}
|
||||
|
||||
if (options.clientSecret) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,14 @@ export enum TrackerMapper {
|
|||
FeedClaimed = 2012,
|
||||
DailyRewardClaimed = 2013,
|
||||
TipSent = 2014,
|
||||
|
||||
// https://docs.google.com/spreadsheets/d/1XlUxTxiXWIQDHFYa2eoPBeuosR1t2h8VFIjXEOqmjhY/edit?gid=0#gid=0
|
||||
Register = 3000,
|
||||
OnBoarding = 3001,
|
||||
Subscribe = 3002,
|
||||
EntryRead = 3003,
|
||||
EntryAction = 3004,
|
||||
ViewAction = 3005,
|
||||
}
|
||||
|
||||
const CodeToTrackerName = Object.fromEntries(
|
||||
|
|
@ -143,6 +151,30 @@ export class TrackerPoints {
|
|||
this.track(TrackerMapper.TipSent, props)
|
||||
}
|
||||
|
||||
register(props: { type: "email" | "social" }) {
|
||||
this.track(TrackerMapper.Register, props)
|
||||
}
|
||||
|
||||
onBoarding(props: { step: number; done: boolean }) {
|
||||
this.track(TrackerMapper.OnBoarding, props)
|
||||
}
|
||||
|
||||
subscribe(props: { feedId?: string; listId?: string; view?: number }) {
|
||||
this.track(TrackerMapper.Subscribe, props)
|
||||
}
|
||||
|
||||
entryRead(props: { entryId: string }) {
|
||||
this.track(TrackerMapper.EntryRead, props)
|
||||
}
|
||||
|
||||
entryAction(props: { entryId: string; action: string }) {
|
||||
this.track(TrackerMapper.EntryAction, props)
|
||||
}
|
||||
|
||||
viewAction(props: { view: string; action: string }) {
|
||||
this.track(TrackerMapper.ViewAction, props)
|
||||
}
|
||||
|
||||
private track(code: TrackerMapper, properties?: Record<string, unknown>) {
|
||||
if (code) {
|
||||
let name = CodeToTrackerName[code]
|
||||
|
|
|
|||
|
|
@ -782,6 +782,9 @@ importers:
|
|||
'@follow/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
'@follow/tracker':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/tracker
|
||||
'@follow/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/utils
|
||||
|
|
@ -1107,6 +1110,9 @@ importers:
|
|||
'@fastify/request-context':
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0
|
||||
'@follow/tracker':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/tracker
|
||||
'@fontsource/sn-pro':
|
||||
specifier: 5.2.5
|
||||
version: 5.2.5
|
||||
|
|
|
|||
Loading…
Reference in New Issue