diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..137d9f4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "marscode.chatLanguage": "cn", + "marscode.codeCompletionPro": { + "enableCodeCompletionPro": true + }, + "marscode.enableInlineCommand": true +} \ No newline at end of file diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index 91ba312..313c004 100644 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -26,8 +26,7 @@ export class GenDevService { deptStaffRecord: Record = {}; terms: Record = { [TaxonomySlug.CATEGORY]: [], - [TaxonomySlug.UNIT]: [], - [TaxonomySlug.TAG]: [], + [TaxonomySlug.TAG]: [] }; depts: Department[] = []; domains: Department[] = []; @@ -67,7 +66,7 @@ export class GenDevService { const domains = this.depts.filter((item) => item.isDomain); for (const domain of domains) { await this.createTerms(domain, TaxonomySlug.CATEGORY, depth, count); - await this.createTerms(domain, TaxonomySlug.UNIT, depth, count); + // await this.createTerms(domain, TaxonomySlug.UNIT, depth, count); } } const termCount = await db.term.count(); @@ -204,6 +203,12 @@ export class GenDevService { const taxonomy = await db.taxonomy.findFirst({ where: { slug: taxonomySlug }, }); + + if (!taxonomy) { + throw new Error(`Taxonomy with slug ${taxonomySlug} not found`); + } + + this.logger.log(`Creating terms for taxonomy: ${taxonomy.name} (${taxonomy.slug})`); let counter = 1; const createTermTree = async ( parentId: string | null, diff --git a/apps/web/src/app/auth/login.tsx b/apps/web/src/app/auth/login.tsx index 9606d49..aee4144 100644 --- a/apps/web/src/app/auth/login.tsx +++ b/apps/web/src/app/auth/login.tsx @@ -23,6 +23,7 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => {
@@ -60,7 +61,7 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => { loading={isLoading} className="w-full h-10 rounded-lg" > - {isLoading ? "Signing in..." : "Sign In"} + {isLoading ? "登录中..." : "登录"}
diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx deleted file mode 100644 index f5c5b4d..0000000 --- a/apps/web/src/app/main/home/page.tsx +++ /dev/null @@ -1,107 +0,0 @@ - -import { Carousel } from '@web/src/components/common/element/Carousel'; -import LeaderCard from '@web/src/components/common/element/LeaderCard'; -import React, { useState, useCallback } from 'react'; -import * as tus from 'tus-js-client'; -interface TusUploadProps { - onSuccess?: (response: any) => void; - onError?: (error: Error) => void; -} -const carouselItems = [ - { - id: 1, - image: 'https://th.bing.com/th/id/OIP.PEtRTLQwIX54HGFX5xNDYwHaE7?rs=1&pid=ImgDetMain', - title: '自然风光', - description: '壮丽的山川河流' - }, - { - id: 2, - image: 'https://eskipaper.com/images/scenery-pictures-15.jpg', - title: '城市景观', - description: '现代化的都市风貌' - } -]; -const TusUploader: React.FC = ({ - onSuccess, - onError -}) => { - const [progress, setProgress] = useState(0); - const [isUploading, setIsUploading] = useState(false); - const [uploadError, setUploadError] = useState(null); - const handleFileUpload = useCallback((file: File) => { - if (!file) return; - setIsUploading(true); - setProgress(0); - setUploadError(null); - // Extract file extension - const extension = file.name.split('.').pop() || ''; - const upload = new tus.Upload(file, { - endpoint: "http://localhost:3000/upload", - retryDelays: [0, 1000, 3000, 5000], - metadata: { - filename: file.name, - size: file.size.toString(), - mimeType: file.type, - extension: extension, - modifiedAt: new Date(file.lastModified).toISOString(), - }, - onProgress: (bytesUploaded, bytesTotal) => { - const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - setProgress(Number(percentage)); - }, - onSuccess: () => { - setIsUploading(false); - setProgress(100); - onSuccess && onSuccess(upload); - }, - onError: (error) => { - setIsUploading(false); - setUploadError(error.message); - onError && onError(error); - } - }); - - upload.start(); - }, - [onSuccess, onError] - ); - - return ( -
- - - { - const file = e.target.files?.[0]; - if (file) handleFileUpload(file); - }} - /> - {isUploading && ( -
- - {progress}% -
- )} - {uploadError && ( -
- 上传错误: {uploadError} -
- )} -
- ); -}; - -export default TusUploader; diff --git a/apps/web/src/app/main/letter/list/Header.tsx b/apps/web/src/app/main/letter/list/Header.tsx index a6aad8a..a0225ab 100644 --- a/apps/web/src/app/main/letter/list/Header.tsx +++ b/apps/web/src/app/main/letter/list/Header.tsx @@ -8,33 +8,31 @@ export function Header() { }; const handleFilterChange = () => { - + }; return (
-
-

公开信件列表

-
-

- 服务宗旨:畅通诉求渠道 • 促进建设发展 • 提升单位战斗力 -

-
- 实时跟踪反馈进度 - 保障信息传递安全 - 高效解决实际问题 -
+

公开信件列表

+
+

+ 服务宗旨:畅通诉求渠道 • 促进建设发展 • 提升单位战斗力 +

+
+ 实时跟踪反馈进度 + 保障信息传递安全 + 高效解决实际问题
-
+
); } diff --git a/apps/web/src/app/main/letter/list/LetterCard.tsx b/apps/web/src/app/main/letter/list/LetterCard.tsx index b6b78ec..61cbc18 100644 --- a/apps/web/src/app/main/letter/list/LetterCard.tsx +++ b/apps/web/src/app/main/letter/list/LetterCard.tsx @@ -1,60 +1,150 @@ -import { StarIcon } from '@heroicons/react/24/outline'; +import { EyeOutlined, LikeOutlined, LikeFilled, UserOutlined, BankOutlined, CalendarOutlined, FileTextOutlined } from '@ant-design/icons'; +import { Button, Typography, Space, Tooltip } from 'antd'; +import toast from 'react-hot-toast'; import { Letter } from './types'; import { getBadgeStyle } from './utils'; +import { useState } from 'react'; + +const { Title, Paragraph, Text } = Typography; interface LetterCardProps { letter: Letter; } export function LetterCard({ letter }: LetterCardProps) { + const [likes, setLikes] = useState(0); + const [liked, setLiked] = useState(false); + const [views] = useState(Math.floor(Math.random() * 100)); // 模拟浏览量数据 + + const handleLike = () => { + if (!liked) { + setLikes(prev => prev + 1); + setLiked(true); + toast.success('已点赞!', { + icon: , + className: 'custom-message', + + }); + } else { + setLikes(prev => prev - 1); + setLiked(false); + toast('已取消点赞', { + className: 'custom-message', + + }); + } + }; + return ( -
-
- {/* Header Section */} -
-
+
+
+ {/* Title & Priority */} +
+ <a - target="_blank" href={`/letters/${letter.id}`} - className="text-xl font-semibold text-navy-900 group-hover:text-blue-700 transition-colors hover:underline" + target="_blank" + className="text-navy-900 transition-all duration-300 relative + before:absolute before:bottom-0 before:left-0 before:w-0 before:h-[2px] before:bg-blue-600 + group-hover:before:w-full before:transition-all before:duration-300 + group-hover:text-blue-600 group-hover:scale-105 group-hover:drop-shadow-md" > {letter.title} </a> - - </div> + + {letter.priority && ( + + )}
- {/* Meta Information */} -
- {letter.sender} | {letter.unit} - {letter.date} -
- - {/* Badges Section */} -
- - - {letter.priority && } + {/* Meta Info */} +
+ + + + {letter.sender} + + | + + + {letter.unit} + + + + + {letter.date} +
{/* Content Preview */} {letter.content && ( -

- {letter.content} -

+
+ + + {letter.content} + +
)} + + {/* Badges & Interactions */} +
+ + + + + +
+
+ + {views} +
+ + + +
+
); } -function Badge({ type, value }: { type: 'priority' | 'category' | 'status'; value: string }) { +function Badge({ + type, + value, + className = '' +}: { + type: 'priority' | 'category' | 'status'; + value: string; + className?: string; +}) { return ( {value.toUpperCase()} diff --git a/apps/web/src/app/main/letter/list/page.tsx b/apps/web/src/app/main/letter/list/page.tsx index 3a68fa1..4ec963b 100644 --- a/apps/web/src/app/main/letter/list/page.tsx +++ b/apps/web/src/app/main/letter/list/page.tsx @@ -22,7 +22,7 @@ export default function LetterListPage() {
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */} -
+
{filteredLetters.map((letter) => ( ))} diff --git a/apps/web/src/app/main/letter/write/LeaderCard.tsx b/apps/web/src/app/main/letter/write/LeaderCard.tsx new file mode 100644 index 0000000..d3c5b6a --- /dev/null +++ b/apps/web/src/app/main/letter/write/LeaderCard.tsx @@ -0,0 +1,92 @@ +import { motion } from 'framer-motion'; +import { PaperAirplaneIcon } from '@heroicons/react/24/outline'; +import { Leader } from './types'; + +interface LeaderCardProps { + leader: Leader; + isSelected: boolean; + onSelect: () => void; +} + +export default function LeaderCard({ leader, isSelected, onSelect }: LeaderCardProps) { + return ( + +
+ {/* Image Container */} +
+ {leader.name} +
+ + {/* Content Container */} +
+
+
+
+

+ {leader.name} +

+ + {leader.rank} + +
+

{leader.division}

+ + {/* Contact Information */} +
+

+ + + + {leader.email} +

+

+ + + + {leader.phone} +

+

+ + + + {leader.office} +

+
+
+ + +
+
+
+
+ ); +} diff --git a/apps/web/src/app/main/letter/write/filter.tsx b/apps/web/src/app/main/letter/write/filter.tsx index 6012465..02de2c1 100644 --- a/apps/web/src/app/main/letter/write/filter.tsx +++ b/apps/web/src/app/main/letter/write/filter.tsx @@ -2,9 +2,17 @@ import { FunnelIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { leaders } from "./mock"; import { useMemo, useState } from "react"; import { Leader } from "./types"; +import { Input, Select } from "antd"; +import { motion } from "framer-motion"; -export default function Filter() { - const [selectedLeader, setSelectedLeader] = useState(null); +const { Search } = Input; + +interface FilterProps { + onSearch?: (query: string) => void; + onDivisionChange?: (division: string) => void; +} + +export default function Filter({ onSearch, onDivisionChange }: FilterProps) { const [searchQuery, setSearchQuery] = useState(''); const [selectedDivision, setSelectedDivision] = useState('all'); @@ -12,30 +20,51 @@ export default function Filter() { return ['all', ...new Set(leaders.map(leader => leader.division))]; }, []); - return
-
- - setSearchQuery(e.target.value)} - /> -
-
- - setSelectedDivision(e.target.value)} - > - {divisions.map(division => ( - - ))} - -
-
-} \ No newline at end of file + onChange={handleDivisionChange} + suffixIcon={} + className="w-full md:w-64" + options={divisions.map(division => ({ + value: division, + label: division === 'all' ? 'All Divisions' : division, + }))} + /> + + ); +} diff --git a/apps/web/src/app/main/letter/write/header.tsx b/apps/web/src/app/main/letter/write/header.tsx index 0b1b6bd..ed12695 100644 --- a/apps/web/src/app/main/letter/write/header.tsx +++ b/apps/web/src/app/main/letter/write/header.tsx @@ -1,5 +1,5 @@ export default function Header() { - return
+ return
{/* 主标题 */}
@@ -38,7 +38,7 @@ export default function Header() { {/* 隐私承诺 */}
-

我们承诺:您的个人信息将被严格保密,不向任何第三方透露。您可以选择匿名反映问题,平台会自动过滤可能暴露身份的信息。

+

您的个人信息将被严格保密,不向任何第三方透露。您可以选择匿名反映问题,平台会自动过滤可能暴露身份的信息。

diff --git a/apps/web/src/app/main/letter/write/page.tsx b/apps/web/src/app/main/letter/write/page.tsx index ecf8934..3c409a6 100644 --- a/apps/web/src/app/main/letter/write/page.tsx +++ b/apps/web/src/app/main/letter/write/page.tsx @@ -1,10 +1,11 @@ import { useState, useMemo } from 'react'; -import { motion } from 'framer-motion'; -import { FunnelIcon, MagnifyingGlassIcon, PaperAirplaneIcon } from '@heroicons/react/24/outline'; +import { motion, AnimatePresence } from 'framer-motion'; import { Leader } from './types'; import { leaders } from './mock'; import Header from './header'; import Filter from './filter'; +import LeaderCard from './LeaderCard'; + export default function WriteLetterPage() { @@ -26,104 +27,57 @@ export default function WriteLetterPage() { }, [searchQuery, selectedDivision]); return ( -
-
- {/* 搜索和筛选区域 */} -
+
+
- - {/* Modified Leader Cards Grid */} -
- {filteredLeaders.map((leader) => ( +
+ + + + {filteredLeaders.length > 0 ? ( -
- {/* Image Container */} -
- {leader.name} ( + setSelectedLeader(leader)} + /> + ))} + + ) : ( + +
+ + -
- - {/* Content Container */} -
-
-
-
-

- {leader.name} -

- - {leader.rank} - -
-

{leader.division}

- - {/* Contact Information */} -
-

- - - - {leader.email} -

-

- - - - {leader.phone} -

-

- - - - {leader.office} -

-
-
- - -
-
+ +

+ No leaders found matching your search criteria +

- ))} -
- {/* 无结果提示 */} - {filteredLeaders.length === 0 && ( -
-

- No leaders found matching your search criteria -

-
- )} + )} +
); diff --git a/apps/web/src/components/common/element/Button.tsx b/apps/web/src/components/common/element/Button.tsx index da90e8c..3f449c0 100644 --- a/apps/web/src/components/common/element/Button.tsx +++ b/apps/web/src/components/common/element/Button.tsx @@ -1,7 +1,7 @@ import { HTMLMotionProps, motion } from 'framer-motion'; import { forwardRef, ReactNode } from 'react'; -import { cn } from '@web/src/utils/classname'; import { LoadingOutlined } from '@ant-design/icons'; +import { twMerge } from 'tailwind-merge'; export interface ButtonProps extends Omit, keyof React.ButtonHTMLAttributes> { variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'success' | 'warning' | 'info' | 'light' | 'dark' | @@ -162,7 +162,7 @@ export const Button = forwardRef( ref={ref} title={title} disabled={disabled || isLoading} - className={cn( + className={twMerge( // Base styles 'relative inline-flex items-center justify-center font-medium', 'transition-all duration-200 ease-in-out', @@ -187,7 +187,7 @@ export const Button = forwardRef( > {isLoading && ( ( /> )} {!isLoading && leftIcon && ( - ( )} {children} {!isLoading && rightIcon && !isIconOnly && ( - diff --git a/apps/web/src/components/common/element/LeaderCard.tsx b/apps/web/src/components/common/element/LeaderCard.tsx deleted file mode 100644 index 937ad10..0000000 --- a/apps/web/src/components/common/element/LeaderCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { Button } from "./Button"; - -type LeaderCardProps = { - name: string; - title: string; - description: string; - imageUrl: string; - - onClick?: () => void; // 添加整个卡片的点击事件 -}; - -const LeaderCard: React.FC = ({ - name, - title, - description, - imageUrl, - onClick, -}) => { - return ( -
{ - if (e.key === 'Enter' || e.key === ' ') { - onClick?.(); - } - }} - > - {/* Image Section */} -
- {name} -
-
- - {/* Content Section */} -
-
- {/* Name and Title */} -
-

- {name} -

-

- {title} -

-
- - {/* Description */} -

- {description} -

-
- - {/* Decorative Element */} -
-
-
-
-
- ); -}; - - -export default LeaderCard; diff --git a/apps/web/src/components/layout/main/Footer.tsx b/apps/web/src/components/layout/main/Footer.tsx index 04a79a5..a78ede2 100644 --- a/apps/web/src/components/layout/main/Footer.tsx +++ b/apps/web/src/components/layout/main/Footer.tsx @@ -19,10 +19,10 @@ export function Footer() {

- 美国空军官方领导机关信箱 + 官方领导机关信箱

- 为美国空军提供安全可靠的通信服务 + 为提供安全可靠的通信服务

diff --git a/apps/web/src/components/layout/main/navigation.tsx b/apps/web/src/components/layout/main/navigation.tsx index e883965..66ad47f 100644 --- a/apps/web/src/components/layout/main/navigation.tsx +++ b/apps/web/src/components/layout/main/navigation.tsx @@ -1,41 +1,52 @@ import { NavLink } from "react-router-dom"; import { useNavItem } from "./useNavItem"; -export default function Navigation() { - const { navItems } = useNavItem() - return ) +} diff --git a/apps/web/src/components/layout/main/types.ts b/apps/web/src/components/layout/main/types.ts new file mode 100644 index 0000000..03d24cb --- /dev/null +++ b/apps/web/src/components/layout/main/types.ts @@ -0,0 +1,5 @@ +export interface MenuItemType { + icon: JSX.Element; + label: string; + action: () => void; +} diff --git a/apps/web/src/components/layout/main/useNavItem.ts b/apps/web/src/components/layout/main/useNavItem.ts index e14e709..d90d947 100644 --- a/apps/web/src/components/layout/main/useNavItem.ts +++ b/apps/web/src/components/layout/main/useNavItem.ts @@ -1,16 +1,7 @@ import { api } from "@nice/client"; import { TaxonomySlug } from "@nice/common"; import { useMemo } from "react"; -// export const NAV_ITEMS = [ -// { to: "/write-letter", label: "咨询求助" }, -// { to: "/write-letter", label: "解难帮困" }, -// { to: "/write-letter", label: "需求提报" }, -// { to: "/write-letter", label: "意见建议" }, -// { to: "/write-letter", label: "问题反映" }, -// { to: "/write-letter", label: "举报投诉" }, - -// ] as const; export function useNavItem() { const { data } = api.term.findMany.useQuery({ where: { @@ -21,7 +12,7 @@ export function useNavItem() { const navItems = useMemo(() => { // 定义固定的导航项 const staticItems = { - letterList: { to: "/letter-list", label: "公开信件" }, + letterList: { to: "/", label: "公开信件" }, letterProgress: { to: "/letter-progress", label: "进度查询" }, help: { to: "/help", label: "使用帮助" } }; diff --git a/apps/web/src/components/layout/main/usermenu.tsx b/apps/web/src/components/layout/main/usermenu.tsx index 72a26fc..30d9987 100644 --- a/apps/web/src/components/layout/main/usermenu.tsx +++ b/apps/web/src/components/layout/main/usermenu.tsx @@ -1,106 +1,214 @@ -// ... existing imports ... - -import { UserCircleIcon, Cog6ToothIcon, QuestionMarkCircleIcon, ArrowLeftStartOnRectangleIcon } from "@heroicons/react/24/outline"; import { useClickOutside } from "@web/src/hooks/useClickOutside"; import { useAuth } from "@web/src/providers/auth-provider"; import { motion, AnimatePresence } from "framer-motion"; -import { useState, useRef } from "react"; +import { useState, useRef, useCallback, useMemo } from "react"; import { Avatar } from "../../common/element/Avatar"; +import { + UserOutlined, + SettingOutlined, + QuestionCircleOutlined, + LogoutOutlined +} from "@ant-design/icons"; +import type { MenuItemType } from "./types"; +import { Spin } from "antd"; + +// USAF Theme Constants +const USAF_THEME = { + colors: { + primary: '#00538E', // Air Force Blue + secondary: '#003F6A', // Darker Blue + accent: '#B22234', // Air Force Red + background: '#F6F9FC', // Light Blue tint + hover: '#E6EEF5', // Lighter hover state + border: '#E5EDF5', // Light Border + text: { + primary: '#00538E', + secondary: '#4A5568', + light: '#718096', + danger: '#B22234' + } + }, + shadows: { + sm: '0 2px 4px 0 rgba(0, 83, 142, 0.08)', + md: '0 4px 8px -1px rgba(0, 83, 142, 0.15), 0 2px 4px -1px rgba(0, 83, 142, 0.08)', + lg: '0 12px 20px -3px rgba(0, 83, 142, 0.15), 0 4px 8px -2px rgba(0, 83, 142, 0.1)', + hover: '0 6px 12px -2px rgba(0, 83, 142, 0.12), 0 3px 6px -1px rgba(0, 83, 142, 0.07)' + } +} as const; + +const menuVariants = { + hidden: { opacity: 0, scale: 0.95, y: -10 }, + visible: { + opacity: 1, + scale: 1, + y: 0, + transition: { + type: "spring", + stiffness: 300, + damping: 30 + } + }, + exit: { + opacity: 0, + scale: 0.95, + y: -10, + transition: { + duration: 0.2 + } + } +}; export function UserMenu() { const [showMenu, setShowMenu] = useState(false); const menuRef = useRef(null); - const { user, logout } = useAuth(); + const { user, logout, isLoading } = useAuth(); + useClickOutside(menuRef, () => setShowMenu(false)); - const menuItems = [ + const toggleMenu = useCallback(() => { + setShowMenu(prev => !prev); + }, []); + + const menuItems: MenuItemType[] = useMemo(() => [ { - icon: , + icon: , label: '个人信息', action: () => { }, - color: 'text-primary-600' }, { - icon: , + icon: , label: '设置', action: () => { }, - color: 'text-gray-600' }, { - icon: , + icon: , label: '帮助', action: () => { }, - color: 'text-gray-600' }, { - icon: , + icon: , label: '注销', action: () => logout(), - color: 'text-red-600' }, - ]; + ], [logout]); + + const handleMenuItemClick = useCallback((action: () => void) => { + action(); + setShowMenu(false); + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } return (
setShowMenu(!showMenu)} - className="relative rounded-full focus:outline-none - focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 - focus:ring-offset-[#13294B]" + aria-label="用户菜单" + aria-haspopup="true" + aria-expanded={showMenu} + aria-controls="user-menu" + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + onClick={toggleMenu} + className="relative rounded-full focus:outline-none + focus:ring-2 focus:ring-[#00538E]/80 focus:ring-offset-2 + focus:ring-offset-white transition-all duration-200 ease-in-out" > + {showMenu && ( -
-

- {user?.showname} -

-

- {user?.username} -

+ {/* User Profile Section */} +
+
+ +
+ + {user?.showname || user?.username} + + + + 在线 + +
+
+ {/* Menu Items */}
{menuItems.map((item, index) => ( - { + e.stopPropagation(); + handleMenuItemClick(item.action); + }} + className={`flex items-center gap-3 w-full px-4 py-3 + text-sm font-medium rounded-lg transition-all + focus:outline-none + focus:ring-2 focus:ring-[#00538E]/20 + group relative overflow-hidden + active:scale-[0.99] + ${item.label === '注销' + ? 'text-[#B22234] hover:bg-red-50/80 hover:text-red-700' + : 'text-[#00538E] hover:bg-[#E6EEF5] hover:text-[#003F6A]' + }`} > - {item.icon} + + {item.icon} + {item.label} - + ))}
diff --git a/apps/web/src/components/models/course/card/CourseCard.tsx b/apps/web/src/components/models/course/card/CourseCard.tsx deleted file mode 100644 index 60e1f48..0000000 --- a/apps/web/src/components/models/course/card/CourseCard.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { CourseDto } from "@nice/common"; -import { Card } from "@web/src/components/common/container/Card"; -import { CourseHeader } from "./CourseHeader"; -import { CourseStats } from "./CourseStats"; -import { Popover } from "@web/src/components/presentation/popover"; -import { useState } from "react"; - -interface CourseCardProps { - course: CourseDto; - onClick?: () => void; -} - -export const CourseCard = ({ course, onClick }: CourseCardProps) => { - return ( - - - - - ); -}; diff --git a/apps/web/src/components/models/course/card/CourseHeader.tsx b/apps/web/src/components/models/course/card/CourseHeader.tsx deleted file mode 100644 index 5a22fd9..0000000 --- a/apps/web/src/components/models/course/card/CourseHeader.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - CalendarIcon, - UserGroupIcon, - AcademicCapIcon, -} from "@heroicons/react/24/outline"; -import { CourseLevelLabel } from "@nice/common"; - -interface CourseHeaderProps { - title: string; - subTitle?: string; - thumbnail?: string; - level?: string; - numberOfStudents?: number; - publishedAt?: Date; -} - -export const CourseHeader = ({ - title, - subTitle, - thumbnail, - level, - numberOfStudents, - publishedAt, -}: CourseHeaderProps) => { - return ( -
- {thumbnail && ( -
- {title} -
- )} -
-

{title}

- {subTitle &&

{subTitle}

} -
- {level && ( -
- - {CourseLevelLabel[level]} -
- )} - {numberOfStudents !== undefined && ( -
- - {numberOfStudents} 人学习中 -
- )} - {publishedAt && ( -
- - {publishedAt.toLocaleDateString()} -
- )} -
-
-
- ); -}; diff --git a/apps/web/src/components/models/course/card/CourseStats.tsx b/apps/web/src/components/models/course/card/CourseStats.tsx deleted file mode 100644 index fe8df81..0000000 --- a/apps/web/src/components/models/course/card/CourseStats.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { StarIcon, ChartBarIcon, ClockIcon } from '@heroicons/react/24/solid'; - -interface CourseStatsProps { - averageRating?: number; - numberOfReviews?: number; - completionRate?: number; - totalDuration?: number; -} - -export const CourseStats = ({ - averageRating, - numberOfReviews, - completionRate, - totalDuration, -}: CourseStatsProps) => { - return ( -
- {averageRating !== undefined && ( -
- -
-
- {averageRating.toFixed(1)} -
-
- {numberOfReviews} 观看量 -
-
-
- )} - {completionRate !== undefined && ( -
- -
-
- {completionRate}% -
-
- 完成率 -
-
-
- )} - {totalDuration !== undefined && ( -
- -
-
- {Math.floor(totalDuration / 60)}h {totalDuration % 60}m -
-
- 总时长 -
-
-
- )} -
- ); -}; \ No newline at end of file diff --git a/apps/web/src/components/models/course/detail/CourseDetail.tsx b/apps/web/src/components/models/course/detail/CourseDetail.tsx deleted file mode 100644 index 402fbd7..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetail.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CourseDetailProvider } from "./CourseDetailContext"; -import CourseDetailLayout from "./CourseDetailLayout"; - -export default function CourseDetail({ id }: { id?: string }) { - const iframeStyle = { - width: "50%", - height: "100vh", - border: "none", - }; - return ( - <> - - - - - ); -} diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx deleted file mode 100644 index 72507b5..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { api, useCourse } from "@nice/client"; -import { courseDetailSelect, CourseDto } from "@nice/common"; -import React, { createContext, ReactNode, useState } from "react"; -import { string } from "zod"; - -interface CourseDetailContextType { - editId?: string; // 添加 editId - course?: CourseDto; - selectedLectureId?: string | undefined; - setSelectedLectureId?: React.Dispatch>; - isLoading?: boolean; - isHeaderVisible: boolean; // 新增 - setIsHeaderVisible: (visible: boolean) => void; // 新增 -} -interface CourseFormProviderProps { - children: ReactNode; - editId?: string; // 添加 editId 参数 -} -export const CourseDetailContext = - createContext(null); -export function CourseDetailProvider({ - children, - editId, -}: CourseFormProviderProps) { - const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } = - api.course.findFirst.useQuery( - { - where: { id: editId }, - include: { - sections: { include: { lectures: true } }, - enrollments: true, - }, - }, - { enabled: Boolean(editId) } - ); - const [selectedLectureId, setSelectedLectureId] = useState< - string | undefined - >(undefined); - const [isHeaderVisible, setIsHeaderVisible] = useState(true); // 新增 - return ( - - {children} - - ); -} diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailDescription.tsx b/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailDescription.tsx deleted file mode 100644 index a3c8114..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailDescription.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { CheckCircleIcon } from "@heroicons/react/24/outline"; -import { Course } from "@nice/common"; -import { motion } from "framer-motion"; -import React from "react"; -import CourseDetailSkeleton from "../CourseDetailSkeleton"; -import CourseDetailNavBar from "./CourseDetailNavBar"; -interface CourseDetailProps { - course: Course; - isLoading: boolean; -} - -export const CourseDetailDescription: React.FC = ({ - course, - isLoading, -}) => { - return ( - <> - - -
- {isLoading || !course ? ( - - ) : ( - - )} -
- - ); -}; diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailNavBar.tsx b/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailNavBar.tsx deleted file mode 100644 index a5b862d..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailDescription/CourseDetailNavBar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { NavBar } from "@web/src/components/presentation/NavBar"; -import { HomeIcon, BellIcon } from "@heroicons/react/24/outline"; -import { - DocumentTextIcon, - MagnifyingGlassIcon, - StarIcon, -} from "@heroicons/react/24/solid"; - -export default function CourseDetailNavBar() { - const navItems = [ - { - id: "search", - icon: , - label: "搜索", - }, - { - id: "overview", - icon: , - label: "概述", - }, - { - id: "notes", - icon: , - label: "备注", - }, - { - id: "announcements", - icon: , - label: "公告", - }, - { - id: "reviews", - icon: , - label: "评价", - }, - ]; - - return ( -
- console.log("Selected:", id)} - /> -
- ); -} diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription/Description/Overview.tsx b/apps/web/src/components/models/course/detail/CourseDetailDescription/Description/Overview.tsx deleted file mode 100644 index 334c36b..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailDescription/Description/Overview.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useContext } from "react"; -import { CourseDetailContext } from "../../CourseDetailContext"; -import { CheckCircleIcon } from "@heroicons/react/24/solid"; - -export function Overview() { - const { course } = useContext(CourseDetailContext); - return ( - <> -
- {/* 课程描述 */} -
-

{course?.description}

-
- - {/* 学习目标 */} -
-

学习目标

-
- {course?.objectives.map((objective, index) => ( -
- - {objective} -
- ))} -
-
- - {/* 适合人群 */} -
-

适合人群

-
- {course?.audiences.map((audience, index) => ( -
- - {audience} -
- ))} -
-
- - {/* 课程要求 */} -
-

课程要求

-
    - {course?.requirements.map((requirement, index) => ( -
  • {requirement}
  • - ))} -
-
- - {/* 可获得技能 */} -
-

可获得技能

-
- {course?.skills.map((skill, index) => ( - - {skill} - - ))} -
-
-
- - ); -} diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription/Description/index.ts b/apps/web/src/components/models/course/detail/CourseDetailDescription/Description/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx deleted file mode 100644 index 2db3ffa..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// components/CourseDetailDisplayArea.tsx -import { motion, useScroll, useTransform } from "framer-motion"; -import React from "react"; -import { VideoPlayer } from "@web/src/components/presentation/video-player/VideoPlayer"; -import { CourseDetailDescription } from "./CourseDetailDescription/CourseDetailDescription"; -import { Course } from "@nice/common"; - -interface CourseDetailDisplayAreaProps { - course: Course; - videoSrc?: string; - videoPoster?: string; - isLoading?: boolean; -} - -export const CourseDetailDisplayArea: React.FC< - CourseDetailDisplayAreaProps -> = ({ course, videoSrc, videoPoster, isLoading = false }) => { - // 创建滚动动画效果 - const { scrollY } = useScroll(); - const videoScale = useTransform(scrollY, [0, 200], [1, 0.8]); - const videoOpacity = useTransform(scrollY, [0, 200], [1, 0.8]); - - return ( -
- {/* 固定的视频区域 */} - {/* 移除 sticky 定位,让视频区域随页面滚动 */} - -
- -
-
- - {/* 课程内容区域 */} - - - -
- ); -}; -export default CourseDetailDisplayArea; diff --git a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx deleted file mode 100644 index 4ef5e54..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// components/Header.tsx -import { motion, useScroll, useTransform } from "framer-motion"; -import { useContext, useEffect, useState } from "react"; -import { CourseDetailContext } from "../CourseDetailContext"; - -export const CourseDetailHeader = () => { - const { scrollY } = useScroll(); - - const [lastScrollY, setLastScrollY] = useState(0); - const { course, isHeaderVisible, setIsHeaderVisible } = - useContext(CourseDetailContext); - useEffect(() => { - const updateHeader = () => { - const current = scrollY.get(); - const direction = current > lastScrollY ? "down" : "up"; - - if (direction === "down" && current > 100) { - setIsHeaderVisible(false); - } else if (direction === "up") { - setIsHeaderVisible(true); - } - - setLastScrollY(current); - }; - - // 使用 requestAnimationFrame 来优化性能 - const unsubscribe = scrollY.on("change", () => { - requestAnimationFrame(updateHeader); - }); - - return () => { - unsubscribe(); - }; - }, [lastScrollY, scrollY, setIsHeaderVisible]); - - return ( - -
-
-

{course?.title}

-
- -
-
- ); -}; - -export default CourseDetailHeader; diff --git a/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx b/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx deleted file mode 100644 index 4f4ecd5..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { motion } from "framer-motion"; -import { useContext, useState } from "react"; -import { CourseDetailContext } from "./CourseDetailContext"; - -import CourseDetailDisplayArea from "./CourseDetailDisplayArea"; -import { CourseSyllabus } from "./CourseSyllabus/CourseSyllabus"; -import CourseDetailHeader from "./CourseDetailHeader/CourseDetailHeader"; - -export default function CourseDetailLayout() { - const { course, selectedLectureId, isLoading, setSelectedLectureId } = - useContext(CourseDetailContext); - - const handleLectureClick = (lectureId: string) => { - setSelectedLectureId(lectureId); - }; - const [isSyllabusOpen, setIsSyllabusOpen] = useState(false); - return ( -
- {/* 添加 Header 组件 */} - {/* 主内容区域 */} - {/* 为了防止 Header 覆盖内容,添加上边距 */} -
- {" "} - {/* 添加这个包装 div */} - - - - {/* 课程大纲侧边栏 */} - setIsSyllabusOpen(!isSyllabusOpen)} - /> -
-
- ); -} diff --git a/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx b/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx deleted file mode 100644 index 95b1049..0000000 --- a/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - SkeletonItem, - SkeletonSection, -} from "@web/src/components/presentation/Skeleton"; - -export const CourseDetailSkeleton = () => { - return ( -
- {/* 标题骨架屏 */} -
- - -
- - {/* 描述骨架屏 */} - - - {/* 学习目标骨架屏 */} - - - {/* 适合人群骨架屏 */} - - - {/* 技能骨架屏 */} -
- -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
-
-
- ); -}; -export default CourseDetailSkeleton; diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/CollapsedButton.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/CollapsedButton.tsx deleted file mode 100644 index 08f04b3..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/CollapsedButton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { BookOpenIcon } from "@heroicons/react/24/outline"; -import { motion } from "framer-motion"; -import React from "react"; -interface CollapsedButtonProps { - onToggle: () => void; -} - -export const CollapsedButton: React.FC = ({ - onToggle, -}) => ( - - - -); diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx deleted file mode 100644 index 58212a1..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { XMarkIcon, BookOpenIcon } from "@heroicons/react/24/outline"; -import { - ChevronDownIcon, - ClockIcon, - PlayCircleIcon, -} from "@heroicons/react/24/outline"; -import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; -import React, { useState, useRef, useContext } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import { SectionDto } from "@nice/common"; -import { SyllabusHeader } from "./SyllabusHeader"; -import { SectionItem } from "./SectionItem"; -import { CollapsedButton } from "./CollapsedButton"; -import { CourseDetailContext } from "../CourseDetailContext"; - -interface CourseSyllabusProps { - sections: SectionDto[]; - onLectureClick?: (lectureId: string) => void; - isOpen: boolean; - onToggle: () => void; -} - -export const CourseSyllabus: React.FC = ({ - sections, - onLectureClick, - isOpen, - onToggle, -}) => { - const { isHeaderVisible } = useContext(CourseDetailContext); - const [expandedSections, setExpandedSections] = useState([]); - const sectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); - - const toggleSection = (sectionId: string) => { - setExpandedSections((prev) => - prev.includes(sectionId) - ? prev.filter((id) => id !== sectionId) - : [...prev, sectionId] - ); - - setTimeout(() => { - sectionRefs.current[sectionId]?.scrollIntoView({ - behavior: "smooth", - block: "start", - }); - }, 100); - }; - - return ( - <> - - {/* 收起时的悬浮按钮 */} - {!isOpen && ( - - - - )} - - - - {isOpen && ( - - - -
-
- {sections.map((section) => ( - - (sectionRefs.current[ - section.id - ] = el) - } - section={section} - isExpanded={expandedSections.includes( - section.id - )} - onToggle={toggleSection} - onLectureClick={onLectureClick} - /> - ))} -
-
-
- )} -
-
- - ); -}; diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx deleted file mode 100644 index 95f6c74..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// components/CourseSyllabus/LectureItem.tsx - -import { Lecture } from "@nice/common"; -import React from "react"; -import { motion } from "framer-motion"; -import { ClockIcon, PlayCircleIcon } from "@heroicons/react/24/outline"; - -interface LectureItemProps { - lecture: Lecture; - onClick: (lectureId: string) => void; -} - -export const LectureItem: React.FC = ({ - lecture, - onClick, -}) => ( - onClick(lecture.id)}> - -
-

