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 { useTranslate } from "@probo/i18n";
|
||||||
import { Card } from "@probo/ui";
|
import { Card } from "@probo/ui";
|
||||||
import type { ReactNode } from "react";
|
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 { CodeSnippets } from "./_components/CodeSnippets";
|
||||||
import { ThemePreview } from "./_components/ThemePreview";
|
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 { __ } = useTranslate();
|
||||||
|
const data = usePreloadedQuery(cookieBannerSnippetPageQuery, queryRef);
|
||||||
|
|
||||||
|
if (data.node.__typename !== "CookieBanner") {
|
||||||
|
throw new Error("invalid type for node");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-10">
|
<div className="space-y-10">
|
||||||
@@ -37,7 +61,7 @@ export default function CookieBannerSnippetPage() {
|
|||||||
title={__("Customize the theme")}
|
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.")}
|
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>
|
||||||
|
|
||||||
<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 { useTranslate } from "@probo/i18n";
|
||||||
import { Button, Card, Field, Input, useToast } from "@probo/ui";
|
import { Button, Card, Field, Input, useToast } from "@probo/ui";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
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 = {
|
type CSSVariable = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -49,7 +53,19 @@ function buildCSSSnippet(values: Record<string, string>): string {
|
|||||||
return `probo-cookie-banner {\n${overrides.join("\n")}\n}`;
|
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 { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
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"
|
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 }}
|
style={{ minHeight: 280, ...previewStyle }}
|
||||||
>
|
>
|
||||||
<BannerPreview />
|
<BannerPreview showBranding={showBranding} />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -168,7 +184,7 @@ export function ThemePreview() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BannerPreview() {
|
function BannerPreview({ showBranding }: { showBranding: boolean }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -275,36 +291,38 @@ function BannerPreview() {
|
|||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
{showBranding && (
|
||||||
style={{
|
<div
|
||||||
textAlign: "center",
|
style={{
|
||||||
fontSize: "calc(var(--probo-font-size, 14px) - 2px)",
|
textAlign: "center",
|
||||||
fontWeight: 400,
|
fontSize: "calc(var(--probo-font-size, 14px) - 2px)",
|
||||||
color: "var(--probo-text-secondary, #555555)",
|
fontWeight: 400,
|
||||||
}}
|
color: "var(--probo-text-secondary, #555555)",
|
||||||
>
|
}}
|
||||||
Privacy by
|
>
|
||||||
{" "}
|
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)">
|
<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" }}>
|
||||||
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" />
|
<g clipPath="url(#pc-preview)">
|
||||||
<path stroke="#fff" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" />
|
||||||
<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 stroke="#fff" 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" />
|
<path stroke="url(#pg-preview)" strokeWidth="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66" />
|
||||||
</g>
|
<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" />
|
||||||
<defs>
|
</g>
|
||||||
<linearGradient id="pg-preview" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse">
|
<defs>
|
||||||
<stop stopColor="#101E1C" stopOpacity="0" />
|
<linearGradient id="pg-preview" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="1" stopColor="#fff" />
|
<stop stopColor="#101E1C" stopOpacity="0" />
|
||||||
</linearGradient>
|
<stop offset="1" stopColor="#fff" />
|
||||||
<clipPath id="pc-preview">
|
</linearGradient>
|
||||||
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
|
<clipPath id="pc-preview">
|
||||||
</clipPath>
|
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
|
||||||
</defs>
|
</clipPath>
|
||||||
</svg>
|
</defs>
|
||||||
{" "}
|
</svg>
|
||||||
Probo
|
{" "}
|
||||||
</div>
|
Probo
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const cookieBannerRoutes = [
|
|||||||
{
|
{
|
||||||
path: "snippet",
|
path: "snippet",
|
||||||
Fallback: LinkCardSkeleton,
|
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!
|
privacyPolicyUrl: String!
|
||||||
consentExpiryDays: Int!
|
consentExpiryDays: Int!
|
||||||
consentMode: CookieConsentMode!
|
consentMode: CookieConsentMode!
|
||||||
|
showBranding: Boolean!
|
||||||
|
|
||||||
organization: Organization @goField(forceResolver: true)
|
organization: Organization @goField(forceResolver: true)
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ func NewCookieBanner(b *coredata.CookieBanner) *CookieBanner {
|
|||||||
PrivacyPolicyURL: b.PrivacyPolicyURL,
|
PrivacyPolicyURL: b.PrivacyPolicyURL,
|
||||||
ConsentExpiryDays: b.ConsentExpiryDays,
|
ConsentExpiryDays: b.ConsentExpiryDays,
|
||||||
ConsentMode: b.ConsentMode,
|
ConsentMode: b.ConsentMode,
|
||||||
|
ShowBranding: b.ShowBranding,
|
||||||
CreatedAt: b.CreatedAt,
|
CreatedAt: b.CreatedAt,
|
||||||
UpdatedAt: b.UpdatedAt,
|
UpdatedAt: b.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user