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,6 +291,7 @@ function BannerPreview() {
|
|||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{showBranding && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
@@ -305,6 +322,7 @@ function BannerPreview() {
|
|||||||
{" "}
|
{" "}
|
||||||
Probo
|
Probo
|
||||||
</div>
|
</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