Browse Source

✨ feat(ui): 集成sonner通知系统替代alert和react-toastify

- 添加Toaster组件到web端和移动端主应用
- 登录页面:使用toast.error替代alert显示登录错误信息
- 注册页面:使用toast.error替代alert显示注册错误信息
- 移动端认证页面:使用toast.error替代alert显示认证错误信息
- 教室组件:使用sonner的toast替代react-toastify实现消息提示
- 考试相关组件:统一替换为sonner的toast组件
- 股票组件:更新为使用sonner的toast组件
- 移除react-toastify依赖及其相关配置

♻️ refactor(classroom): 优化教室页面组件结构

- 移除ClassroomPage中的冗余ToastContainer组件
- 简化ClassroomPage的JSX结构,提高可读性
yourname 6 months ago
parent
commit
54c57a181a

+ 2 - 0
src/client/home/index.tsx

@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
 import { AuthProvider } from './hooks/AuthProvider'
 import { RouterProvider } from 'react-router-dom'
 import { router } from './routes'
+import { Toaster } from '../components/ui/sonner'
 
 // 创建QueryClient实例
 const queryClient = new QueryClient();
@@ -15,6 +16,7 @@ const App = () => {
       <AuthProvider>
         <RouterProvider router={router} />
       </AuthProvider>
+      <Toaster />
     </QueryClientProvider>
   )
 };

+ 2 - 1
src/client/home/pages/LoginPage.tsx

@@ -3,6 +3,7 @@ import { useForm } from 'react-hook-form';
 import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
 import { useNavigate } from 'react-router-dom';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
