Track clicks from feed
This commit is contained in:
parent
a6e6e2f52f
commit
280ae97070
12
common/tracking.ts
Normal file
12
common/tracking.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export type View = {
|
||||||
|
contractId: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserEvent = ClickEvent
|
||||||
|
|
||||||
|
export type ClickEvent = {
|
||||||
|
type: 'click'
|
||||||
|
contractId: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
export type View = {
|
|
||||||
contractId: string
|
|
||||||
timestamp: number
|
|
||||||
}
|
|
|
@ -26,6 +26,10 @@ service cloud.firestore {
|
||||||
allow create: if userId == request.auth.uid;
|
allow create: if userId == request.auth.uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /private-users/{userId}/events/{eventId} {
|
||||||
|
allow create: if userId == request.auth.uid;
|
||||||
|
}
|
||||||
|
|
||||||
match /contracts/{contractId} {
|
match /contracts/{contractId} {
|
||||||
allow read;
|
allow read;
|
||||||
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
||||||
|
|
|
@ -46,7 +46,6 @@ export function ContractActivity(props: {
|
||||||
return (
|
return (
|
||||||
<FeedItems
|
<FeedItems
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
|
||||||
items={items}
|
items={items}
|
||||||
className={className}
|
className={className}
|
||||||
betRowClassName={betRowClassName}
|
betRowClassName={betRowClassName}
|
||||||
|
|
|
@ -45,19 +45,19 @@ import { AnswerBetPanel } from '../answers/answer-bet-panel'
|
||||||
import { useSaveSeenContract } from '../../hooks/use-seen-contracts'
|
import { useSaveSeenContract } from '../../hooks/use-seen-contracts'
|
||||||
import { User } from '../../../common/user'
|
import { User } from '../../../common/user'
|
||||||
import { Modal } from '../layout/modal'
|
import { Modal } from '../layout/modal'
|
||||||
|
import { trackClick } from '../../lib/firebase/tracking'
|
||||||
|
|
||||||
export function FeedItems(props: {
|
export function FeedItems(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
user: User | null | undefined
|
|
||||||
items: ActivityItem[]
|
items: ActivityItem[]
|
||||||
className?: string
|
className?: string
|
||||||
betRowClassName?: string
|
betRowClassName?: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, user, items, className, betRowClassName } = props
|
const { contract, items, className, betRowClassName } = props
|
||||||
const { outcomeType } = contract
|
const { outcomeType } = contract
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
const ref = useRef<HTMLDivElement | null>(null)
|
||||||
useSaveSeenContract(ref, contract, user)
|
useSaveSeenContract(ref, contract)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('flow-root pr-2 md:pr-0', className)} ref={ref}>
|
<div className={clsx('flow-root pr-2 md:pr-0', className)} ref={ref}>
|
||||||
|
@ -347,6 +347,7 @@ export function FeedQuestion(props: {
|
||||||
href={
|
href={
|
||||||
props.contractPath ? props.contractPath : contractPath(contract)
|
props.contractPath ? props.contractPath : contractPath(contract)
|
||||||
}
|
}
|
||||||
|
onClick={() => trackClick(contract.id)}
|
||||||
className="text-lg text-indigo-700 sm:text-xl"
|
className="text-lg text-indigo-700 sm:text-xl"
|
||||||
>
|
>
|
||||||
{question}
|
{question}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import Link from 'next/link'
|
||||||
export const SiteLink = (props: {
|
export const SiteLink = (props: {
|
||||||
href: string
|
href: string
|
||||||
children?: any
|
children?: any
|
||||||
|
onClick?: () => void
|
||||||
className?: string
|
className?: string
|
||||||
}) => {
|
}) => {
|
||||||
const { href, children, className } = props
|
const { href, children, onClick, className } = props
|
||||||
|
|
||||||
return href.startsWith('http') ? (
|
return href.startsWith('http') ? (
|
||||||
<a
|
<a
|
||||||
|
@ -17,7 +18,10 @@ export const SiteLink = (props: {
|
||||||
)}
|
)}
|
||||||
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (onClick) onClick()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
|
@ -29,7 +33,10 @@ export const SiteLink = (props: {
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (onClick) onClick()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useEffect, RefObject, useState } from 'react'
|
import { useEffect, RefObject, useState } from 'react'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { trackView } from '../lib/firebase/tracking'
|
||||||
import { logView } from '../lib/firebase/views'
|
|
||||||
import { useIsVisible } from './use-is-visible'
|
import { useIsVisible } from './use-is-visible'
|
||||||
|
|
||||||
export const useSeenContracts = () => {
|
export const useSeenContracts = () => {
|
||||||
|
@ -19,8 +18,7 @@ export const useSeenContracts = () => {
|
||||||
|
|
||||||
export const useSaveSeenContract = (
|
export const useSaveSeenContract = (
|
||||||
ref: RefObject<Element>,
|
ref: RefObject<Element>,
|
||||||
contract: Contract,
|
contract: Contract
|
||||||
user: User | null | undefined
|
|
||||||
) => {
|
) => {
|
||||||
const isVisible = useIsVisible(ref)
|
const isVisible = useIsVisible(ref)
|
||||||
|
|
||||||
|
@ -32,9 +30,9 @@ export const useSaveSeenContract = (
|
||||||
}
|
}
|
||||||
localStorage.setItem(key, JSON.stringify(newSeenContracts))
|
localStorage.setItem(key, JSON.stringify(newSeenContracts))
|
||||||
|
|
||||||
if (user) logView(user.id, contract.id)
|
trackView(contract.id)
|
||||||
}
|
}
|
||||||
}, [isVisible, contract, user])
|
}, [isVisible, contract])
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = 'feed-seen-contracts'
|
const key = 'feed-seen-contracts'
|
||||||
|
|
36
web/lib/firebase/tracking.ts
Normal file
36
web/lib/firebase/tracking.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { doc, collection, setDoc } from 'firebase/firestore'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import { db } from './init'
|
||||||
|
import { ClickEvent, View } from '../../../common/tracking'
|
||||||
|
import { listenForLogin, User } from './users'
|
||||||
|
|
||||||
|
let user: User | null = null
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
listenForLogin((u) => (user = u))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function trackView(contractId: string) {
|
||||||
|
if (!user) return
|
||||||
|
const ref = doc(collection(db, 'private-users', user.id, 'views'))
|
||||||
|
|
||||||
|
const view: View = {
|
||||||
|
contractId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return await setDoc(ref, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function trackClick(contractId: string) {
|
||||||
|
if (!user) return
|
||||||
|
const ref = doc(collection(db, 'private-users', user.id, 'events'))
|
||||||
|
|
||||||
|
const clickEvent: ClickEvent = {
|
||||||
|
type: 'click',
|
||||||
|
contractId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return await setDoc(ref, clickEvent)
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
import { doc, collection, setDoc } from 'firebase/firestore'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
import { db } from './init'
|
|
||||||
import { View } from '../../../common/view'
|
|
||||||
|
|
||||||
export async function logView(userId: string, contractId: string) {
|
|
||||||
const ref = doc(collection(db, 'private-users', userId, 'views'))
|
|
||||||
|
|
||||||
const view: View = {
|
|
||||||
contractId,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return await setDoc(ref, view)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user