Display cookie pattern source badge in category table
Show the origin of each cookie pattern (Script vs Pre-existing) as a badge with a tooltip in the cookies configuration table. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -51,6 +51,7 @@ export function AddCookieRow({
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
maxAgeSeconds: toMaxAgeSeconds(data.duration.value, data.duration.unit),
|
maxAgeSeconds: toMaxAgeSeconds(data.duration.value, data.duration.unit),
|
||||||
description: data.description,
|
description: data.description,
|
||||||
|
excluded: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ export function AddCookieRow({
|
|||||||
placeholder={__("Cookie name")}
|
placeholder={__("Cookie name")}
|
||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td />
|
||||||
<Td className="pr-3">
|
<Td className="pr-3">
|
||||||
<Controller
|
<Controller
|
||||||
name="duration"
|
name="duration"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
Thead,
|
Thead,
|
||||||
|
Toggle,
|
||||||
Tr,
|
Tr,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
@@ -50,6 +51,7 @@ export interface CookieEntry {
|
|||||||
name: string;
|
name: string;
|
||||||
maxAgeSeconds: number | null;
|
maxAgeSeconds: number | null;
|
||||||
description: string;
|
description: string;
|
||||||
|
excluded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const categorySectionFragment = graphql`
|
export const categorySectionFragment = graphql`
|
||||||
@@ -71,6 +73,8 @@ export const categorySectionFragment = graphql`
|
|||||||
displayName
|
displayName
|
||||||
maxAgeSeconds
|
maxAgeSeconds
|
||||||
description
|
description
|
||||||
|
excluded
|
||||||
|
source
|
||||||
...EditCookieRowFragment
|
...EditCookieRowFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,6 +131,8 @@ const createPatternMutation = graphql`
|
|||||||
displayName
|
displayName
|
||||||
maxAgeSeconds
|
maxAgeSeconds
|
||||||
description
|
description
|
||||||
|
excluded
|
||||||
|
source
|
||||||
...EditCookieRowFragment
|
...EditCookieRowFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +158,7 @@ const updatePatternMutation = graphql`
|
|||||||
displayName
|
displayName
|
||||||
maxAgeSeconds
|
maxAgeSeconds
|
||||||
description
|
description
|
||||||
|
excluded
|
||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
cookieBanner {
|
cookieBanner {
|
||||||
@@ -342,6 +349,7 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
|||||||
displayName: cookie.name,
|
displayName: cookie.name,
|
||||||
maxAgeSeconds: cookie.maxAgeSeconds,
|
maxAgeSeconds: cookie.maxAgeSeconds,
|
||||||
description: cookie.description,
|
description: cookie.description,
|
||||||
|
excluded: cookie.excluded,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onCompleted(_response, errors) {
|
onCompleted(_response, errors) {
|
||||||
@@ -378,6 +386,37 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleExcluded = (patternId: string, excluded: boolean) => {
|
||||||
|
updatePattern({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
cookiePatternId: patternId,
|
||||||
|
excluded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCompleted(_response, errors) {
|
||||||
|
if (errors?.length) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: errors[0].message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: formatError(
|
||||||
|
__("Failed to update cookie"),
|
||||||
|
error as GraphQLError,
|
||||||
|
),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteCookie = (patternId: string) => {
|
const handleDeleteCookie = (patternId: string) => {
|
||||||
deletePattern({
|
deletePattern({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -568,7 +607,8 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
|||||||
<table className="w-full text-left">
|
<table className="w-full text-left">
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>{__("Name")}</Th>
|
<Th><span className="pl-10">{__("Name")}</span></Th>
|
||||||
|
<Th>{__("Source")}</Th>
|
||||||
<Th>{__("Duration")}</Th>
|
<Th>{__("Duration")}</Th>
|
||||||
<Th>{__("Description")}</Th>
|
<Th>{__("Description")}</Th>
|
||||||
<Th className="w-20" />
|
<Th className="w-20" />
|
||||||
@@ -587,9 +627,30 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Tr key={pattern.id}>
|
<Tr key={pattern.id} className={pattern.excluded ? "opacity-50" : undefined}>
|
||||||
<Td>
|
<Td>
|
||||||
<code className="text-sm font-mono">{pattern.displayName}</code>
|
<div className="flex items-center gap-2">
|
||||||
|
<Toggle
|
||||||
|
size="sm"
|
||||||
|
checked={!pattern.excluded}
|
||||||
|
onChange={() => handleToggleExcluded(pattern.id, !pattern.excluded)}
|
||||||
|
disabled={isUpdatingPattern}
|
||||||
|
title={__("Include this cookie in the banner")}
|
||||||
|
/>
|
||||||
|
<code className="text-sm font-mono">{pattern.displayName}</code>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge
|
||||||
|
variant={pattern.source === "SCRIPT" ? "info" : "neutral"}
|
||||||
|
title={
|
||||||
|
pattern.source === "SCRIPT"
|
||||||
|
? __("Set by a script at runtime")
|
||||||
|
: __("Already present when the page was loaded")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pattern.source === "SCRIPT" ? __("Script") : __("Pre-existing")}
|
||||||
|
</Badge>
|
||||||
</Td>
|
</Td>
|
||||||
<Td className="text-sm text-muted-foreground">
|
<Td className="text-sm text-muted-foreground">
|
||||||
{humanizeSeconds(pattern.maxAgeSeconds ?? null)}
|
{humanizeSeconds(pattern.maxAgeSeconds ?? null)}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import { fromMaxAgeSeconds, toMaxAgeSeconds } from "@probo/helpers";
|
import { fromMaxAgeSeconds, toMaxAgeSeconds } from "@probo/helpers";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Button, DurationInput, Input, Td, Tr } from "@probo/ui";
|
import { Button, DurationInput, Input, Td, Toggle, Tr } from "@probo/ui";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { useFragment } from "react-relay";
|
import { useFragment } from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
@@ -28,6 +28,7 @@ export const editCookieRowFragment = graphql`
|
|||||||
displayName
|
displayName
|
||||||
maxAgeSeconds
|
maxAgeSeconds
|
||||||
description
|
description
|
||||||
|
excluded
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ interface CookieFormValues {
|
|||||||
name: string;
|
name: string;
|
||||||
duration: { value: string; unit: string };
|
duration: { value: string; unit: string };
|
||||||
description: string;
|
description: string;
|
||||||
|
excluded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditCookieRowProps {
|
interface EditCookieRowProps {
|
||||||
@@ -59,6 +61,7 @@ export function EditCookieRow({
|
|||||||
name: cookie.displayName,
|
name: cookie.displayName,
|
||||||
duration: initial,
|
duration: initial,
|
||||||
description: cookie.description,
|
description: cookie.description,
|
||||||
|
excluded: cookie.excluded,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,17 +70,33 @@ export function EditCookieRow({
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
maxAgeSeconds: toMaxAgeSeconds(data.duration.value, data.duration.unit),
|
maxAgeSeconds: toMaxAgeSeconds(data.duration.value, data.duration.unit),
|
||||||
description: data.description,
|
description: data.description,
|
||||||
|
excluded: data.excluded,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td className="pr-3">
|
<Td className="pr-3">
|
||||||
<Input
|
<div className="flex items-center gap-2">
|
||||||
{...register("name")}
|
<Controller
|
||||||
placeholder={__("Cookie name")}
|
name="excluded"
|
||||||
/>
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Toggle
|
||||||
|
size="sm"
|
||||||
|
checked={!field.value}
|
||||||
|
onChange={checked => field.onChange(!checked)}
|
||||||
|
title={__("Include this cookie in the banner")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
{...register("name")}
|
||||||
|
placeholder={__("Cookie name")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td />
|
||||||
<Td className="pr-3">
|
<Td className="pr-3">
|
||||||
<Controller
|
<Controller
|
||||||
name="duration"
|
name="duration"
|
||||||
@@ -99,7 +118,7 @@ export function EditCookieRow({
|
|||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => void handleSubmit(onSubmit)()}
|
onClick={() => void handleSubmit(onSubmit)()}
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
|
|||||||
@@ -12,28 +12,39 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
default: { track: { width: 44, height: 24, padding: 2 }, thumb: 20 },
|
||||||
|
sm: { track: { width: 32, height: 18, padding: 2 }, thumb: 14 },
|
||||||
|
} as const;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onChange: (checked: boolean) => void;
|
onChange: (checked: boolean) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
size?: keyof typeof sizes;
|
||||||
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Toggle({ checked, onChange, disabled = false }: Props) {
|
export function Toggle({ checked, onChange, disabled = false, size = "default", title }: Props) {
|
||||||
|
const { track, thumb } = sizes[size];
|
||||||
|
const travel = track.width - thumb - track.padding * 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
title={title}
|
||||||
onClick={() => !disabled && onChange(!checked)}
|
onClick={() => !disabled && onChange(!checked)}
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
width: 44,
|
width: track.width,
|
||||||
height: 24,
|
height: track.height,
|
||||||
padding: 2,
|
padding: track.padding,
|
||||||
borderRadius: 9999,
|
borderRadius: 9999,
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor: disabled ? "not-allowed" : "pointer",
|
cursor: disabled ? "not-allowed" : "pointer",
|
||||||
@@ -47,13 +58,13 @@ export function Toggle({ checked, onChange, disabled = false }: Props) {
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "block",
|
display: "block",
|
||||||
width: 20,
|
width: thumb,
|
||||||
height: 20,
|
height: thumb,
|
||||||
borderRadius: 9999,
|
borderRadius: 9999,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
|
boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
|
||||||
transition: "transform 200ms ease-in-out",
|
transition: "transform 200ms ease-in-out",
|
||||||
transform: checked ? "translateX(20px)" : "translateX(0)",
|
transform: checked ? `translateX(${travel}px)` : "translateX(0)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user