diff --git a/Dockerfile b/Dockerfile index 3a0c29f..b54ace9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM node:22-alpine AS base # Install pnpm -RUN corepack enable && corepack prepare pnpm@latest --activate +RUN corepack enable && corepack prepare pnpm@10 --activate FROM base AS deps @@ -75,4 +75,4 @@ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" -CMD ["npx", "tsx", "src/server.ts"] +CMD ["npx", "tsx", "src/server.ts"] \ No newline at end of file diff --git a/README.md b/README.md index 551fa77..7250e88 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +Hey # AskEasy A real-time classroom Q&A platform built for live lectures at the University of Toronto. Students post questions anonymously or publicly, upvote what matters most, and get answers from instructors — all updating instantly during class. Professors see exactly what the room is confused about, right now. @@ -16,17 +17,17 @@ AskEasy is built for that moment. It gives every lecture a live Q&A room where t ┌─────────────────────────────────────────────────────────────┐ │ Production │ │ │ -│ Browser ──HTTPS──▶ Apache + mod_shib ──localhost──▶ App │ +│ Browser ──HTTPS──▶ Apache + mod_shib ──localhost──▶ App │ │ │ │ │ ▼ │ │ U of T IdP (SAML) │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ -│ Application Layer │ +│ Application Layer │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ -│ │ Node.js Custom Server (server.ts) │ │ +│ │ Node.js Custom Server (server.ts) │ │ │ │ │ │ │ │ ┌─────────────────┐ ┌────────────────────────┐ │ │ │ │ │ Next.js App │ │ Socket.IO Server │ │ │ diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3170f94..d7b8ad1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,6 +9,7 @@ services: - ./src/server.ts:/app/src/server.ts:ro - ./src/app/api/auth/session/route.ts:/app/src/app/api/auth/session/route.ts:ro - ./whitelist.txt:/app/whitelist.txt:ro + - ./admin_whitelist.txt:/app/admin_whitelist.txt:ro - ./uploads:/app/uploads env_file: .env environment: diff --git a/package.json b/package.json index fea4fae..a4832bb 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,6 @@ "prisma": { "seed": "pnpm run db:seed" }, - "pnpm": { - "onlyBuiltDependencies": [ - "@prisma/engines", - "prisma" - ] - }, "dependencies": { "@embedpdf/core": "^2.3.0", "@embedpdf/engines": "^2.3.0", @@ -98,4 +92,4 @@ "vite-tsconfig-paths": "^6.0.3", "vitest": "^4.0.16" } -} \ No newline at end of file +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8984764..cde4117 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,8 @@ packages: - . -onlyBuiltDependencies: '["@prisma/engines", "prisma"]' +nodeLinker: hoisted + +onlyBuiltDependencies: + - '@prisma/engines' + - prisma diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..f480140 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://askeasy.utm.utoronto.ca/sitemap.xml \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..3776883 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,15 @@ + + + + https://askeasy.utm.utoronto.ca + 2026-05-24 + weekly + 1.0 + + + https://askeasy.utm.utoronto.ca/classes + 2026-05-24 + weekly + 0.8 + + diff --git a/src/app/demo-prof/page.tsx b/src/app/demo-prof/page.tsx deleted file mode 100644 index ae94ff8..0000000 --- a/src/app/demo-prof/page.tsx +++ /dev/null @@ -1,562 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { - BookOpen, - Calendar, - Radio, - Settings, - Square, - PanelRightClose, - Users, - GraduationCap, - Search, - Download, - X, -} from "lucide-react"; - -import type { Question, Comment } from "@/utils/types"; -import QuestionPost from "@/app/room/classChat/post/QuestionPost"; -import CommentPost from "@/app/room/classChat/post/CommentPost"; -import FilterTabs from "@/app/room/classChat/FilterTabs"; -import ChatInput from "@/app/room/classChat/ChatInput"; - -// --------------------------------------------------------------------------- -// Mock data -// --------------------------------------------------------------------------- - -const MOCK_QUESTIONS: Question[] = [ - { - id: "q1", - type: "question", - user: { username: "Sarah Chen", pfp: "", role: "STUDENT" }, - timestamp: "2:14 PM", - content: - "Can you go over that last example one more time? I didn't quite follow the recursion step.", - upvotes: 12, - isResolved: false, - isAnonymous: false, - replies: [ - { - id: "a1", - type: "comment", - user: { username: "Prof. Williams", pfp: "", role: "PROFESSOR" }, - timestamp: "2:16 PM", - content: - "Sure! The key idea is that each recursive call reduces the problem size by half. I'll draw it out on the board in a moment.", - upvotes: 5, - isAnonymous: false, - }, - ], - }, - { - id: "q2", - type: "question", - user: { username: "Alex Kim", pfp: "", role: "STUDENT" }, - timestamp: "2:18 PM", - content: "How does binary search handle edge cases like an empty array?", - upvotes: 24, - isResolved: true, - isAnonymous: false, - replies: [ - { - id: "a2", - type: "comment", - user: { username: "Jordan Lee", pfp: "", role: "TA" }, - timestamp: "2:20 PM", - content: - "Great question! When the array is empty, the base case triggers immediately since low > high, and we return -1.", - upvotes: 8, - isAnonymous: false, - }, - ], - }, - { - id: "q3", - type: "question", - user: null, - timestamp: "2:22 PM", - content: "Is this topic going to be on the midterm?", - upvotes: 7, - isResolved: false, - isAnonymous: true, - replies: [], - }, - { - id: "q4", - type: "question", - user: { username: "Marcus Johnson", pfp: "", role: "STUDENT" }, - timestamp: "2:25 PM", - content: "What's the time complexity of merge sort vs quicksort in the worst case?", - upvotes: 15, - isResolved: false, - isAnonymous: false, - replies: [ - { - id: "a3", - type: "comment", - user: { username: "Jordan Lee", pfp: "", role: "TA" }, - timestamp: "2:27 PM", - content: - "Merge sort is always O(n log n), while quicksort degrades to O(n²) when the pivot is poorly chosen.", - upvotes: 11, - isAnonymous: false, - }, - ], - }, -]; - -// --------------------------------------------------------------------------- -// Shared: Mock nav bar -// --------------------------------------------------------------------------- - -function MockNav() { - return ( -
-
- AskEasy - - My Lectures - -
-
- JW -
-
- ); -} - -// --------------------------------------------------------------------------- -// Shared: Chat header -// --------------------------------------------------------------------------- - -function MockChatHeader({ - title, - answerMode, -}: { - title: string; - answerMode?: "all" | "instructors_only"; -}) { - const mode = answerMode ?? "instructors_only"; - return ( -
-
-
- -

{title}

-
-
- - -
-
-
- ); -} - -// --------------------------------------------------------------------------- -// Shared: Question list -// --------------------------------------------------------------------------- - -function MockQuestionList({ - questions, - commentView, -}: { - questions: Question[]; - commentView: string; -}) { - return ( -
- {questions - .filter((q) => { - if (commentView === "unresolved") return !q.isResolved; - if (commentView === "resolved") return q.isResolved; - return true; - }) - .map((q) => ( - {}} - onResolve={() => {}} - onDelete={() => {}} - onSubmitAnswer={() => {}} - renderReply={(reply) => ( - {}} - onDelete={() => {}} - /> - )} - /> - ))} -
- ); -} - -// --------------------------------------------------------------------------- -// Shared: Mock PDF slide -// --------------------------------------------------------------------------- - -function MockSlide() { - return ( -
-
-
-

- Lecture 5: Recursion & Binary Search -

-

CSC108 — Introduction to Computer Science

-
-
-
-

Binary Search Algorithm

-
    -
  • - Works on sorted arrays only -
  • -
  • Repeatedly divides the search interval in half
  • -
  • Compare target with the middle element
  • -
  • - Time complexity:{" "} - - O(log n) - -
  • -
-
-
-
# Python
-
- def{" "} - binary_search(arr, target): -
-
- low, high = 0,{" "} - len(arr) -{" "} - 1 -
-
- while low <= high: -
-
- mid = (low + high) // 2 -
-
- if arr[mid] == target:{" "} - return mid -
-
- elif arr[mid] < target: low = mid +{" "} - 1 -
-
- else: high = mid -{" "} - 1 -
-
- return - - 1 -
-
-
-
- Prof. Williams - 5 / 24 -
-
-
- ); -} - -// =================================================================== -// SCENE 1: "Go Live & Share Slides" — Prof dashboard -// =================================================================== - -function Scene_GoLive() { - const courses = [ - { id: "1", name: "CSC108", full: "Introduction to Computer Science", semester: "Winter 2026" }, - { id: "2", name: "CSC148", full: "Data Structures", semester: "Winter 2026" }, - { - id: "3", - name: "CSC236", - full: "Introduction to the Theory of Computation", - semester: "Winter 2026", - }, - ]; - - return ( -
- -
-
-
-