+import { toast } from 'sonner';
 
 const LoginPage: React.FC = () => {
   const { register, handleSubmit, formState: { errors } } = useForm();
@@ -18,7 +19,7 @@ const LoginPage: React.FC = () => {
       navigate('/');
     } catch (error) {
       console.error('Login error:', error);
-      alert((error as Error).message || '登录失败,请检查用户名和密码');
+      toast.error((error as Error).message || '登录失败,请检查用户名和密码');
     } finally {
       setLoading(false);
     }

+ 2 - 1
src/client/home/pages/RegisterPage.tsx

@@ -4,6 +4,7 @@ import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/reac
 import { useNavigate } from 'react-router-dom';
 import { useAuth } from '@/client/home/hooks/AuthProvider';
 import { authClient } from '@/client/api';
+import { toast } from 'sonner';
 
 const RegisterPage: React.FC = () => {
   const { register, handleSubmit, watch, formState: { errors } } = useForm();
@@ -38,7 +39,7 @@ const RegisterPage: React.FC = () => {
       navigate('/');
     } catch (error) {
       console.error('Registration error:', error);
-      alert((error as Error).message || '注册失败,请稍后重试');
+      toast.error((error as Error).message || '注册失败,请稍后重试');
     } finally {
       setLoading(false);
     }

+ 8 - 2
src/client/mobile/components/Classroom/useClassroom.ts

@@ -4,7 +4,7 @@ import { useParams } from 'react-router';
 // @ts-types="../../../share/aliyun-rtc-sdk.d.ts"
 // @ts-types="./alivc-im.iife.d.ts"
 import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
-import { toast } from 'react-toastify';
+import { toast } from 'sonner';
 import { User } from '../../hooks/AuthProvider';
 import { aliyunClient } from '@/client/api';
 import { UserType } from '@/server/modules/users/user.enum';
@@ -109,7 +109,13 @@ export const useClassroom = ({ user }:{ user : User }) => {
   };
 
   const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
-    toast[type](message);
+    if (type === 'info') {
+      toast.info(message);
+    } else if (type === 'success') {
+      toast.success(message);
+    } else if (type === 'error') {
+      toast.error(message);
+    }
   };
 
 

+ 1 - 1
src/client/mobile/components/Exam/ExamCard.tsx

@@ -8,7 +8,7 @@ import type { QuizState } from './types';
 import type { AnswerRecord, Answer } from './types';
 import { useAuth } from '@/client/mobile/hooks/AuthProvider';
 import { ClassroomStatus } from '@/server/modules/classroom/classroom-data.schema';
-import { toast } from 'react-toastify';
+import { toast } from 'sonner';
 
 // 答题卡页面
 export default function ExamCard() {

+ 1 - 1
src/client/mobile/components/Exam/ExamIndex.tsx

@@ -4,7 +4,7 @@ import dayjs from 'dayjs';
 import { classroomDataClient } from '@/client/api';
 import { ClassroomStatus } from '@/server/modules/classroom/classroom-data.schema';
 import type { InferResponseType } from 'hono/client';
-import { toast } from 'react-toastify';
+import { toast } from 'sonner';
 
 type ClassroomDataResponse = InferResponseType<typeof classroomDataClient.$get, 200>;
 type ClassroomData = ClassroomDataResponse['data'][0];

+ 1 - 1
src/client/mobile/components/stock/stock_main.tsx

@@ -1,7 +1,7 @@
 import React, { useRef, useState, useCallback, useEffect } from 'react';
 import { useStockSocket } from './hooks/useStockSocketClient';
 import { useSearchParams } from 'react-router';
-import { toast} from 'react-toastify';
+import { toast } from 'sonner';
 import { StockChart, MemoToggle, TradePanel, useTradeRecords, useStockQueries, useProfitCalculator, ProfitDisplay, useStockDataFilter, DrawingToolbar } from './components/stock-chart/mod';
 import type { StockChartRef } from './components/stock-chart/mod.ts';
 import { ActiveType } from "./components/stock-chart/src/types/index";

+ 2 - 2
src/client/mobile/index.tsx

@@ -8,7 +8,7 @@ import 'dayjs/locale/zh-cn';
 
 import { AuthProvider } from './hooks/AuthProvider';
 import { router } from './routes';
-import { ToastContainer } from 'react-toastify';
+import { Toaster } from '../components/ui/sonner';
 
 // 配置 dayjs 插件
 dayjs.extend(weekday);
@@ -27,7 +27,7 @@ const App = () => {
       <AuthProvider>
         <RouterProvider router={router} />
       </AuthProvider>
-      <ToastContainer />
+      <Toaster /> 
     </QueryClientProvider>
   )
 };

+ 1 - 1
src/client/mobile/pages/AuthPage.tsx

@@ -63,7 +63,7 @@ const AuthPage: React.FC = () => {
       }
     } catch (error) {
       console.error('Authentication error:', error);
-      alert((error as Error).message || '认证失败,请稍后重试');
+      toast.error((error as Error).message || '认证失败,请稍后重试');
     } finally {
       setLoading(false);
     }

+ 3 - 17
src/client/mobile/pages/ClassroomPage.tsx

@@ -7,7 +7,6 @@ import { AuthLayout } from '@/client/mobile/components/Classroom/AuthLayout';
 import { ClassroomProvider, useClassroomContext } from "@/client/mobile/components/Classroom/ClassroomProvider";
 import { TeacherView } from '@/client/mobile/components/Classroom/TeacherView';
 import { StudentView } from '@/client/mobile/components/Classroom/StudentView';
-import { ToastContainer } from 'react-toastify';
 import { Button } from '@/client/components/ui/button';
 import { Input } from '@/client/components/ui/input';
 import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card';
@@ -204,21 +203,8 @@ const Classroom = () => {
 export const ClassroomPage = () => {
   const { user } = useAuth();
   return (
-    <>
-      <ClassroomProvider user={user!}>
-        <Classroom />
-      </ClassroomProvider>
-      <ToastContainer
-        position="top-right"
-        autoClose={500}
-        hideProgressBar={false}
-        newestOnTop={false}
-        closeOnClick
-        rtl={false}
-        pauseOnFocusLoss
-        draggable
-        pauseOnHover
-      />
-    </>
+    <ClassroomProvider user={user!}>
+      <Classroom />
+    </ClassroomProvider>
   )
 }