{lecture.title}

- {lecture.description && ( -

- {lecture.description} -

- )} -
-
- - {lecture.duration}分钟 -
-
-); diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx deleted file mode 100644 index 930f9dd..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { SectionDto } from "@nice/common"; -import { AnimatePresence, motion } from "framer-motion"; -import React from "react"; -import { LectureItem } from "./LectureItem"; - -// components/CourseSyllabus/SectionItem.tsx -interface SectionItemProps { - section: SectionDto; - isExpanded: boolean; - onToggle: (sectionId: string) => void; - onLectureClick: (lectureId: string) => void; - ref: React.RefObject; -} - -export const SectionItem = React.forwardRef( - ({ section, isExpanded, onToggle, onLectureClick }, ref) => ( - - - - - {isExpanded && ( - - {section.lectures.map((lecture) => ( - - ))} - - )} - - - ) -); diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/SyllabusHeader.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/SyllabusHeader.tsx deleted file mode 100644 index 1b4d6bf..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/SyllabusHeader.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// components/CourseSyllabus/SyllabusHeader.tsx -import React from "react"; - -import { XMarkIcon } from "@heroicons/react/24/outline"; -interface SyllabusHeaderProps { - onToggle: () => void; -} - -export const SyllabusHeader: React.FC = ({ onToggle }) => ( -
-