- My Lectures -

-

Manage your lectures and start live sessions.

-
- -
-
- {courses.map((course, i) => ( -
-
-
- -
-
-
-

- {course.name} -

-

{course.full}

-
-
- - {course.semester} -
-
- - -
-
- ))} -
-
-
- ); -} - -// =================================================================== -// SCENE 2: "Engage With Your Class" — Live split view -// =================================================================== - -function Scene_Interactive() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - return ( -
- {/* Slide panel */} -
- -
- - - Live - - - - 47 - -
- - 5 / 24 - -
- -
-
- -
- - {/* Chat panel */} -
- -
-
-
-
- -
-
-
- -
-
- {}} - isAnonymous={isAnonymous} - onAnonymousChange={setIsAnonymous} - /> -
-
- ); -} - -// =================================================================== -// SCENE 3: "Manage & Export" — End session modal -// =================================================================== - -function Scene_ManageExport() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - return ( -
- {/* Session view behind the modal */} -
-
- -
- - - Live - - - - 47 - -
- -
-
-
-
- -
-
-
-
- -
-
-
- -
-
- {}} - isAnonymous={isAnonymous} - onAnonymousChange={setIsAnonymous} - /> -
-
- - {/* Modal overlay */} -
-
-
-

End Session

-

- Would you like to download the chat history for{" "} - CSC108 Live Session before ending? -

-
-
- - - -
-
-
-
- ); -} - -// --------------------------------------------------------------------------- -// Main page -// --------------------------------------------------------------------------- - -type Scene = 1 | 2 | 3; -const SCENE_LABELS: Record = { - 1: "1. Go Live", - 2: "2. Engage With Your Class", - 3: "3. Manage & Export", -}; - -export default function DemoProfPage() { - const [scene, setScene] = useState(1); - - return ( -
- {/* Minimal scene switcher — hide this before screenshotting */} -
- - Prof Onboarding: - - {([1, 2, 3] as Scene[]).map((s) => ( - - ))} -
- - {/* Scene — fills remaining height */} -
- {scene === 1 && } - {scene === 2 && } - {scene === 3 && } -
-
- ); -} diff --git a/src/app/demo/page.tsx b/src/app/demo/page.tsx deleted file mode 100644 index bc1bdcd..0000000 --- a/src/app/demo/page.tsx +++ /dev/null @@ -1,758 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { - Monitor, - BookOpen, - Calendar, - ArrowRight, - Radio, - Settings, - Square, - PanelRightClose, - Users, - GraduationCap, - Search, - Download, - X, -} from "lucide-react"; - -import type { Question, Comment } from "@/utils/types"; -import QuestionPost from "@/app/room/classChat/post/QuestionPost"; -import CommentPost from "@/app/room/classChat/post/CommentPost"; -import FilterTabs from "@/app/room/classChat/FilterTabs"; -import ChatInput from "@/app/room/classChat/ChatInput"; - -// --------------------------------------------------------------------------- -// Mock data -// --------------------------------------------------------------------------- - -const MOCK_QUESTIONS: Question[] = [ - { - id: "q1", - type: "question", - user: { username: "Sarah Chen", pfp: "", role: "STUDENT" }, - timestamp: "2:14 PM", - content: - "Can you go over that last example one more time? I didn't quite follow the recursion step.", - upvotes: 12, - isResolved: false, - isAnonymous: false, - replies: [ - { - id: "a1", - type: "comment", - user: { username: "Prof. Williams", pfp: "", role: "PROFESSOR" }, - timestamp: "2:16 PM", - content: - "Sure! The key idea is that each recursive call reduces the problem size by half. I'll draw it out on the board in a moment.", - upvotes: 5, - isAnonymous: false, - }, - ], - }, - { - id: "q2", - type: "question", - user: { username: "Alex Kim", pfp: "", role: "STUDENT" }, - timestamp: "2:18 PM", - content: "How does binary search handle edge cases like an empty array?", - upvotes: 24, - isResolved: true, - isAnonymous: false, - replies: [ - { - id: "a2", - type: "comment", - user: { username: "Jordan Lee", pfp: "", role: "TA" }, - timestamp: "2:20 PM", - content: - "Great question! When the array is empty, the base case triggers immediately since low > high, and we return -1. This is why checking the base case first is so important.", - upvotes: 8, - isAnonymous: false, - }, - ], - }, - { - id: "q3", - type: "question", - user: null, - timestamp: "2:22 PM", - content: "Is this topic going to be on the midterm?", - upvotes: 7, - isResolved: false, - isAnonymous: true, - replies: [], - }, - { - id: "q4", - type: "question", - user: { username: "Marcus Johnson", pfp: "", role: "STUDENT" }, - timestamp: "2:25 PM", - content: "What's the time complexity of merge sort vs quicksort in the worst case?", - upvotes: 15, - isResolved: false, - isAnonymous: false, - replies: [ - { - id: "a3", - type: "comment", - user: { username: "Jordan Lee", pfp: "", role: "TA" }, - timestamp: "2:27 PM", - content: - "Merge sort is always O(n log n) in the worst case, while quicksort degrades to O(n\u00B2) when the pivot is poorly chosen (e.g. already sorted data with first-element pivot).", - upvotes: 11, - isAnonymous: false, - }, - ], - }, - { - id: "q5", - type: "question", - user: { username: "Emily Park", pfp: "", role: "STUDENT" }, - timestamp: "2:30 PM", - content: "Could you show a visual example of how the call stack looks during recursion?", - upvotes: 9, - isResolved: false, - isAnonymous: false, - replies: [], - }, -]; - -// --------------------------------------------------------------------------- -// Shared: Chat header (inline — avoids context deps) -// --------------------------------------------------------------------------- - -function MockChatHeader({ - title, - role, - answerMode, -}: { - title: string; - role: "STUDENT" | "PROFESSOR"; - answerMode?: "all" | "instructors_only"; -}) { - const mode = answerMode ?? "instructors_only"; - return ( -
-
-
- -

{title}

-
-
- - {role === "PROFESSOR" && ( - - )} -
-
-
- ); -} - -// --------------------------------------------------------------------------- -// Shared: Question list -// --------------------------------------------------------------------------- - -function MockQuestionList({ - questions, - commentView, - role, -}: { - questions: Question[]; - commentView: string; - role: "STUDENT" | "PROFESSOR"; -}) { - return ( -
- {questions - .filter((q) => { - if (commentView === "unresolved") return !q.isResolved; - if (commentView === "resolved") return q.isResolved; - return true; - }) - .map((q) => ( - {}} - onResolve={role === "PROFESSOR" ? () => {} : undefined} - onDelete={role === "PROFESSOR" ? () => {} : undefined} - onSubmitAnswer={() => {}} - renderReply={(reply) => ( - {}} - onDelete={role === "PROFESSOR" ? () => {} : undefined} - /> - )} - /> - ))} -
- ); -} - -// --------------------------------------------------------------------------- -// Shared: Mock PDF slide -// --------------------------------------------------------------------------- - -function MockSlide() { - return ( -
-
-
-

- Lecture 5: Recursion & Binary Search -

-

CSC108 — Introduction to Computer Science

-
-
-
-

Binary Search Algorithm

-
    -
  • - Works on sorted arrays only -
  • -
  • Repeatedly divides the search interval in half
  • -
  • Compare target with the middle element
  • -
  • - Time complexity:{" "} - - O(log n) - -
  • -
