feat: enhance banner management with color options and improved layout components
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
1826116a82
commit
4afee4788a
7 changed files with 137 additions and 176 deletions
|
|
@ -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: "📋" });
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
<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",
|
||||
accentColor,
|
||||
)}
|
||||
>
|
||||
{bannerData.titolo}{" "}
|
||||
</div>
|
||||
{bannerData.titolo && (
|
||||
<div
|
||||
className={cn(
|
||||
"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}
|
||||
</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 &&
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue