Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.wrangler
.DS_Store
.env
.dev.vars
*.log
build
node_modules
Expand Down
82 changes: 82 additions & 0 deletions app/hooks/use-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useEffect } from 'react';

interface UseAnalyticsOptions {
token?: string | null;
enabled?: boolean;
}

export function useAnalytics({ token, enabled = true }: UseAnalyticsOptions) {
useEffect(() => {
if (!enabled || !token) {
if (!token) {
console.warn('Cloudflare Analytics: No token provided');
}
return;
}

// Check if the beacon script is already loaded
const existingScript = document.querySelector('script[src*="beacon.min.js"]');
if (existingScript) {
console.log('Cloudflare Analytics: Beacon script already loaded');
return;
}

// Create and load the analytics script
const script = document.createElement('script');
script.src = 'https://static.cloudflareinsights.com/beacon.min.js';
script.setAttribute('data-cf-beacon', JSON.stringify({ token }));
script.defer = true;

script.onload = () => {
console.log('Cloudflare Analytics: Beacon loaded successfully');
};

script.onerror = (error) => {
console.error('Cloudflare Analytics: Failed to load beacon', error);
};

// Add the script to the document head
document.head.appendChild(script);

// Cleanup function to remove the script if needed
return () => {
if (script.parentNode) {
script.parentNode.removeChild(script);
}
};
}, [token, enabled]);
}

// Alternative function for manual initialization
export function initializeCloudflareAnalytics(token: string) {
if (!token) {
console.warn('Cloudflare Analytics: No token provided for manual initialization');
return;
}

// Set up global settings
(window as any)._cfSettings = (window as any)._cfSettings || {};
(window as any)._cfSettings.token = token;

// Check if Cloudflare beacon is available
if (typeof (window as any).__cfBeacon !== 'undefined') {
console.log('Cloudflare Analytics: Beacon already initialized');
return;
}

// Load the beacon script
const script = document.createElement('script');
script.src = 'https://static.cloudflareinsights.com/beacon.min.js';
script.setAttribute('data-cf-beacon', JSON.stringify({ token }));
script.async = true;

script.onload = () => {
console.log('Cloudflare Analytics: Manual initialization successful');
};

script.onerror = () => {
console.error('Cloudflare Analytics: Manual initialization failed');
};

document.head.appendChild(script);
}
44 changes: 43 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { MetaFunction } from '@remix-run/cloudflare';
import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/cloudflare';
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
isRouteErrorResponse,
useLoaderData,
useRouteError,
} from '@remix-run/react';

Expand All @@ -17,9 +18,16 @@ import {
ThemeSwitcherSafeHTML,
ThemeSwitcherScript,
} from '@/components/theme-switcher';
import { useAnalytics } from '@/hooks/use-analytics';

import './globals.css';

export const loader = async ({ context }: LoaderFunctionArgs) => {
return {
analyticsToken: context.CLOUDFLARE_ANALYTICS_TOKEN || null,
};
};

export const meta: MetaFunction = ({ error, location }) => {
const title = 'Hack@UCF - UCF Collegiate Cyber Defense Club';
const defaultDescription =
Expand Down Expand Up @@ -53,12 +61,46 @@ export const meta: MetaFunction = ({ error, location }) => {
};

function App({ children }: { children: React.ReactNode }) {
const { analyticsToken } = useLoaderData<typeof loader>();

// Use analytics hook as fallback
useAnalytics({ token: analyticsToken, enabled: true });

return (
<ThemeSwitcherSafeHTML lang="en">
<head>
<Meta />
<Links />
<ThemeSwitcherScript />
{/* Cloudflare Web Analytics */}
{analyticsToken && (
<>
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon={`{"token": "${analyticsToken}"}`}
onError={() => console.error('Failed to load Cloudflare Analytics beacon')}
onLoad={() => console.log('Cloudflare Analytics beacon loaded successfully')}
/>
<script
dangerouslySetInnerHTML={{
__html: `
console.log('Analytics token available:', '${analyticsToken}');
// Fallback for manual initialization if needed
window._cfSettings = window._cfSettings || {};
window._cfSettings.token = '${analyticsToken}';
`,
}}
/>
</>
)}
{!analyticsToken && (
<script
dangerouslySetInnerHTML={{
__html: `console.warn('Cloudflare Analytics token not found. Set CLOUDFLARE_ANALYTICS_TOKEN environment variable.');`,
}}
/>
)}
</head>
<body>
<GlobalPendingIndicator />
Expand Down
7 changes: 7 additions & 0 deletions app/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import '@remix-run/cloudflare';

declare module '@remix-run/cloudflare' {
interface AppLoadContext {
CLOUDFLARE_ANALYTICS_TOKEN?: string;
}
}
9 changes: 6 additions & 3 deletions context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { AppLoadContext } from '@remix-run/cloudflare';

// biome-ignore lint/suspicious/noEmptyInterface: Fill this in with your own types for your use-case
export interface Env {}
export interface Env {
CLOUDFLARE_ANALYTICS_TOKEN?: string;
}

export function getLoadContext(env: Env): AppLoadContext {
return {};
return {
CLOUDFLARE_ANALYTICS_TOKEN: env.CLOUDFLARE_ANALYTICS_TOKEN,
};
}