added more things

This commit is contained in:
rgrgogu
2026-08-03 18:08:40 +08:00
parent 3c7da8944f
commit 2e649fc96e
3 changed files with 108 additions and 64 deletions
@@ -1,29 +1,47 @@
import { resolveAssetSrc } from "@/utils/media.util";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react";
// Mirrors components/generic/AnnouncementDetailsDialog.jsx's real layout
// Mirrors components/generic/AnnouncementDetailsDialog.jsx's real markup/classes
// (text left, optional image right, single column when no image) so what's
// shown here while composing an alert is what recipients actually see when
// they open it from the sticky banner.
// they open it from the sticky banner. Plain divs, not an actual Dialog —
// this renders inline inside the admin wizard, not as an overlay.
export default function AlertLayoutPreview({ title, message, imageAsset, linkLabel }) {
const imageSrc = imageAsset ? resolveAssetSrc(imageAsset) : null;
return (
<div className="rounded-lg border overflow-hidden bg-background">
<div
className={[
"relative rounded-xl bg-popover text-popover-foreground ring-1 ring-foreground/10 overflow-hidden",
imageSrc ? "sm:max-w-2xl" : "sm:max-w-md",
].join(" ")}
>
<Button type="button" variant="ghost" size="icon-sm" className="absolute top-2 right-2" tabIndex={-1}>
<XIcon />
<span className="sr-only">Close</span>
</Button>
<div className={imageSrc ? "grid sm:grid-cols-2" : ""}>
<div className="p-4 flex flex-col gap-2 min-w-0">
<p className="text-sm font-semibold truncate">{title || "Alert"}</p>
<p className="text-xs text-muted-foreground whitespace-pre-wrap line-clamp-4">
{message || "Your message will appear here."}
</p>
<div className="p-6 flex flex-col gap-3 min-w-0">
<div className="flex flex-col gap-2 text-left">
<p className="font-heading text-lg leading-none font-medium">{title || "Alert"}</p>
<p className="whitespace-pre-wrap text-left text-foreground text-sm">
{message || "Your message will appear here."}
</p>
</div>
{linkLabel && (
<span className="self-start mt-1 rounded-md border px-2.5 py-1 text-xs font-medium">
{linkLabel}
</span>
<div className="mt-auto pt-4">
<Button type="button" className="self-start">
{linkLabel}
</Button>
</div>
)}
</div>
{imageSrc && (
<div className="aspect-video sm:aspect-auto sm:h-32 bg-muted flex items-center justify-center overflow-hidden">
<div className="aspect-video sm:aspect-auto sm:h-96 bg-muted flex items-center justify-center overflow-hidden">
<img src={imageSrc} alt="" className="w-full h-full object-cover" />
</div>
)}
@@ -26,7 +26,7 @@ import { DateTimePicker } from "@/components/ui/date-time-picker";
import { TARGET_TYPE_OPTIONS, TARGET_TYPE_MAP } from "@/data/notificationBroadcast.data";
import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard";
import { TIER_COLOR_OPTIONS, getTierColor, shadeColor, getContrastText } from "@/utils/tierColors";
import { TIER_COLOR_OPTIONS, getTierColor, getContrastText } from "@/utils/tierColors";
import { normalizeExternalUrl } from "@/components/generic/notificationDisplay";
import { resolveAssetSrc } from "@/utils/media.util";
@@ -451,36 +451,26 @@ export default function AddNotificationBroadcast() {
{showInSticky && (
<div className="space-y-2 pt-1">
<Label>Sticky banner color</Label>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-1.5">
{TIER_COLOR_OPTIONS.map((opt) => {
const selected = (color || "indigo") === opt.key;
const bg = selected ? shadeColor(opt.swatch, -20) : opt.swatch;
return (
<button
key={opt.key}
type="button"
title={opt.label}
onClick={() => setValue("color", opt.key, { shouldDirty: true })}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all ${selected ? "scale-105 shadow-md" : "opacity-80 hover:opacity-100"}`}
style={{ backgroundColor: bg, color: getContrastText(bg, opt.key) }}
>
{selected && <Check className="size-3" />}
{opt.label}
</button>
className={[
"w-5 h-5 rounded-full border-2 transition-all",
selected
? "border-foreground scale-110 shadow-sm"
: "border-transparent hover:border-muted-foreground/50",
].join(" ")}
style={{ backgroundColor: opt.swatch }}
/>
);
})}
</div>
<div
className="w-full flex items-center justify-center gap-3 rounded-md px-3 py-2 text-sm font-semibold"
style={{ backgroundColor: getTierColor(color || "indigo").swatch, color: getContrastText(getTierColor(color || "indigo").swatch, color || "indigo") }}
>
<span className="truncate">{watch("title") || "Sticky banner preview"}</span>
{linkMode === "link" && (
<Button type="button" variant="outline" size="sm" className="shrink-0 text-foreground">
{watch("link_label") || "Open Link"}
</Button>
)}
</div>
</div>
)}
</SectionCard>
@@ -547,12 +537,35 @@ export default function AddNotificationBroadcast() {
{showInSticky && (
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
<AlertLayoutPreview
title={watch("title")}
message={watch("message")}
imageAsset={imageAsset}
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
/>
<div className="space-y-3">
<div className="space-y-1.5">
<p className="text-xs text-muted-foreground">Sticky banner (collapsed):</p>
<div
className="w-full flex items-center justify-center gap-3 rounded-md px-3 py-2 text-sm font-semibold"
style={{
backgroundColor: getTierColor(color || "indigo").swatch,
color: getContrastText(getTierColor(color || "indigo").swatch, color || "indigo"),
}}
>
<span className="truncate">{watch("title") || "Sticky banner preview"}</span>
{linkMode === "link" && (
<Button type="button" variant="outline" size="sm" className="shrink-0 text-foreground">
{watch("link_label") || "Open Link"}
</Button>
)}
</div>
</div>
<div className="space-y-1.5">
<p className="text-xs text-muted-foreground">When clicked (full content):</p>
<AlertLayoutPreview
title={watch("title")}
message={watch("message")}
imageAsset={imageAsset}
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
/>
</div>
</div>
</SectionCard>
)}
</>
@@ -30,7 +30,7 @@ import {
import { TARGET_TYPE_OPTIONS, TARGET_TYPE_MAP } from "@/data/notificationBroadcast.data";
import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard";
import { TIER_COLOR_OPTIONS, getTierColor, shadeColor, getContrastText } from "@/utils/tierColors";
import { TIER_COLOR_OPTIONS, getTierColor, getContrastText } from "@/utils/tierColors";
import { normalizeExternalUrl } from "@/components/generic/notificationDisplay";
import { resolveAssetSrc } from "@/utils/media.util";
@@ -493,36 +493,26 @@ export default function EditNotificationBroadcast() {
{showInSticky && (
<div className="space-y-2 pt-1">
<Label>Sticky banner color</Label>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-1.5">
{TIER_COLOR_OPTIONS.map((opt) => {
const selected = (color || "indigo") === opt.key;
const bg = selected ? shadeColor(opt.swatch, -20) : opt.swatch;
return (
<button
key={opt.key}
type="button"
title={opt.label}
onClick={() => setValue("color", opt.key, { shouldDirty: true })}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all ${selected ? "scale-105 shadow-md" : "opacity-80 hover:opacity-100"}`}
style={{ backgroundColor: bg, color: getContrastText(bg, opt.key) }}
>
{selected && <Check className="size-3" />}
{opt.label}
</button>
className={[
"w-5 h-5 rounded-full border-2 transition-all",
selected
? "border-foreground scale-110 shadow-sm"
: "border-transparent hover:border-muted-foreground/50",
].join(" ")}
style={{ backgroundColor: opt.swatch }}
/>
);
})}
</div>
<div
className="w-full flex items-center justify-center gap-3 rounded-md px-3 py-2 text-sm font-semibold"
style={{ backgroundColor: getTierColor(color || "indigo").swatch, color: getContrastText(getTierColor(color || "indigo").swatch, color || "indigo") }}
>
<span className="truncate">{watch("title") || "Sticky banner preview"}</span>
{linkMode === "link" && (
<Button type="button" variant="outline" size="sm" className="shrink-0 text-foreground">
{watch("link_label") || "Open Link"}
</Button>
)}
</div>
</div>
)}
</SectionCard>
@@ -589,12 +579,35 @@ export default function EditNotificationBroadcast() {
{showInSticky && (
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
<AlertLayoutPreview
title={watch("title")}
message={watch("message")}
imageAsset={imageAsset}
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
/>
<div className="space-y-3">
<div className="space-y-1.5">
<p className="text-xs text-muted-foreground">Sticky banner (collapsed):</p>
<div
className="w-full flex items-center justify-center gap-3 rounded-md px-3 py-2 text-sm font-semibold"
style={{
backgroundColor: getTierColor(color || "indigo").swatch,
color: getContrastText(getTierColor(color || "indigo").swatch, color || "indigo"),
}}
>
<span className="truncate">{watch("title") || "Sticky banner preview"}</span>
{linkMode === "link" && (
<Button type="button" variant="outline" size="sm" className="shrink-0 text-foreground">
{watch("link_label") || "Open Link"}
</Button>
)}
</div>
</div>
<div className="space-y-1.5">
<p className="text-xs text-muted-foreground">When clicked (full content):</p>
<AlertLayoutPreview
title={watch("title")}
message={watch("message")}
imageAsset={imageAsset}
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
/>
</div>
</div>
</SectionCard>
)}
</>