课程大纲

- -
-); diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/index.ts b/apps/web/src/components/models/course/detail/CourseSyllabus/index.ts deleted file mode 100644 index a294db3..0000000 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CourseSyllabus"; diff --git a/apps/web/src/components/models/course/detail/course-objectives.tsx b/apps/web/src/components/models/course/detail/course-objectives.tsx deleted file mode 100644 index b849eac..0000000 --- a/apps/web/src/components/models/course/detail/course-objectives.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { CheckOutlined } from '@ant-design/icons'; -import React from 'react'; -interface CourseObjectivesProps { - objectives: string[]; - title?: string; -} -const CourseObjectives: React.FC = ({ - objectives, - title = "您将会学到" -}) => { - return ( -
-

{title}

-
- {objectives.map((objective, index) => ( -
- - {objective} -
- ))} -
-
- ); -}; - -export default CourseObjectives; \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx deleted file mode 100644 index aa3233f..0000000 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { createContext, useContext, ReactNode, useEffect } from "react"; -import { useForm, FormProvider, SubmitHandler } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -// import { CourseDto, CourseLevel, CourseStatus } from "@nice/common"; -// import { api, useCourse } from "@nice/client"; -import toast from "react-hot-toast"; -import { useNavigate } from "react-router-dom"; -// 定义课程表单验证 Schema -const courseSchema = z.object({ - title: z.string().min(1, "课程标题不能为空"), - subTitle: z.string().nullish(), - description: z.string().nullish(), - thumbnail: z.string().url().nullish(), - // level: z.nativeEnum(CourseLevel), - requirements: z.array(z.string()).nullish(), - objectives: z.array(z.string()).nullish(), -}); -export type CourseFormData = z.infer; -interface CourseEditorContextType { - onSubmit: SubmitHandler; - editId?: string; // 添加 editId - part?: string; - // course?: CourseDto; -} -interface CourseFormProviderProps { - children: ReactNode; - editId?: string; // 添加 editId 参数 - part?: string; -} -const CourseEditorContext = createContext(null); -export function CourseFormProvider({ - children, - editId, -}: CourseFormProviderProps) { - // const { create, update } = useCourse() - // const { data: course }: { data: CourseDto } = api.course.findFirst.useQuery({ where: { id: editId } }, { enabled: Boolean(editId) }) - const navigate = useNavigate(); - const methods = useForm({ - resolver: zodResolver(courseSchema), - defaultValues: { - // level: CourseLevel.BEGINNER, - requirements: [], - objectives: [], - }, - }); - // useEffect(() => { - // if (course) { - // const formData = { - // title: course.title, - // subTitle: course.subTitle, - // description: course.description, - // thumbnail: course.thumbnail, - // level: course.level, - // requirements: course.requirements, - // objectives: course.objectives, - // status: course.status, - // }; - // methods.reset(formData as any); - // } - // }, [course, methods]); - const onSubmit: SubmitHandler = async ( - data: CourseFormData - ) => { - try { - if (editId) { - // await update.mutateAsync({ - // where: { id: editId }, - // data: { - // ...data - // } - // }) - toast.success("课程更新成功!"); - } else { - // const result = await create.mutateAsync({ - // data: { - // status: CourseStatus.DRAFT, - // ...data - // } - // }) - // navigate(`/course/${result.id}/editor`, { replace: true }); - toast.success("课程创建成功!"); - } - methods.reset(data); - } catch (error) { - console.error("Error submitting form:", error); - toast.error("操作失败,请重试!"); - } - }; - return ( - - {children} - - ); -} - -export const useCourseEditor = () => { - const context = useContext(CourseEditorContext); - if (!context) { - throw new Error( - "useCourseEditor must be used within CourseFormProvider" - ); - } - return context; -}; diff --git a/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx b/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx deleted file mode 100644 index ab6a430..0000000 --- a/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { SubmitHandler, useFormContext } from 'react-hook-form'; -import { CourseLevel, CourseLevelLabel } from '@nice/common'; - -import { FormSelect } from '@web/src/components/common/form/FormSelect'; -import { FormArrayField } from '@web/src/components/common/form/FormArrayField'; -import { convertToOptions } from '@nice/client'; -import { CourseFormData } from '../context/CourseEditorContext'; -import QuillEditor from '@web/src/components/common/editor/quill/QuillEditor'; -import { FormQuillInput } from '@web/src/components/common/form/FormQuillInput'; - -export function CourseBasicForm() { - const { register, formState: { errors }, watch, handleSubmit } = useFormContext(); - return ( -
- {/* */} - {/* */} - - - - - - ); -} - diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm.tsx deleted file mode 100644 index c2c864e..0000000 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button } from "@web/src/components/common/element/Button"; -import { useState } from "react"; -import { PlusIcon, } from "@heroicons/react/24/outline"; -import { Section, UUIDGenerator } from "@nice/common"; -import SectionFormList from "./SectionFormList"; - -const CourseContentFormHeader = () => -
-

