@@ -567,7 +539,6 @@ export default function AddAdvertisement() {
end_date: "",
order: 0,
is_active: true,
- size: null,
},
});
@@ -584,7 +555,6 @@ export default function AddAdvertisement() {
const description = watch("description");
const redirectLink = watch("redirect_link");
const format = PLACEMENT_MAP[placement]?.format;
- const isBanner = format === "banner";
const steps = useMemo(
() => ALL_STEPS.filter((s) => !s.skippable || !redirectLink?.trim()),
@@ -620,7 +590,6 @@ export default function AddAdvertisement() {
landing_page: values.redirect_link ? null : values.landing_page,
start_date: values.start_date || null,
end_date: values.end_date || null,
- size: PLACEMENT_MAP[values.placement]?.format === "banner" ? (values.size || "md") : null,
createdBy: user?.user_id ?? null,
};
@@ -655,8 +624,6 @@ export default function AddAdvertisement() {
setValue={setValue}
errors={errors}
format={format}
- isBanner={isBanner}
- watch={watch}
/>
)}
{current.id === "content" && (
diff --git a/src/modules/admin/pages/advertisements/EditAdvertisement.jsx b/src/modules/admin/pages/advertisements/EditAdvertisement.jsx
index 8fde34c..beee98c 100644
--- a/src/modules/admin/pages/advertisements/EditAdvertisement.jsx
+++ b/src/modules/admin/pages/advertisements/EditAdvertisement.jsx
@@ -56,7 +56,6 @@ const schema = z.object({
end_date: z.string().optional(),
order: z.coerce.number().min(0).default(0),
is_active: z.boolean().default(true),
- size: z.enum(["sm", "md", "lg", "xl"]).nullable().optional(),
}).superRefine((data, ctx) => {
const format = PLACEMENT_MAP[data.placement]?.format;
if (format === "hero" && (data.description?.length ?? 0) > 200) {
@@ -92,13 +91,6 @@ function SectionCard({ title, description, children }) {
);
}
-const BANNER_SIZES = [
- { value: "sm", label: "Small" },
- { value: "md", label: "Medium" },
- { value: "lg", label: "Large" },
- { value: "xl", label: "Extra Large" },
-];
-
const CTA_VARIANTS = [
{ value: "default", label: "Primary" },
{ value: "outline", label: "Outline" },
@@ -144,7 +136,6 @@ export default function EditAdvertisement() {
end_date: "",
order: 0,
is_active: true,
- size: null,
},
});
@@ -158,7 +149,6 @@ export default function EditAdvertisement() {
const contentMode = watch("content_mode");
const redirectLink = watch("redirect_link");
const format = PLACEMENT_MAP[placement]?.format;
- const isBanner = format === "banner";
const breadcrumbItems = [
{ label: "Home", icon:
, to: "/admin" },
@@ -204,7 +194,6 @@ export default function EditAdvertisement() {
end_date: ad.end_date ?? "",
order: ad.order ?? 0,
is_active: ad.is_active ?? true,
- size: ad.size ?? null,
});
setReady(true);
@@ -222,7 +211,6 @@ export default function EditAdvertisement() {
landing_page: values.redirect_link ? null : values.landing_page,
start_date: values.start_date || null,
end_date: values.end_date || null,
- size: PLACEMENT_MAP[values.placement]?.format === "banner" ? (values.size || "md") : null,
updatedBy: user?.user_id ?? null,
};
@@ -277,25 +265,6 @@ export default function EditAdvertisement() {
Format:
{format} — determined by the placement above.
)}
-
- {isBanner && (
-
-
-
-
- Controls the banner's height. Width always stretches full-width.
-
-
- )}
diff --git a/src/modules/client/pages/CourseDetails.jsx b/src/modules/client/pages/CourseDetails.jsx
index dfecf73..941478e 100644
--- a/src/modules/client/pages/CourseDetails.jsx
+++ b/src/modules/client/pages/CourseDetails.jsx
@@ -5,7 +5,7 @@ import api from "@/utils/api.util";
import {
House, Timer, Layers, GitBranch, Medal, CircleStar, LockIcon,
SendHorizonal, CheckCheck, CheckCircle2, Clock, FileQuestion, ClipboardList,
- Hourglass, Check,
+ Hourglass, Check, Megaphone
} from "lucide-react";
import { cn } from "@/lib/utils";
import CourseBadge from "@/modules/admin/components/courses/CourseBadge";
@@ -516,7 +516,7 @@ const CourseDetails = () => {
const { getCourse, course, courseBlocked, courseLoading, resetCourse } = useClientCourses();
const { myTier, getMyTier } = useClientTiers();
const { fetchCourseProgress, fetchCourseProgressSummary, summary: progressSummary, isCompleted, resetProgress } = useCourseReadingProgress();
- const { advertisements, loading: adLoading, getActiveAdvertisements, handleAdCtaClick } = useClientAdvertisements();
+ const { adLists, listLoading: adLoading, getActiveAdvertisementList, handleAdCtaClick } = useClientAdvertisements();
const [tierMap, setTierMap] = useState({});
const [badgeImageUrl, setBadgeImageUrl] = useState(null);
@@ -544,12 +544,12 @@ const CourseDetails = () => {
getCourse(courseId);
fetchCourseProgress(courseId);
fetchCourseProgressSummary(courseId);
- getActiveAdvertisements(["course_details.banner"]);
+ getActiveAdvertisementList("course_details.banner");
return () => { resetCourse(); resetProgress(); setBadgeImageUrl(null); };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [courseId]);
- const bannerAd = advertisements["course_details.banner"] ?? null;
+ const bannerAds = adLists["course_details.banner"] ?? [];
// Resolve badge image once course loads — issue a client stream token for
// private S3 assets so the badge preview works on this page.
@@ -613,7 +613,24 @@ const CourseDetails = () => {
Coming Soon
) : (
- <>
+
+
- >
+
)}
@@ -713,14 +730,7 @@ const CourseDetails = () => {
- {/* Advertisement Banner */}
-
- {adLoading["course_details.banner"] ? (
-
- ) : (
-
- )}
-
+
{/* Body */}
diff --git a/src/modules/client/pages/PlanList.jsx b/src/modules/client/pages/PlanList.jsx
index fc7e314..b4cf78f 100644
--- a/src/modules/client/pages/PlanList.jsx
+++ b/src/modules/client/pages/PlanList.jsx
@@ -84,8 +84,8 @@ const PREVIEW_COURSE_LIMIT = 2;
const PlanCard = ({ plan, myTier, tierMap, onSelect, onView, onRefund, refundSecsLeft }) => {
const { fmtCurrency } = useDateFormat();
- const [coursesOpen, setCoursesOpen] = useState(false);
- const [notAvailableOpen, setNotAvailableOpen] = useState(false);
+ const [coursesOpen, setCoursesOpen] = useState(false);
+ const [notAvailableOpen, setNotAvailableOpen] = useState(false);
const { label: tierLabel, cls: badgeCls, rank } = resolveTierBadge(plan.tier, tierMap);
const Icon = LucideIcons[tierMap[plan.tier]?.badge_icon] ?? Tag;
const ring = rank > 0 ? "ring-2 ring-primary/30" : "";
@@ -166,8 +166,8 @@ const PlanCard = ({ plan, myTier, tierMap, onSelect, onView, onRefund, refundSec
type="button"
// onClick={() => setCoursesOpen(true)}
// DIALOG DISABLED
- variant="secondary"
-
+ variant="secondary"
+
>
+{extraCount} more course{extraCount !== 1 ? "s" : ""}
@@ -344,7 +344,7 @@ export default function PlanList() {
const navigate = useNavigate();
const { plans, plansLoading, myTier, tierLoading, tierMap, getPlans, getMyTier, getTierCategories, resetMyTier } = useClientTiers();
const { fmtDate, fmtCurrency } = useDateFormat();
- const { advertisements, loading: adLoading, getActiveAdvertisement, handleAdCtaClick } = useClientAdvertisements();
+ const { adLists, listLoading: adLoading, getActiveAdvertisementList, handleAdCtaClick } = useClientAdvertisements();
const [view, setView] = useState("grid");
const [refundPlan, setRefundPlan] = useState(null); // plan being refunded
@@ -356,11 +356,11 @@ export default function PlanList() {
getPlans();
getMyTier();
getTierCategories();
- getActiveAdvertisement("tier_plans.banner");
+ getActiveAdvertisementList("tier_plans.banner");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getPlans, getMyTier]);
- const bannerAd = advertisements["tier_plans.banner"] ?? null;
+ const bannerAds = adLists["tier_plans.banner"] ?? [];
useEffect(() => {
clearInterval(refundTimerRef.current);
@@ -404,68 +404,70 @@ export default function PlanList() {
+
+ {/* Advertisement Banner */}
+ {adLoading["tier_plans.banner"] ? (
+
+ ) : (
+
+ )}
- {/* Advertisement Banner */}
- {adLoading["tier_plans.banner"] ? (
-
- ) : (
-
- )}
-
- {/* Section Header */}
-
-
-
Available Plans
-
- Choose a subscription that matches your goals.
-
+ {/* Section Header */}
+
+
+
Available Plans
+
+ Choose a subscription that matches your goals.
+
+
+ {!plansLoading && plans.length > 0 && (
+
+
+
+
+ )}
- {!plansLoading && plans.length > 0 && (
-
-
-
+
+ {/* Plan Cards / Comparison Table */}
+ {plansLoading || tierLoading ? (
+
+ {Array.from({ length: 3 }).map((_, i) =>
)}
+
+ ) : plans.length === 0 ? (
+
+
+
No plans available at the moment.
+
+ ) : view === "table" ? (
+
+ ) : (
+
+ {plans.map((plan) => (
+
+ ))}
)}
-
- {/* Plan Cards / Comparison Table */}
- {plansLoading || tierLoading ? (
-
- {Array.from({ length: 3 }).map((_, i) =>
)}
-
- ) : plans.length === 0 ? (
-
-
-
No plans available at the moment.
-
- ) : view === "table" ? (
-
- ) : (
-
- {plans.map((plan) => (
-
- ))}
-
- )}
+