diff --git a/common/tracking.ts b/common/tracking.ts new file mode 100644 index 00000000..29a1365c --- /dev/null +++ b/common/tracking.ts @@ -0,0 +1,12 @@ +export type View = { + contractId: string + timestamp: number +} + +export type UserEvent = ClickEvent + +export type ClickEvent = { + type: 'click' + contractId: string + timestamp: number +} diff --git a/common/view.ts b/common/view.ts deleted file mode 100644 index 7355bf89..00000000 --- a/common/view.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type View = { - contractId: string - timestamp: number -} diff --git a/firestore.rules b/firestore.rules index 099aa51d..542bd5ec 100644 --- a/firestore.rules +++ b/firestore.rules @@ -26,6 +26,10 @@ service cloud.firestore { allow create: if userId == request.auth.uid; } + match /private-users/{userId}/events/{eventId} { + allow create: if userId == request.auth.uid; + } + match /contracts/{contractId} { allow read; allow update: if request.resource.data.diff(resource.data).affectedKeys() diff --git a/web/components/feed/contract-activity.tsx b/web/components/feed/contract-activity.tsx index d686ef3d..d79b462a 100644 --- a/web/components/feed/contract-activity.tsx +++ b/web/components/feed/contract-activity.tsx @@ -46,7 +46,6 @@ export function ContractActivity(props: { return ( (null) - useSaveSeenContract(ref, contract, user) + useSaveSeenContract(ref, contract) return (
@@ -347,6 +347,7 @@ export function FeedQuestion(props: { href={ props.contractPath ? props.contractPath : contractPath(contract) } + onClick={() => trackClick(contract.id)} className="text-lg text-indigo-700 sm:text-xl" > {question} diff --git a/web/components/site-link.tsx b/web/components/site-link.tsx index 8b19d93b..f3c187ef 100644 --- a/web/components/site-link.tsx +++ b/web/components/site-link.tsx @@ -4,9 +4,10 @@ import Link from 'next/link' export const SiteLink = (props: { href: string children?: any + onClick?: () => void className?: string }) => { - const { href, children, className } = props + const { href, children, onClick, className } = props return href.startsWith('http') ? ( e.stopPropagation()} + onClick={(e) => { + e.stopPropagation() + if (onClick) onClick() + }} > {children} @@ -29,7 +33,10 @@ export const SiteLink = (props: { className )} style={{ /* For iOS safari */ wordBreak: 'break-word' }} - onClick={(e) => e.stopPropagation()} + onClick={(e) => { + e.stopPropagation() + if (onClick) onClick() + }} > {children} diff --git a/web/hooks/use-seen-contracts.ts b/web/hooks/use-seen-contracts.ts index 68e4c71d..58ff1c54 100644 --- a/web/hooks/use-seen-contracts.ts +++ b/web/hooks/use-seen-contracts.ts @@ -1,8 +1,7 @@ import _ from 'lodash' import { useEffect, RefObject, useState } from 'react' import { Contract } from '../../common/contract' -import { User } from '../../common/user' -import { logView } from '../lib/firebase/views' +import { trackView } from '../lib/firebase/tracking' import { useIsVisible } from './use-is-visible' export const useSeenContracts = () => { @@ -19,8 +18,7 @@ export const useSeenContracts = () => { export const useSaveSeenContract = ( ref: RefObject, - contract: Contract, - user: User | null | undefined + contract: Contract ) => { const isVisible = useIsVisible(ref) @@ -32,9 +30,9 @@ export const useSaveSeenContract = ( } 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' diff --git a/web/lib/firebase/tracking.ts b/web/lib/firebase/tracking.ts new file mode 100644 index 00000000..4d609f68 --- /dev/null +++ b/web/lib/firebase/tracking.ts @@ -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) +} diff --git a/web/lib/firebase/views.ts b/web/lib/firebase/views.ts deleted file mode 100644 index af110b6b..00000000 --- a/web/lib/firebase/views.ts +++ /dev/null @@ -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) -}