forgot useMemo usage for AssetPickerSheet

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-15 11:09:09 +08:00
parent 1029177a91
commit 0013c22523
2 changed files with 62 additions and 2 deletions
+53 -2
View File
@@ -313,7 +313,46 @@ exports.getCourse = async (req, res) => {
// Access check — plan association → subscription field → individual purchase
if (!await canAccessCourse(req.user.user_id, courseId)) {
return R.error(res, "You do not have access to this course.", 403);
// Still send the course's own basic info — including its unit/lesson
// structure (titles + durations, no lesson content/blocks/quiz answers)
// so the client can render the "Course content" outline as a preview,
// with an upsell prompt on every actionable button.
const blockedCourse = await Course.findOne({
where: { course_id: courseId, ...notDeleted },
attributes: COURSE_LIST_ATTRS,
include: [
{
model: Unit, as: "units",
where: notDeleted, required: false,
attributes: ["unit_id", "uuid", "title", "description", "duration_seconds"],
through: { attributes: ["order_index"] },
include: [
{
model: Lesson, as: "lessons",
where: notDeleted, required: false,
attributes: ["lesson_id", "uuid", "title", "description", "duration_seconds"],
through: { attributes: ["order_index"] },
},
{
model: UnitQuiz, as: "quiz",
required: false,
attributes: ["quiz_id", "uuid", "title"],
},
],
},
],
});
const product = blockedCourse && await mdl_Product.findOne({
where: { purchasable_type: 'course', purchasable_id: courseId, is_active: true },
attributes: ['id', 'name', 'price', 'currency', 'access_days'],
});
const blockedPlain = blockedCourse ? blockedCourse.toJSON() : null;
if (blockedPlain) blockedPlain.units = flattenUnits(blockedPlain.units);
return res.status(403).json({
status: "error",
message: "You do not have access to this course.",
item: blockedPlain ? { ...blockedPlain, product: product ?? null } : null,
});
}
const course = await Course.findOne({
@@ -1311,7 +1350,19 @@ exports.getLessonsByUnitUuid = async (req, res) => {
status: "error",
message: "You do not have access to this unit.",
course: first ? { title: first.title, subscription: first.subscription } : null,
item: { uuid: unit.uuid, subscription: unit.subscription },
item: {
uuid: unit.uuid, subscription: unit.subscription,
title: unit.title, description: unit.description,
duration_seconds: unit.duration_seconds,
// Structure only (titles/durations) — no page.blocks/objectives content,
// no quiz questions — so the client can render the lessons/quiz outline
// as a preview without leaking any actual gated content.
lessons: (unit.lessons ?? []).map((l) => ({
lesson_id: l.lesson_id, uuid: l.uuid,
title: l.title, duration_seconds: l.duration_seconds,
})),
quiz: unit.quiz ? { quiz_id: unit.quiz.quiz_id, uuid: unit.quiz.uuid, title: unit.quiz.title } : null,
},
});
}
@@ -195,6 +195,15 @@ const DEFAULT_HANDLERS = {
*/
async function evaluateEntity({ entityType, entityId, userId, courseId = null, transaction = null }) {
const t = transaction;
// Units always complete by passing their own quiz — overrides whatever
// completion_requirement type (if any) is configured on the unit. A unit
// with no quiz attached never completes (isUnitQuizPassed returns false).
if (entityType === 'unit') {
const completed = await isUnitQuizPassed({ unitId: entityId, userId, t });
return { status: completed ? 'completed' : 'in_progress', evaluated_via: 'quiz_required', satisfied: [] };
}
const rows = await CompletionRequirement.findAll({
where: { entity_type: entityType, entity_id: entityId },
order: [['order', 'ASC']],