From 45416a41d127ffed607bd496a120bf2fa6da3771 Mon Sep 17 00:00:00 2001 From: Michael Sukkarieh Date: Mon, 28 Jul 2025 23:39:32 -0700 Subject: [PATCH] feat(misc): Add GitHub star toast (#409) * github star toast * changelog --- CHANGELOG.md | 1 + .../[domain]/components/githubStarToast.tsx | 84 +++++++++++++++++++ packages/web/src/app/[domain]/layout.tsx | 2 + packages/web/src/lib/posthogEvents.ts | 3 + 4 files changed, 90 insertions(+) create mode 100644 packages/web/src/app/[domain]/components/githubStarToast.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 89540918..33963c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add search context to ask sourcebot context selector. [#397](https://github.com/sourcebot-dev/sourcebot/pull/397) - Add ability to include/exclude connection in search context. [#399](https://github.com/sourcebot-dev/sourcebot/pull/399) - Search context refactor to search scope and demo card UI changes. [#405](https://github.com/sourcebot-dev/sourcebot/pull/405) +- Add GitHub star toast. [#409](https://github.com/sourcebot-dev/sourcebot/pull/409) ### Fixed - Fixed multiple writes race condition on config file watcher. [#398](https://github.com/sourcebot-dev/sourcebot/pull/398) diff --git a/packages/web/src/app/[domain]/components/githubStarToast.tsx b/packages/web/src/app/[domain]/components/githubStarToast.tsx new file mode 100644 index 00000000..2e482ff1 --- /dev/null +++ b/packages/web/src/app/[domain]/components/githubStarToast.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { useToast } from "@/components/hooks/use-toast"; +import { ToastAction } from "@/components/ui/toast"; +import { useEffect } from "react"; +import { GitHubLogoIcon } from "@radix-ui/react-icons"; +import { captureEvent } from "@/hooks/useCaptureEvent"; + +const POPUP_SHOWN_COOKIE = "github_popup_shown"; +const POPUP_START_TIME_COOKIE = "github_popup_start_time"; +const POPUP_DELAY_S = 60; +const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot"; + +function getCookie(name: string): string | null { + if (typeof document === "undefined") return null; + + const cookies = document.cookie.split(';').map(cookie => cookie.trim()); + const targetCookie = cookies.find(cookie => cookie.startsWith(`${name}=`)); + + if (!targetCookie) return null; + + return targetCookie.substring(`${name}=`.length); +} + +function setCookie(name: string, value: string, days: number = 365) { + if (typeof document === "undefined") return; + + try { + const expires = new Date(); + expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000)); + document.cookie = `${name}=${value}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`; + } catch (error) { + console.warn('Failed to set GitHub popup cookie:', error); + } +} + +export const GitHubStarToast = () => { + const { toast } = useToast(); + + useEffect(() => { + const hasShownPopup = getCookie(POPUP_SHOWN_COOKIE); + const startTime = getCookie(POPUP_START_TIME_COOKIE); + + if (hasShownPopup) { + return; + } + + const currentTime = Date.now(); + if (!startTime) { + setCookie(POPUP_START_TIME_COOKIE, currentTime.toString()); + return; + } + + const elapsed = currentTime - parseInt(startTime, 10); + if (elapsed >= (POPUP_DELAY_S * 1000)) { + toast({ + title: "Star us on GitHub ❤️", + description: "If you've found Sourcebot useful, please consider starring us on GitHub. Your support means a lot!", + duration: 15 * 1000, + action: ( +
+ { + captureEvent('wa_github_star_toast_clicked', {}); + window.open(SOURCEBOT_GITHUB_URL, "_blank"); + }} + > +
+ + Sourcebot +
+
+
+ ) + }); + + captureEvent('wa_github_star_toast_displayed', {}); + setCookie(POPUP_SHOWN_COOKIE, "true"); + } + }, [toast]); + + return null; +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx index bca73f77..99834d14 100644 --- a/packages/web/src/app/[domain]/layout.tsx +++ b/packages/web/src/app/[domain]/layout.tsx @@ -21,6 +21,7 @@ import { GcpIapAuth } from "./components/gcpIapAuth"; import { getAnonymousAccessStatus, getMemberApprovalRequired } from "@/actions"; import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard"; import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; +import { GitHubStarToast } from "./components/githubStarToast"; interface LayoutProps { children: React.ReactNode, @@ -134,6 +135,7 @@ export default async function Layout({ {children} + ) } \ No newline at end of file diff --git a/packages/web/src/lib/posthogEvents.ts b/packages/web/src/lib/posthogEvents.ts index 51d7680f..b20e06db 100644 --- a/packages/web/src/lib/posthogEvents.ts +++ b/packages/web/src/lib/posthogEvents.ts @@ -293,5 +293,8 @@ export type PosthogEventMap = { exampleTitle: string, exampleUrl: string, }, + ////////////////////////////////////////////////////////////////// + wa_github_star_toast_displayed: {}, + wa_github_star_toast_clicked: {}, } export type PosthogEvent = keyof PosthogEventMap; \ No newline at end of file