mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
feat(chat): add RemoveAdsBanner component to FreeChatPage
- Integrated the RemoveAdsBanner component into the FreeChatPage to enhance user experience by providing ad-free interaction.
This commit is contained in:
parent
ae0e61305c
commit
02e3e1375d
2 changed files with 81 additions and 1 deletions
|
|
@ -37,6 +37,7 @@ import { BACKEND_URL } from "@/lib/env-config";
|
||||||
import { trackAnonymousChatMessageSent } from "@/lib/posthog/events";
|
import { trackAnonymousChatMessageSent } from "@/lib/posthog/events";
|
||||||
import { FreeModelSelector } from "./free-model-selector";
|
import { FreeModelSelector } from "./free-model-selector";
|
||||||
import { FreeThread } from "./free-thread";
|
import { FreeThread } from "./free-thread";
|
||||||
|
import { RemoveAdsBanner } from "./remove-ads-banner";
|
||||||
|
|
||||||
// Render all tool calls via ToolFallback; backend keeps persisted
|
// Render all tool calls via ToolFallback; backend keeps persisted
|
||||||
// payloads bounded by summarising / truncating outputs.
|
// payloads bounded by summarising / truncating outputs.
|
||||||
|
|
@ -135,7 +136,7 @@ export function FreeChatPage() {
|
||||||
pendingRetryRef.current = null;
|
pendingRetryRef.current = null;
|
||||||
}, [resetKey, modelSlug, tokenUsageStore]);
|
}, [resetKey, modelSlug, tokenUsageStore]);
|
||||||
|
|
||||||
const cancelRun = useCallback(() => {
|
const cancelRun = useCallback(async () => {
|
||||||
if (abortControllerRef.current) {
|
if (abortControllerRef.current) {
|
||||||
abortControllerRef.current.abort();
|
abortControllerRef.current.abort();
|
||||||
abortControllerRef.current = null;
|
abortControllerRef.current = null;
|
||||||
|
|
@ -487,6 +488,8 @@ export function FreeChatPage() {
|
||||||
<FreeModelSelector />
|
<FreeModelSelector />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<RemoveAdsBanner />
|
||||||
|
|
||||||
{captchaRequired && TURNSTILE_SITE_KEY && (
|
{captchaRequired && TURNSTILE_SITE_KEY && (
|
||||||
<div className="flex justify-center border-b bg-muted/30 px-4 py-4">
|
<div className="flex justify-center border-b bg-muted/30 px-4 py-4">
|
||||||
<Alert className="w-auto max-w-md">
|
<Alert className="w-auto max-w-md">
|
||||||
|
|
|
||||||
77
surfsense_web/components/free-chat/remove-ads-banner.tsx
Normal file
77
surfsense_web/components/free-chat/remove-ads-banner.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Sparkles, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const ADSENSE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID;
|
||||||
|
|
||||||
|
// Versioned key so the copy can change later without resurfacing for users who
|
||||||
|
// already dismissed an older variant (bump the version to re-show).
|
||||||
|
const DISMISS_KEY = "surfsense:remove-ads-banner-dismissed:v1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismissible promo shown on the free /free/[model_slug] chat pages, nudging
|
||||||
|
* anonymous users to sign up to remove ads. Dismissal is persisted in
|
||||||
|
* localStorage so it stays hidden across reloads and navigations. The free
|
||||||
|
* chat keeps working whether or not the banner is dismissed.
|
||||||
|
*
|
||||||
|
* Renders nothing when AdSense is not configured (dev/preview), since there are
|
||||||
|
* no ads to remove in that case.
|
||||||
|
*/
|
||||||
|
export function RemoveAdsBanner({ className }: { className?: string }) {
|
||||||
|
// Default hidden so dismissed users never see a flash before the stored
|
||||||
|
// value is read on the client (avoids a hydration/flicker mismatch).
|
||||||
|
const [dismissed, setDismissed] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
setDismissed(localStorage.getItem(DISMISS_KEY) === "1");
|
||||||
|
} catch {
|
||||||
|
// localStorage can throw in private browsing / when disabled.
|
||||||
|
setDismissed(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
setDismissed(true);
|
||||||
|
try {
|
||||||
|
localStorage.setItem(DISMISS_KEY, "1");
|
||||||
|
} catch {
|
||||||
|
// Ignore: dismissal just won't persist across reloads.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ADSENSE_CLIENT_ID || dismissed) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("shrink-0 border-b bg-muted/30 px-4 py-3", className)}>
|
||||||
|
<Alert className="relative mx-auto w-full max-w-2xl pr-10">
|
||||||
|
<Sparkles />
|
||||||
|
<AlertTitle>Go ad-free with a free account</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
<p>
|
||||||
|
Create a free SurfSense account to remove ads, unlock $5 of premium credit, and save
|
||||||
|
your chat history. You can keep chatting for free either way.
|
||||||
|
</p>
|
||||||
|
<Button asChild size="sm" className="mt-1">
|
||||||
|
<Link href="/login">Create Free Account</Link>
|
||||||
|
</Button>
|
||||||
|
</AlertDescription>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
aria-label="Dismiss"
|
||||||
|
className="absolute top-2 right-2 size-6"
|
||||||
|
>
|
||||||
|
<X />
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue