Hai hành trình của trang khoá
Trang khoá ở tokyo (com.conan.school/app/routes/courses.$slug.tsx) phục vụ hai người khác nhau, theo người soạn (27.09.2026). Trang đổi hình dạng theo enrollment.status.
Hành trình 1: đang khám phá
Điều kiện: không có ghi danh, hoặc trạng thái không phải active / completed / paused. Người đọc hỏi "khoá này có hợp mình không, tốn bao lâu". Trang là một "phòng trưng bày" yên tĩnh:
- Hero: cover 16:9 (
cover_url), tên khoá, câu hỏi cốt lõi làm dòng dẫn. - Ba khối ngắn, một hàng (xếp dọc trên điện thoại):
- Bạn sẽ làm được gì, 3 outcome đầu, "+N" để mở phần còn lại.
- Nhịp học, không hiện tổng giờ làm con số chính. Quy ước ~20 phút/ngày, 5 ngày/tuần:
tuần = ceil(estimated_minutes / 100). Tổng giờ chỉ là chữ phụ.estimated_minutesnull → bỏ khối. - Bạn mang về,
production_output, không có thìyour_results; cả hai trống → bỏ khối, hai khối kia giãn ra.
- Một nút chính "Bắt đầu học". Logic chặn giữ nguyên: free →
/membership, đủ số khoá →/my-courses. - Liên kết phụ "Học thử bài đầu (miễn phí)" tới
/learn/lesson/<preview_lesson_id>, chỉ khi có. - Lộ trình gập sau một nút "Xem lộ trình · N unit": chỉ tên unit và câu hỏi của unit.
Phương án C: tự đánh giá "Khoá này có hợp với tôi?"
Thêm 28.09.2026, theo người soạn. Nằm giữa phòng trưng bày và lúc bắt đầu học; chỉ có ở hành trình 1. Mã: com.conan.school/app/components/course/CourseFitCheck.tsx.
- Lối vào: nút phụ "Khoá này có hợp với tôi? (30 giây)" cạnh "Bắt đầu học". Bấm là mở một panel ngay tại chỗ (motion, không đổi route). Ba câu, từng câu một, có nút quay lại.
- Không có nội dung soạn riêng cho từng khoá. Câu hỏi dựng từ dữ liệu có sẵn:
- Câu 1, câu hỏi cốt lõi của khoá + "Việc này đang làm bạn vướng ở mức nào?" (Đang vướng thật / Thỉnh thoảng gặp / Chưa gặp).
- Câu 2, số phút mỗi ngày: 10 / 20 / 30 / 45.
- Câu 3, danh sách câu hỏi cốt lõi của từng unit (
unit_details, theo thứ tự unit), cộng "Chưa câu nào". Unit chưa có câu hỏi thì hiện tên unit.
- Kết quả tính ở máy khách:
- "Chưa gặp" → "Khoá này có thể chưa cần với bạn lúc này", kèm "Học thử bài đầu" và "Vẫn bắt đầu" (câu 3 bỏ qua vì không đổi được kết luận).
- Unit bắt đầu = unit đầu tiên chưa tick. Tick hết → "Bạn đã nắm phần lớn…"; tick một phần → "Bạn đã nắm Unit 1–k, vào thẳng Unit k+1"; không tick → "Hợp, bắt đầu từ Unit 1".
- Nhịp:
ngày = ceil(phút còn lại / phút mỗi ngày), phút còn lại = tổngestimated_minutescủa các unit từ unit bắt đầu trở đi; thiếu phút ở bất kỳ unit nào thì chia đềuestimated_minutescủa khoá theo số unit. Hiện "~X phút/ngày · khoảng N tuần (5 buổi/tuần)",N = ceil(ngày / 5). - Nút "Bắt đầu từ Unit N" →
/learn/unit/:id, qua cùng cổng với "Bắt đầu học": free →/membership, đủ số khoá →/my-courses.
- Nhảy cóc và khoá unit.
computeUnitStateshiện đặtunlocked = true(cổng tuần tự đang mở), nên vào thẳng unit sau là hợp lệ. Nếu cổng bật lại và unit đích đang khoá (unit_states[].unlocked = false), panel không vượt khoá: nói "bắt đầu từ Unit 1, lướt nhanh phần đã biết" và trỏ Unit 1. - Lưu nhẹ, chỉ ở trình duyệt. Câu trả lời lưu
localStoragekhoáconan:course-fit:<slug>(bọc try/catch); quay lại trang thì kết quả hiện lại, "Làm lại" xoá nó. Không gọiPOST /courses/:slug/fit: endpoint đó đòi mọicourse_fit_promptstrả lời trên thang 1–5 (sai hình dạng → 422), ghi đècourse_fit_assessments, có thể trả 402 khi hồ sơ chưa đủ, và đặt ghi danh thànhconsidering.consideringkhông lật trang sang hành trình 2, nhưng một bài tự đánh giá 30 giây không nên để lại ghi danh. Không thêm bảng nào.
Hành trình 2: đang học
Tập trung hiểu bài, không phân tâm. Mọi khối giới thiệu ẩn đi.
- Khối đầu: "Đang học:
<unit>", thanh tiến độ mảnh (trung bình % bài, cùng công thức 9 mốc với trang Unit), và một nút "Vào học" tới unit hiện tại (resumeUnit). - Danh sách unit: unit hiện tại mở sẵn kèm bài và tiến độ; unit đã xong gập lại với dấu check; unit sau và unit khoá mờ và gập.
- "Bạn sẽ học được gì" gập ở cuối trang.
Chế độ tập trung
Mọi trang /learn/* (và /concepts/*) đã ẩn khung app trong root.tsx (isFocusMode: không HubHeader, footer, chat widget); FocusShell là cả màn hình. Nút thoát về /courses/:slug nằm ở bảng trái trên màn rộng và ở thanh trên cùng trên màn hẹp, trước 27.09 nó chỉ nằm trong ngăn kéo.
API
GET /api/course-intelligence/courses/:slug/experience thêm hai trường, giữ nguyên mọi trường cũ:
cover_url,withCourseArt(legacy_courses row).image_url, hoặcnull.preview_lesson_id,coursePreviewLessonId()(bài đầu của unit đầu), hoặcnull.