diff --git a/src/app/courses/[courseId]/modules/[moduleId]/page.tsx b/src/app/courses/[courseId]/modules/[moduleId]/page.tsx index 4075384..79ea4b5 100644 --- a/src/app/courses/[courseId]/modules/[moduleId]/page.tsx +++ b/src/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -5,6 +5,7 @@ import { useModule, useCourseDetail } from "@/lib/hooks/use-courses"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { LoadingSkeleton } from "@/components/shared/loading-skeleton"; +import { BackButton } from "@/components/shared/back-button"; import { ProgressBar } from "@/components/course/progress-bar"; import { useCourseStore } from "@/store/course-store"; import { useToastContext } from "@/components/shared/toast"; @@ -83,15 +84,7 @@ export default function ModulePage({ return (
{/* Breadcrumb */} -
- - - Back to Course - -
+ {/* Progress */} {courseProgress && ( diff --git a/src/app/courses/[courseId]/page.tsx b/src/app/courses/[courseId]/page.tsx index 136ed0f..e4b56ef 100644 --- a/src/app/courses/[courseId]/page.tsx +++ b/src/app/courses/[courseId]/page.tsx @@ -9,6 +9,8 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge, difficultyVariant } from "@/components/ui/badge"; import { AutoBreadcrumb } from "@/components/ui/breadcrumb"; +import { BackButton } from "@/components/shared/back-button"; +import { ShareButton } from "@/components/shared/share-button"; import { LoadingSkeleton } from "@/components/shared/loading-skeleton"; import { useToastContext } from "@/components/shared/toast"; import { @@ -86,10 +88,16 @@ export default function CourseDetailPage({ return (
- + +
+ + +
{/* Hero */}
diff --git a/src/app/courses/[courseId]/quiz/page.tsx b/src/app/courses/[courseId]/quiz/page.tsx index 77a5218..ba519a6 100644 --- a/src/app/courses/[courseId]/quiz/page.tsx +++ b/src/app/courses/[courseId]/quiz/page.tsx @@ -3,6 +3,7 @@ import { lazy, Suspense, useState, useCallback } from "react"; import { useQuiz } from "@/lib/hooks/use-quiz"; import { LoadingSkeleton } from "@/components/shared/loading-skeleton"; +import { BackButton } from "@/components/shared/back-button"; import { Button } from "@/components/ui/button"; import { ArrowLeft } from "lucide-react"; import Link from "next/link"; @@ -79,15 +80,7 @@ export default function QuizPage({ return (
-
- - - Back to Course - -
+ }> + {/* Badge */}
Download Certificate - - - Share Verification Link - +
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index d52441d..6be08c2 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/navigation"; import { useAuth } from "@/lib/hooks/use-auth"; import { useToastContext } from "@/components/shared/toast"; import { getProfile, updateProfile } from "@/lib/api/auth"; +import { AvatarUpload } from "@/components/shared/avatar-upload"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -115,6 +116,20 @@ export default function SettingsPage() { Profile Information + {/* Avatar Upload */} +
+ { + // In a real app, upload the file to storage and update profile + // For now, simulate upload + await new Promise((resolve) => setTimeout(resolve, 1000)); + addToast("Avatar uploaded successfully", "success"); + }} + /> +
+ {/* Display Name */}
+ + + + + Crop Image + +
+ {imageSrc && ( + + )} +
+ + + + +
+
+
+ ); +} diff --git a/src/components/shared/back-button.tsx b/src/components/shared/back-button.tsx new file mode 100644 index 0000000..6e11f55 --- /dev/null +++ b/src/components/shared/back-button.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { ArrowLeft } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + +export function BackButton() { + const router = useRouter(); + + return ( + + ); +} diff --git a/src/components/shared/loading-skeleton.tsx b/src/components/shared/loading-skeleton.tsx index 2da6d8d..b8a97b2 100644 --- a/src/components/shared/loading-skeleton.tsx +++ b/src/components/shared/loading-skeleton.tsx @@ -22,7 +22,7 @@ export function LoadingSkeleton({ variant = "card", }: LoadingSkeletonProps) { return ( -
+
{Array.from({ length: count }).map((_, i) => { if (variant === "text") { return ; diff --git a/src/components/shared/share-button.tsx b/src/components/shared/share-button.tsx new file mode 100644 index 0000000..b469ebf --- /dev/null +++ b/src/components/shared/share-button.tsx @@ -0,0 +1,136 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Share2, Copy, Twitter, Linkedin, Check } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; +import { QrCode } from '@/components/credentials/qr-code'; +import { cn } from '@/lib/utils/cn'; + +interface ShareButtonProps { + url: string; + title?: string; + text?: string; + className?: string; + variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"; +} + +export function ShareButton({ + url, + title = 'Check this out!', + text = '', + className, + variant = 'outline' +}: ShareButtonProps) { + const [open, setOpen] = useState(false); + const [copied, setCopied] = useState(false); + const [canShare, setCanShare] = useState(false); + + useEffect(() => { + if (typeof navigator !== 'undefined' && navigator.share) { + setCanShare(true); + } + }, []); + + const handleShareClick = async () => { + if (canShare) { + try { + await navigator.share({ + title, + text, + url, + }); + return; + } catch (err) { + if ((err as Error).name !== 'AbortError') { + console.error('Error sharing', err); + setOpen(true); + } + } + } else { + setOpen(true); + } + }; + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy', err); + } + }; + + const shareToTwitter = () => { + const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text || title)}`; + window.open(twitterUrl, '_blank', 'noopener,noreferrer'); + }; + + const shareToLinkedIn = () => { + const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`; + window.open(linkedinUrl, '_blank', 'noopener,noreferrer'); + }; + + return ( + <> + + + + + + Share link + + Share this page with others or scan the QR code. + + + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+
+ + ); +} diff --git a/src/components/shared/toast.tsx b/src/components/shared/toast.tsx index 1862647..c0771c9 100644 --- a/src/components/shared/toast.tsx +++ b/src/components/shared/toast.tsx @@ -104,6 +104,7 @@ export function Toast({ return (