Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 0 additions & 5 deletions src/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { GoogleAnalytics } from '@hacktoolkit/nextjs-htk/components';

import { ANALYTICS } from '@/config/analytics';

import StructuredData from '@/components/StructuredData';

import '@/styles/globals.css';

export default function App({ Component, pageProps }) {
return (
<>
<GoogleAnalytics measurementId={ANALYTICS.google.measurementId} />
<StructuredData />
<Component {...pageProps} />
Comment on lines 1 to 9

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GoogleAnalytics was removed from _app.js, but there is no replacement GA/gtag initialization anywhere else in the repo (no googletagmanager.com/gtag script or gtag('config', ...) found). As a result, window.gtag will never be defined and analytics/click tracking won’t fire. Add the GA script/config in a place that is included in the static export output (e.g., _document.js via next/script, or another exported HTML entrypoint) and wire it to ANALYTICS.google.measurementId.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. GA is now initialized from _document.js, so the gtag.js loader and gtag('config', measurementId) are present in the statically exported HTML as well.

</>
Expand Down
13 changes: 13 additions & 0 deletions src/pages/_document.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { Head, Html, Main, NextScript } from 'next/document';

import { ANALYTICS } from '@/config/analytics';

const googleAnalyticsSrc = `https://www.googletagmanager.com/gtag/js?id=${ANALYTICS.google.measurementId}`;
const googleAnalyticsConfig = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
window.gtag = gtag;
gtag('js', new Date());
gtag('config', '${ANALYTICS.google.measurementId}');
`;

export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="icon" href="/favicon.ico" />
<script async src={googleAnalyticsSrc}></script>
<script dangerouslySetInnerHTML={{ __html: googleAnalyticsConfig }} />
</Head>
<body>
<Main />
Expand Down
56 changes: 53 additions & 3 deletions src/pages/order-online-tearekz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,54 @@ import { ORDERING_PARTNERS } from '@/config/orderingPartners';

import { BasicPageLayout } from '@/components/BasicPageLayout';

import { trackOutboundClick } from '@/utils/analytics';

import styles from '@/styles/BasicPage.module.css';

export default function OrderOnlineTeaRekz() {
const toastPartner = ORDERING_PARTNERS.find((partner) => partner.key === 'toast');
const orderUrl = toastPartner?.url || 'https://order.toasttab.com/online/skilletz-cafe';

useEffect(() => {
// Redirect after component mounts
window.location.href = orderUrl;
let fallbackId: number | undefined;
let hasRedirected = false;

const redirectToOrdering = () => {
if (fallbackId !== undefined) {
window.clearTimeout(fallbackId);
}

if (hasRedirected) {
return;
}

hasRedirected = true;
window.location.href = orderUrl;
};

const isTracked = trackOutboundClick({
destination: orderUrl,
eventTimeout: 1000,
label: 'order_online:tearekz_redirect',
onComplete: redirectToOrdering,
});

if (!isTracked) {
redirectToOrdering();
return undefined;
}

if (hasRedirected) {
return undefined;
}

fallbackId = window.setTimeout(redirectToOrdering, 1200);

return () => {
if (fallbackId !== undefined) {
window.clearTimeout(fallbackId);
}
};
}, [orderUrl]);

return (
Expand All @@ -22,7 +61,18 @@ export default function OrderOnlineTeaRekz() {
intro="Redirecting you to our online ordering system..."
>
<div className={styles.card}>
<a href={orderUrl} target="_blank" rel="noopener noreferrer" className={styles.link}>
<a
href={orderUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.link}
onClick={() =>
trackOutboundClick({
destination: orderUrl,
label: 'order_online:tearekz_link',
})
}
>
Order Tea-Rek&apos;z Online
</a>
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/pages/order-online.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ORDERING_PARTNERS } from '@/config';

import { BasicPageLayout } from '@/components/BasicPageLayout';

import { trackOutboundClick } from '@/utils/analytics';

import styles from '@/styles/BasicPage.module.css';

export default function OrderOnline() {
Expand All @@ -22,6 +24,12 @@ export default function OrderOnline() {
target="_blank"
rel="noopener noreferrer"
className={styles.link}
onClick={() =>
trackOutboundClick({
destination: partner.url,
label: `order_online:${partner.key}`,
})
}
style={{
fontSize: '1.25rem',
padding: '1rem 2rem',
Expand Down
61 changes: 61 additions & 0 deletions src/utils/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
interface OutboundClickGtagParams {
event_category: 'outbound';
event_label: string;
link_url: string;
link_domain: string | null;
page_location: string;
transport_type: 'beacon';
event_callback?: () => void;
event_timeout?: number;
}

type Gtag = (command: 'event', eventName: 'click', params: OutboundClickGtagParams) => void;

declare global {
interface Window {
gtag?: Gtag;
}
}

interface TrackOutboundClickParams {
destination: string;
eventTimeout?: number;
label: string;
onComplete?: () => void;
pageLocation?: string;
}

export function trackOutboundClick({
destination,
eventTimeout,
label,
onComplete,
pageLocation,
}: TrackOutboundClickParams): boolean {
if (typeof window === 'undefined' || typeof window.gtag !== 'function') {
return false;
}

window.gtag('event', 'click', {
event_category: 'outbound',
event_label: label,
link_url: destination,
link_domain: (() => {
try {
return new URL(destination).hostname;
} catch {
return null;
}
})(),
page_location: pageLocation ?? window.location.href,
transport_type: 'beacon',
...(onComplete
? {
event_callback: onComplete,
event_timeout: eventTimeout ?? 1000,
}
: {}),
});

return true;
}
Loading