build ok,

This commit is contained in:
louiscklaw
2025-04-14 09:26:24 +08:00
commit 6c931c1fe8
770 changed files with 63959 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import type { StepIconProps } from '@mui/material/StepIcon';
import { Check as CheckIcon } from '@phosphor-icons/react/dist/ssr/Check';
export function ChapterStepIcon({ active, completed, icon }: StepIconProps): React.JSX.Element {
const highlight = active || completed;
return (
<Avatar
sx={{
'--Avatar-size': '24px',
...(highlight
? { bgcolor: 'var(--mui-palette-primary-main)', color: 'var(--mui-palette-primary-contrastText)' }
: { bgcolor: 'var(--mui-palette-background-level2)', color: 'var(--mui-palette-text-primary)' }),
}}
variant="circular"
>
{completed ? <CheckIcon /> : icon}
</Avatar>
);
}

View File

@@ -0,0 +1,63 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { Lesson } from './lesson';
import type { Chapter } from './types';
export interface ChapterViewProps {
chapter: Chapter;
}
export function ChapterView({ chapter }: ChapterViewProps): React.JSX.Element {
return (
<Box sx={{ position: 'relative', pb: 6 }}>
<Card>
<CardHeader subheader={chapter.description} title={chapter.title} />
<Tabs sx={{ px: 3 }} value="lesson">
<Tab label="Lesson" tabIndex={0} value="lesson" />
<Tab label="Resources" tabIndex={0} value="resources" />
</Tabs>
<Divider />
<CardContent>
<Lesson content={chapter.lesson} />
</CardContent>
</Card>
<Box
sx={{
bottom: 20,
display: 'flex',
justifyContent: 'center',
left: 0,
position: 'absolute',
right: 0,
zIndex: 1,
}}
>
<Card sx={{ boxShadow: 'var(--mui-shadows-16)' }}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'center', p: 1 }}>
<Button color="secondary" size="small" startIcon={<ArrowLeftIcon />}>
Prev
</Button>
<Typography color="text.secondary" variant="subtitle2">
1/3
</Typography>
<Button color="secondary" size="small" startIcon={<ArrowRightIcon />}>
Next
</Button>
</Stack>
</Card>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import LinearProgress from '@mui/material/LinearProgress';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { Clock as ClockIcon } from '@phosphor-icons/react/dist/ssr/Clock';
import { paths } from '@/paths';
import type { Course } from './types';
export interface CourseCardProps {
course: Course;
}
export function CourseCard({ course }: CourseCardProps): React.JSX.Element {
return (
<Card variant="outlined">
<CardMedia
component={RouterLink}
href={paths.dashboard.academy.details('1')}
image={course.media}
sx={{ height: '180px' }}
/>
<CardContent>
<Stack spacing={1}>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.academy.details('1')}
underline="none"
variant="subtitle1"
>
{course.title}
</Link>
<Typography color="text.secondary" variant="body2">
{course.description}
</Typography>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
<ClockIcon fontSize="var(--icon-fontSize-sm)" />
<Typography color="text.secondary" variant="caption">
{course.duration}
</Typography>
</Stack>
</Stack>
</CardContent>
<LinearProgress value={course.progress} variant="determinate" />
<Box sx={{ display: 'flex', justifyContent: 'flex-end', p: 1 }}>
<Button
color="secondary"
component={RouterLink}
endIcon={<ArrowRightIcon />}
href={paths.dashboard.academy.details('1')}
>
Continue
</Button>
</Box>
</Card>
);
}

View File

