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
6 changes: 3 additions & 3 deletions apps/web/.source/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import * as __fd_glob_7 from "../content/docs/form-component.mdx?collection=docs
import * as __fd_glob_6 from "../content/docs/custom-rendering.mdx?collection=docs"
import * as __fd_glob_5 from "../content/docs/configuration.mdx?collection=docs"
import * as __fd_glob_4 from "../content/docs/conditional-logic.mdx?collection=docs"
import { default as __fd_glob_3 } from "../content/docs/fields/layout/meta.json?collection=docs"
import { default as __fd_glob_2 } from "../content/docs/fields/data/meta.json?collection=docs"
import { default as __fd_glob_3 } from "../content/docs/fields/data/meta.json?collection=docs"
import { default as __fd_glob_2 } from "../content/docs/fields/layout/meta.json?collection=docs"
import { default as __fd_glob_1 } from "../content/docs/fields/meta.json?collection=docs"
import { default as __fd_glob_0 } from "../content/docs/meta.json?collection=docs"
import { server } from 'fumadocs-mdx/runtime/server';
Expand All @@ -39,4 +39,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
}
}>({"doc":{"passthroughs":["extractedReferences"]}});

export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/data/meta.json": __fd_glob_2, "fields/layout/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/data/checkbox-group.mdx": __fd_glob_15, "fields/data/checkbox.mdx": __fd_glob_16, "fields/data/date.mdx": __fd_glob_17, "fields/data/number.mdx": __fd_glob_18, "fields/data/password.mdx": __fd_glob_19, "fields/data/radio.mdx": __fd_glob_20, "fields/data/select.mdx": __fd_glob_21, "fields/data/switch.mdx": __fd_glob_22, "fields/data/tags.mdx": __fd_glob_23, "fields/data/text.mdx": __fd_glob_24, "fields/data/textarea.mdx": __fd_glob_25, "fields/data/upload.mdx": __fd_glob_26, "fields/layout/array.mdx": __fd_glob_27, "fields/layout/collapsible.mdx": __fd_glob_28, "fields/layout/group.mdx": __fd_glob_29, "fields/layout/row.mdx": __fd_glob_30, "fields/layout/tabs.mdx": __fd_glob_31, });
export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/layout/meta.json": __fd_glob_2, "fields/data/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/data/checkbox-group.mdx": __fd_glob_15, "fields/data/checkbox.mdx": __fd_glob_16, "fields/data/date.mdx": __fd_glob_17, "fields/data/number.mdx": __fd_glob_18, "fields/data/password.mdx": __fd_glob_19, "fields/data/radio.mdx": __fd_glob_20, "fields/data/select.mdx": __fd_glob_21, "fields/data/switch.mdx": __fd_glob_22, "fields/data/tags.mdx": __fd_glob_23, "fields/data/text.mdx": __fd_glob_24, "fields/data/textarea.mdx": __fd_glob_25, "fields/data/upload.mdx": __fd_glob_26, "fields/layout/array.mdx": __fd_glob_27, "fields/layout/collapsible.mdx": __fd_glob_28, "fields/layout/group.mdx": __fd_glob_29, "fields/layout/row.mdx": __fd_glob_30, "fields/layout/tabs.mdx": __fd_glob_31, });
214 changes: 214 additions & 0 deletions apps/web/app/cookies/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { siteConfig } from "@/lib/constants";
import { Badge } from "@/components/ui/badge";
import { SiteHeader } from "@/components/landing/site-header";
import { SiteFooter } from "@/components/landing/site-footer";

export const metadata = {
title: `Cookie Policy`,
description: "How BuzzForm uses cookies and similar technologies.",
};

const lastUpdated = "February 23, 2026";

