From fdd042d23f9489e65558a5a081123ec362bb8e65 Mon Sep 17 00:00:00 2001 From: sirily11 <32106111+sirily11@users.noreply.github.com> Date: Thu, 21 May 2026 16:58:05 +0800 Subject: [PATCH] Add website analytics tracking --- RxCodeMobile/Views/MobileChatView.swift | 2 +- .../Views/MobileKeyboardDismissModifier.swift | 33 ++++++++++- website/README.md | 5 ++ website/app/analytics.tsx | 57 +++++++++++++++++++ website/app/layout.tsx | 8 ++- website/app/page-view-tracker.tsx | 25 ++++++++ website/app/page.tsx | 29 +++++++--- website/app/release/page.tsx | 15 +++-- website/app/tracked-link.tsx | 42 ++++++++++++++ 9 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 website/app/analytics.tsx create mode 100644 website/app/page-view-tracker.tsx create mode 100644 website/app/tracked-link.tsx diff --git a/RxCodeMobile/Views/MobileChatView.swift b/RxCodeMobile/Views/MobileChatView.swift index 963d6a2..78affd1 100644 --- a/RxCodeMobile/Views/MobileChatView.swift +++ b/RxCodeMobile/Views/MobileChatView.swift @@ -349,7 +349,7 @@ struct MobileChatView: View { .environment(\.chatTrackedMessageID, trackedUserMessageID) .environment(\.chatTrackedMessageGeometry, updateLatestUserMinY) } - .scrollDismissesKeyboard(.interactively) + .mobileDismissesKeyboardOnScroll(.interactively) .onGeometryChange(for: CGRect.self) { proxy in proxy.frame(in: .global) } action: { rect in diff --git a/RxCodeMobile/Views/MobileKeyboardDismissModifier.swift b/RxCodeMobile/Views/MobileKeyboardDismissModifier.swift index 2040e4c..bde8247 100644 --- a/RxCodeMobile/Views/MobileKeyboardDismissModifier.swift +++ b/RxCodeMobile/Views/MobileKeyboardDismissModifier.swift @@ -1,9 +1,40 @@ import SwiftUI +import UIKit + +private struct MobileKeyboardDismissOnScrollModifier: ViewModifier { + let mode: ScrollDismissesKeyboardMode + @GestureState private var isDragging = false + + func body(content: Content) -> some View { + content + .scrollDismissesKeyboard(mode) + .simultaneousGesture( + DragGesture(minimumDistance: 2) + .updating($isDragging) { _, state, _ in + if !state { + UIApplication.shared.dismissKeyboard() + } + state = true + } + ) + } +} extension View { func mobileDismissesKeyboardOnScroll( _ mode: ScrollDismissesKeyboardMode = .interactively ) -> some View { - scrollDismissesKeyboard(mode) + modifier(MobileKeyboardDismissOnScrollModifier(mode: mode)) + } +} + +private extension UIApplication { + func dismissKeyboard() { + sendAction( + #selector(UIResponder.resignFirstResponder), + to: nil, + from: nil, + for: nil + ) } } diff --git a/website/README.md b/website/README.md index e215bc4..2ddb274 100644 --- a/website/README.md +++ b/website/README.md @@ -18,6 +18,11 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Environment + +Set `NEXT_PUBLIC_GOOGLE_ANALYTICS_ID` to enable Google Analytics page-view +tracking and CTA click events. + This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. ## Learn More diff --git a/website/app/analytics.tsx b/website/app/analytics.tsx new file mode 100644 index 0000000..db271f6 --- /dev/null +++ b/website/app/analytics.tsx @@ -0,0 +1,57 @@ +import Script from "next/script"; + +const GOOGLE_ANALYTICS_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID?.trim(); + +export const isGoogleAnalyticsEnabled = Boolean(GOOGLE_ANALYTICS_ID); + +export type AnalyticsEventName = + | "download_button_click" + | "app_store_button_click"; + +type AnalyticsEventParams = Record; + +declare global { + interface Window { + dataLayer?: unknown[]; + gtag?: ( + command: "js" | "config" | "event", + target: string | Date, + params?: Record + ) => void; + } +} + +export function GoogleAnalytics() { + if (!GOOGLE_ANALYTICS_ID) { + return null; + } + + return ( + <> + + + ); +} + +export function trackAnalyticsEvent( + eventName: AnalyticsEventName, + params: AnalyticsEventParams = {} +) { + if (!isGoogleAnalyticsEnabled || typeof window === "undefined") { + return; + } + + window.gtag?.("event", eventName, params); +} + diff --git a/website/app/layout.tsx b/website/app/layout.tsx index 9e6f0f9..c8d7244 100644 --- a/website/app/layout.tsx +++ b/website/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Inter, JetBrains_Mono } from "next/font/google"; +import { GoogleAnalytics } from "./analytics"; import "./globals.css"; +import { PageViewTracker } from "./page-view-tracker"; const inter = Inter({ variable: "--font-inter", @@ -63,7 +65,11 @@ export default function RootLayout({ lang="en" className={`${inter.variable} ${geist.variable} ${jetbrainsMono.variable}`} > - {children} + + {children} + + + ); } diff --git a/website/app/page-view-tracker.tsx b/website/app/page-view-tracker.tsx new file mode 100644 index 0000000..3e54fb2 --- /dev/null +++ b/website/app/page-view-tracker.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { useEffect } from "react"; +import { isGoogleAnalyticsEnabled } from "./analytics"; + +export function PageViewTracker() { + const pathname = usePathname(); + + useEffect(() => { + if (!isGoogleAnalyticsEnabled || typeof window === "undefined") { + return; + } + + const pagePath = `${window.location.pathname}${window.location.search}`; + + window.gtag?.("event", "page_view", { + page_path: pagePath, + page_location: window.location.href, + page_title: document.title, + }); + }, [pathname]); + + return null; +} diff --git a/website/app/page.tsx b/website/app/page.tsx index 1946fb5..99db865 100644 --- a/website/app/page.tsx +++ b/website/app/page.tsx @@ -2,6 +2,7 @@ import Image from "next/image"; import Link from "next/link"; import { AgentTalkFeature } from "./agent-talk-feature"; import { formatSize, getLatestRelease } from "./lib/release"; +import { TrackedLink } from "./tracked-link"; const GITHUB_REPO_URL = "https://github.com/rxtech-lab/rxcode"; @@ -202,12 +203,15 @@ function TopNav({ - Download for macOS - + ); @@ -244,8 +248,11 @@ function Hero({

- @@ -255,7 +262,7 @@ function Hero({ {release.tag} {sizeLabel ? ` ยท ${sizeLabel}` : ""} - + {appStoreUrl ? ( - - + ) : null}
@@ -523,13 +533,16 @@ function CTA({ start driving your agents visually.

- Download RxCode {release.tag} - +

Release notes are temporarily unavailable.

- Download latest build - +
); @@ -196,12 +200,15 @@ function ReleaseCard({