mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: perf improvements and bug fixes
This commit is contained in:
parent
93d0e2a104
commit
59bae82ee7
10 changed files with 200 additions and 112 deletions
|
|
@ -31,6 +31,7 @@ interface CourseRun {
|
|||
|
||||
interface Course {
|
||||
id: string
|
||||
course_uuid: string
|
||||
authors: Author[]
|
||||
trail?: {
|
||||
runs: CourseRun[]
|
||||
|
|
@ -51,6 +52,7 @@ interface CourseActionsMobileProps {
|
|||
course: Course & {
|
||||
org_id: number
|
||||
}
|
||||
trailData?: any
|
||||
}
|
||||
|
||||
// Component for displaying multiple authors
|
||||
|
|
@ -122,7 +124,7 @@ const MultipleAuthors = ({ authors }: { authors: Author[] }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const CourseActionsMobile = ({ courseuuid, orgslug, course }: CourseActionsMobileProps) => {
|
||||
const CourseActionsMobile = ({ courseuuid, orgslug, course, trailData }: CourseActionsMobileProps) => {
|
||||
const router = useRouter()
|
||||
const session = useLHSession() as any
|
||||
const [linkedProducts, setLinkedProducts] = useState<any[]>([])
|
||||
|
|
@ -131,9 +133,15 @@ const CourseActionsMobile = ({ courseuuid, orgslug, course }: CourseActionsMobil
|
|||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [hasAccess, setHasAccess] = useState<boolean | null>(null)
|
||||
|
||||
const isStarted = course.trail?.runs?.some(
|
||||
(run) => run.status === 'STATUS_IN_PROGRESS' && run.course_id === course.id
|
||||
) ?? false
|
||||
// Clean up course UUID by removing 'course_' prefix if it exists
|
||||
const cleanCourseUuid = course.course_uuid?.replace('course_', '');
|
||||
|
||||
const isStarted = trailData?.runs?.find(
|
||||
(run: any) => {
|
||||
const cleanRunCourseUuid = run.course?.course_uuid?.replace('course_', '');
|
||||
return cleanRunCourseUuid === cleanCourseUuid;
|
||||
}
|
||||
) ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLinkedProducts = async () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { removeCourse, startCourse } from '@services/courses/activity'
|
|||
import { revalidateTags } from '@services/utils/ts/requests'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
|
||||
import { getAPIUrl, getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
|
||||
import { getProductsByCourse } from '@services/payments/products'
|
||||
import { LogIn, LogOut, ShoppingCart, AlertCircle, UserPen, ClockIcon, ArrowRight, Sparkles, BookOpen } from 'lucide-react'
|
||||
import Modal from '@components/Objects/StyledElements/Modal/Modal'
|
||||
|
|
@ -14,6 +14,8 @@ import toast from 'react-hot-toast'
|
|||
import { useContributorStatus } from '../../../../hooks/useContributorStatus'
|
||||
import CourseProgress from '../CourseProgress/CourseProgress'
|
||||
import UserAvatar from '@components/Objects/UserAvatar'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { mutate } from 'swr'
|
||||
|
||||
interface CourseRun {
|
||||
status: string
|
||||
|
|
@ -26,6 +28,7 @@ interface CourseRun {
|
|||
|
||||
interface Course {
|
||||
id: string
|
||||
course_uuid: string
|
||||
trail?: {
|
||||
runs: CourseRun[]
|
||||
}
|
||||
|
|
@ -46,9 +49,10 @@ interface CourseActionsProps {
|
|||
course: Course & {
|
||||
org_id: number
|
||||
}
|
||||
trailData?: any
|
||||
}
|
||||
|
||||
function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
|
||||
function CoursesActions({ courseuuid, orgslug, course, trailData }: CourseActionsProps) {
|
||||
const router = useRouter()
|
||||
const session = useLHSession() as any
|
||||
const [linkedProducts, setLinkedProducts] = useState<any[]>([])
|
||||
|
|
@ -59,10 +63,17 @@ function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
|
|||
const [hasAccess, setHasAccess] = useState<boolean | null>(null)
|
||||
const { contributorStatus, refetch } = useContributorStatus(courseuuid)
|
||||
const [isProgressOpen, setIsProgressOpen] = useState(false)
|
||||
const org = useOrg() as any
|
||||
|
||||
const isStarted = course.trail?.runs?.some(
|
||||
(run) => run.status === 'STATUS_IN_PROGRESS' && run.course_id === course.id
|
||||
) ?? false
|
||||
// Clean up course UUID by removing 'course_' prefix if it exists
|
||||
const cleanCourseUuid = course.course_uuid?.replace('course_', '');
|
||||
|
||||
const isStarted = trailData?.runs?.find(
|
||||
(run: any) => {
|
||||
const cleanRunCourseUuid = run.course?.course_uuid?.replace('course_', '');
|
||||
return cleanRunCourseUuid === cleanCourseUuid;
|
||||
}
|
||||
) ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLinkedProducts = async () => {
|
||||
|
|
@ -120,12 +131,11 @@ function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
|
|||
try {
|
||||
if (isStarted) {
|
||||
await removeCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
mutate(`${getAPIUrl()}trail/org/${org?.id}/trail`)
|
||||
toast.success('Successfully left the course', { id: loadingToast })
|
||||
router.refresh()
|
||||
} else {
|
||||
await startCourse('course_' + courseuuid, orgslug, session.data?.tokens?.access_token)
|
||||
await revalidateTags(['courses'], orgslug)
|
||||
mutate(`${getAPIUrl()}trail/org/${org?.id}/trail`)
|
||||
toast.success('Successfully started the course', { id: loadingToast })
|
||||
|
||||
// Get the first activity from the first chapter
|
||||
|
|
@ -139,7 +149,7 @@ function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
|
|||
`/course/${courseuuid}/activity/${firstActivity.activity_uuid.replace('activity_', '')}`
|
||||
)
|
||||
} else {
|
||||
router.refresh()
|
||||
mutate(`${getAPIUrl()}trail/org/${org?.id}/trail`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -262,10 +272,16 @@ function CoursesActions({ courseuuid, orgslug, course }: CourseActionsProps) {
|
|||
|
||||
const renderProgressSection = () => {
|
||||
const totalActivities = course.chapters?.reduce((acc: number, chapter: any) => acc + chapter.activities.length, 0) || 0;
|
||||
const completedActivities = course.trail?.runs?.find(
|
||||
(run: CourseRun) => run.course_id === course.id
|
||||
)?.steps?.filter((step) => step.complete)?.length || 0;
|
||||
|
||||
// Find the correct run using the cleaned UUID
|
||||
const run = trailData?.runs?.find(
|
||||
(run: any) => {
|
||||
const cleanRunCourseUuid = run.course?.course_uuid?.replace('course_', '');
|
||||
return cleanRunCourseUuid === cleanCourseUuid;
|
||||
}
|
||||
);
|
||||
|
||||
const completedActivities = run?.steps?.filter((step: any) => step.complete)?.length || 0;
|
||||
const progressPercentage = Math.round((completedActivities / totalActivities) * 100);
|
||||
|
||||
if (!isStarted) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ interface ActivityChapterDropdownProps {
|
|||
course: any
|
||||
currentActivityId: string
|
||||
orgslug: string
|
||||
trailData?: any
|
||||
}
|
||||
|
||||
export default function ActivityChapterDropdown(props: ActivityChapterDropdownProps): React.ReactNode {
|
||||
|
|
@ -16,6 +17,9 @@ export default function ActivityChapterDropdown(props: ActivityChapterDropdownPr
|
|||
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
// Clean up course UUID by removing 'course_' prefix if it exists
|
||||
const cleanCourseUuid = props.course.course_uuid?.replace('course_', '');
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
React.useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
|
|
@ -100,9 +104,20 @@ export default function ActivityChapterDropdown(props: ActivityChapterDropdownPr
|
|||
<div className="py-0.5">
|
||||
{chapter.activities.map((activity: any) => {
|
||||
const cleanActivityUuid = activity.activity_uuid?.replace('activity_', '');
|
||||
const cleanCourseUuid = props.course.course_uuid?.replace('course_', '');
|
||||
const isCurrent = cleanActivityUuid === props.currentActivityId.replace('activity_', '');
|
||||
|
||||
// Find the correct run and check if activity is complete
|
||||
const run = props.trailData?.runs?.find(
|
||||
(run: any) => {
|
||||
const cleanRunCourseUuid = run.course?.course_uuid?.replace('course_', '');
|
||||
return cleanRunCourseUuid === cleanCourseUuid;
|
||||
}
|
||||
);
|
||||
|
||||
const isComplete = run?.steps?.find(
|
||||
(step: any) => step.activity_id === activity.id && step.complete === true
|
||||
);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={activity.id}
|
||||
|
|
@ -117,11 +132,7 @@ export default function ActivityChapterDropdown(props: ActivityChapterDropdownPr
|
|||
>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<div className="flex items-center">
|
||||
{props.course.trail?.runs?.find(
|
||||
(run: any) => run.course_id === props.course.id
|
||||
)?.steps?.find(
|
||||
(step: any) => (step.activity_id === activity.id || step.activity_id === activity.activity_uuid) && step.complete === true
|
||||
) ? (
|
||||
{isComplete ? (
|
||||
<div className="relative cursor-pointer">
|
||||
<Check size={14} className="stroke-[2.5] text-teal-600" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface Props {
|
|||
course_uuid: string
|
||||
current_activity?: string
|
||||
enableNavigation?: boolean
|
||||
trailData?: any
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
|
@ -109,8 +110,6 @@ function ActivityIndicators(props: Props) {
|
|||
const black_activity_style = 'bg-zinc-300 hover:bg-zinc-400'
|
||||
const current_activity_style = 'bg-gray-600 animate-pulse hover:bg-gray-700'
|
||||
|
||||
const trail = props.course.trail
|
||||
|
||||
// Flatten all activities for navigation and rendering
|
||||
const allActivities = useMemo(() => {
|
||||
return course.chapters.flatMap((chapter: any) =>
|
||||
|
|
@ -131,14 +130,23 @@ function ActivityIndicators(props: Props) {
|
|||
|
||||
// Memoize activity status checks
|
||||
const isActivityDone = useMemo(() => (activity: any) => {
|
||||
let run = props.course.trail?.runs.find(
|
||||
(run: any) => run.course_id == props.course.id
|
||||
)
|
||||
// Clean up course UUID by removing 'course_' prefix if it exists
|
||||
const cleanCourseUuid = course.course_uuid?.replace('course_', '');
|
||||
|
||||
let run = props.trailData?.runs?.find(
|
||||
(run: any) => {
|
||||
const cleanRunCourseUuid = run.course?.course_uuid?.replace('course_', '');
|
||||
return cleanRunCourseUuid === cleanCourseUuid;
|
||||
}
|
||||
);
|
||||
|
||||
if (run) {
|
||||
return run.steps.find((step: any) => step.activity_id == activity.id)
|
||||
return run.steps.find(
|
||||
(step: any) => step.activity_id === activity.id && step.complete === true
|
||||
);
|
||||
}
|
||||
return false
|
||||
}, [props.course]);
|
||||
return false;
|
||||
}, [props.trailData, course.course_uuid]);
|
||||
|
||||
const isActivityCurrent = useMemo(() => (activity: any) => {
|
||||
let activity_uuid = activity.activity_uuid.replace('activity_', '')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue