mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Compare commits
17 commits
51cebdaba1
...
bc1504717c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc1504717c | ||
|
|
62df610d62 | ||
|
|
f13b58d89b | ||
|
|
a66631c60b | ||
|
|
5a5897d25c | ||
|
|
20bfb8774f | ||
|
|
1de68418e0 | ||
|
|
7c7fe031f8 | ||
|
|
a3e0ba70f9 | ||
|
|
a4d36389c7 | ||
|
|
dc7fb82d97 | ||
|
|
fb41980f29 | ||
|
|
0243270fdb | ||
|
|
241854159b | ||
|
|
312ccd0b52 | ||
|
|
ef192eb784 | ||
|
|
01deaee487 |
14 changed files with 634 additions and 535 deletions
|
|
@ -1,3 +1,2 @@
|
||||||
VITE_BACKEND_URL=http://localhost:4400
|
VITE_BACKEND_URL=http://localhost:4400
|
||||||
VITE_BACKEND_SOCKET_URL=http://localhost:4400
|
VITE_BACKEND_SOCKET_URL=http://localhost:4400
|
||||||
VITE_IMG_URL=http://localhost:4400
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
VITE_BACKEND_URL=http://localhost:4400
|
VITE_BACKEND_URL=http://localhost:4400
|
||||||
VITE_AZURE_BACKEND_URL=http://localhost:4400
|
VITE_AZURE_BACKEND_URL=http://localhost:4400
|
||||||
VITE_IMG_URL=http://localhost:4400
|
|
||||||
880
client/package-lock.json
generated
880
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,7 @@
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "jest --colors",
|
"test": "jest --colors --silent",
|
||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -18,19 +18,20 @@
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@mui/icons-material": "^6.4.6",
|
"@mui/icons-material": "^7.0.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.153",
|
"@mui/lab": "^5.0.0-alpha.153",
|
||||||
"@mui/material": "^6.4.6",
|
"@mui/material": "^7.0.1",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
"dompurify": "^3.2.3",
|
"dompurify": "^3.2.5",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.2",
|
||||||
"gift-pegjs": "^2.0.0-beta.1",
|
"gift-pegjs": "^2.0.0-beta.1",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
"marked": "^14.1.2",
|
"marked": "^15.0.8",
|
||||||
"nanoid": "^5.1.2",
|
"nanoid": "^5.1.5",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-modal": "^3.16.3",
|
"react-modal": "^3.16.3",
|
||||||
|
|
@ -38,40 +39,40 @@
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^11.1.0",
|
||||||
"vite-plugin-checker": "^0.9.0"
|
"vite-plugin-checker": "^0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-react": "^7.26.3",
|
"@babel/preset-react": "^7.26.3",
|
||||||
"@babel/preset-typescript": "^7.23.3",
|
"@babel/preset-typescript": "^7.27.0",
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.24.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/jest": "^29.5.13",
|
"@types/jest": "^29.5.13",
|
||||||
"@types/node": "^22.13.5",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-latex": "^2.0.3",
|
"@types/react-latex": "^2.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||||
"@typescript-eslint/parser": "^8.25.0",
|
"@typescript-eslint/parser": "^8.29.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.8.0",
|
"@vitejs/plugin-react-swc": "^3.8.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||||
"eslint-plugin-jest": "^28.11.0",
|
"eslint-plugin-jest": "^28.11.0",
|
||||||
"eslint-plugin-react": "^7.37.3",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912",
|
"eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^29.2.6",
|
"ts-jest": "^29.3.1",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.25.0",
|
"typescript-eslint": "^8.29.1",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.0",
|
||||||
"vite-plugin-environment": "^1.1.3"
|
"vite-plugin-environment": "^1.1.3"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,11 @@ jest.mock('react-router-dom', () => ({
|
||||||
}));
|
}));
|
||||||
jest.mock('src/pages/Teacher/ManageRoom/RoomContext');
|
jest.mock('src/pages/Teacher/ManageRoom/RoomContext');
|
||||||
|
|
||||||
|
jest.mock('qrcode.react', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
QRCodeCanvas: ({ value }: { value: string }) => <div data-testid="qr-code">{value}</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
const mockSocket = {
|
const mockSocket = {
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
off: jest.fn(),
|
off: jest.fn(),
|
||||||
|
|
@ -325,5 +330,53 @@ describe('ManageRoom', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Affiche la modale QR Code lorsqu’on clique sur le bouton", async () => {
|
||||||
|
render(<MemoryRouter><ManageRoom /></MemoryRouter>);
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: /lien de participation/i });
|
||||||
|
fireEvent.click(button);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByRole('heading', { name: /Rejoindre la salle/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Scannez ce QR code ou partagez le lien ci-dessous/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('qr-code')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Ferme la modale QR Code lorsqu’on clique sur le bouton Fermer", async () => {
|
||||||
|
render(<MemoryRouter><ManageRoom /></MemoryRouter>);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /lien de participation/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /fermer/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Affiche le bon lien de participation', () => {
|
||||||
|
render(<MemoryRouter><ManageRoom /></MemoryRouter>);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /lien de participation/i }));
|
||||||
|
|
||||||
|
const roomUrl = `${window.location.origin}/student/join-room?roomName=Test Room`;
|
||||||
|
expect(screen.getByTestId('qr-code')).toHaveTextContent(roomUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Vérifie que le QR code contient la bonne URL', () => {
|
||||||
|
render(<MemoryRouter><ManageRoom /></MemoryRouter>);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /lien de participation/i }));
|
||||||
|
|
||||||
|
const roomUrl = `${window.location.origin}/student/join-room?roomName=Test Room`;
|
||||||
|
expect(screen.getByTestId('qr-code')).toHaveTextContent(roomUrl);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { ImageType } from "../../Types/ImageType";
|
import { ImageType } from "../../Types/ImageType";
|
||||||
import ApiService from "../../services/ApiService";
|
import ApiService from "../../services/ApiService";
|
||||||
import { Upload } from "@mui/icons-material";
|
import { Upload } from "@mui/icons-material";
|
||||||
import { ENV_VARIABLES } from '../../constants';
|
|
||||||
import { escapeForGIFT } from "src/utils/giftUtils";
|
import { escapeForGIFT } from "src/utils/giftUtils";
|
||||||
|
import { ENV_VARIABLES } from "src/constants";
|
||||||
|
|
||||||
interface ImagesProps {
|
interface ImagesProps {
|
||||||
handleCopy?: (id: string) => void;
|
handleCopy?: (id: string) => void;
|
||||||
|
|
@ -83,9 +83,9 @@ const ImageGallery: React.FC<ImagesProps> = ({ handleCopy, handleDelete }) => {
|
||||||
|
|
||||||
const defaultHandleCopy = (id: string) => {
|
const defaultHandleCopy = (id: string) => {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
const link = `${ENV_VARIABLES.IMG_URL}/api/image/get/${id}`;
|
const link = `${ENV_VARIABLES.BACKEND_URL}/api/image/get/${id}`;
|
||||||
const imgTag = `[markdown]} "texte de l'infobulle") {T}`;
|
const imgTag = `[markdown] } "texte de l'infobulle (ne fonctionne pas sur écran tactile généralement)") `;
|
||||||
setSnackbarMessage("Le lien Markdown de l’image a été copié dans le presse-papiers");
|
setSnackbarMessage("Le lien Markdown de l'image a été copié dans le presse-papiers");
|
||||||
setSnackbarSeverity("success");
|
setSnackbarSeverity("success");
|
||||||
setSnackbarOpen(true);
|
setSnackbarOpen(true);
|
||||||
navigator.clipboard.writeText(imgTag);
|
navigator.clipboard.writeText(imgTag);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
const ENV_VARIABLES = {
|
const ENV_VARIABLES = {
|
||||||
MODE: process.env.MODE || "production",
|
MODE: process.env.MODE || "production",
|
||||||
VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "",
|
VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "",
|
||||||
IMG_URL: process.env.MODE == "development" ? process.env.VITE_BACKEND_URL : process.env.VITE_IMG_URL,
|
|
||||||
BACKEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.BACKEND_PORT}` : ''}` : process.env.VITE_BACKEND_URL || '',
|
BACKEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.BACKEND_PORT}` : ''}` : process.env.VITE_BACKEND_URL || '',
|
||||||
FRONTEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.PORT}` : ''}` : ''
|
FRONTEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.PORT}` : ''}` : ''
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ import { QuestionType } from '../../../Types/QuestionType';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
import LoginContainer from 'src/components/LoginContainer/LoginContainer'
|
import LoginContainer from 'src/components/LoginContainer/LoginContainer';
|
||||||
|
|
||||||
import ApiService from '../../../services/ApiService'
|
import ApiService from '../../../services/ApiService';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
export type AnswerType = Array<string | number | boolean>;
|
export type AnswerType = Array<string | number | boolean>;
|
||||||
|
|
||||||
|
|
@ -30,6 +31,17 @@ const JoinRoom: React.FC = () => {
|
||||||
const [answers, setAnswers] = useState<AnswerSubmissionToBackendType[]>([]);
|
const [answers, setAnswers] = useState<AnswerSubmissionToBackendType[]>([]);
|
||||||
const [connectionError, setConnectionError] = useState<string>('');
|
const [connectionError, setConnectionError] = useState<string>('');
|
||||||
const [isConnecting, setIsConnecting] = useState<boolean>(false);
|
const [isConnecting, setIsConnecting] = useState<boolean>(false);
|
||||||
|
const [isQRCodeJoin, setIsQRCodeJoin] = useState(false);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const roomFromUrl = searchParams.get('roomName');
|
||||||
|
if (roomFromUrl) {
|
||||||
|
setRoomName(roomFromUrl);
|
||||||
|
setIsQRCodeJoin(true);
|
||||||
|
console.log('Mode QR Code détecté, salle:', roomFromUrl);
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleCreateSocket();
|
handleCreateSocket();
|
||||||
|
|
@ -198,9 +210,11 @@ const JoinRoom: React.FC = () => {
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<LoginContainer
|
<LoginContainer
|
||||||
title='Rejoindre une salle'
|
title={isQRCodeJoin ? `Rejoindre la salle ${roomName}` : 'Rejoindre une salle'}
|
||||||
error={connectionError}>
|
error={connectionError}
|
||||||
|
>
|
||||||
|
{/* Afficher champ salle SEULEMENT si pas de QR code */}
|
||||||
|
{!isQRCodeJoin && (
|
||||||
<TextField
|
<TextField
|
||||||
type="text"
|
type="text"
|
||||||
label="Nom de la salle"
|
label="Nom de la salle"
|
||||||
|
|
@ -212,7 +226,9 @@ const JoinRoom: React.FC = () => {
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
onKeyDown={handleReturnKey}
|
onKeyDown={handleReturnKey}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Champ username toujours visible */}
|
||||||
<TextField
|
<TextField
|
||||||
label="Nom d'utilisateur"
|
label="Nom d'utilisateur"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|
@ -229,9 +245,10 @@ const JoinRoom: React.FC = () => {
|
||||||
onClick={handleSocket}
|
onClick={handleSocket}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ marginBottom: `${connectionError && '2rem'}` }}
|
sx={{ marginBottom: `${connectionError && '2rem'}` }}
|
||||||
disabled={!username || !roomName}
|
disabled={!username || (isQRCodeJoin && !roomName)}
|
||||||
>Rejoindre</LoadingButton>
|
>
|
||||||
|
{isQRCodeJoin ? 'Rejoindre avec QR Code' : 'Rejoindre'}
|
||||||
|
</LoadingButton>
|
||||||
</LoginContainer>
|
</LoginContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import ImageGalleryModal from 'src/components/ImageGallery/ImageGalleryModal/Ima
|
||||||
|
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
import { escapeForGIFT } from '../../../utils/giftUtils';
|
import { escapeForGIFT } from '../../../utils/giftUtils';
|
||||||
import { ENV_VARIABLES } from '../../../constants';
|
import { ENV_VARIABLES } from 'src/constants';
|
||||||
|
|
||||||
interface EditQuizParams {
|
interface EditQuizParams {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -170,7 +170,7 @@ const QuizForm: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCopyImage = (id: string) => {
|
const handleCopyImage = (id: string) => {
|
||||||
const escLink = `${ENV_VARIABLES.IMG_URL}/api/image/get/${id}`;
|
const escLink = `${ENV_VARIABLES.BACKEND_URL}/api/image/get/${id}`;
|
||||||
setImageLinks(prevLinks => [...prevLinks, escLink]);
|
setImageLinks(prevLinks => [...prevLinks, escLink]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import webSocketService, {
|
||||||
import { QuizType } from '../../../Types/QuizType';
|
import { QuizType } from '../../../Types/QuizType';
|
||||||
import GroupIcon from '@mui/icons-material/Group';
|
import GroupIcon from '@mui/icons-material/Group';
|
||||||
import './manageRoom.css';
|
import './manageRoom.css';
|
||||||
|
import QRCodeIcon from '@mui/icons-material/QrCode';
|
||||||
import { ENV_VARIABLES } from 'src/constants';
|
import { ENV_VARIABLES } from 'src/constants';
|
||||||
import { StudentType, Answer } from '../../../Types/StudentType';
|
import { StudentType, Answer } from '../../../Types/StudentType';
|
||||||
import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle';
|
import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle';
|
||||||
|
|
@ -18,8 +19,18 @@ import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||||
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
import { QuestionType } from 'src/Types/QuestionType';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
import { Button } from '@mui/material';
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions
|
||||||
|
} from '@mui/material';
|
||||||
import { checkIfIsCorrect } from './useRooms';
|
import { checkIfIsCorrect } from './useRooms';
|
||||||
|
import { QRCodeCanvas } from 'qrcode.react';
|
||||||
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||||
|
|
||||||
|
|
||||||
const ManageRoom: React.FC = () => {
|
const ManageRoom: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -34,6 +45,16 @@ const ManageRoom: React.FC = () => {
|
||||||
const [quizStarted, setQuizStarted] = useState<boolean>(false);
|
const [quizStarted, setQuizStarted] = useState<boolean>(false);
|
||||||
const [formattedRoomName, setFormattedRoomName] = useState('');
|
const [formattedRoomName, setFormattedRoomName] = useState('');
|
||||||
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
|
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
|
||||||
|
const roomUrl = `${window.location.origin}/student/join-room?roomName=${roomName}`;
|
||||||
|
const [showQrModal, setShowQrModal] = useState(false);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(roomUrl).then(() => {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Handle the newly connected user in useEffect, because it needs state info
|
// Handle the newly connected user in useEffect, because it needs state info
|
||||||
// not available in the socket.on() callback
|
// not available in the socket.on() callback
|
||||||
|
|
@ -77,6 +98,15 @@ const ManageRoom: React.FC = () => {
|
||||||
verifyLogin();
|
verifyLogin();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!roomName) {
|
||||||
|
console.error('Room name is missing!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Joining room: ${roomName}`);
|
||||||
|
}, [roomName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomName || !quizId) {
|
if (!roomName || !quizId) {
|
||||||
window.alert(
|
window.alert(
|
||||||
|
|
@ -386,7 +416,62 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="room">
|
<div className="room">
|
||||||
<h1>Salle : {formattedRoomName}</h1>
|
{/* En-tête avec titre et bouton QR code*/}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 style={{ margin: 0 }}>Salle : {formattedRoomName}</h1>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => setShowQrModal(true)}
|
||||||
|
startIcon={<QRCodeIcon />}
|
||||||
|
>
|
||||||
|
Lien de participation
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modale QR Code */}
|
||||||
|
<Dialog
|
||||||
|
open={showQrModal}
|
||||||
|
onClose={() => setShowQrModal(false)}
|
||||||
|
aria-labelledby="qr-modal-title"
|
||||||
|
>
|
||||||
|
<DialogTitle id="qr-modal-title">Rejoindre la salle: {formattedRoomName}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Scannez ce QR code ou partagez le lien ci-dessous pour rejoindre la salle :
|
||||||
|
</DialogContentText>
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'center', margin: '20px 0' }}>
|
||||||
|
<QRCodeCanvas value={roomUrl} size={256} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ wordBreak: 'break-all', textAlign: 'center' }}>
|
||||||
|
<h3>URL de participation :</h3>
|
||||||
|
<p>{roomUrl}</p>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<ContentCopyIcon />}
|
||||||
|
onClick={handleCopy}
|
||||||
|
style={{ marginTop: '10px' }}
|
||||||
|
>
|
||||||
|
{copied ? "Copié !" : "Copier le lien"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowQrModal(false)} color="primary">
|
||||||
|
Fermer
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
<div className="roomHeader">
|
<div className="roomHeader">
|
||||||
<DisconnectButton
|
<DisconnectButton
|
||||||
onReturn={handleReturn}
|
onReturn={handleReturn}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ services:
|
||||||
context: ./client
|
context: ./client
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
environment:
|
|
||||||
VITE_IMG_URL: http://localhost
|
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ services:
|
||||||
- VITE_BACKEND_URL=
|
- VITE_BACKEND_URL=
|
||||||
# Define empty VITE_BACKEND_SOCKET_URL so it will default to window.location.host
|
# Define empty VITE_BACKEND_SOCKET_URL so it will default to window.location.host
|
||||||
- VITE_BACKEND_SOCKET_URL=
|
- VITE_BACKEND_SOCKET_URL=
|
||||||
- VITE_IMG_URL=https://evalsa.etsmtl.ca
|
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
20
server/package-lock.json
generated
20
server/package-lock.json
generated
|
|
@ -24,6 +24,7 @@
|
||||||
"passport-oauth2": "^1.8.0",
|
"passport-oauth2": "^1.8.0",
|
||||||
"passport-openidconnect": "^0.1.2",
|
"passport-openidconnect": "^0.1.2",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
|
|
@ -5850,6 +5851,15 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode.react": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
|
@ -5894,6 +5904,16 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
"passport-oauth2": "^1.8.0",
|
"passport-oauth2": "^1.8.0",
|
||||||
"passport-openidconnect": "^0.1.2",
|
"passport-openidconnect": "^0.1.2",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue