Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: google analytics #164

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ ENV NEXT_PUBLIC_MW_URL=https://middleware.ecosystem.vision
ENV NEXT_PUBLIC_FRONTEND_URL=https://ecosystem.vision

ENV NEXT_PUBLIC_PAGE_SIZE=20
ENV NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-9ZY0R2T7KR
RUN pnpm build

# Production image, copy all the files and run next
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"dependencies": {
"@next/bundle-analyzer": "^14.2.5",
"@next/third-parties": "^15.0.3",
"@nextui-org/react": "^2.3.6",
"@sentry/nextjs": "^8",
"@splinetool/react-spline": "^4.0.0",
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import './swiper.css';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';

import { GoogleAnalytics } from '@next/third-parties/google';

import { GOOGLE_ANALYTICS_ID } from '@/shared/core/envs';
import { grotesk, interTight } from '@/shared/core/fonts';
import { InitPathSyncer } from '@/shared/components/init-path-syncer';
import { NavLayout } from '@/shared/components/nav-space-layout';
Expand Down Expand Up @@ -43,6 +46,8 @@ const RootLayout: React.FC<RootLayoutProps> = ({ children }) => (
<PageScrollDisabler />
<InitPathSyncer />
<Toaster />

<GoogleAnalytics gaId={GOOGLE_ANALYTICS_ID ?? ''} />
</body>
</html>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import { useState } from 'react';
import { createPortal } from 'react-dom';

import { sendGAEvent } from '@next/third-parties/google';
import { Button, Textarea } from '@nextui-org/react';
import { useAtom, useAtomValue } from 'jotai';

import { GA_EVENT } from '@/shared/core/constants';
import { useIsMounted } from '@/shared/hooks/use-is-mounted';
import { useIsDesktop } from '@/shared/hooks/use-media-query';
import { AiIcon } from '@/shared/components/icons/ai-icon';
Expand Down Expand Up @@ -36,6 +38,9 @@ export const AiGrantProgramFinderPortal = () => {
};

const onFind = () => {
sendGAEvent('event', GA_EVENT.GRANTS.AI_GRANT_PROGRAM_FINDER_SUBMIT, {
value: aiQueryInput,
});
setAiQuery(aiQueryInput);
};

Expand Down
7 changes: 6 additions & 1 deletion src/grants/components/grant-card/full-grant-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Avatar } from '@nextui-org/react';

import { GA_EVENT } from '@/shared/core/constants';
import { cn } from '@/shared/utils/cn';
import { getLogoUrl } from '@/shared/utils/get-logo-url';

Expand Down Expand Up @@ -87,7 +88,11 @@ export const FullGrantCard = ({ grant }: Props) => {
name={name}
/>
</div>
<ApplyButton url={url} />
<ApplyButton
url={url}
gaEvent={GA_EVENT.GRANTS.VIEW_PROGRAM}
value={slug}
/>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/grants/components/grant-list/ai-grant-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const AiGrantList = () => {
<VirtualWrapper count={grants.length}>
{(index) => (
<div className="pt-6 lg:pt-8">
<GrantListItem grant={grants[index]} />
<GrantListItem isAiResult grant={grants[index]} />
</div>
)}
</VirtualWrapper>
Expand Down
13 changes: 12 additions & 1 deletion src/grants/components/grant-list/apply-button.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
'use client';

import { sendGAEvent } from '@next/third-parties/google';
import { Button } from '@nextui-org/react';

import { openNewTab } from '@/shared/utils/open-new-tab';

interface Props {
url: string | null;
gaEvent: string;
value: string;
text?: string;
}

/**
* Need to implement this as button (instead of link) to avoid nested links
* Parent card is already a link, so it'll throw react minification error
*/
export const ApplyButton = ({ url, text = 'View Program' }: Props) => {
export const ApplyButton = ({
url,
gaEvent,
value,
text = 'View Program',
}: Props) => {
if (!url) return null;

const onClick: React.MouseEventHandler = (e) => {
e.preventDefault();
e.stopPropagation();

sendGAEvent('event', gaEvent, { value });

openNewTab(url);
};

Expand Down
36 changes: 36 additions & 0 deletions src/grants/components/grant-list/grant-list-item-link-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import Link from 'next/link';
import React from 'react';

import { sendGAEvent } from '@next/third-parties/google';

import { GA_EVENT, ROUTE_SECTIONS } from '@/shared/core/constants';

import { GRANT_TEST_IDS } from '@/grants/core/constants';

interface Props {
slug: string;
children: React.ReactNode;
}

export const GrantListItemLinkWrapper = ({ slug, children }: Props) => {
const sendAnalytics = () => {
sendGAEvent('event', GA_EVENT.GRANTS.GRANT_ITEM_CLICK, {
value: slug,
});
};

return (
<Link
prefetch
href={`/${ROUTE_SECTIONS.GRANT_IMPACT}/${slug}`}
className="flex flex-wrap items-center justify-between rounded-2xl bg-gradient-to-r from-[#191919] to-[#0D0D0D] p-4 text-13 text-white transition-all duration-300 md:p-5 lg:flex-nowrap"
data-uuid={slug}
data-testid={GRANT_TEST_IDS.GRANT_ITEM}
onClick={sendAnalytics}
>
{children}
</Link>
);
};
47 changes: 20 additions & 27 deletions src/grants/components/grant-list/grant-list-item.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Link from 'next/link';
import { Avatar } from '@nextui-org/react';

import { Avatar, Button } from '@nextui-org/react';

import { ROUTE_SECTIONS } from '@/shared/core/constants';
import { GA_EVENT } from '@/shared/core/constants';
import { cn } from '@/shared/utils/cn';
import { getLogoUrl } from '@/shared/utils/get-logo-url';

import { GRANT_TEST_IDS } from '@/grants/core/constants';
import { Grant } from '@/grants/core/schemas';
import { getGrantCardData } from '@/grants/utils/get-grant-card-data';
import { ApplyButton } from '@/grants/components/grant-list/apply-button';
import { GrantListItemLinkWrapper } from '@/grants/components/grant-list/grant-list-item-link-wrapper';
import { ViewImpactButton } from '@/grants/components/grant-list/view-impact-button';
import { DetailItems } from '@/grants/components/ui/base/detail-item';
import { Title } from '@/grants/components/ui/base/title';
import { WebLinks } from '@/grants/components/ui/base/web-links/web-links';
Expand All @@ -19,11 +19,15 @@ interface Props {
grant: Grant;
isLink?: boolean;
ctaText?: string;
isAiResult?: boolean;
}

export const GrantListItem = ({ grant, isLink = true, ctaText }: Props) => {
// TODO: JOB-679

export const GrantListItem = ({
grant,
isLink = true,
ctaText,
isAiResult,
}: Props) => {
const {
slug,
logo,
Expand All @@ -39,6 +43,12 @@ export const GrantListItem = ({ grant, isLink = true, ctaText }: Props) => {
hasWebLinks,
} = getGrantCardData(grant);

const gaEvent = ctaText
? isAiResult
? GA_EVENT.GRANTS.APPLY_AI_ACTIVE_GRANT
: GA_EVENT.GRANTS.APPLY_ACTIVE_GRANT
: GA_EVENT.GRANTS.VIEW_PROGRAM;

const wrapperClassName =
'flex flex-wrap items-center justify-between rounded-2xl bg-gradient-to-r from-[#191919] to-[#0D0D0D] p-4 text-13 text-white transition-all duration-300 md:p-5 lg:flex-nowrap';

Expand Down Expand Up @@ -96,18 +106,9 @@ export const GrantListItem = ({ grant, isLink = true, ctaText }: Props) => {
</div>
</div>
<div className="flex w-full flex-col items-center justify-end gap-4 pt-6 md:flex-row lg:max-w-[180px] lg:pt-0">
<ApplyButton url={url} text={ctaText} />
<ApplyButton url={url} text={ctaText} gaEvent={gaEvent} value={slug} />

{!ctaText && (
<div className="flex w-full lg:hidden">
<Button
className="mx-auto w-full rounded-xl border border-white/20 font-semibold"
variant="bordered"
>
<span>View Impact</span>
</Button>
</div>
)}
{!ctaText && <ViewImpactButton slug={slug} />}

<div className="hidden lg:flex">
<CaretRightIcon />
Expand All @@ -118,15 +119,7 @@ export const GrantListItem = ({ grant, isLink = true, ctaText }: Props) => {

if (isLink) {
return (
<Link
prefetch
href={`/${ROUTE_SECTIONS.GRANT_IMPACT}/${slug}`}
className={wrapperClassName}
data-uuid={slug}
data-testid={GRANT_TEST_IDS.GRANT_ITEM}
>
{content}
</Link>
<GrantListItemLinkWrapper slug={slug}>{content}</GrantListItemLinkWrapper>
);
}

Expand Down
28 changes: 28 additions & 0 deletions src/grants/components/grant-list/view-impact-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { sendGAEvent } from '@next/third-parties/google';
import { Button } from '@nextui-org/react';

import { GA_EVENT } from '@/shared/core/constants';

interface Props {
slug: string;
}

export const ViewImpactButton = ({ slug }: Props) => {
const sendAnalytics = () => {
sendGAEvent('event', GA_EVENT.GRANTS.VIEW_IMPACT, { value: slug });
};

return (
<div className="flex w-full lg:hidden">
<Button
className="mx-auto w-full rounded-xl border border-white/20 font-semibold"
variant="bordered"
onClick={sendAnalytics}
>
<span>View Impact</span>
</Button>
</div>
);
};
11 changes: 10 additions & 1 deletion src/grants/components/grantee-list/item/client-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import Link from 'next/link';
import { useParams } from 'next/navigation';

import { ROUTE_SECTIONS } from '@/shared/core/constants';
import { sendGAEvent } from '@next/third-parties/google';

import { GA_EVENT, ROUTE_SECTIONS } from '@/shared/core/constants';
import { cn } from '@/shared/utils/cn';

import { GRANT_TEST_IDS } from '@/grants/core/constants';
Expand All @@ -26,6 +28,12 @@ export const ClientWrapper = ({
const href = `/${ROUTE_SECTIONS.GRANT_IMPACT}/${params.grantId}/grantees/${granteeId}`;
const isActive = isActiveBypass || params.granteeId === granteeId;

const sendAnalytics = () => {
sendGAEvent('event', GA_EVENT.GRANTS.GRANTEE_ITEM_CLICK, {
value: granteeId,
});
};

return (
<Link
href={href}
Expand All @@ -40,6 +48,7 @@ export const ClientWrapper = ({
},
className,
)}
onClick={sendAnalytics}
{...props}
>
{children}
Expand Down
10 changes: 10 additions & 0 deletions src/grants/components/project-selections/project-selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import Link from 'next/link';
import { useParams } from 'next/navigation';

import { sendGAEvent } from '@next/third-parties/google';

import { GA_EVENT } from '@/shared/core/constants';
import { cn } from '@/shared/utils/cn';

interface Props {
Expand All @@ -22,6 +25,12 @@ export const ProjectSelection = ({

const isActive = isActiveBypass || paramsProjectId === projectId;

const sendAnalytics = () => {
sendGAEvent('event', GA_EVENT.GRANTS.GRANTEE_PROJECT_SELECTION, {
value: name,
});
};

return (
<Link
prefetch
Expand All @@ -34,6 +43,7 @@ export const ProjectSelection = ({
'is-active': isActive,
},
)}
onClick={sendAnalytics}
>
{name}
</Link>
Expand Down
Loading
Loading