创建您的课程大纲

-

- 通过组织清晰的章节和课时,帮助学员更好地学习。建议: -

-
    -
  • 将相关内容组织到章节中
  • -
  • 每个章节建议包含 3-7 个课时
  • -
  • 课时可以是视频、文章或测验
  • -
-
-const CourseSectionEmpty = () => ( -
-
- -

开始创建您的课程内容

-

点击下方按钮添加第一个章节

-
-
-) - -export default function CourseContentForm() { - const [sections, setSections] = useState([]); - const addSection = () => { - setSections(prev => [...prev, { - id: UUIDGenerator.generate(), - title: '新章节', - lectures: [] - }]); - }; - - return ( -
- -
- {sections.length === 0 ? ( - - ) : ( - - )} - -
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/CourseGoalForm.tsx b/apps/web/src/components/models/course/editor/form/CourseGoalForm.tsx deleted file mode 100644 index da9f813..0000000 --- a/apps/web/src/components/models/course/editor/form/CourseGoalForm.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { FormArrayField } from "@web/src/components/common/form/FormArrayField"; -import { useFormContext } from "react-hook-form"; -import { CourseFormData } from "../context/CourseEditorContext"; - -export function CourseGoalForm() { - const { register, formState: { errors }, watch, handleSubmit } = useFormContext(); - - return ( -
- - -
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/CourseSettingForm.tsx b/apps/web/src/components/models/course/editor/form/CourseSettingForm.tsx deleted file mode 100644 index 11a2864..0000000 --- a/apps/web/src/components/models/course/editor/form/CourseSettingForm.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function CourseSettingForm() { - return <>Setting -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/LectureFormItem.tsx b/apps/web/src/components/models/course/editor/form/LectureFormItem.tsx deleted file mode 100644 index da27041..0000000 --- a/apps/web/src/components/models/course/editor/form/LectureFormItem.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { VideoCameraIcon, DocumentTextIcon, QuestionMarkCircleIcon, Bars3Icon, PencilIcon, TrashIcon, CloudArrowUpIcon, CheckIcon, ChevronUpIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; -import { Lecture } from "@nice/common"; -import { Card } from "@web/src/components/common/container/Card"; -import { Button } from "@web/src/components/common/element/Button"; -// import { FormInput } from "@web/src/components/common/form/FormInput"; -import { FormQuillInput } from "@web/src/components/common/form/FormQuillInput"; -import FileUploader from "@web/src/components/common/uploader/FileUploader"; -import { useState } from "react"; -const LectureTypeIcon = ({ type }: { type: string }) => { - const iconClass = "h-5 w-5"; - switch (type) { - case 'video': - return ; - case 'article': - return ; - case 'quiz': - return ; - default: - return null; - } -}; -interface LectureHeaderProps { - lecture: Lecture; - index: number; - isExpanded: boolean; - onToggle: () => void; - onDelete: (id: string) => void; -} -export function LectureHeader({ - lecture, - index, - isExpanded, - onToggle, - onDelete -}: LectureHeaderProps) { - return ( -
-
- - -
-
- -
- - {index + 1} - - -
-
- -
- -
-
- ); -} -interface LectureEditorProps { - lecture: Lecture; - onUpdate: (lecture: Lecture) => void; - -} -export function LectureEditor({ lecture, onUpdate }: LectureEditorProps) { - const tabs = [ - { key: 'video', icon: VideoCameraIcon, label: '视频' }, - { key: 'article', icon: DocumentTextIcon, label: '文章' } - ]; - return ( -
-
- {tabs.map(({ key, icon: Icon, label }) => ( - - ))} -
- -
- {lecture.type === 'video' && ( -
- -
- )} - {lecture.type === 'article' && ( -
- -
- )} -
- -
-
-
- ); -} - -interface LectureFormItemProps { - lecture: Lecture; - index: number; - onUpdate: (lecture: Lecture) => void; - onDelete: (id: string) => void; -} - -export function LectureFormItem(props: LectureFormItemProps) { - const [isExpanded, setIsExpanded] = useState(false); - const handleToggle = () => { - setIsExpanded(!isExpanded); - }; - return ( - - - {isExpanded && ( - - )} - - ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/LectureFormList.tsx b/apps/web/src/components/models/course/editor/form/LectureFormList.tsx deleted file mode 100644 index bce800c..0000000 --- a/apps/web/src/components/models/course/editor/form/LectureFormList.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { Lecture } from "packages/common/dist"; -import { useCallback } from "react"; -import { LectureFormItem } from "./LectureFormItem"; -import { Bars3Icon } from "@heroicons/react/24/outline"; -import { - DndContext, - closestCenter, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, - DragEndEvent, -} from "@dnd-kit/core"; -import { - SortableContext, - sortableKeyboardCoordinates, - verticalListSortingStrategy, - useSortable, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import { Button } from "@web/src/components/common/element/Button"; - -interface LectureFormListProps { - lectures: Lecture[]; - sectionId: string; - onUpdate: (lectures: Lecture[]) => void; -} - -interface SortableItemProps { - lecture: Lecture; - index: number; - onUpdate: (lecture: Lecture) => void; - onDelete: (lectureId: string) => void; -} - -function SortableItem({ lecture, index, onUpdate, onDelete }: SortableItemProps) { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id: lecture.id }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - zIndex: isDragging ? 1 : 0, - }; - - return ( -
- - -
- ); -} - -export function LectureFormList({ lectures, sectionId, onUpdate }: LectureFormListProps) { - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - distance: 8, - }, - }), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }) - ); - - const handleUpdate = useCallback((updatedLecture: Lecture) => { - onUpdate(lectures.map(l => l.id === updatedLecture.id ? updatedLecture : l)); - }, [lectures, onUpdate]); - - const handleDelete = useCallback((lectureId: string) => { - onUpdate(lectures.filter(l => l.id !== lectureId)); - }, [lectures, onUpdate]); - - const handleDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - if (over && active.id !== over.id) { - const oldIndex = lectures.findIndex((lecture) => lecture.id === active.id); - const newIndex = lectures.findIndex((lecture) => lecture.id === over.id); - const newLectures = [...lectures]; - const [removed] = newLectures.splice(oldIndex, 1); - newLectures.splice(newIndex, 0, removed); - onUpdate(newLectures); - } - }; - - if (lectures.length === 0) { - return ( -
- 暂无课程内容 -
- ); - } - - return ( - - lecture.id)} - strategy={verticalListSortingStrategy} - > -
- {lectures.map((lecture, index) => ( - - ))} -
-
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/SectionFormItem.tsx b/apps/web/src/components/models/course/editor/form/SectionFormItem.tsx deleted file mode 100644 index 4e87400..0000000 --- a/apps/web/src/components/models/course/editor/form/SectionFormItem.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { - TrashIcon, - PlusIcon, - ChevronDownIcon -} from "@heroicons/react/24/outline"; -import { Section, Lecture } from "@nice/common"; -import { useState } from "react"; -import { LectureFormList } from "./LectureFormList"; -import { cn } from "@web/src/utils/classname"; -// import { FormInput } from "@web/src/components/common/form/FormInput"; -import { Button } from "@web/src/components/common/element/Button"; -import { Card } from "@web/src/components/common/container/Card"; -interface SectionProps { - section: Section; - index: number; - onUpdate: (section: Section) => void; - onDelete: (id: string) => void; -} - -export function SectionFormItem({ section, index, onUpdate, onDelete }: SectionProps) { - const [isCollapsed, setIsCollapsed] = useState(false); - const handleAddLecture = () => { - const newLecture: Lecture = { - id: Date.now().toString(), - title: '新课时', - type: 'video' - }; - onUpdate({ - ...section, - lectures: [...section.lectures, newLecture] - }); - }; - - return ( - -
-
- - - {index + 1} - - {/* */} - -
- -
- -
-
- -
-
-
- - onUpdate({ ...section, lectures: updatedLectures })} - /> - - -
-
-
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/form/SectionFormList.tsx b/apps/web/src/components/models/course/editor/form/SectionFormList.tsx deleted file mode 100644 index 14da135..0000000 --- a/apps/web/src/components/models/course/editor/form/SectionFormList.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { Section } from "@nice/common"; -import { SectionFormItem } from "./SectionFormItem"; -import { Bars3Icon } from "@heroicons/react/24/outline"; -import { - DndContext, - closestCenter, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, - DragEndEvent, -} from "@dnd-kit/core"; -import { - SortableContext, - sortableKeyboardCoordinates, - verticalListSortingStrategy, - useSortable, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import { Button } from "@web/src/components/common/element/Button"; - -interface SectionFormListProps { - sections: Section[]; - setSections: React.Dispatch>; -} - -interface SortableItemProps { - section: Section; - index: number; - onUpdate: (section: Section) => void; - onDelete: (sectionId: string) => void; -} - -function SortableItem({ section, index, onUpdate, onDelete }: SortableItemProps) { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id: section.id }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - zIndex: isDragging ? 1 : 0, - }; - - return ( -
- - - -
- ); -} - -export default function SectionFormList({ sections, setSections }: SectionFormListProps) { - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - distance: 8, // 8px的移动距离后才开始拖拽 - }, - }), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }) - ); - - const updateSection = (updatedSection: Section) => { - setSections(prev => prev.map(section => - section.id === updatedSection.id ? updatedSection : section - )); - }; - - const deleteSection = (sectionId: string) => { - setSections(prev => prev.filter(section => section.id !== sectionId)); - }; - - const handleDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - - if (over && active.id !== over.id) { - setSections((prev) => { - const oldIndex = prev.findIndex((section) => section.id === active.id); - const newIndex = prev.findIndex((section) => section.id === over.id); - - const newSections = [...prev]; - const [removed] = newSections.splice(oldIndex, 1); - newSections.splice(newIndex, 0, removed); - - return newSections; - }); - } - }; - - return ( - - section.id)} - strategy={verticalListSortingStrategy} - > -
- {sections.map((section, index) => ( - - ))} -
-
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx b/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx deleted file mode 100644 index 07be5b4..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { ArrowLeftIcon, ClockIcon } from '@heroicons/react/24/outline'; -import { SubmitHandler, useFormContext } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; -import { Button } from '@web/src/components/common/element/Button'; -import { CourseStatus, CourseStatusLabel } from '@nice/common'; -import Tag from '@web/src/components/common/element/Tag'; -import { CourseFormData, useCourseEditor } from '../context/CourseEditorContext'; -const courseStatusVariant: Record = { - [CourseStatus.DRAFT]: 'default', - [CourseStatus.UNDER_REVIEW]: 'warning', - [CourseStatus.PUBLISHED]: 'success', - [CourseStatus.ARCHIVED]: 'danger' -}; -export default function CourseEditorHeader() { - const navigate = useNavigate(); - - const { handleSubmit, formState: { isValid, isDirty, errors } } = useFormContext() - - const { onSubmit, course } = useCourseEditor() - return ( -
-
-
- -
-

{course?.title || '新建课程'}

- - {course?.status ? CourseStatusLabel[course.status] : CourseStatusLabel[CourseStatus.DRAFT]} - - {course?.totalDuration ? ( -
- - 总时长 {course?.totalDuration} -
- ) : null} -
-
- -
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/layout/CourseEditorLayout.tsx b/apps/web/src/components/models/course/editor/layout/CourseEditorLayout.tsx deleted file mode 100644 index caf8953..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseEditorLayout.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { ReactNode, useEffect, useState } from "react"; -import { Outlet, useNavigate, useParams } from "react-router-dom"; -import CourseEditorHeader from "./CourseEditorHeader"; -import { motion } from "framer-motion"; -import { NavItem } from "@nice/client" -import CourseEditorSidebar from "./CourseEditorSidebar"; -import { CourseFormProvider } from "../context/CourseEditorContext"; -import { getNavItems } from "../navItems"; - - -export default function CourseEditorLayout() { - const { id } = useParams(); - const [isHovered, setIsHovered] = useState(false); - const [selectedSection, setSelectedSection] = useState(0); - const [navItems, setNavItems] = useState(getNavItems(id)); - useEffect(() => { - setNavItems(getNavItems(id)) - }, [id]) - useEffect(() => { - const currentPath = location.pathname; - const index = navItems.findIndex(item => item.path === currentPath); - if (index !== -1) { - setSelectedSection(index); - } - }, [location.pathname, navItems]); - const navigate = useNavigate(); - const handleNavigation = (item: NavItem, index: number) => { - setSelectedSection(index); - navigate(item.path, { replace: true }); - }; - return ( - -
- -
- - -
-
-

- {navItems[selectedSection]?.label} -

-
-
- -
-
-
-
-
-
- - ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/layout/CourseEditorSidebar.tsx b/apps/web/src/components/models/course/editor/layout/CourseEditorSidebar.tsx deleted file mode 100644 index 0aeb9fa..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseEditorSidebar.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { motion } from 'framer-motion'; -import { NavItem } from "@nice/client" -interface CourseSidebarProps { - isHovered: boolean; - setIsHovered: (value: boolean) => void; - navItems: NavItem[]; - selectedSection: number; - onNavigate: (item: NavItem, index: number) => void; -} - -export default function CourseEditorSidebar({ - isHovered, - setIsHovered, - navItems, - selectedSection, - onNavigate -}: CourseSidebarProps) { - return ( - setIsHovered(true)} - onHoverEnd={() => setIsHovered(false)} - className="fixed left-0 top-16 h-[calc(100vh-4rem)] bg-white border-r border-gray-200 shadow-lg overflow-x-hidden" - > -
- {navItems.map((item, index) => ( - - ))} -
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseBasicForm.tsx b/apps/web/src/components/models/course/editor/layout/CourseForms/CourseBasicForm.tsx deleted file mode 100644 index b908e2b..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseBasicForm.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { SubmitHandler, useFormContext } from "react-hook-form"; - -import { CourseFormData } from "../../context/CourseEditorContext"; -import { CourseLevel, CourseLevelLabel } from "@nice/common"; -import { FormInput } from "@web/src/components/common/form/FormInput"; -import { FormSelect } from "@web/src/components/common/form/FormSelect"; -import { convertToOptions } from "@nice/client"; -import { useEffect } from "react"; - -export function CourseBasicForm() { - const { - register, - formState: { errors }, - watch, - handleSubmit, - } = useFormContext(); - // useEffect(() => { - // console.log(watch("audiences")); - // }, [watch("audiences")]); - return ( -
- - - - - {/* */} - - ); -} diff --git a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseContentForm.tsx b/apps/web/src/components/models/course/editor/layout/CourseForms/CourseContentForm.tsx deleted file mode 100644 index 9875740..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseContentForm.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { SubmitHandler, useFormContext } from "react-hook-form"; - -import { CourseFormData } from "../../context/CourseEditorContext"; -import { CourseLevel, CourseLevelLabel } from "@nice/common"; -import { FormInput } from "@web/src/components/common/form/FormInput"; -import { FormSelect } from "@web/src/components/common/form/FormSelect"; -import { convertToOptions } from "@nice/client"; -import { useEffect } from "react"; - -export function CourseContentForm() { - const { - register, - formState: { errors }, - watch, - handleSubmit, - } = useFormContext(); - // useEffect(() => { - // console.log(watch("audiences")); - // }, [watch("audiences")]); - return ( -
- - - - - {/* */} - - ); -} diff --git a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseForm.tsx b/apps/web/src/components/models/course/editor/layout/CourseForms/CourseForm.tsx deleted file mode 100644 index 3743403..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseForm.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useContext } from "react"; -import { useCourseEditor } from "../../context/CourseEditorContext"; -import { CoursePart } from "../enum"; -import { CourseBasicForm } from "./CourseBasicForm"; -import { CourseTargetForm } from "./CourseTargetForm"; -import { CourseContentForm } from "./CourseContentForm"; - -export default function CourseForm() { - const { part } = useCourseEditor(); - if (part === CoursePart.OVERVIEW) { - return ; - } - if (part === CoursePart.TARGET) { - return ; - } - if (part === CoursePart.CONTENT) { - return ; - } - if (part === CoursePart.SETTING) { - return <>; - } - return ; -} diff --git a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseSettingForm.tsx b/apps/web/src/components/models/course/editor/layout/CourseForms/CourseSettingForm.tsx deleted file mode 100644 index 8bab274..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseSettingForm.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { SubmitHandler, useFormContext } from "react-hook-form"; - -import { CourseFormData } from "../../context/CourseEditorContext"; -import { CourseLevel, CourseLevelLabel } from "@nice/common"; -import { FormInput } from "@web/src/components/common/form/FormInput"; -import { FormSelect } from "@web/src/components/common/form/FormSelect"; -import { convertToOptions } from "@nice/client"; - -export function CourseContentForm() { - const { - register, - formState: { errors }, - watch, - handleSubmit, - } = useFormContext(); - - return ( -
- - - - - {/* */} - - ); -} diff --git a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseTargetForm.tsx b/apps/web/src/components/models/course/editor/layout/CourseForms/CourseTargetForm.tsx deleted file mode 100644 index 2b9bf07..0000000 --- a/apps/web/src/components/models/course/editor/layout/CourseForms/CourseTargetForm.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { SubmitHandler, useFormContext } from "react-hook-form"; - -import { CourseFormData } from "../../context/CourseEditorContext"; -import { CourseLevel, CourseLevelLabel } from "@nice/common"; -import { convertToOptions } from "@nice/client"; -import { FormDynamicInputs } from "@web/src/components/common/form/FormDynamicInputs"; - -export function CourseTargetForm() { - const { - register, - formState: { errors }, - watch, - handleSubmit, - } = useFormContext(); - - return ( -
- - - - - - {/* */} -
- ); -} diff --git a/apps/web/src/components/models/course/editor/layout/enum.ts b/apps/web/src/components/models/course/editor/layout/enum.ts deleted file mode 100644 index 81a97a6..0000000 --- a/apps/web/src/components/models/course/editor/layout/enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum CoursePart { - OVERVIEW = "overview", - TARGET = "target", - CONTENT = "content", - SETTING = "settings", -} diff --git a/apps/web/src/components/models/course/editor/navItems.tsx b/apps/web/src/components/models/course/editor/navItems.tsx deleted file mode 100644 index af0d715..0000000 --- a/apps/web/src/components/models/course/editor/navItems.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { AcademicCapIcon, BookOpenIcon, Cog6ToothIcon, VideoCameraIcon } from "@heroicons/react/24/outline"; -import { NavItem } from "@nice/client"; - -export const getNavItems = (courseId?: string): (NavItem & { isCompleted?: boolean })[] => [ - { - label: "课程概述", - icon: , - path: `/course/${courseId ? `${courseId}/` : ''}editor` - }, - { - label: "目标学员", - icon: , - path: `/course/${courseId ? `${courseId}/` : ''}editor/goal` - }, - { - label: "课程内容", - icon: , - path: `/course/${courseId ? `${courseId}/` : ''}editor/content` - }, - { - label: "课程设置", - icon: , - path: `/course/${courseId ? `${courseId}/` : ''}editor/setting` - }, -]; \ No newline at end of file diff --git a/apps/web/src/components/models/course/list/course-list.tsx b/apps/web/src/components/models/course/list/course-list.tsx deleted file mode 100644 index 64c8e83..0000000 --- a/apps/web/src/components/models/course/list/course-list.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// CourseList.tsx -import { motion } from "framer-motion"; -import { Course, CourseDto } from "@nice/common"; -import { EmptyState } from "@web/src/components/common/space/Empty"; -import { Pagination } from "@web/src/components/common/element/Pagination"; -import React from "react"; - -interface CourseListProps { - courses?: CourseDto[]; - renderItem: (course: CourseDto) => React.ReactNode; - emptyComponent?: React.ReactNode; - // 新增分页相关属性 - currentPage: number; - totalPages: number; - onPageChange: (page: number) => void; -} - -const container = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.05, - duration: 0.3, - }, - }, -}; -export const CourseList = ({ - courses, - renderItem, - emptyComponent: EmptyComponent, - currentPage, - totalPages, - onPageChange, -}: CourseListProps) => { - if (!courses || courses.length === 0) { - return EmptyComponent || ; - } - - return ( -
- - {courses.map((course) => ( - - {renderItem(course)} - - ))} - - - -
- ); -}; diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 4956304..83f3579 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -13,20 +13,12 @@ import RoleAdminPage from "../app/admin/role/page"; import WithAuth from "../components/utils/with-auth"; import BaseSettingPage from "../app/admin/base-setting/page"; import { MainLayout } from "../components/layout/main/MainLayout"; -import HomePage from "../app/main/home/page"; -import { CourseBasicForm } from "../components/models/course/editor/form/CourseBasicForm"; -import CourseContentForm from "../components/models/course/editor/form/CourseContentForm"; -import { CourseGoalForm } from "../components/models/course/editor/form/CourseGoalForm"; -import CourseSettingForm from "../components/models/course/editor/form/CourseSettingForm"; -import CourseEditorLayout from "../components/models/course/editor/layout/CourseEditorLayout"; import WriteLetterPage from "../app/main/letter/write/page"; import LetterListPage from "../app/main/letter/list/page"; import LetterProgressPage from "../app/main/letter/progress/page"; import HelpPage from "../app/main/help/page"; import AuthPage from "../app/auth/page"; import React from "react"; -import LetterEditorLayout from "../components/models/post/LetterEditor/layout/LetterEditorLayout"; -import { LetterBasicForm } from "../components/models/post/LetterEditor/form/LetterBasicForm"; import EditorLetterPage from "../app/main/letter/editor/page"; import LetterDetailPage from "../app/main/letter/detail/page"; @@ -69,7 +61,7 @@ export const routes: CustomRouteObject[] = [ children: [ { index: true, - element: , + element: }, { path: ":id?/detail", @@ -83,10 +75,7 @@ export const routes: CustomRouteObject[] = [ path: "write-letter", element: , }, - { - path: "letter-list", - element: - } + , { path: 'letter-progress', element: @@ -97,38 +86,6 @@ export const routes: CustomRouteObject[] = [ } ], }, - - { - path: "course", - children: [ - { - path: ":id?/editor", - element: , - children: [ - { - index: true, - element: , - }, - { - path: "goal", - element: , - }, - { - path: "content", - element: ( - - ), - }, - { - path: "setting", - element: ( - - ), - }, - ], - }, - ], - }, { path: "admin", children: [ diff --git a/apps/web/src/utils/classname.ts b/apps/web/src/utils/classname.ts deleted file mode 100644 index 4834418..0000000 --- a/apps/web/src/utils/classname.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} \ No newline at end of file diff --git a/packages/common/src/enum.ts b/packages/common/src/enum.ts index 92084ab..46b6689 100755 --- a/packages/common/src/enum.ts +++ b/packages/common/src/enum.ts @@ -8,8 +8,7 @@ export enum PostType { } export enum TaxonomySlug { CATEGORY = "category", - UNIT = "unit", - TAG = "tag", + TAG = "tag" } export enum VisitType { STAR = "star", diff --git a/packages/theme/src/colors.ts b/packages/theme/src/colors.ts index 2aaf574..50d976a 100644 --- a/packages/theme/src/colors.ts +++ b/packages/theme/src/colors.ts @@ -9,7 +9,7 @@ export function generateColorScale(baseColor: string): ColorScale { const scale = Object.fromEntries( keys.map((key, index) => [ key, - color.lighten(-steps[index]).hex() + color.lighten(-steps[index]).rgb().string() ]) ) as ColorScale; diff --git a/packages/theme/src/styles.ts b/packages/theme/src/styles.ts index 70d2bb0..c75f67f 100644 --- a/packages/theme/src/styles.ts +++ b/packages/theme/src/styles.ts @@ -18,12 +18,13 @@ export function themeToCssVariables(theme: Theme): Record { const flattenedToken = flattenObject(theme.token) console.log(flattenedToken) const cssVars: Record = {} - for (const [path, value] of Object.entries(flattenedToken)) { + for (const [path, value] of Object.entries(flattenedToken)) { const cssVarName = createCssVariableName(path.split('.')) cssVars[cssVarName] = value } + return cssVars } @@ -97,4 +98,4 @@ export function createTailwindTheme(theme: Theme): Partial { } console.log(result) return result -} \ No newline at end of file +}