-
-
-
# Python
-
- def{" "} - binary_search(arr, target): -
-
- low, high = 0,{" "} - len(arr) -{" "} - 1 -
-
- while low <= high: -
-
- mid = (low + high) // 2 -
-
- if arr[mid] == target: -
-
- return mid -
-
- elif arr[mid] < target: -
-
- low = mid + 1 -
-
- else: -
-
- high = mid - 1 -
-
- return - - 1 -
-
-
-

Key Insight

-

- Each recursive call{" "} - reduces the problem size by half - . For an array of 1,000,000 elements, we need at most ~20 comparisons. -

-
-
-
- Prof. Williams - 5 / 24 -
-
-
- ); -} - -// =================================================================== -// SCENE 1: "Welcome to AskEasy" — Student dashboard, 1 live lecture -// =================================================================== - -function Scene_JoinClass() { - const courses = [ - { id: "1", name: "CSC108", semester: "Winter 2026", isActive: true }, - { id: "2", name: "MAT137", semester: "Winter 2026", isActive: false }, - { id: "3", name: "CSC148", semester: "Winter 2026", isActive: false }, - ]; - - return ( -
-
-
-

My Lectures

-

- Select a live lecture to join its session. If the lecture is not live, just wait! -

-
-
-
- {courses.map((course) => ( -
-
-
- {course.isActive ? ( - - ) : ( - - )} -
- {course.isActive && ( - - - LIVE - - )} -
-
-

- {course.name} -

-
-
- - {course.semester} - {course.isActive && ( -
- Join - -
- )} -
-
- ))} -
-
- ); -} - -// =================================================================== -// SCENE 2: "Ask & Get Answers Live" — Q&A with questions + TA/prof replies -// =================================================================== - -function Scene_AskQuestions() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - return ( -
- -
-
-
-
- -
-
-
- -
-
- {}} isAnonymous={isAnonymous} onAnonymousChange={setIsAnonymous} /> -
- ); -} - -// =================================================================== -// SCENE 3: "Upvote & Track Questions" — upvotes, resolved, filters -// =================================================================== - -function Scene_Upvote() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - // Show all 5 questions — mix of resolved and unresolved, various upvote counts - return ( -
- -
-
-
-
- -
-
-
- -
-
- {}} isAnonymous={isAnonymous} onAnonymousChange={setIsAnonymous} /> -
- ); -} - -// =================================================================== -// SCENE 4: "Stay Anonymous" — anonymous toggle ON, anonymous question visible -// =================================================================== - -function Scene_Anonymous() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - - // Show the chat input area with anonymous mode ON prominently - return ( -
- -
-
-
-
- -
-
-
- -
-
- {/* Anonymous mode ON */} - {}} isAnonymous={true} onAnonymousChange={() => {}} /> -
- ); -} - -// =================================================================== -// SCENE 5: "Go Live & Share Slides" — Prof dashboard + split view -// =================================================================== - -function Scene_GoLive() { - const courses = [ - { id: "1", name: "CSC108", semester: "Winter 2026", isActive: false }, - { id: "2", name: "CSC148", semester: "Winter 2026", isActive: false }, - { id: "3", name: "CSC236", semester: "Winter 2026", isActive: false }, - ]; - - return ( -
-
-
-

My Lectures

-

Manage your lectures and start live sessions.

-
-
-
- {courses.map((course) => ( -
-
-
- -
-
-
-

- {course.name} -

-
-
- - {course.semester} -
-
- - -
-
- ))} -
-
- ); -} - -// =================================================================== -// SCENE 6: "Engage With Your Class" — Prof chat with answer mode toggle -// =================================================================== - -function Scene_Interactive() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - return ( -
- {/* Slide side */} -
- -
- - - Live - - - - 47 - -
- - 5 / 24 - -
-
- -
- - {/* Chat side — professor view with answer mode */} -
- -
-
-
-
- -
-
-
- -
-
- {}} - isAnonymous={isAnonymous} - onAnonymousChange={setIsAnonymous} - /> -
-
- ); -} - -// =================================================================== -// SCENE 7: "Manage & Export" — End session modal + chat behind it -// =================================================================== - -function Scene_ManageExport() { - const [commentView, setCommentView] = useState<"all" | "unresolved" | "resolved">("all"); - const [isAnonymous, setIsAnonymous] = useState(false); - - return ( -
- {/* Chat behind modal (dimmed) */} -
-
- -
- - - Live - - - - 47 - -
- -
-
-
-
- -
-
-
-
- -
-
-
- -
-
- {}} - isAnonymous={isAnonymous} - onAnonymousChange={setIsAnonymous} - /> -
-
- - {/* End session modal overlay */} -
-
-
-

