Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-12 12:39:47 +08:00
parent 71f758fe0b
commit fa92d924f4
50 changed files with 2202 additions and 2623 deletions
+1 -2
View File
@@ -1,4 +1,4 @@
import { Users, GitFork, FolderOpen, BookText, BookCheck, FileText, ListCheck, ShieldCheck, Megaphone, Bell, Trophy, Cog } from "lucide-react";
import { Users, GitFork, FolderOpen, BookText, BookCheck, FileText, ListCheck, ShieldCheck, Megaphone, Bell, Cog } from "lucide-react";
export const ADMIN_SECTIONS = [
{
@@ -43,7 +43,6 @@ export const ADMIN_SECTIONS = [
tiles: [
{ key: "advertisements", label: "Advertisements", icon: Megaphone, link: "/admin/advertisements" },
{ key: "notifications", label: "Announcements", icon: Bell, link: "/admin/announcements" },
{ key: "achievements", label: "Achievements", icon: Trophy, link: "/admin/achievements" },
],
},
{
+9 -13
View File
@@ -1,40 +1,36 @@
// data/advertisements.data.js
import { Megaphone, Image, BellRing, PanelRight } from "lucide-react";
import { Megaphone, Image } from "lucide-react";
// ─── Types ──────────────────────────────────────────────────────────────────
// Drives: filter dropdown options, type badge label/icon on each card,
// and which fields the Add/Edit form shows (hero needs headline/description/ctas,
// banner/popup/sidebar are closer to image-only).
// banner is closer to image-only).
// TODO(ads-1): Once placements are re-categorized (Hero -> Dashboard, Banner ->
// Tier Plans) and popup/sidebar placements are removed, drop the "popup" and
// "sidebar" entries here too — see data/placement.data.js.
export const ADVERTISEMENT_TYPES = [
{ value: "hero", label: "Hero", icon: Megaphone, description: "Large featured banner with headline, description, and CTAs" },
{ value: "banner", label: "Banner", icon: Image, description: "Simple image banner" },
{ value: "popup", label: "Popup", icon: BellRing, description: "Modal-style popup shown on page load" },
{ value: "sidebar", label: "Sidebar", icon: PanelRight, description: "Compact image placed in a sidebar slot" },
];
export const ADVERTISEMENT_TYPE_MAP = Object.fromEntries(
ADVERTISEMENT_TYPES.map((t) => [t.value, t])
);
// Types that show the rich content fields (headline, description, CTAs) in the form
export const RICH_CONTENT_TYPES = ["hero"];
// ─── Content modes ──────────────────────────────────────────────────────────
// Whether an ad is image-only or carries badge/headline/description/CTAs
// alongside the image — an explicit admin choice, decoupled from placement/format.
export const CONTENT_MODES = [
{ value: "image", label: "Full image" },
{ value: "content", label: "Content + image" },
];
// ─── Statuses ───────────────────────────────────────────────────────────────
// Drives: filter dropdown options, status badge color/label on each card.
// TODO(ads-4): Remove "archived" from this list — it should no longer show up
// in the "All statuses" filter on AdvertisementList.jsx (archived ads live in
// their own separate Archived list/table, not mixed into the active filter).
export const ADVERTISEMENT_STATUSES = [
{ value: "draft", label: "Draft", badgeClass: "bg-muted text-muted-foreground" },
{ value: "active", label: "Active", badgeClass: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400" },
{ value: "scheduled", label: "Scheduled", badgeClass: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400" },
{ value: "expired", label: "Expired", badgeClass: "bg-muted text-muted-foreground" },
{ value: "archived", label: "Archived", badgeClass: "bg-muted text-muted-foreground" },
];
export const ADVERTISEMENT_STATUS_MAP = Object.fromEntries(
@@ -1,20 +0,0 @@
// Reference-only registry of {{placeholder}} tokens available per system
// notification type. Purely informational for the admin editor — the backend
// derives the real substitution data from wherever renderNotification({ type,
// data }) is called in code, this just tells the admin what's actually
// available to reference. Mirrors emailTemplatePlaceholders.data.js.
export const NOTIFICATION_TEMPLATE_PLACEHOLDERS = {
task_overdue: ["count", "task_word"],
user_registration: ["groupName", "groupCode", "userEmail"],
nogrp_user_registered: ["userEmail", "regType"],
task_requirements_updated: ["taskName", "taskListId", "groupId"],
user_task_overdue: ["count", "task_label", "task_list_ids"],
task_reminder: ["taskName", "deadline", "taskListId", "groupId"],
course_unlocked: ["courseTitle", "courseUuid"],
course_completed: ["courseTitle", "courseUuid"],
certificate_issued: ["courseTitle", "courseUuid"],
welcome: ["greeting", "group_suffix", "groupName", "groupCode", "accType"],
nogrp_welcome: [],
assessment_updated: ["assessmentTitle", "courseTitle", "courseUuid"],
tier_expired: ["planLabel", "tier", "label", "planId"],
};
@@ -1,20 +0,0 @@
import { ListChecks, GraduationCap, Megaphone, ClipboardCheck, CreditCard, UserPlus, Users } from "lucide-react";
// Groups notification templates by the `notify_type` written into the
// delivered notification row — every row here is is_system, so a category
// axis (like email templates' announcement/advertisement/system/other) would
// always resolve to a single value and add nothing. This is the axis that
// actually varies. Mirrors the shape of emailTemplateCategories.data.js.
export const NOTIFICATION_TEMPLATE_TYPES = [
{ value: "task_overdue", label: "Tasks (Admin)", icon: ListChecks },
{ value: "task", label: "Tasks (User)", icon: ListChecks },
{ value: "course", label: "Courses", icon: GraduationCap },
{ value: "announcement", label: "Announcements", icon: Megaphone },
{ value: "assessment", label: "Assessments", icon: ClipboardCheck },
{ value: "tier_expired", label: "Subscriptions", icon: CreditCard },
{ value: "user_registration", label: "New Registrations", icon: UserPlus },
{ value: "nogrp_user_registered", label: "Unaffiliated Users", icon: Users },
];
export const getNotificationTemplateType = (value) =>
NOTIFICATION_TEMPLATE_TYPES.find((t) => t.value === value) ?? null;
+1 -8
View File
@@ -6,17 +6,10 @@
// backend registry when adding a new placement — same pattern as ADVERTISEMENT_TYPES already
// mirroring the backend type ENUM.
// TODO(ads-1): Re-categorize placements — Hero -> Dashboard, Banner -> Tier Plans.
// Remove the "popup" and "sidebar" formats entirely (dashboard.popup,
// course_details.sidebar). Keep in sync with the backend registry at
// new_starr/models/advertisements/advertisements.placements.js.
export const PLACEMENTS = [
{ key: "dashboard.hero", format: "hero", page: "dashboard", pageLabel: "Dashboard", slotLabel: "Hero (top of page)" },
{ key: "dashboard.popup", format: "popup", page: "dashboard", pageLabel: "Dashboard", slotLabel: "Popup (on load)" },
{ key: "course_list.banner", format: "banner", page: "course_list", pageLabel: "Courses", slotLabel: "Banner (above course grid)" },
{ key: "tier_plans.banner", format: "banner", page: "tier_plans", pageLabel: "Tier Plans", slotLabel: "Banner (above plan cards)" },
{ key: "course_details.banner", format: "banner", page: "course_details", pageLabel: "Course Details", slotLabel: "Banner (below hero)" },
{ key: "course_details.sidebar", format: "sidebar", page: "course_details", pageLabel: "Course Details", slotLabel: "Sidebar (beside course content)" },
{ key: "plans.banner", format: "banner", page: "plans", pageLabel: "Plans", slotLabel: "Banner (above plan cards)" },
];
export const PLACEMENT_MAP = Object.fromEntries(PLACEMENTS.map((p) => [p.key, p]));
+2 -33
View File
@@ -26,46 +26,15 @@ export const PLACEMENT_LAYOUTS = {
{ kind: "grid", label: "Courses" },
],
},
"dashboard.popup": {
blocks: [
{ kind: "nav" },
{ kind: "bar", size: "xl" },
{ kind: "list", label: "My Groups" },
{ kind: "grid", label: "Courses" },
],
overlay: { label: "Popup" },
},
"course_list.banner": {
blocks: [
{ kind: "nav" },
{ kind: "filters" },
{ kind: "bar", size: "md", highlight: true, label: "Banner" },
{ kind: "grid", label: "Course cards" },
],
},
"course_details.banner": {
blocks: [
{ kind: "nav" },
{ kind: "bar", size: "lg", label: "Course hero" },
{ kind: "bar", size: "sm", highlight: true, label: "Banner" },
{ kind: "row", columns: [
{ label: "Course content", width: "flex-1" },
{ label: "Sidebar", width: "w-1/4" },
] },
{ kind: "list", label: "Course content" },
],
},
"course_details.sidebar": {
blocks: [
{ kind: "nav" },
{ kind: "bar", size: "lg", label: "Course hero" },
{ kind: "bar", size: "sm", label: "Banner" },
{ kind: "row", columns: [
{ label: "Course content", width: "flex-1" },
{ label: "Sidebar", width: "w-1/4", highlight: true },
] },
],
},
"plans.banner": {
"tier_plans.banner": {
blocks: [
{ kind: "nav" },
{ kind: "bar", size: "md", highlight: true, label: "Banner" },