Skip to content

Commit cb23bff

Browse files
feat: added conversion api implementation
1 parent 7ad55a8 commit cb23bff

File tree

17 files changed

+202
-2
lines changed

17 files changed

+202
-2
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
3+
import { sendFacebookEvent } from "@/lib/analytics";
4+
import { IAnalytics } from "@litespace/types";
5+
import { usePathname, useSearchParams } from "next/navigation";
6+
import { useEffect } from "react";
7+
8+
const Conversion: React.FC<{ eventName?: IAnalytics.EventType }> = ({
9+
eventName = IAnalytics.EventType.PageView,
10+
}) => {
11+
const searchParams = useSearchParams();
12+
const path = usePathname();
13+
14+
useEffect(() => {
15+
const fbclid = searchParams.get("fbclid");
16+
if (!fbclid) return;
17+
18+
sendFacebookEvent({
19+
page: path,
20+
eventName,
21+
fbclid,
22+
});
23+
}, [path, eventName, searchParams]);
24+
25+
return null;
26+
};
27+
28+
export default Conversion;

apps/landing/components/Layout/Layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import cn from "classnames";
55
import Navbar from "@/components/Layout/Navbar";
66
import Sidebar from "@/components/Layout/Sidebar";
77
import Footer from "@/components/Layout/Footer";
8+
import Conversion from "@/components/Analytics/Conversion";
89
import clarity, { getCustomeId, sessionId } from "@/lib/clarity";
910
import { usePathname } from "next/navigation";
11+
import { IAnalytics } from "@litespace/types";
1012

1113
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
1214
const [showSidebar, setShowSidebar] = useState(false);
@@ -27,6 +29,7 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
2729
{showSidebar ? <Sidebar hide={() => setShowSidebar(false)} /> : null}
2830
{children}
2931
<Footer />
32+
<Conversion eventName={IAnalytics.EventType.PageView} />
3033
</body>
3134
);
3235
};

apps/landing/lib/analytics.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { formatFbc } from "@litespace/utils/analytics";
2+
import { api } from "@/lib/api";
3+
import { IAnalytics } from "@litespace/types";
4+
5+
export function sendFacebookEvent({
6+
page,
7+
eventName,
8+
fbclid,
9+
}: {
10+
page: string;
11+
fbclid?: string | null;
12+
eventName: IAnalytics.EventType;
13+
}) {
14+
return api.analytics.trackFacebookEvents({
15+
eventName: eventName,
16+
eventSourceUrl: window.location.href,
17+
fbc: formatFbc(fbclid),
18+
customData: {
19+
page: page,
20+
},
21+
});
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Base } from "@/lib/base";
2+
import { IAnalytics } from "@litespace/types";
3+
4+
export class Analytics extends Base {
5+
async trackFacebookEvents(
6+
payload: IAnalytics.ConversionEventPayload
7+
): Promise<void> {
8+
return this.post({
9+
route: "/api/v1/analytics/fb/track",
10+
payload,
11+
});
12+
}
13+
}

packages/atlas/src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import { Fawry } from "@/api/fawry";
2222
import { ConfirmationCode } from "@/api/confirmationCode";
2323
import { Subscription } from "@/api/subscription";
2424
import { Transaction } from "@/api/transaction";
25+
import { Analytics } from "@/api/analytics";
2526

2627
export class Api {
2728
public readonly user: User;
2829
public readonly auth: Auth;
30+
public readonly analytics: Analytics;
2931
public readonly availabilitySlot: AvailabilitySlot;
3032
public readonly contactRequest: ContactRequest;
3133
public readonly plan: Plan;
@@ -51,6 +53,7 @@ export class Api {
5153
const client = createClient("api", server, token);
5254
this.user = new User(client);
5355
this.auth = new Auth(client);
56+
this.analytics = new Analytics(client);
5457
this.availabilitySlot = new AvailabilitySlot(client);
5558
this.contactRequest = new ContactRequest(client);
5659
this.plan = new Plan(client);

packages/types/src/analytics.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export type ConversionEventPayload = {
2+
eventName: EventType;
3+
userId?: string;
4+
fbc?: string;
5+
eventSourceUrl?: string;
6+
customData?: Record<string, string | number>;
7+
};
8+
9+
export type ConversionEvent = {
10+
event_name: EventType;
11+
event_time: number;
12+
event_source_url?: string;
13+
custom_data?: Record<string, string | number>;
14+
action_source: "website";
15+
user_id?: string;
16+
user_data: {
17+
client_user_agent: string;
18+
client_ip_address?: string;
19+
fbc?: string;
20+
};
21+
};
22+
23+
export enum EventType {
24+
PageView = "page-view",
25+
Register = "register",
26+
Login = "login",
27+
Depth = "depth",
28+
Engagement = "engagement",
29+
}

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * as IAnalytics from "@/analytics";
12
export * as IContactRequest from "@/contactRequest";
23
export * as IConfirmationCode from "@/confirmationCode";
34
export * as ISessionEvent from "@/sessionEvent";

packages/utils/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@
117117
"import": "./dist/esm/plan.js",
118118
"require": "./dist/cjs/plan.js"
119119
},
120+
"./analytics": {
121+
"import": "./dist/esm/analytics.js",
122+
"require": "./dist/cjs/analytics.js"
123+
},
120124
"./filterQuery": {
121125
"import": "./dist/esm/filterQuery/index.js",
122126
"require": "./dist/cjs/filterQuery/index.js"

packages/utils/src/analytics.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { dayjs } from "@/dayjs";
2+
3+
/**
4+
* see docs for fbc: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
5+
* @param fbclid parameter that is passed with the URL of an advertiser's website when a user
6+
* clicks an ad on Facebook and/or Instagram. You get that from url search params
7+
* @returns fbc which is a unique id for each user
8+
*/
9+
export function formatFbc(fbclid?: string | null) {
10+
if (!fbclid) return undefined;
11+
12+
const host = window.location.hostname;
13+
const hostIndex = host.startsWith("www.") ? 2 : 1;
14+
const now = dayjs().valueOf();
15+
16+
return `fb.${hostIndex}.${now}.${fbclid}`;
17+
}

services/server/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ MESSENGER_PASSWORD='litespace'
3131

3232
LIVEKIT_API_KEY=devkey
3333
LIVEKIT_API_SECRET=secret
34+
35+
CONVERSION_API_URL="https://graph.facebook.com/v22.0/1149846573218428/events"
36+
CONVERSION_API_ACCESS_TOKEN="<token>"

0 commit comments

Comments
 (0)