Skip to content

Commit f861aa3

Browse files
author
iitzIrFan
committed
Enhance Get Started page with learning path completion tracking and progress indicators
1 parent 3a67108 commit f861aa3

File tree

1 file changed

+217
-39
lines changed

1 file changed

+217
-39
lines changed

src/pages/get-started/index.tsx

Lines changed: 217 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import React, { ReactNode, useState, useEffect } from "react";
1+
import React, { ReactNode, useState, useEffect, useRef } from "react";
22
import Layout from "@theme/Layout";
33
import Link from "@docusaurus/Link";
44
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
55
import { motion, useAnimation, useInView } from "framer-motion";
6+
import Head from '@docusaurus/Head';
67
import styles from "./styles.module.css";
7-
import Head from "@docusaurus/Head";
88

99
// Type definitions
1010
interface AnimatedSectionProps {
@@ -30,15 +30,18 @@ interface LearningPathProps {
3030
description: string;
3131
icon: string;
3232
color: string;
33+
isCompleted?: boolean;
34+
onToggleComplete?: () => void;
35+
index?: number;
3336
}
3437

3538
// Animated component for staggered animations
3639
const AnimatedSection = ({ children, delay = 0 }: AnimatedSectionProps) => {
3740
const controls = useAnimation();
38-
const ref = React.useRef<HTMLDivElement>(null);
41+
const ref = useRef<HTMLDivElement>(null);
3942
const isInView = useInView(ref, { once: true });
4043

41-
React.useEffect(() => {
44+
useEffect(() => {
4245
if (isInView) {
4346
controls.start({
4447
opacity: 1,
@@ -256,10 +259,10 @@ function Feature({ title, description, icon, color }: FeatureProps) {
256259
const [isHovered, setIsHovered] = useState(false);
257260

258261
// Create a ref to apply the custom property
259-
const featureRef = React.useRef<HTMLElement>(null);
262+
const featureRef = useRef<HTMLElement>(null);
260263

261264
// Apply the color as a CSS variable when the component mounts or color changes
262-
React.useEffect(() => {
265+
useEffect(() => {
263266
if (featureRef.current) {
264267
featureRef.current.style.setProperty('--feature-color', color);
265268
}
@@ -354,7 +357,15 @@ function StatCard({ value, label, index = 0 }: StatCardProps) {
354357
);
355358
}
356359

357-
const LearningPath = ({ title, description, icon, color, index }: LearningPathProps & { index: number }) => {
360+
const LearningPath = ({
361+
title,
362+
description,
363+
icon,
364+
color,
365+
index = 0,
366+
isCompleted = false,
367+
onToggleComplete = () => {}
368+
}: LearningPathProps) => {
358369
const isEven = index % 2 === 0;
359370
const delay = index * 0.15;
360371

@@ -363,7 +374,7 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
363374

364375
return (
365376
<motion.article
366-
className={`${styles.pathCard} ${isEven ? styles.left : styles.right} group`}
377+
className={`${styles.pathCard} ${isEven ? styles.left : styles.right} group relative`}
367378
style={{
368379
'--card-color': color || '#3b82f6',
369380
'--card-color-light': color ? `${color}20` : 'rgba(59, 130, 246, 0.2)',
@@ -373,10 +384,14 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
373384
transform: isEven ? 'none' : 'translateX(50%)',
374385
marginLeft: isEven ? '0' : 'auto',
375386
marginRight: isEven ? 'auto' : '0',
387+
opacity: isCompleted ? 0.9 : 1,
388+
filter: isCompleted ? 'saturate(0.9)' : 'none',
389+
transition: 'all 0.3s ease-in-out',
390+
border: isCompleted ? `1px solid ${color}40` : '1px solid rgba(255, 255, 255, 0.1)'
376391
} as React.CSSProperties}
377392
initial={{ opacity: 0, x: isEven ? -100 : 100, y: 20 }}
378393
animate={{
379-
opacity: 1,
394+
opacity: 1,
380395
x: 0,
381396
y: 0,
382397
transition: {
@@ -389,14 +404,15 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
389404
>
390405
<div className="flex items-start gap-4">
391406
<motion.div
392-
className="flex-shrink-0 w-14 h-14 flex items-center justify-center rounded-xl text-2xl"
407+
className="flex-shrink-0 w-14 h-14 flex items-center justify-center rounded-xl text-2xl relative"
393408
style={{
394409
background: `linear-gradient(135deg, ${color || '#3b82f6'}, ${color ? `${color}80` : '#2563eb'})`,
395-
boxShadow: `0 4px 15px -5px ${color || '#3b82f6'}80`
410+
boxShadow: `0 4px 15px -5px ${color || '#3b82f6'}80`,
411+
opacity: isCompleted ? 0.9 : 1
396412
}}
397413
whileHover={{
398414
scale: 1.1,
399-
rotate: 5,
415+
rotate: isCompleted ? 0 : 5,
400416
transition: { duration: 0.2 }
401417
}}
402418
>
@@ -405,33 +421,131 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
405421
<div className="flex-1">
406422
<h3 className="text-xl font-bold mb-2 text-white">{title}</h3>
407423
<p className="text-gray-300 mb-4 leading-relaxed">{description}</p>
408-
<Link
409-
to="/docs"
410-
className="inline-flex items-center text-sm font-medium text-blue-400 hover:text-blue-300 transition-colors group-hover:underline"
411-
>
412-
Start Learning
413-
<svg
414-
className="w-4 h-4 ml-2 transition-transform duration-300 group-hover:translate-x-1"
415-
fill="none"
416-
viewBox="0 0 24 24"
417-
stroke="currentColor"
424+
425+
<div className="flex items-center justify-between mt-4 pt-3 border-t border-gray-700/50">
426+
<Link
427+
to="/docs"
428+
className="inline-flex items-center text-sm font-medium text-blue-400 hover:text-blue-300 transition-colors group-hover:underline"
418429
>
419-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
420-
</svg>
421-
</Link>
430+
{isCompleted ? 'Continue Learning' : 'Start Learning'}
431+
<svg
432+
className="w-4 h-4 ml-2 transition-transform duration-300 group-hover:translate-x-1"
433+
fill="none"
434+
viewBox="0 0 24 24"
435+
stroke="currentColor"
436+
>
437+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
438+
</svg>
439+
</Link>
440+
441+
{/* Completion Toggle */}
442+
<button
443+
onClick={(e) => {
444+
e.preventDefault();
445+
onToggleComplete();
446+
}}
447+
className={`relative inline-flex items-center h-6 rounded-full w-11 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 ${
448+
isCompleted
449+
? 'bg-green-500 focus:ring-green-500'
450+
: 'bg-gray-600 focus:ring-blue-500'
451+
}`}
452+
aria-pressed={isCompleted}
453+
aria-label={isCompleted ? 'Mark as incomplete' : 'Mark as complete'}
454+
>
455+
<span className="sr-only">
456+
{isCompleted ? 'Mark as incomplete' : 'Mark as complete'}
457+
</span>
458+
<span
459+
className={`${
460+
isCompleted ? 'translate-x-6' : 'translate-x-1'
461+
} inline-block w-4 h-4 transform bg-white rounded-full transition-transform duration-200 ease-in-out`}
462+
>
463+
{isCompleted && (
464+
<svg
465+
className="w-3 h-3 text-green-500 mx-auto mt-0.5"
466+
fill="currentColor"
467+
viewBox="0 0 20 20"
468+
xmlns="http://www.w3.org/2000/svg"
469+
>
470+
<path
471+
fillRule="evenodd"
472+
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
473+
clipRule="evenodd"
474+
/>
475+
</svg>
476+
)}
477+
</span>
478+
</button>
479+
</div>
422480
</div>
423481
</div>
482+
483+
{/* Progress bar background */}
484+
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-700/50 rounded-b-lg overflow-hidden">
485+
<motion.div
486+
className="h-full bg-green-500"
487+
initial={{ width: '0%' }}
488+
animate={{ width: isCompleted ? '100%' : '0%' }}
489+
transition={{ duration: 0.5, ease: 'easeOut' }}
490+
/>
491+
</div>
492+
493+
{/* Completion badge */}
494+
{isCompleted && (
495+
<div className="absolute -top-2 -right-2 w-8 h-8 bg-green-500 rounded-full flex items-center justify-center shadow-lg">
496+
<svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
497+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
498+
</svg>
499+
</div>
500+
)}
424501
</motion.article>
425502
);
426503
};
427504

428505
export default function GetStarted() {
429506
const { siteConfig } = useDocusaurusContext();
430-
const currentYear = new Date().getFullYear();
507+
type CompletedPaths = Record<string, boolean>;
508+
509+
const [completedPaths, setCompletedPaths] = useState<CompletedPaths>(() => {
510+
if (typeof window !== 'undefined') {
511+
try {
512+
const saved = localStorage.getItem('completedPaths');
513+
return saved ? JSON.parse(saved) : {};
514+
} catch (e) {
515+
console.error('Failed to parse completedPaths from localStorage', e);
516+
return {};
517+
}
518+
}
519+
return {};
520+
});
521+
522+
// Toggle completion status for a path
523+
const togglePathCompletion = React.useCallback((pathTitle: string) => {
524+
setCompletedPaths(prev => ({
525+
...prev,
526+
[pathTitle]: !prev[pathTitle]
527+
}));
528+
}, []);
529+
530+
// Calculate completion percentage
531+
const completionPercentage = Math.round(
532+
(Object.values(completedPaths).filter(Boolean).length / learningPaths.length) * 100
533+
) || 0;
534+
535+
// Save to localStorage whenever completedPaths changes
536+
useEffect(() => {
537+
if (typeof window !== 'undefined') {
538+
try {
539+
localStorage.setItem('completedPaths', JSON.stringify(completedPaths));
540+
} catch (e) {
541+
console.error('Failed to save completedPaths to localStorage', e);
542+
}
543+
}
544+
}, [completedPaths]);
431545

432546
return (
433547
<Layout
434-
title={`Get Started | ${siteConfig.title}`}
548+
title={`Get Started | ${siteConfig?.title || 'Recode Hive'}`}
435549
description="Start your coding journey with Recode Hive. Learn to code with our interactive platform and structured learning paths."
436550
>
437551
<Head>
@@ -517,19 +631,83 @@ export default function GetStarted() {
517631
</motion.h2>
518632
</div>
519633

520-
<div className={styles.timelineContainer}>
521-
<div className={styles.timeline}></div>
522-
<div className={styles.pathsContainer}>
523-
{learningPaths.map((path, idx) => (
524-
<LearningPath
525-
key={path.title}
526-
title={path.title}
527-
description={path.description}
528-
icon={path.icon}
529-
color={path.color}
530-
index={idx}
634+
<div className="relative">
635+
{/* Progress Overview */}
636+
<motion.div
637+
className="mb-12 p-6 bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-gray-700/50 shadow-lg"
638+
initial={{ opacity: 0, y: 20 }}
639+
whileInView={{ opacity: 1, y: 0 }}
640+
viewport={{ once: true, margin: "-50px 0px -50px 0px" }}
641+
transition={{ duration: 0.5, delay: 0.2 }}
642+
>
643+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4">
644+
<div>
645+
<h3 className="text-xl font-bold text-white mb-1">Your Learning Progress</h3>
646+
<p className="text-gray-300 text-sm">
647+
{completionPercentage}% Complete • {Object.values(completedPaths).filter(Boolean).length} of {learningPaths.length} paths started
648+
</p>
649+
</div>
650+
<div className="text-right">
651+
<span className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
652+
{completionPercentage}%
653+
</span>
654+
</div>
655+
</div>
656+
657+
{/* Progress Bar */}
658+
<div className="w-full h-3 bg-gray-700/50 rounded-full overflow-hidden">
659+
<motion.div
660+
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full relative"
661+
initial={{ width: 0 }}
662+
animate={{ width: `${completionPercentage}%` }}
663+
transition={{ duration: 1, ease: "easeOut" }}
664+
>
665+
<motion.div
666+
className="absolute inset-0 bg-white/20"
667+
animate={{
668+
left: ['0%', '100%']
669+
}}
670+
transition={{
671+
duration: 1.5,
672+
repeat: Infinity,
673+
repeatType: 'loop',
674+
ease: 'easeInOut',
675+
}}
676+
style={{
677+
width: '20%',
678+
height: '100%',
679+
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',
680+
transform: 'skewX(-20deg)'
681+
}}
682+
/>
683+
</motion.div>
684+
</div>
685+
</motion.div>
686+
687+
<div className={styles.timelineContainer}>
688+
<div className={styles.timeline}>
689+
{/* Animated progress indicator on the timeline */}
690+
<motion.div
691+
className="absolute left-0 top-0 w-1 bg-gradient-to-b from-blue-400 to-purple-500 h-0"
692+
initial={{ height: 0 }}
693+
animate={{ height: `${(completionPercentage / 100) * 100}%` }}
694+
transition={{ duration: 1.5, ease: 'easeInOut' }}
531695
/>
532-
))}
696+
</div>
697+
<div className={styles.pathsContainer}>
698+
{learningPaths.map((path, idx) => (
699+
<LearningPath
700+
key={path.title}
701+
title={path.title}
702+
description={path.description}
703+
icon={path.icon}
704+
color={path.color}
705+
index={idx}
706+
isCompleted={!!completedPaths[path.title]}
707+
onToggleComplete={() => togglePathCompletion(path.title)}
708+
/>
709+
))}
710+
</div>
533711
</div>
534712
</div>
535713

0 commit comments

Comments
 (0)