feat: enhance banner management with color options and improved layout components

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Marco Pedone 2026-05-04 12:50:43 +02:00
parent 1826116a82
commit 4afee4788a
7 changed files with 137 additions and 176 deletions

View file

@ -103,7 +103,7 @@ const iconOptions = [
"bell",
"target",
"thumbs-up",
"note"
"note",
] as const;
export type IconType = (typeof iconOptions)[number];
@ -211,7 +211,11 @@ export const IconMatrix = ({ type, ...rest }: IconMatrixProps) => {
}
};
export const IconMatrixTooltip = () => {
export const IconMatrixTooltip = ({
cb,
}: {
cb?: (icon: IconType) => void;
}) => {
return (
<TooltipProvider delayDuration={300}>
<Tooltip>
@ -222,9 +226,14 @@ export const IconMatrixTooltip = () => {
<div className="grid grid-cols-4 gap-2">
{iconOptions.map((icon) => (
<button
className="flex flex-col items-center rounded-xl bg-muted p-2 hover:opacity-75"
className="flex flex-col items-center rounded-xl bg-secondary p-2 text-secondary-foreground hover:opacity-75"
key={icon}
onClick={async () => {
if (cb) {
cb(icon);
return;
}
await navigator.clipboard.writeText(icon);
toast("Copiato", { icon: "📋" });
}}

View file

@ -5,7 +5,6 @@ import {
SIDEBAR_COOKIE_NAME,
Sidebar,
} from "~/components/area-riservata/sidebar";
import { BannerSection } from "~/components/banners";
import { Footer } from "~/components/footer";
import { LoadingPage } from "~/components/loading";
import { SiteHeader } from "~/components/navbar/site-header";
@ -40,7 +39,6 @@ export const Layout = ({
)}
>
<SiteHeader />
<BannerSection area_riservata={false} />
<main
className={cn("flex h-full flex-1 grow flex-col", containerClassName)}
@ -152,7 +150,6 @@ export const AreaRiservataLayout = ({
)}
>
<SiteHeader />
<BannerSection area_riservata={true} />
<main
className={cn("flex h-full flex-1 overflow-auto", containerClassName)}
>

View file

@ -29,36 +29,68 @@ export const BannerSection = ({
</div>
);
};
export const BannerColors = [
"primary",
"secondary",
"muted",
"accent",
"blu",
"rosso",
"verde",
"indaco",
] as const;
const BannerFactory = (bannerData: Banners) => {
const NEW_BANNER_KEY = `bannercookie_${bannerData.idbanner}`;
let bgColor = "";
let mainClassName = "";
let accentColor = "";
let closeHoverColor = "";
switch (bannerData.color) {
case "primary":
mainClassName = "bg-primary text-primary-foreground";
accentColor = "bg-accent text-accent-foreground";
closeHoverColor = "hover:bg-primary/90";
break;
case "secondary":
mainClassName = "bg-secondary text-secondary-foreground";
accentColor = "bg-primary text-primary-foreground";
closeHoverColor = "hover:bg-secondary/90";
break;
case "muted":
mainClassName = "bg-muted text-muted-foreground";
accentColor = "bg-primary text-primary-foreground";
closeHoverColor = "hover:bg-muted/90";
break;
case "accent":
mainClassName = "bg-accent text-accent-foreground";
accentColor = "bg-primary text-primary-foreground";
closeHoverColor = "hover:bg-accent/90";
break;
case "blu":
bgColor = "bg-blue-600";
mainClassName = "bg-blue-600 text-white";
accentColor = "bg-blue-800";
closeHoverColor = "hover:bg-blue-500";
break;
case "rosso":
bgColor = "bg-red-600";
mainClassName = "bg-red-600 text-white";
accentColor = "bg-red-800";
closeHoverColor = "hover:bg-red-500";
break;
case "verde":
bgColor = "bg-green-600";
mainClassName = "bg-green-600 text-white";
accentColor = "bg-green-800";
closeHoverColor = "hover:bg-green-500";
break;
case "indaco":
bgColor = "bg-indigo-600";
mainClassName = "bg-indigo-600 text-white";
accentColor = "bg-indigo-800";
closeHoverColor = "hover:bg-indigo-500";
break;
default:
bgColor = "bg-indigo-600";
mainClassName = "bg-indigo-600 text-white";
accentColor = "bg-indigo-800";
closeHoverColor = "hover:bg-indigo-500";
break;
@ -91,21 +123,23 @@ const BannerFactory = (bannerData: Banners) => {
return (
<div
className={cn(
"mx-auto flex max-w-7xl items-center justify-between rounded-md bg-indigo-600 py-1 text-sm text-white sm:items-center sm:text-base",
bgColor,
"mx-auto flex max-w-7xl items-center justify-between rounded-md py-1 text-xs sm:items-center sm:text-sm",
mainClassName,
)}
>
<div className="flex flex-1 items-center justify-center gap-x-2 px-2 sm:gap-x-4">
{bannerData.titolo && (
<div
className={cn(
"flex flex-none items-center justify-center rounded-full bg-indigo-800 px-2 py-0.5 font-medium sm:px-3 sm:py-1",
"flex flex-none items-center justify-center rounded-full px-2 py-0.5 font-medium sm:px-3 sm:py-1",
accentColor,
)}
>
{bannerData.titolo}{" "}
{bannerData.titolo}
</div>
)}
<p className="flex gap-2 p-1 font-medium">
<span className="break-all"> {bannerData.testo}</span>
<span className="break-normal"> {bannerData.testo}</span>
{bannerData.has_cta &&
bannerData.cta_href &&

View file

@ -1,6 +1,7 @@
"use client";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { BannerSection } from "~/components/banners";
import { UserHeaderSection } from "~/components/navbar/login-button";
import { MainNav } from "~/components/navbar/main-nav";
import { MobileNav } from "~/components/navbar/mobile-nav";
@ -23,6 +24,7 @@ export function SiteHeader() {
};
return (
<header className="sticky top-0 z-50 w-full border-muted border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 dark:border-muted-foreground/15">
<BannerSection area_riservata={pathname.includes("/area-riservata")} />
<div className="mx-auto flex h-14 w-full items-center gap-2 pr-3 pl-4 md:pr-4 md:pl-5">
<MainNav />
<MobileNav

View file

@ -33,17 +33,10 @@ export const BannersTable = (props: {
const columns_titles = {
actions: "Azioni",
color: "Colore",
cta_href: "Indirizzo",
cta_icon: "Icona",
cta_text: "Testo",
has_cta: "Ha link",
hide_duration: "Durata",
idbanner: "Codice",
is_active: "Attivo",
is_unskippable: "Non saltabile",
show_private: "A. Riservata",
show_public: "Pubblico",
testo: "Testo",
titolo: "Titolo",
};
@ -70,72 +63,12 @@ export const BannersTable = (props: {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.testo} />
),
},
{
accessorKey: "is_unskippable",
cell: ({ row }) => {
const data = row.original;
return data.is_unskippable ? "Vero" : "Falso";
return <p className="max-w-sm truncate">{data.testo}</p>;
},
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.is_unskippable}
/>
),
},
{
accessorKey: "has_cta",
cell: ({ row }) => {
const data = row.original;
return data.has_cta ? "Vero" : "Falso";
},
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.has_cta} />
),
},
{
accessorKey: "hide_duration",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.hide_duration}
/>
),
},
{
accessorKey: "cta_href",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.cta_href}
/>
),
},
{
accessorKey: "cta_icon",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.cta_icon}
/>
),
},
{
accessorKey: "cta_text",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.cta_text}
/>
),
},
{
accessorKey: "color",
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.color} />
),
},
{
accessorKey: "is_active",
cell: ({ row }) => {
@ -149,32 +82,7 @@ export const BannersTable = (props: {
/>
),
},
{
accessorKey: "show_public",
cell: ({ row }) => {
const data = row.original;
return data.show_public ? "Vero" : "Falso";
},
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.show_public}
/>
),
},
{
accessorKey: "show_private",
cell: ({ row }) => {
const data = row.original;
return data.show_private ? "Vero" : "Falso";
},
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.show_private}
/>
),
},
{
accessorKey: "actions",

View file

@ -1,4 +1,5 @@
import { z } from "zod/v4";
import { BannerColors } from "~/components/banners";
import {
Form,
FormControl,
@ -18,6 +19,7 @@ import {
SelectValue,
} from "~/components/ui/select";
import { Switch } from "~/components/ui/switch";
import { Textarea } from "~/components/ui/textarea";
import { NullableStringOnChange } from "~/lib/form_utils";
import { useZodForm } from "~/lib/zodForm";
import type { Banners, BannersIdbanner } from "~/schemas/public/Banners";
@ -84,7 +86,6 @@ export const FormBanners = ({
};
submitMutation(values);
}
const COLORI_OPTIONS = ["blu", "rosso", "verde", "indaco"];
return (
<>
@ -139,8 +140,8 @@ export const FormBanners = ({
<FormMessage />
</div>
<FormControl>
<Input
placeholder=""
<Textarea
className="min-h-37.5"
{...field}
id="testo"
onChange={(e) => NullableStringOnChange(e, field.onChange)}
@ -150,6 +151,36 @@ export const FormBanners = ({
</FormItem>
)}
/>
<FormField
control={form.control}
name="color"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-color">Colore</FormLabel>
<FormMessage />
</div>
<Select
defaultValue={field.value || ""}
onValueChange={field.onChange}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent id="select-color">
{BannerColors.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
<hr />
<FormField
control={form.control}
name="is_unskippable"
@ -170,26 +201,6 @@ export const FormBanners = ({
</FormItem>
)}
/>
<FormField
control={form.control}
name="has_cta"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="hcta">Ha Link</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="hcta"
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="hide_duration"
@ -197,7 +208,7 @@ export const FormBanners = ({
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="durata">
Per quanto il banner stà nascosto
Per quanto il banner stà nascosto (giorni)
</FormLabel>
<FormMessage />
</div>
@ -216,6 +227,28 @@ export const FormBanners = ({
</FormItem>
)}
/>
<hr />
<FormField
control={form.control}
name="has_cta"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="hcta">Ha Link</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="hcta"
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="cta_href"
@ -227,7 +260,7 @@ export const FormBanners = ({
</div>
<FormControl>
<Input
placeholder=""
placeholder="https://infoalloggi.it"
{...field}
id="cta_href"
onChange={(e) => NullableStringOnChange(e, field.onChange)}
@ -244,7 +277,12 @@ export const FormBanners = ({
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel className="flex flex-row gap-1" htmlFor="cta_icon">
Icona Link - <IconMatrixTooltip />
Icona Link -{" "}
<IconMatrixTooltip
cb={(icon) => {
form.setValue("cta_icon", icon);
}}
/>
</FormLabel>
<FormMessage />
</div>
@ -281,35 +319,8 @@ export const FormBanners = ({
</FormItem>
)}
/>
<FormField
control={form.control}
name="color"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-color">Colore</FormLabel>
<FormMessage />
</div>
<Select
defaultValue={field.value || ""}
onValueChange={field.onChange}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent id="select-color">
{COLORI_OPTIONS.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
<hr />
<FormField
control={form.control}
name="is_active"

View file

@ -156,7 +156,7 @@ const EditModal = ({
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild />
<DialogContent className="sm:max-w-106.25">
<DialogContent className="max-h-[80vh] overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>
{!initial ? "Aggiungi Banner" : "Modifica Valori"}