End Session

-

- Would you like to download the chat history for{" "} - CSC108 Live Session before ending? -

-
-
- - - -
-
-
-
- ); -} - -// --------------------------------------------------------------------------- -// Scenes config -// --------------------------------------------------------------------------- - -type Scene = - | "s1-join" - | "s2-ask" - | "s3-upvote" - | "s4-anonymous" - | "p1-golive" - | "p2-interactive" - | "p3-manage"; - -const SCENES: { key: Scene; label: string; group: string }[] = [ - { key: "s1-join", label: "1. Join Class", group: "Student" }, - { key: "s2-ask", label: "2. Ask & Answer", group: "Student" }, - { key: "s3-upvote", label: "3. Upvote & Track", group: "Student" }, - { key: "s4-anonymous", label: "4. Anonymous", group: "Student" }, - { key: "p1-golive", label: "1. Go Live", group: "Professor" }, - { key: "p2-interactive", label: "2. Engage", group: "Professor" }, - { key: "p3-manage", label: "3. Manage & Export", group: "Professor" }, -]; - -// --------------------------------------------------------------------------- -// Main Demo Page -// --------------------------------------------------------------------------- - -export default function DemoPage() { - const [scene, setScene] = useState("s1-join"); - - return ( -
- {/* Scene selector bar */} -
-
- - Student: - - {SCENES.filter((s) => s.group === "Student").map((s) => ( - - ))} -
- - Prof: - - {SCENES.filter((s) => s.group === "Professor").map((s) => ( - - ))} -
-
- - {/* Scene content */} -
- {scene === "s1-join" && } - {scene === "s2-ask" && } - {scene === "s3-upvote" && } - {scene === "s4-anonymous" && } - {scene === "p1-golive" && } - {scene === "p2-interactive" && } - {scene === "p3-manage" && } -
-
- ); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 87e371b..8b4f41a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,6 +15,26 @@ const geistMono = Geist_Mono({ export const metadata: Metadata = { title: "AskEasy", description: "Real-time classroom Q&A and lecture platform", + keywords: [ + "classroom", + "Q&A", + "questions", + "answers", + "lecture", + "interactive learning", + "student engagement", + "real-time", + ], + authors: [{ name: "AskEasy Team" }], + robots: { + index: true, + follow: true, + nocache: false, + googleBot: { + index: true, + follow: true, + }, + }, icons: { icon: "/icon.png", apple: "/icon.png", diff --git a/src/app/room/classChat/ChatHeader.tsx b/src/app/room/classChat/ChatHeader.tsx index 6513aa2..003f5bf 100644 --- a/src/app/room/classChat/ChatHeader.tsx +++ b/src/app/room/classChat/ChatHeader.tsx @@ -2,20 +2,11 @@ import { Input } from "@/components/ui/input"; import { useContext, useState } from "react"; -import { - PanelRightClose, - Users, - GraduationCap, - Search, - X, - ArrowBigRight, - UserPlus, -} from "lucide-react"; +import { PanelRightClose, Users, GraduationCap, Search, X, UserPlus } from "lucide-react"; import ManageTAsModal from "./ManageTAsModal"; import { useMediaQuery } from "@/hooks/use-media-query"; import { SlideUpdateContext } from "../SlideUpdateContext"; import type { Role } from "@/utils/types"; -import Link from "next/link"; interface ChatHeaderProps { role: Role; @@ -176,14 +167,6 @@ export default function ChatHeader({ )} - - Back - -
)}