mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
250 lines
8.7 KiB
React
250 lines
8.7 KiB
React
import { createContext, useCallback, useContext, useState } from "react";
|
|
import api from "@/utils/api.util";
|
|
import { toast } from "sonner";
|
|
|
|
// ─── Context ──────────────────────────────────────────────────────────────────
|
|
|
|
const ClientCoursesContext = createContext(null);
|
|
|
|
// ─── Provider ─────────────────────────────────────────────────────────────────
|
|
|
|
export function ClientCoursesProvider({ children }) {
|
|
// ── List state
|
|
const [courses, setCourses] = useState([]);
|
|
const [coursesLoading, setCoursesLoading] = useState(false);
|
|
|
|
// ── Detail state
|
|
const [course, setCourse] = useState(null);
|
|
const [courseLoading, setCourseLoading] = useState(false);
|
|
const [courseBlocked, setCourseBlocked] = useState(false); // true = 403, prompt upgrade
|
|
|
|
// ── Unit state
|
|
const [unit, setUnit] = useState(null);
|
|
const [unitLoading, setUnitLoading] = useState(false);
|
|
|
|
// ── Lesson state
|
|
const [lesson, setLesson] = useState(null);
|
|
const [lessonLoading, setLessonLoading] = useState(false);
|
|
|
|
// ── Quiz state
|
|
const [quiz, setQuiz] = useState(null);
|
|
const [quizLoading, setQuizLoading] = useState(false);
|
|
|
|
// ── Assessment state
|
|
const [assessment, setAssessment] = useState(null);
|
|
const [assessmentLoading, setAssessmentLoading] = useState(false);
|
|
|
|
// ─── Actions ────────────────────────────────────────────────────────────────
|
|
|
|
const getCourses = useCallback(async () => {
|
|
setCoursesLoading(true);
|
|
try {
|
|
const { data } = await api.get("/client/courses");
|
|
setCourses(data.data ?? []);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load courses.");
|
|
} finally {
|
|
setCoursesLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const getCourse = useCallback(async (courseId) => {
|
|
setCourseLoading(true);
|
|
setCourseBlocked(false);
|
|
try {
|
|
const { data } = await api.get(`/client/courses/${courseId}`);
|
|
setCourse(data.data ?? null);
|
|
} catch (err) {
|
|
if (err?.response?.status === 403) {
|
|
setCourseBlocked(true); // let the UI show an upgrade prompt
|
|
} else {
|
|
toast.error(err?.response?.data?.message ?? "Could not load course.");
|
|
}
|
|
} finally {
|
|
setCourseLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const getUnit = useCallback(async (courseId, unitId) => {
|
|
setUnitLoading(true);
|
|
try {
|
|
const { data } = await api.get(`/client/courses/${courseId}/units/${unitId}`);
|
|
setUnit(data.data ?? null);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load unit.");
|
|
} finally {
|
|
setUnitLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const getLesson = useCallback(async (courseId, unitId, lessonId) => {
|
|
setLessonLoading(true);
|
|
try {
|
|
const { data } = await api.get(
|
|
`/client/courses/${courseId}/units/${unitId}/lessons/${lessonId}`
|
|
);
|
|
setLesson(data.data ?? null);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load lesson.");
|
|
} finally {
|
|
setLessonLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const getUnitQuiz = useCallback(async (courseId, unitId) => {
|
|
setQuizLoading(true);
|
|
try {
|
|
const { data } = await api.get(
|
|
`/client/courses/${courseId}/units/${unitId}/quiz`
|
|
);
|
|
setQuiz(data.data ?? null);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load quiz.");
|
|
} finally {
|
|
setQuizLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const getCourseAssessment = useCallback(async (courseId) => {
|
|
setAssessmentLoading(true);
|
|
try {
|
|
const { data } = await api.get(`/client/courses/${courseId}/assessment`);
|
|
setAssessment(data.data ?? null);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load assessment.");
|
|
} finally {
|
|
setAssessmentLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const submitUnitQuiz = useCallback(async (courseId, unitId, quizId, answers) => {
|
|
try {
|
|
const { data } = await api.post(
|
|
`/client/courses/${courseId}/units/${unitId}/quiz/${quizId}/submit`,
|
|
{ answers }
|
|
);
|
|
return data.data ?? null;
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not submit quiz.");
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const submitCourseAssessment = useCallback(async (courseId, assessmentId, answers) => {
|
|
try {
|
|
const { data } = await api.post(
|
|
`/client/courses/${courseId}/assessment/${assessmentId}/submit`,
|
|
{ answers }
|
|
);
|
|
return data.data ?? null;
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not submit assessment.");
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
// ─── Course Purchases ───────────────────────────────────────────────────────
|
|
|
|
const [purchases, setPurchases] = useState([]);
|
|
const [purchasesLoading, setPurchasesLoading] = useState(false);
|
|
const [purchaseLoading, setPurchaseLoading] = useState(false);
|
|
|
|
const getMyPurchases = useCallback(async () => {
|
|
setPurchasesLoading(true);
|
|
try {
|
|
const { data } = await api.get("/client/course-purchases");
|
|
setPurchases(data.data ?? []);
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not load purchases.");
|
|
} finally { setPurchasesLoading(false); }
|
|
}, []);
|
|
|
|
const createCourseOrder = useCallback(async (productId) => {
|
|
setPurchaseLoading(true);
|
|
try {
|
|
const { data } = await api.post("/client/course-purchases/order", { product_id: productId });
|
|
return data.data ?? null;
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not create order.");
|
|
return null;
|
|
} finally { setPurchaseLoading(false); }
|
|
}, []);
|
|
|
|
const captureCourseOrder = useCallback(async (orderId) => {
|
|
setPurchaseLoading(true);
|
|
try {
|
|
const { data } = await api.post("/client/course-purchases/capture", { order_id: orderId });
|
|
toast.success("Purchase confirmed! You now have access to this course.");
|
|
return data.data ?? null;
|
|
} catch (err) {
|
|
toast.error(err?.response?.data?.message ?? "Could not capture payment.");
|
|
return null;
|
|
} finally { setPurchaseLoading(false); }
|
|
}, []);
|
|
|
|
const cancelCourseOrder = useCallback(async (orderId) => {
|
|
try {
|
|
await api.post("/client/course-purchases/cancel", { order_id: orderId });
|
|
} catch { /* silent */ }
|
|
}, []);
|
|
|
|
// ─── Reset helpers ──────────────────────────────────────────────────────────
|
|
|
|
const resetCourse = useCallback(() => { setCourse(null); setCourseBlocked(false); }, []);
|
|
const resetUnit = useCallback(() => setUnit(null), []);
|
|
const resetLesson = useCallback(() => setLesson(null), []);
|
|
const resetQuiz = useCallback(() => setQuiz(null), []);
|
|
const resetAssessment = useCallback(() => setAssessment(null), []);
|
|
|
|
// ─── Value ──────────────────────────────────────────────────────────────────
|
|
|
|
const value = {
|
|
// state
|
|
courses, coursesLoading,
|
|
course, courseLoading, courseBlocked,
|
|
unit, unitLoading,
|
|
lesson, lessonLoading,
|
|
quiz, quizLoading,
|
|
assessment, assessmentLoading,
|
|
|
|
// actions
|
|
getCourses,
|
|
getCourse,
|
|
getUnit,
|
|
getLesson,
|
|
getUnitQuiz,
|
|
getCourseAssessment,
|
|
submitUnitQuiz,
|
|
submitCourseAssessment,
|
|
|
|
// purchases
|
|
purchases, purchasesLoading, purchaseLoading,
|
|
getMyPurchases,
|
|
createCourseOrder,
|
|
captureCourseOrder,
|
|
cancelCourseOrder,
|
|
|
|
// resets
|
|
resetCourse,
|
|
resetUnit,
|
|
resetLesson,
|
|
resetQuiz,
|
|
resetAssessment,
|
|
};
|
|
|
|
return (
|
|
<ClientCoursesContext.Provider value={value}>
|
|
{children}
|
|
</ClientCoursesContext.Provider>
|
|
);
|
|
}
|
|
|
|
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
|
|
export function useClientCourses() {
|
|
const ctx = useContext(ClientCoursesContext);
|
|
if (!ctx) throw new Error("useClientCourses must be used within ClientCoursesProvider");
|
|
return ctx;
|
|
}
|
|
|
|
export default ClientCoursesContext; |