export default function CookiesPage() {
return (
<div className="flex min-h-screen flex-col bg-background text-foreground selection:bg-primary/20">
<SiteHeader />
<main className="relative flex-1">
<section className="relative overflow-hidden py-12 md:py-16">
<div className="container mx-auto px-4 text-center md:px-8">
<Badge
variant="outline"
className="mx-auto mb-4 w-fit rounded-full border-primary/20 bg-primary/5 px-3 py-1 text-primary"
>
Legal
</Badge>
<h1 className="bg-linear-to-b from-foreground to-foreground/70 bg-clip-text text-3xl font-extrabold tracking-tight text-transparent sm:text-4xl lg:text-5xl">
Cookie Policy
</h1>
<p className="mx-auto mt-4 max-w-2xl text-base text-muted-foreground md:text-lg">
This policy explains which cookies and similar technologies we use
on {siteConfig.name}, and how you can control them.
</p>
<div className="mt-6 flex items-center justify-center gap-2 text-sm text-muted-foreground">
<span className="rounded-full border border-border/60 bg-card/60 px-3 py-1">
Last updated: {lastUpdated}
</span>
</div>
</div>
<div className="absolute top-1/2 left-1/2 -z-10 h-125 w-125 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/10 blur-[120px]" />
<div className="absolute -top-24 right-10 -z-10 h-48 w-48 rounded-full bg-foreground/5 blur-[80px]" />
</section>

<section className="container mx-auto px-4 pb-16 md:px-8">
<div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_260px]">
<div className="rounded-2xl border border-border/60 bg-card/50 p-6 shadow-sm">
<div className="space-y-8">
<section id="scope">
<h2 className="text-lg font-semibold">
1. What This Policy Covers
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We use cookies and similar technologies (such as local
storage) to keep {siteConfig.name} working and, if you
allow it, to measure usage trends and improve the product.
</p>
</section>

<section id="types">
<h2 className="text-lg font-semibold">
2. Types of Technologies We Use
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We currently use the following categories:
</p>
<ul className="mt-4 space-y-2 text-sm text-muted-foreground">
<li>
<span className="font-medium text-foreground">
Essential technologies:
</span>{" "}
required for core site behavior and security.
</li>
<li>
<span className="font-medium text-foreground">
Analytics technologies:
</span>{" "}
optional, used to understand traffic and product usage.
</li>
</ul>
</section>

<section id="consent">
<h2 className="text-lg font-semibold">3. Consent Choices</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We use Google Consent Mode v2. On first visit, consent is
set to denied by default until you choose in the cookie
banner. If you accept, consent is updated to granted. If
you decline, consent remains denied.
</p>
</section>

<section id="storage">
<h2 className="text-lg font-semibold">
4. How We Store Your Choice
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We store your preference locally in your browser using a{" "}
<code className="rounded bg-muted px-1 py-0.5 text-xs">
cookie_consent
</code>{" "}
value of{" "}
<code className="rounded bg-muted px-1 py-0.5 text-xs">
granted
</code>{" "}
or{" "}
<code className="rounded bg-muted px-1 py-0.5 text-xs">
denied
</code>
. You can remove this value anytime by clearing site data in
your browser.
</p>
</section>

<section id="third-party">
<h2 className="text-lg font-semibold">
5. Third-Party Analytics Providers
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We use Google Tag Manager to manage analytics tags. When
consent is denied, tags operate under denied consent
settings. When consent is granted, analytics storage and
related consent signals are enabled. These providers may
process data according to their own privacy policies.
</p>
</section>

<section id="updates">
<h2 className="text-lg font-semibold">6. Policy Updates</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We may update this policy as our product or legal
obligations evolve. We will post the latest version on this
page and update the &quot;Last updated&quot; date.
</p>
</section>

<section id="contact">
<h2 className="text-lg font-semibold">7. Contact</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
If you have cookie-related questions, contact us through our
official channels listed in the footer.
</p>
</section>
</div>
</div>

<aside className="h-fit rounded-2xl border border-border/60 bg-muted/40 p-6 text-sm text-muted-foreground shadow-sm lg:sticky lg:top-20">
<h3 className="text-sm font-semibold text-foreground">
On this page
</h3>
<ul className="mt-4 space-y-2">
<li>
<a
className="transition-colors hover:text-foreground"
href="#scope"
>
Scope
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#types"
>
Types of technologies
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#consent"
>
Consent choices
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#storage"
>
Preference storage
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#third-party"
>
Third-party providers
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#updates"
>
Policy updates
</a>
</li>
<li>
<a
className="transition-colors hover:text-foreground"
href="#contact"
>
Contact
</a>
</li>
</ul>
</aside>
</div>
</section>
</main>
<SiteFooter />
</div>
);
}
4 changes: 4 additions & 0 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import "./globals.css";
import { Providers } from "@/providers";
import { Toaster } from "@/components/ui/sonner";
import { Analytics } from "@vercel/analytics/next";
import { CookieConsent } from "@/components/analytics/cookie-consent";
import { GTM } from "@/components/analytics/gtm";

const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });

Expand Down Expand Up @@ -87,8 +89,10 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<GTM />
<Providers>
{children}
<CookieConsent />
<Toaster position="bottom-right" richColors />
<Analytics />
</Providers>
Expand Down
7 changes: 6 additions & 1 deletion apps/web/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Badge } from "@/components/ui/badge";
import { SiteHeader } from "@/components/landing/site-header";
import { SiteFooter } from "@/components/landing/site-footer";
import Link from "next/link";

export const metadata = {
title: `Privacy Policy`,
Expand Down Expand Up @@ -82,7 +83,11 @@ export default function PrivacyPage() {
We use analytics tools to understand how people interact with
BuzzForm. These tools may use cookies or similar technologies
to collect usage data. You can control cookies through your
browser settings.
browser settings. For details, review our{" "}
<Link href="/cookies" className="underline underline-offset-4">
Cookie Policy
</Link>
.
</p>
</section>

Expand Down
48 changes: 48 additions & 0 deletions apps/web/components/analytics/consent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export const COOKIE_CONSENT_STORAGE_KEY = "cookie_consent";
export const COOKIE_CONSENT_UPDATED_EVENT = "buzzform-cookie-consent-updated";

export type CookieConsentValue = "granted" | "denied";

export function isGrantedConsent(value: string | null): value is "granted" {
return value === "granted";
}

export function isCookieConsentValue(
value: string | null,
): value is CookieConsentValue {
return value === "granted" || value === "denied";
}

type ConsentUpdatePayload = {
ad_storage: CookieConsentValue;
analytics_storage: CookieConsentValue;
ad_user_data: CookieConsentValue;
ad_personalization: CookieConsentValue;
};

export function toConsentUpdatePayload(
consent: CookieConsentValue,
): ConsentUpdatePayload {
return {
ad_storage: consent,
analytics_storage: consent,
ad_user_data: consent,
ad_personalization: consent,
};
}

export function updateGoogleConsent(consent: CookieConsentValue): void {
if (typeof window === "undefined") {
return;
}

const runtimeWindow = window as Window & { dataLayer?: unknown[] };
runtimeWindow.dataLayer = runtimeWindow.dataLayer || [];

const gtag: (...args: unknown[]) => void = function () {
// eslint-disable-next-line prefer-rest-params
runtimeWindow.dataLayer?.push(arguments);
};

gtag("consent", "update", toConsentUpdatePayload(consent));
}
Loading
Loading