mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
added more things
This commit is contained in:
@@ -1,29 +1,47 @@
|
|||||||
import { resolveAssetSrc } from "@/utils/media.util";
|
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
|
// (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
|
// 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 }) {
|
export default function AlertLayoutPreview({ title, message, imageAsset, linkLabel }) {
|
||||||
const imageSrc = imageAsset ? resolveAssetSrc(imageAsset) : null;
|
const imageSrc = imageAsset ? resolveAssetSrc(imageAsset) : null;
|
||||||
|
|
||||||
return (
|
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={imageSrc ? "grid sm:grid-cols-2" : ""}>
|
||||||
<div className="p-4 flex flex-col gap-2 min-w-0">
|
<div className="p-6 flex flex-col gap-3 min-w-0">
|
||||||
<p className="text-sm font-semibold truncate">{title || "Alert"}</p>
|
<div className="flex flex-col gap-2 text-left">
|
||||||
<p className="text-xs text-muted-foreground whitespace-pre-wrap line-clamp-4">
|
<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."}
|
{message || "Your message will appear here."}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{linkLabel && (
|
{linkLabel && (
|
||||||
<span className="self-start mt-1 rounded-md border px-2.5 py-1 text-xs font-medium">
|
<div className="mt-auto pt-4">
|
||||||
|
<Button type="button" className="self-start">
|
||||||
{linkLabel}
|
{linkLabel}
|
||||||
</span>
|
</Button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{imageSrc && (
|
{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" />
|
<img src={imageSrc} alt="" className="w-full h-full object-cover" />
|
||||||
</div>
|
</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 { TARGET_TYPE_OPTIONS, TARGET_TYPE_MAP } from "@/data/notificationBroadcast.data";
|
||||||
import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard";
|
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 { normalizeExternalUrl } from "@/components/generic/notificationDisplay";
|
||||||
import { resolveAssetSrc } from "@/utils/media.util";
|
import { resolveAssetSrc } from "@/utils/media.util";
|
||||||
|
|
||||||
@@ -451,36 +451,26 @@ export default function AddNotificationBroadcast() {
|
|||||||
{showInSticky && (
|
{showInSticky && (
|
||||||
<div className="space-y-2 pt-1">
|
<div className="space-y-2 pt-1">
|
||||||
<Label>Sticky banner color</Label>
|
<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) => {
|
{TIER_COLOR_OPTIONS.map((opt) => {
|
||||||
const selected = (color || "indigo") === opt.key;
|
const selected = (color || "indigo") === opt.key;
|
||||||
const bg = selected ? shadeColor(opt.swatch, -20) : opt.swatch;
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={opt.key}
|
key={opt.key}
|
||||||
type="button"
|
type="button"
|
||||||
title={opt.label}
|
title={opt.label}
|
||||||
onClick={() => setValue("color", opt.key, { shouldDirty: true })}
|
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"}`}
|
className={[
|
||||||
style={{ backgroundColor: bg, color: getContrastText(bg, opt.key) }}
|
"w-5 h-5 rounded-full border-2 transition-all",
|
||||||
>
|
selected
|
||||||
{selected && <Check className="size-3" />}
|
? "border-foreground scale-110 shadow-sm"
|
||||||
{opt.label}
|
: "border-transparent hover:border-muted-foreground/50",
|
||||||
</button>
|
].join(" ")}
|
||||||
|
style={{ backgroundColor: opt.swatch }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
@@ -547,12 +537,35 @@ export default function AddNotificationBroadcast() {
|
|||||||
|
|
||||||
{showInSticky && (
|
{showInSticky && (
|
||||||
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
|
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
|
||||||
|
<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
|
<AlertLayoutPreview
|
||||||
title={watch("title")}
|
title={watch("title")}
|
||||||
message={watch("message")}
|
message={watch("message")}
|
||||||
imageAsset={imageAsset}
|
imageAsset={imageAsset}
|
||||||
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
|
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
|
|
||||||
import { TARGET_TYPE_OPTIONS, TARGET_TYPE_MAP } from "@/data/notificationBroadcast.data";
|
import { TARGET_TYPE_OPTIONS, TARGET_TYPE_MAP } from "@/data/notificationBroadcast.data";
|
||||||
import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard";
|
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 { normalizeExternalUrl } from "@/components/generic/notificationDisplay";
|
||||||
import { resolveAssetSrc } from "@/utils/media.util";
|
import { resolveAssetSrc } from "@/utils/media.util";
|
||||||
|
|
||||||
@@ -493,36 +493,26 @@ export default function EditNotificationBroadcast() {
|
|||||||
{showInSticky && (
|
{showInSticky && (
|
||||||
<div className="space-y-2 pt-1">
|
<div className="space-y-2 pt-1">
|
||||||
<Label>Sticky banner color</Label>
|
<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) => {
|
{TIER_COLOR_OPTIONS.map((opt) => {
|
||||||
const selected = (color || "indigo") === opt.key;
|
const selected = (color || "indigo") === opt.key;
|
||||||
const bg = selected ? shadeColor(opt.swatch, -20) : opt.swatch;
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={opt.key}
|
key={opt.key}
|
||||||
type="button"
|
type="button"
|
||||||
title={opt.label}
|
title={opt.label}
|
||||||
onClick={() => setValue("color", opt.key, { shouldDirty: true })}
|
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"}`}
|
className={[
|
||||||
style={{ backgroundColor: bg, color: getContrastText(bg, opt.key) }}
|
"w-5 h-5 rounded-full border-2 transition-all",
|
||||||
>
|
selected
|
||||||
{selected && <Check className="size-3" />}
|
? "border-foreground scale-110 shadow-sm"
|
||||||
{opt.label}
|
: "border-transparent hover:border-muted-foreground/50",
|
||||||
</button>
|
].join(" ")}
|
||||||
|
style={{ backgroundColor: opt.swatch }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
@@ -589,12 +579,35 @@ export default function EditNotificationBroadcast() {
|
|||||||
|
|
||||||
{showInSticky && (
|
{showInSticky && (
|
||||||
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
|
<SectionCard title="Preview" description="What this alert looks like when opened from the sticky banner.">
|
||||||
|
<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
|
<AlertLayoutPreview
|
||||||
title={watch("title")}
|
title={watch("title")}
|
||||||
message={watch("message")}
|
message={watch("message")}
|
||||||
imageAsset={imageAsset}
|
imageAsset={imageAsset}
|
||||||
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
|
linkLabel={linkMode === "link" ? (watch("link_label") || "Open Link") : null}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user