@@ -0,0 +1,71 @@
import * as React from 'react';
import LinearProgress from '@mui/material/LinearProgress';
import Stack from '@mui/material/Stack';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import Stepper from '@mui/material/Stepper';
import Typography from '@mui/material/Typography';
import { Clock as ClockIcon } from '@phosphor-icons/react/dist/ssr/Clock';
import { ChapterStepIcon } from './chapter-step-icon';
import type { Chapter, Course } from './types';
export interface CourseSummaryProps {
chapters: Chapter[];
course: Course;
currentChapterNumber?: number;
}
export function CourseSummary({ chapters, course, currentChapterNumber }: CourseSummaryProps): React.JSX.Element {
return (
<Stack spacing={3}>
<Stack spacing={1}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<LinearProgress sx={{ flex: '1 1 auto', height: '8px' }} value={course.progress} variant="determinate" />
<Typography color="text.secondary" variant="body2">
{new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(
course.progress / 100
)}
</Typography>
</Stack>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
<ClockIcon fontSize="var(--icon-fontSize-sm)" />
<Typography color="text.secondary" variant="caption">
{course.duration}
</Typography>
</Stack>
</Stack>
<div>
<Typography variant="subtitle1">{course.title}</Typography>
<Typography color="text.secondary" variant="body2">
{course.description}
</Typography>
</div>
<Stepper
activeStep={currentChapterNumber ? currentChapterNumber - 1 : 0}
orientation="vertical"
sx={{
'& .MuiStepLabel-iconContainer': { pr: 3 },
'& .MuiStepConnector-line': { borderLeft: '2px solid var(--mui-palette-divider)' },
}}
>
{chapters.map((chapter, index) => {
const isCompleted = currentChapterNumber ? index < currentChapterNumber - 1 : false;
return (
<Step key={chapter.title}>
<StepLabel StepIconComponent={ChapterStepIcon}>
<Typography color={isCompleted ? 'primary.main' : 'text.primary'} variant="subtitle2">
{chapter.title}
</Typography>
<Typography color="text.secondary" variant="body2">
{chapter.description}
</Typography>
</StepLabel>
</Step>
);
})}
</Stepper>
</Stack>
);
}

View File

