diff --git a/apps/web/.source/server.ts b/apps/web/.source/server.ts
index 38c91b3..a158859 100644
--- a/apps/web/.source/server.ts
+++ b/apps/web/.source/server.ts
@@ -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';
@@ -39,4 +39,4 @@ const create = server({"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, });
\ No newline at end of file
+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, });
\ No newline at end of file
diff --git a/apps/web/app/cookies/page.tsx b/apps/web/app/cookies/page.tsx
new file mode 100644
index 0000000..a7bc7d6
--- /dev/null
+++ b/apps/web/app/cookies/page.tsx
@@ -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 (
+
+
+
+
+
+
+ Legal
+
+
+ Cookie Policy
+
+
+ This policy explains which cookies and similar technologies we use
+ on {siteConfig.name}, and how you can control them.
+
+
+
+ Last updated: {lastUpdated}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1. What This Policy Covers
+
+
+ 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.
+
+
+
+
+
+ 2. Types of Technologies We Use
+
+
+ We currently use the following categories:
+
+
+
+
+ Essential technologies:
+ {" "}
+ required for core site behavior and security.
+
+
+
+ Analytics technologies:
+ {" "}
+ optional, used to understand traffic and product usage.
+
+
+
+
+
+
3. Consent Choices
+
+ 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.
+
+
+
+
+
+ 4. How We Store Your Choice
+
+
+ We store your preference locally in your browser using a{" "}
+
+ cookie_consent
+ {" "}
+ value of{" "}
+
+ granted
+ {" "}
+ or{" "}
+
+ denied
+
+ . You can remove this value anytime by clearing site data in
+ your browser.
+
+
+
+
+
+ 5. Third-Party Analytics Providers
+
+
+ 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.
+
+
+
+
+
6. Policy Updates
+
+ We may update this policy as our product or legal
+ obligations evolve. We will post the latest version on this
+ page and update the "Last updated" date.
+
+
+
+
+
7. Contact
+
+ If you have cookie-related questions, contact us through our
+ official channels listed in the footer.
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 878b7ef..f0fb7d8 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -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" });
@@ -87,8 +89,10 @@ export default function RootLayout({
+
{children}
+
diff --git a/apps/web/app/privacy/page.tsx b/apps/web/app/privacy/page.tsx
index 501b8a6..b50a5df 100644
--- a/apps/web/app/privacy/page.tsx
+++ b/apps/web/app/privacy/page.tsx
@@ -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`,
@@ -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{" "}
+
+ Cookie Policy
+
+ .
diff --git a/apps/web/components/analytics/consent.ts b/apps/web/components/analytics/consent.ts
new file mode 100644
index 0000000..f17e531
--- /dev/null
+++ b/apps/web/components/analytics/consent.ts
@@ -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));
+}
diff --git a/apps/web/components/analytics/cookie-consent.tsx b/apps/web/components/analytics/cookie-consent.tsx
new file mode 100644
index 0000000..245010c
--- /dev/null
+++ b/apps/web/components/analytics/cookie-consent.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ COOKIE_CONSENT_STORAGE_KEY,
+ COOKIE_CONSENT_UPDATED_EVENT,
+ isCookieConsentValue,
+ updateGoogleConsent,
+} from "./consent";
+
+export function CookieConsent() {
+ const [showConsent, setShowConsent] = useState(false);
+
+ useEffect(() => {
+ // Check if the user has already made a choice
+ const consent = localStorage.getItem(COOKIE_CONSENT_STORAGE_KEY);
+
+ if (isCookieConsentValue(consent)) {
+ updateGoogleConsent(consent);
+ return;
+ }
+
+ if (!consent) {
+ // Defer state update to avoid React Compiler synchronous setState warning
+ requestAnimationFrame(() => {
+ setShowConsent(true);
+ });
+ return;
+ }
+
+ // Unknown stored value should be treated as no consent.
+ localStorage.removeItem(COOKIE_CONSENT_STORAGE_KEY);
+ requestAnimationFrame(() => {
+ setShowConsent(true);
+ });
+ }, []);
+
+ const acceptCookies = () => {
+ localStorage.setItem(COOKIE_CONSENT_STORAGE_KEY, "granted");
+ updateGoogleConsent("granted");
+ window.dispatchEvent(new Event(COOKIE_CONSENT_UPDATED_EVENT));
+ setShowConsent(false);
+ };
+
+ const declineCookies = () => {
+ localStorage.setItem(COOKIE_CONSENT_STORAGE_KEY, "denied");
+ updateGoogleConsent("denied");
+ window.dispatchEvent(new Event(COOKIE_CONSENT_UPDATED_EVENT));
+ setShowConsent(false);
+ };
+
+ const gtmId = process.env.NEXT_PUBLIC_GTM_ID;
+
+ if (!gtmId || !showConsent) {
+ return null;
+ }
+
+ return (
+
+
+ We use essential cookies for core functionality and optional analytics
+ cookies to understand site usage. Read our{" "}
+
+ Cookie Policy
+ {" "}
+ for details.
+