Expose showBranding as read-only GraphQL field and reflect it in theme preview
Uses the Loader + Page + Fragment pattern so the snippet page fetches its own data instead of relying on Outlet context. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -15,12 +15,36 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Card } from "@probo/ui";
|
||||
import type { ReactNode } from "react";
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { CookieBannerSnippetPageQuery } from "#/__generated__/core/CookieBannerSnippetPageQuery.graphql";
|
||||
|
||||
import { CodeSnippets } from "./_components/CodeSnippets";
|
||||
import { ThemePreview } from "./_components/ThemePreview";
|
||||
|
||||
export default function CookieBannerSnippetPage() {
|
||||
export const cookieBannerSnippetPageQuery = graphql`
|
||||
query CookieBannerSnippetPageQuery($cookieBannerId: ID!) {
|
||||
node(id: $cookieBannerId) {
|
||||
__typename
|
||||
... on CookieBanner {
|
||||
...ThemePreview_cookieBanner
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface CookieBannerSnippetPageProps {
|
||||
queryRef: PreloadedQuery<CookieBannerSnippetPageQuery>;
|
||||
}
|
||||
|
||||
export default function CookieBannerSnippetPage({ queryRef }: CookieBannerSnippetPageProps) {
|
||||
const { __ } = useTranslate();
|
||||
const data = usePreloadedQuery(cookieBannerSnippetPageQuery, queryRef);
|
||||
|
||||
if (data.node.__typename !== "CookieBanner") {
|
||||
throw new Error("invalid type for node");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
@@ -37,7 +61,7 @@ export default function CookieBannerSnippetPage() {
|
||||
title={__("Customize the theme")}
|
||||
description={__("Adjust colors, fonts, and spacing to match your brand. The generated CSS snippet can be added to your website to override the default banner styles.")}
|
||||
>
|
||||
<ThemePreview />
|
||||
<ThemePreview cookieBannerKey={data.node} />
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import type { CookieBannerSnippetPageQuery } from "#/__generated__/core/CookieBannerSnippetPageQuery.graphql";
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
|
||||
import CookieBannerSnippetPage, { cookieBannerSnippetPageQuery } from "./CookieBannerSnippetPage";
|
||||
|
||||
export default function CookieBannerSnippetPageLoader() {
|
||||
const { cookieBannerId } = useParams<{ cookieBannerId: string }>();
|
||||
const [queryRef, loadQuery] = useQueryLoader<CookieBannerSnippetPageQuery>(cookieBannerSnippetPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (cookieBannerId) {
|
||||
loadQuery({ cookieBannerId });
|
||||
}
|
||||
}, [loadQuery, cookieBannerId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<PageSkeleton />}>
|
||||
<CookieBannerSnippetPage queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Card, Field, Input, useToast } from "@probo/ui";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { ThemePreview_cookieBanner$key } from "#/__generated__/core/ThemePreview_cookieBanner.graphql";
|
||||
|
||||
type CSSVariable = {
|
||||
key: string;
|
||||
@@ -49,7 +53,19 @@ function buildCSSSnippet(values: Record<string, string>): string {
|
||||
return `probo-cookie-banner {\n${overrides.join("\n")}\n}`;
|
||||
}
|
||||
|
||||
export function ThemePreview() {
|
||||
export const themePreviewFragment = graphql`
|
||||
fragment ThemePreview_cookieBanner on CookieBanner {
|
||||
showBranding
|
||||
}
|
||||
`;
|
||||
|
||||
interface ThemePreviewProps {
|
||||
cookieBannerKey: ThemePreview_cookieBanner$key;
|
||||
}
|
||||
|
||||
export function ThemePreview({ cookieBannerKey }: ThemePreviewProps) {
|
||||
const cookieBanner = useFragment(themePreviewFragment, cookieBannerKey);
|
||||
const { showBranding } = cookieBanner;
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -148,7 +164,7 @@ export function ThemePreview() {
|
||||
className="relative flex items-end justify-center bg-[repeating-conic-gradient(#e5e7eb_0%_25%,transparent_0%_50%)] bg-size-[20px_20px] p-8"
|
||||
style={{ minHeight: 280, ...previewStyle }}
|
||||
>
|
||||
<BannerPreview />
|
||||
<BannerPreview showBranding={showBranding} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -168,7 +184,7 @@ export function ThemePreview() {
|
||||
);
|
||||
}
|
||||
|
||||
function BannerPreview() {
|
||||
function BannerPreview({ showBranding }: { showBranding: boolean }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -275,36 +291,38 @@ function BannerPreview() {
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: "calc(var(--probo-font-size, 14px) - 2px)",
|
||||
fontWeight: 400,
|
||||
color: "var(--probo-text-secondary, #555555)",
|
||||
}}
|
||||
>
|
||||
Privacy by
|
||||
{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 25 24" fill="none" aria-hidden="true" style={{ verticalAlign: "-2px", display: "inline" }}>
|
||||
<g clipPath="url(#pc-preview)">
|
||||
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" />
|
||||
<path stroke="#fff" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
||||
<path stroke="url(#pg-preview)" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
||||
<path fill="#fff" d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="pg-preview" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#101E1C" stopOpacity="0" />
|
||||
<stop offset="1" stopColor="#fff" />
|
||||
</linearGradient>
|
||||
<clipPath id="pc-preview">
|
||||
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
{" "}
|
||||
Probo
|
||||
</div>
|
||||
{showBranding && (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: "calc(var(--probo-font-size, 14px) - 2px)",
|
||||
fontWeight: 400,
|
||||
color: "var(--probo-text-secondary, #555555)",
|
||||
}}
|
||||
>
|
||||
Privacy by
|
||||
{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 25 24" fill="none" aria-hidden="true" style={{ verticalAlign: "-2px", display: "inline" }}>
|
||||
<g clipPath="url(#pc-preview)">
|
||||
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" />
|
||||
<path stroke="#fff" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
||||
<path stroke="url(#pg-preview)" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
||||
<path fill="#fff" d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="pg-preview" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#101E1C" stopOpacity="0" />
|
||||
<stop offset="1" stopColor="#fff" />
|
||||
</linearGradient>
|
||||
<clipPath id="pc-preview">
|
||||
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
{" "}
|
||||
Probo
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const cookieBannerRoutes = [
|
||||
{
|
||||
path: "snippet",
|
||||
Fallback: LinkCardSkeleton,
|
||||
Component: lazy(() => import("#/pages/organizations/cookie-banners/configuration/snippet/CookieBannerSnippetPage")),
|
||||
Component: lazy(() => import("#/pages/organizations/cookie-banners/configuration/snippet/CookieBannerSnippetPageLoader")),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -84,6 +84,7 @@ type CookieBanner implements Node {
|
||||
privacyPolicyUrl: String!
|
||||
consentExpiryDays: Int!
|
||||
consentMode: CookieConsentMode!
|
||||
showBranding: Boolean!
|
||||
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ func NewCookieBanner(b *coredata.CookieBanner) *CookieBanner {
|
||||
PrivacyPolicyURL: b.PrivacyPolicyURL,
|
||||
ConsentExpiryDays: b.ConsentExpiryDays,
|
||||
ConsentMode: b.ConsentMode,
|
||||
ShowBranding: b.ShowBranding,
|
||||
CreatedAt: b.CreatedAt,
|
||||
UpdatedAt: b.UpdatedAt,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user