@@ -0,0 +1,58 @@
'use client';
import * as React from 'react';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { MagnifyingGlass as MagnifyingGlassIcon } from '@phosphor-icons/react/dist/ssr/MagnifyingGlass';
import { dayjs } from '@/lib/dayjs';
import { Option } from '@/components/core/option';
export function CoursesFilters(): React.JSX.Element {
return (
<Card>
<Stack direction="row" spacing={3} sx={{ alignItems: 'flex-end', flexWrap: 'wrap', p: 3 }}>
<FormControl sx={{ maxWidth: '100%', width: '240px' }}>
<InputLabel>Search</InputLabel>
<OutlinedInput fullWidth name="query" placeholder="Keywords" />
</FormControl>
<FormControl sx={{ maxWidth: '100%', width: '240px' }}>
<InputLabel>Platform</InputLabel>
<Select defaultValue="" fullWidth name="category">
<Option value="">All categories</Option>
<Option value="fullstack">Fullstack</Option>
<Option value="devops">DevOps</Option>
<Option value="design">Design</Option>
</Select>
</FormControl>
<DatePicker
format="MMM D, YYYY"
label="From"
onChange={() => {
// noop
}}
sx={{ maxWidth: '100%', width: '240px' }}
value={dayjs().subtract(1, 'month')}
/>
<DatePicker
format="MMM D, YYYY"
label="To"
onChange={() => {
// noop
}}
sx={{ maxWidth: '100%', width: '240px' }}
value={dayjs()}
/>
<Button startIcon={<MagnifyingGlassIcon />} variant="contained">
Search
</Button>
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,102 @@
'use client';
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { RadialBar, RadialBarChart } from 'recharts';
import { NoSsr } from '@/components/core/no-ssr';
export interface DailyProgressProps {
timeCurrent: number;
timeGoal: number;
}
export function DailyProgress({ timeCurrent, timeGoal }: DailyProgressProps): React.JSX.Element {
const chartSize = 250;
const timeLeft = timeGoal - timeCurrent;
const progress = Math.round((timeCurrent / timeGoal) * 100);
const data = [
{ name: 'Empty', value: 100 },
{ name: 'Usage', value: progress },
] satisfies { name: string; value: number }[];
return (
<Card>
<CardContent>
<Stack spacing={2}>
<Box sx={{ display: 'flex' }}>
<NoSsr fallback={<Box sx={{ height: `${chartSize}px` }} />}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
position: 'relative',
// hide the empty bar
'& .recharts-layer path[name="Empty"]': { display: 'none' },
'& .recharts-layer .recharts-radial-bar-background-sector': {
fill: 'var(--mui-palette-neutral-100)',
},
}}
>
<RadialBarChart
barSize={20}
data={data}
endAngle={-10}
height={chartSize}
innerRadius={166}
startAngle={190}
width={chartSize}
>
<RadialBar
animationDuration={300}
background
cornerRadius={10}
dataKey="value"
endAngle={-320}
fill="var(--mui-palette-primary-main)"
startAngle={20}
/>
</RadialBarChart>
<Box
sx={{
alignItems: 'center',
bottom: 0,
display: 'flex',
justifyContent: 'center',
left: 0,
position: 'absolute',
right: 0,
top: 0,
}}
>
<Box sx={{ textAlign: 'center', mt: '-50px' }}>
<Typography variant="subtitle1">Time left</Typography>
<Typography variant="body2">{timeLeft} min</Typography>
</Box>
</Box>
</Box>
</NoSsr>
</Box>
<Box sx={{ mt: '-80px' }}>
<Typography variant="h6">Today&apos;s progress of your {timeGoal}-minutes goal</Typography>
<Typography color="text.secondary" variant="body2">
You have used{' '}
{new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(progress / 100)} of
your available spots. Upgrade plan to create more projects.
</Typography>
</Box>
<div>
<Button variant="contained">Continue: React and Redux Tutorial</Button>
</div>
</Stack>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,58 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { File as FileIcon } from '@phosphor-icons/react/dist/ssr/File';
import { UserPlus as UserPlusIcon } from '@phosphor-icons/react/dist/ssr/UserPlus';
export function Help(): React.JSX.Element {
return (
<Card sx={{ height: '100%' }} variant="outlined">
<CardContent>
<Stack divider={<Divider />} spacing={2}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '0 0 auto' }}>
<FileIcon fontSize="var(--icon-fontSize-lg)" />
</Box>
<Stack spacing={2}>
<div>
<Typography variant="subtitle1">Find courses</Typography>
<Typography color="text.secondary" variant="body2">
Browse the latest written articles
</Typography>
</div>
<div>
<Button endIcon={<ArrowRightIcon />} size="small" variant="contained">
Courses
</Button>
</div>
</Stack>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '0 0 auto' }}>
<UserPlusIcon fontSize="var(--icon-fontSize-lg)" />
</Box>
<Stack spacing={2}>
<div>
<Typography variant="subtitle1">Find tutors</Typography>
<Typography color="text.secondary" variant="body2">
Find tutors to help you with your studies
</Typography>
</div>
<div>
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small" variant="contained">
Tutors
</Button>
</div>
</Stack>
</Stack>
</Stack>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Markdown from 'react-markdown';
import type { Components } from 'react-markdown';
import { CodeHighlighter } from '@/components/core/code-highlighter';
const components = { code: CodeHighlighter as Components['code'] } satisfies Components;
export interface LessonProps {
content: string;
}
export function Lesson({ content }: LessonProps): React.JSX.Element {
return (
<Box
sx={{
'& h2': { fontWeight: 500, fontSize: '1.5rem', lineHeight: 1.2, mb: 3 },
'& h3': { fontWeight: 500, fontSize: '1.25rem', lineHeight: 1.2, mb: 3 },
'& p': { fontWeight: 400, fontSize: '1rem', lineHeight: 1.5, mb: 2, mt: 0 },
}}
>
<Markdown components={components}>{content}</Markdown>
</Box>
);
}

View File

@@ -0,0 +1,16 @@
export interface Course {
id: string;
title: string;
description: string;
duration: string;
media?: string;
progress: number;
}
export interface Chapter {
id: string;
number: number;
description: string;
lesson: string;
title: string;
}