mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
merge new_starr_app@qas history into apps/web
git-subtree-dir: apps/web git-subtree-mainline:eaa6d2276agit-subtree-split:459af9d46a
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
// Action registry — label + badge colour per action key
|
||||
|
||||
export const ACTION_CONFIG = {
|
||||
// ── Auth ────────────────────────────────────────────────────────────────────
|
||||
login: { label: "Logged In", group: "auth" },
|
||||
logout: { label: "Logged Out", group: "auth" },
|
||||
register: { label: "Registered", group: "auth" },
|
||||
password_change: { label: "Changed Password", group: "auth" },
|
||||
|
||||
// ── Profile ─────────────────────────────────────────────────────────────────
|
||||
update_profile: { label: "Updated Profile", group: "profile" },
|
||||
revoke_session: { label: "Revoked Session", group: "profile" },
|
||||
|
||||
// ── Learning ────────────────────────────────────────────────────────────────
|
||||
lesson_read: { label: "Read Lesson", group: "learn" },
|
||||
submit_task: { label: "Submitted Task", group: "learn" },
|
||||
visit_link: { label: "Visited Link", group: "learn" },
|
||||
|
||||
// ── User management ─────────────────────────────────────────────────────────
|
||||
create_staff: { label: "Created Staff", group: "mgmt" },
|
||||
update_user: { label: "Updated User", group: "mgmt" },
|
||||
deactivate_user: { label: "Deactivated User", group: "danger" },
|
||||
restore_user: { label: "Restored User", group: "success" },
|
||||
terminate_session: { label: "Terminated Session", group: "danger" },
|
||||
set_user_status: { label: "Set User Status", group: "mgmt" },
|
||||
|
||||
// ── Courses ─────────────────────────────────────────────────────────────────
|
||||
create_course: { label: "Created Course", group: "course" },
|
||||
update_course: { label: "Updated Course", group: "course" },
|
||||
archive_course: { label: "Archived Course", group: "course" },
|
||||
restore_course: { label: "Restored Course", group: "course" },
|
||||
bulk_archive_courses: { label: "Bulk Archived Courses", group: "course" },
|
||||
bulk_restore_courses: { label: "Bulk Restored Courses", group: "course" },
|
||||
sync_prerequisites: { label: "Synced Prerequisites", group: "course" },
|
||||
sync_instructors: { label: "Synced Instructors", group: "course" },
|
||||
|
||||
// ── Units ───────────────────────────────────────────────────────────────────
|
||||
create_unit: { label: "Created Unit", group: "course" },
|
||||
update_unit: { label: "Updated Unit", group: "course" },
|
||||
archive_unit: { label: "Archived Unit", group: "course" },
|
||||
restore_unit: { label: "Restored Unit", group: "course" },
|
||||
bulk_archive_units: { label: "Bulk Archived Units", group: "course" },
|
||||
bulk_restore_units: { label: "Bulk Restored Units", group: "course" },
|
||||
|
||||
// ── Lessons ─────────────────────────────────────────────────────────────────
|
||||
create_lesson: { label: "Created Lesson", group: "course" },
|
||||
update_lesson: { label: "Updated Lesson", group: "course" },
|
||||
archive_lesson: { label: "Archived Lesson", group: "course" },
|
||||
restore_lesson: { label: "Restored Lesson", group: "course" },
|
||||
bulk_archive_lessons: { label: "Bulk Archived Lessons", group: "course" },
|
||||
bulk_restore_lessons: { label: "Bulk Restored Lessons", group: "course" },
|
||||
upsert_lesson_page: { label: "Updated Lesson Page", group: "course" },
|
||||
|
||||
// ── Quizzes ─────────────────────────────────────────────────────────────────
|
||||
create_quiz: { label: "Created Quiz", group: "course" },
|
||||
update_quiz: { label: "Updated Quiz", group: "course" },
|
||||
archive_quiz: { label: "Archived Quiz", group: "course" },
|
||||
restore_quiz: { label: "Restored Quiz", group: "course" },
|
||||
|
||||
// ── Questions ───────────────────────────────────────────────────────────────
|
||||
create_question: { label: "Created Question", group: "course" },
|
||||
update_question: { label: "Updated Question", group: "course" },
|
||||
archive_question: { label: "Archived Question", group: "course" },
|
||||
restore_question: { label: "Restored Question", group: "course" },
|
||||
bulk_archive_questions: { label: "Bulk Archived Questions", group: "course" },
|
||||
bulk_restore_questions: { label: "Bulk Restored Questions", group: "course" },
|
||||
|
||||
// ── Assessments ─────────────────────────────────────────────────────────────
|
||||
create_assessment: { label: "Created Assessment", group: "course" },
|
||||
update_assessment: { label: "Updated Assessment", group: "course" },
|
||||
archive_assessment: { label: "Archived Assessment", group: "course" },
|
||||
restore_assessment: { label: "Restored Assessment", group: "course" },
|
||||
|
||||
// ── Task Lists ──────────────────────────────────────────────────────────────
|
||||
create_task_list: { label: "Created Task List", group: "task" },
|
||||
update_task_list: { label: "Updated Task List", group: "task" },
|
||||
archive_task_list: { label: "Archived Task List", group: "task" },
|
||||
restore_task_list: { label: "Restored Task List", group: "task" },
|
||||
bulk_archive_task_lists: { label: "Bulk Archived Task Lists", group: "task" },
|
||||
bulk_restore_task_lists: { label: "Bulk Restored Task Lists", group: "task" },
|
||||
assign_groups: { label: "Assigned Groups", group: "task" },
|
||||
unassign_groups: { label: "Unassigned Groups", group: "task" },
|
||||
|
||||
// ── Tasks ───────────────────────────────────────────────────────────────────
|
||||
create_task: { label: "Created Task", group: "task" },
|
||||
update_task: { label: "Updated Task", group: "task" },
|
||||
archive_task: { label: "Archived Task", group: "task" },
|
||||
restore_task: { label: "Restored Task", group: "task" },
|
||||
bulk_archive_tasks: { label: "Bulk Archived Tasks", group: "task" },
|
||||
bulk_restore_tasks: { label: "Bulk Restored Tasks", group: "task" },
|
||||
|
||||
// ── Task Completions ────────────────────────────────────────────────────────
|
||||
archive_completion: { label: "Archived Completion", group: "task" },
|
||||
restore_completion: { label: "Restored Completion", group: "task" },
|
||||
bulk_archive_completions: { label: "Bulk Archived Completions", group: "task" },
|
||||
bulk_restore_completions: { label: "Bulk Restored Completions", group: "task" },
|
||||
|
||||
// ── Groups ──────────────────────────────────────────────────────────────────
|
||||
create_group: { label: "Created Group", group: "grp" },
|
||||
update_group: { label: "Updated Group", group: "grp" },
|
||||
deactivate_group: { label: "Deactivated Group", group: "grp" },
|
||||
restore_group: { label: "Restored Group", group: "grp" },
|
||||
bulk_deactivate_groups:{ label: "Bulk Deactivated Groups", group: "grp" },
|
||||
bulk_restore_groups: { label: "Bulk Restored Groups", group: "grp" },
|
||||
add_user_to_group: { label: "Added User to Group", group: "grp" },
|
||||
remove_user_from_group:{ label: "Removed User from Group", group: "grp" },
|
||||
|
||||
// ── Subscriptions ───────────────────────────────────────────────────────────
|
||||
create_tier_plan: { label: "Created Subscription", group: "commerce" },
|
||||
update_tier_plan: { label: "Updated Subscription", group: "commerce" },
|
||||
archive_tier_plan: { label: "Archived Subscription", group: "commerce" },
|
||||
restore_tier_plan: { label: "Restored Subscription", group: "commerce" },
|
||||
bulk_archive_tier_plans: { label: "Bulk Archived Subscriptions", group: "commerce" },
|
||||
bulk_restore_tier_plans: { label: "Bulk Restored Subscriptions", group: "commerce" },
|
||||
sync_plan_courses: { label: "Synced Plan Courses", group: "commerce" },
|
||||
grant_tier: { label: "Granted Subscription", group: "success" },
|
||||
revoke_tier: { label: "Revoked Subscription", group: "danger" },
|
||||
|
||||
// ── Products & Categories ───────────────────────────────────────────────────
|
||||
upsert_course_product: { label: "Set Course Product", group: "commerce" },
|
||||
remove_course_product: { label: "Removed Course Product", group: "commerce" },
|
||||
sync_course_categories: { label: "Synced Course Categories", group: "commerce" },
|
||||
create_category: { label: "Created Category", group: "commerce" },
|
||||
update_category: { label: "Updated Category", group: "commerce" },
|
||||
archive_category: { label: "Archived Category", group: "commerce" },
|
||||
restore_category: { label: "Restored Category", group: "commerce" },
|
||||
|
||||
// ── Files ───────────────────────────────────────────────────────────────────
|
||||
upload_asset: { label: "Uploaded File", group: "content" },
|
||||
update_asset: { label: "Updated File", group: "content" },
|
||||
archive_asset: { label: "Archived File", group: "content" },
|
||||
restore_asset: { label: "Restored File", group: "content" },
|
||||
bulk_archive_assets:{ label: "Bulk Archived Files", group: "content" },
|
||||
bulk_restore_assets:{ label: "Bulk Restored Files", group: "content" },
|
||||
|
||||
// ── Advertisements ──────────────────────────────────────────────────────────
|
||||
create_advertisement: { label: "Created Ad", group: "content" },
|
||||
update_advertisement: { label: "Updated Ad", group: "content" },
|
||||
archive_advertisement: { label: "Archived Ad", group: "content" },
|
||||
restore_advertisement: { label: "Restored Ad", group: "content" },
|
||||
bulk_archive_advertisements: { label: "Bulk Archived Ads", group: "content" },
|
||||
bulk_restore_advertisements: { label: "Bulk Restored Ads", group: "content" },
|
||||
};
|
||||
|
||||
// Badge class per group
|
||||
const GROUP_STYLES = {
|
||||
auth: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
|
||||
profile: "bg-violet-50 text-violet-700 border-violet-200 dark:bg-violet-950 dark:text-violet-300 dark:border-violet-800",
|
||||
learn: "bg-teal-50 text-teal-700 border-teal-200 dark:bg-teal-950 dark:text-teal-300 dark:border-teal-800",
|
||||
mgmt: "bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
|
||||
danger: "bg-red-50 text-red-700 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-800",
|
||||
success: "bg-green-50 text-green-700 border-green-200 dark:bg-green-950 dark:text-green-300 dark:border-green-800",
|
||||
course: "bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
|
||||
task: "bg-indigo-50 text-indigo-700 border-indigo-200 dark:bg-indigo-950 dark:text-indigo-300 dark:border-indigo-800",
|
||||
grp: "bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950 dark:text-sky-300 dark:border-sky-800",
|
||||
commerce: "bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
|
||||
content: "bg-slate-100 text-slate-600 border-slate-200 dark:bg-slate-800 dark:text-slate-400 dark:border-slate-700",
|
||||
};
|
||||
|
||||
const FALLBACK = "bg-muted text-muted-foreground border-border";
|
||||
|
||||
export function getActionBadge(action) {
|
||||
const cfg = ACTION_CONFIG[action];
|
||||
return {
|
||||
label: cfg?.label ?? action,
|
||||
className: cfg ? (GROUP_STYLES[cfg.group] ?? FALLBACK) : FALLBACK,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Centralizes all icon maps, link maps, and chart link maps for the
|
||||
// admin dashboard. To add a new section (e.g. Tasks), just add a new
|
||||
// export block here and import it in UsersDashboard.jsx.
|
||||
|
||||
import {
|
||||
Users, UserCheck, UserMinus, ShieldCheck,
|
||||
Archive, Layers,
|
||||
} from "lucide-react";
|
||||
|
||||
// ─── Users ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export const USER_STAT_MAP = {
|
||||
total: { label: "Total Users", icon: <Users className="size-5 text-muted-foreground" /> },
|
||||
active: { label: "Active", icon: <UserCheck className="size-5 text-green-500" /> },
|
||||
inactive: { label: "Inactive", icon: <UserMinus className="size-5 text-red-400" /> },
|
||||
verified: { label: "Verified", icon: <ShieldCheck className="size-5 text-blue-500" /> },
|
||||
archived: { label: "Archived", icon: <Archive className="size-5 text-muted-foreground" /> },
|
||||
};
|
||||
|
||||
// ─── User Groups ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const GROUP_STAT_MAP = {
|
||||
total: { label: "Total Groups", icon: <Layers className="size-5 text-muted-foreground" /> },
|
||||
active: { label: "Active", icon: <UserCheck className="size-5 text-green-500" /> },
|
||||
inactive: { label: "Inactive", icon: <UserMinus className="size-5 text-red-400" /> },
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Users, GitFork, FolderOpen, BookText, BookCheck, FileText, ListCheck, ShieldCheck, Megaphone, Bell, Cog } from "lucide-react";
|
||||
|
||||
export const ADMIN_SECTIONS = [
|
||||
{
|
||||
id: "section-users",
|
||||
tab: "User Management",
|
||||
title: "User Management",
|
||||
description: "Manage people across different systems",
|
||||
tiles: [
|
||||
{ key: "users", label: "Users", icon: Users, link: "/admin/users" },
|
||||
{ key: "user-groups", label: "User Groups", icon: GitFork, link: "/admin/groups" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "section-resources",
|
||||
tab: "Resource Management",
|
||||
title: "Resource Management",
|
||||
description: "It includes files management and subscriptions.",
|
||||
tiles: [
|
||||
{ key: "assets", label: "Files", icon: FolderOpen, link: "/admin/assets" },
|
||||
{ key: "tiers", label: "Subscriptions", icon: ShieldCheck, link: "/admin/tiers/plans" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "section-courses",
|
||||
tab: "Content Management",
|
||||
title: "Content Management",
|
||||
description: "Manage tasks and courses",
|
||||
tiles: [
|
||||
{ key: "courses", label: "Courses", icon: BookText, link: "/admin/courses" },
|
||||
{ key: "units", label: "Units", icon: BookCheck, link: "/admin/units" },
|
||||
{ key: "lessons", label: "Lessons", icon: FileText, link: "/admin/lessons" },
|
||||
{ key: "tasks", label: "Tasks", icon: ListCheck, link: "/admin/taskList" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "section-site",
|
||||
tab: "Site Content",
|
||||
title: "Site Content",
|
||||
description: "Manage public-facing content",
|
||||
tiles: [
|
||||
{ key: "advertisements", label: "Ads", icon: Megaphone, link: "/admin/advertisements" },
|
||||
{ key: "notifications", label: "Alerts", icon: Bell, link: "/admin/announcements" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "section-system",
|
||||
tab: "System",
|
||||
title: "System",
|
||||
description: "Background jobs and automation",
|
||||
tiles: [
|
||||
{ key: "jobs", label: "Jobs", icon: Cog, link: "/admin/jobs" },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
// data/advertisements.data.js
|
||||
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 is closer to image-only).
|
||||
|
||||
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" },
|
||||
];
|
||||
|
||||
export const ADVERTISEMENT_TYPE_MAP = Object.fromEntries(
|
||||
ADVERTISEMENT_TYPES.map((t) => [t.value, t])
|
||||
);
|
||||
|
||||
// ─── Statuses ───────────────────────────────────────────────────────────────
|
||||
// Drives: filter dropdown options, status badge color/label on each card.
|
||||
|
||||
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" },
|
||||
];
|
||||
|
||||
export const ADVERTISEMENT_STATUS_MAP = Object.fromEntries(
|
||||
ADVERTISEMENT_STATUSES.map((s) => [s.value, s])
|
||||
);
|
||||
|
||||
// Filterable subset for the main (non-archived) list. "expired" is excluded —
|
||||
// the expire_advertisements cron sweeps any ad past its end_date into the
|
||||
// Archived table within a minute, so this list will practically never hold
|
||||
// one; offering it as a filter here just returns an empty result.
|
||||
export const ADVERTISEMENT_FILTERABLE_STATUSES = ADVERTISEMENT_STATUSES.filter(
|
||||
(s) => s.value !== "expired"
|
||||
);
|
||||
|
||||
// Max number of CTAs allowed per advertisement (matches backend normalizeCtas slice(0,2))
|
||||
export const MAX_CTAS = 2;
|
||||
|
||||
// Max number of badge label chips allowed per advertisement (matches backend normalizeBadgeLabels slice(0,2))
|
||||
export const MAX_BADGE_LABELS = 2;
|
||||
@@ -0,0 +1,34 @@
|
||||
// data/cronPresets.data.js
|
||||
// Mirrors backend data/cronPresets.data.js — the only schedule options the UI offers.
|
||||
|
||||
export const CRON_PRESET_OPTIONS = [
|
||||
{ value: "every_minute", label: "Every minute" },
|
||||
{ value: "every_5_min", label: "Every 5 minutes" },
|
||||
{ value: "every_15_min", label: "Every 15 minutes" },
|
||||
{ value: "hourly", label: "Hourly" },
|
||||
{ value: "every_6_hours", label: "Every 6 hours" },
|
||||
{ value: "daily", label: "Daily at midnight" },
|
||||
];
|
||||
|
||||
export const CRON_PRESET_MAP = Object.fromEntries(
|
||||
CRON_PRESET_OPTIONS.map((p) => [p.value, p])
|
||||
);
|
||||
|
||||
// job_name -> friendly copy (label/description come from the API too, but this
|
||||
// is used as a fallback and for the settings icon).
|
||||
export const JOB_LABELS = {
|
||||
taskOverdue: { label: "Task Alerts (Admin)", description: "Automatically marks expired tasks as overdue or completed, and notifies admins." },
|
||||
userNotifications: { label: "Task Alerts (Users)", description: "Notifies affected users when their tasks are automatically marked overdue or completed." },
|
||||
issueCertificates: { label: "Certificate Issued", description: "Notifies users when a course certificate is ready." },
|
||||
expireUserTiers: { label: "Subscription Expired", description: "Notifies users when their subscription expires." },
|
||||
};
|
||||
|
||||
// The only jobs that support a configurable target_status, and the values it accepts.
|
||||
export const TARGET_STATUS_OPTIONS = [
|
||||
{ value: "overdue", label: "Overdue" },
|
||||
{ value: "completed", label: "Completed" },
|
||||
];
|
||||
|
||||
export const TARGET_STATUS_MAP = Object.fromEntries(
|
||||
TARGET_STATUS_OPTIONS.map((s) => [s.value, s])
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Mail, Globe } from "lucide-react";
|
||||
|
||||
export const ContactData = [
|
||||
{ id: 1, icon: Mail, label: "Email:", value: "info@philproperties.ph" },
|
||||
{ id: 2, icon: Globe, label: "Website:", value: "philproperties.ph" },
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
// data/notificationBroadcast.data.js
|
||||
import { Shield, Users, Megaphone, ListCheck, BookText, ShieldCheck } from "lucide-react";
|
||||
|
||||
// ─── Target types ─────────────────────────────────────────────────────────────
|
||||
// Drives: target select in the Add form, target badge on each card.
|
||||
// needsTarget: true means the form must also show a picker for target_id.
|
||||
|
||||
export const TARGET_TYPE_OPTIONS = [
|
||||
{ value: "admin", label: "Admins", icon: Shield, description: "Visible only in the admin notification bell", needsTarget: false },
|
||||
{ value: "user", label: "Users", icon: Users, description: "Sent to every active user", needsTarget: false },
|
||||
{ value: "both", label: "Admins & Users", icon: Megaphone, description: "Sent to admins and every active user", needsTarget: false },
|
||||
{ value: "task_list", label: "Task List", icon: ListCheck, description: "Sent to everyone assigned to a specific task list", needsTarget: true },
|
||||
{ value: "course", label: "Course", icon: BookText, description: "Sent to everyone with access to a specific course", needsTarget: true },
|
||||
{ value: "tier_plan", label: "Subscription", icon: ShieldCheck, description: "Sent to everyone currently on a specific subscription plan", needsTarget: true },
|
||||
];
|
||||
|
||||
export const TARGET_TYPE_MAP = Object.fromEntries(
|
||||
TARGET_TYPE_OPTIONS.map((a) => [a.value, a])
|
||||
);
|
||||
|
||||
// ─── Statuses ───────────────────────────────────────────────────────────────
|
||||
// Drives: filter dropdown options, status badge color/label on each card.
|
||||
|
||||
export const BROADCAST_STATUSES = [
|
||||
{ value: "draft", label: "Draft", badgeClass: "bg-muted text-muted-foreground" },
|
||||
{ value: "sent", label: "Sent", badgeClass: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400" },
|
||||
{ value: "archived", label: "Archived", badgeClass: "bg-muted text-muted-foreground" },
|
||||
];
|
||||
|
||||
export const BROADCAST_STATUS_MAP = Object.fromEntries(
|
||||
BROADCAST_STATUSES.map((s) => [s.value, s])
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
// A template is "sent" once it has a live title/message — that's the only
|
||||
// content services/notificationTemplate.service.js's renderNotification() ever
|
||||
// reads on the backend. Editing a sent template writes to draft_title/
|
||||
// draft_message instead, so "pending changes" means there's a draft sitting on
|
||||
// top of the live version. Mirrors emailTemplateStatus.data.js.
|
||||
export const hasPendingChanges = (t) =>
|
||||
t?.status === "sent" && (t?.draft_title != null || t?.draft_message != null);
|
||||
|
||||
export const STATUS_META = {
|
||||
draft: { label: "Draft", badgeClass: "bg-muted text-muted-foreground border-border" },
|
||||
sent: { label: "Sent", badgeClass: "bg-emerald-100 text-emerald-700 border-emerald-400 dark:bg-emerald-900/40 dark:text-emerald-400 dark:border-emerald-700" },
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// data/placement.data.js
|
||||
//
|
||||
// Mirrors the backend placement registry (models/advertisements/advertisements.placements.js
|
||||
// in new_starr). Each entry is a page + position slot; the format (hero/banner/popup/sidebar)
|
||||
// is derived from the placement, never chosen independently. Keep this list in sync with the
|
||||
// backend registry when adding a new placement — same pattern as ADVERTISEMENT_TYPES already
|
||||
// mirroring the backend type ENUM.
|
||||
|
||||
export const PLACEMENTS = [
|
||||
{ key: "dashboard.hero", format: "hero", page: "dashboard", pageLabel: "Dashboard", slotLabel: "Hero (top of page)" },
|
||||
{ key: "tier_plans.banner", format: "banner", page: "tier_plans", pageLabel: "Subscriptions", slotLabel: "Banner (above plan cards)" },
|
||||
{ key: "course_details.banner", format: "banner", page: "course_details", pageLabel: "Course Details", slotLabel: "Banner (below hero)" },
|
||||
];
|
||||
|
||||
export const PLACEMENT_MAP = Object.fromEntries(PLACEMENTS.map((p) => [p.key, p]));
|
||||
|
||||
// Grouped by page — powers the cascading Page → Position picker in Add/EditAdvertisement.
|
||||
export const AD_PAGES = Object.values(
|
||||
PLACEMENTS.reduce((acc, p) => {
|
||||
if (!acc[p.page]) acc[p.page] = { page: p.page, pageLabel: p.pageLabel, placements: [] };
|
||||
acc[p.page].placements.push(p);
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
// data/placementLayouts.data.js
|
||||
//
|
||||
// Declarative "wireframe recipe" per placement — powers <PlacementSkeleton />
|
||||
// in the advertisement creation wizard so admins can see roughly where their
|
||||
// ad will land before saving. Keys mirror PLACEMENTS in placement.data.js.
|
||||
// Adding a new placement: add one entry here, nothing else needs to change.
|
||||
//
|
||||
// Block kinds:
|
||||
// nav — thin top bar stand-in
|
||||
// bar — a rounded block; `size` controls height (sm/md/lg/xl);
|
||||
// `highlight: true` marks it as the ad slot, `label` names it
|
||||
// filters — a row of small pill shapes
|
||||
// grid — a row of small equal boxes (card grid stand-in)
|
||||
// list — a couple of thin stacked rows (table/list stand-in)
|
||||
// row — a flex row of `columns`, each rendered like a `bar`
|
||||
//
|
||||
// `overlay` (optional, top-level) renders the base `blocks` dimmed with a
|
||||
// centered floating highlighted box on top — used for popups.
|
||||
|
||||
export const PLACEMENT_LAYOUTS = {
|
||||
"dashboard.hero": {
|
||||
blocks: [
|
||||
{ kind: "nav" },
|
||||
{ kind: "bar", size: "xl", highlight: true, label: "Hero" },
|
||||
{ kind: "list", label: "My Groups" },
|
||||
{ kind: "grid", label: "Courses" },
|
||||
],
|
||||
},
|
||||
"course_details.banner": {
|
||||
blocks: [
|
||||
{ kind: "nav" },
|
||||
{ kind: "bar", size: "lg", label: "Course hero" },
|
||||
{ kind: "bar", size: "sm", highlight: true, label: "Banner" },
|
||||
{ kind: "list", label: "Course content" },
|
||||
],
|
||||
},
|
||||
"tier_plans.banner": {
|
||||
blocks: [
|
||||
{ kind: "nav" },
|
||||
{ kind: "bar", size: "md", highlight: true, label: "Banner" },
|
||||
{ kind: "grid", label: "Plan cards" },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// ─── Config ───────────────────────────────────────────────────────────────────
|
||||
export const ROLE_CONFIG = {
|
||||
admin: { label: 'Administrator', variant: 'destructive' },
|
||||
staff: { label: 'Staff', variant: 'secondary' },
|
||||
client: { label: 'Client', variant: 'default' },
|
||||
user: { label: 'Client', variant: 'default' }, // ← add this
|
||||
}
|
||||
|
||||
export const AVATAR_COLORS = {
|
||||
admin: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
||||
staff: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
|
||||
client: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
}
|
||||
Reference in New Issue
Block a user