mirror of
https://github.com/Leado123/Leado123.github.io.git
synced 2026-10-01 00:32:59 +00:00
made things better
This commit is contained in:
@@ -17,13 +17,19 @@
|
|||||||
"@nextui-org/select": "^2.4.9",
|
"@nextui-org/select": "^2.4.9",
|
||||||
"@react-pdf/renderer": "^4.2.1",
|
"@react-pdf/renderer": "^4.2.1",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"@types/react-autocomplete": "^1.8.11",
|
||||||
|
"@types/react-autosuggest": "^10.1.11",
|
||||||
|
"accessible-autocomplete": "^3.0.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"autosuggest": "^0.0.2",
|
||||||
"flowbite": "^2.5.2",
|
"flowbite": "^2.5.2",
|
||||||
"flowbite-react": "^0.10.2",
|
"flowbite-react": "^0.10.2",
|
||||||
"gh-pages": "^6.3.0",
|
"gh-pages": "^6.3.0",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.10.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-autocomplete": "^1.8.1",
|
||||||
|
"react-autosuggest": "^10.1.0",
|
||||||
"react-datasheet-grid": "^4.11.5",
|
"react-datasheet-grid": "^4.11.5",
|
||||||
"react-doc-viewer": "^0.1.14",
|
"react-doc-viewer": "^0.1.14",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -31,6 +37,7 @@
|
|||||||
"react-pro-sidebar": "^1.1.0",
|
"react-pro-sidebar": "^1.1.0",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
"react-select": "^5.10.0",
|
"react-select": "^5.10.0",
|
||||||
|
"react-tabs": "^6.1.0",
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+53
-42
@@ -6,7 +6,7 @@ import SyllabusListing, { ClassListing } from './syllabus_comp';
|
|||||||
import { Accordion, FileInput } from "flowbite-react";
|
import { Accordion, FileInput } from "flowbite-react";
|
||||||
|
|
||||||
import { Button, Label, Modal, TextInput } from "flowbite-react";
|
import { Button, Label, Modal, TextInput } from "flowbite-react";
|
||||||
import Marquee from 'react-fast-marquee';
|
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export const classOptions = [ // TODO REMOVE
|
export const classOptions = [ // TODO REMOVE
|
||||||
@@ -145,51 +145,62 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
|
||||||
{isMobile === false && <div className="p-10 flex justify-center items-center">
|
{isMobile === false && <div className=" flex justify-center items-center">
|
||||||
<div className="w-1/2">
|
<Tabs className="w-full bg-zinc-950 p-4">
|
||||||
<div className="flex flex-row">
|
<TabList className="flex space-x-2 pb-1">
|
||||||
<h3 className="text-5xl mb-5 font-bold text-left flex-1">📚 Share Syllabus</h3>
|
<Tab selectedClassName="rounded-lg p-1 underline decoration-wavy" className=" focus:outline-none p-1 cursor-pointer">Syllabuses</Tab>
|
||||||
</div>
|
<Tab selectedClassName="rounded-lg p-1 underline decoration-wavy" className=" focus:outline-none p-1 cursor-pointer">ObjectSearch (In Development)</Tab>
|
||||||
<p className="text-left ">
|
</TabList>
|
||||||
A platform for sharing and exploring course syllabi, helping students compare class structures, assignments, and topics while earning community service hours by contributing syllabuses to the knowledge base.</p>
|
|
||||||
<div className="pt-1 text-left flex flex-row space-x-2">
|
|
||||||
<p className="text-xs font-bold">Contacts:</p>
|
|
||||||
<Marquee className="text-xs flex-1" speed={25}>
|
|
||||||
<p className=" "> lwen3@student.rccd.edu 336722@students.cnusd.k12.ca.us College Success Club @college_success_club | here to assist those who wish to know how hard classes are before taking them at Norco College | do you have a computer running linux 24/7 with port forwarding? contact us to help host this website! |</p>
|
|
||||||
</Marquee>
|
|
||||||
</div>
|
|
||||||
<div className="w-full p-6 flex justify-between text-sm underline">
|
|
||||||
<a href="https://portal.rcc.edu">MyPortal</a>
|
|
||||||
<a href="https://www.norcocollege.edu/articulation/documents/norco-igetc-2024-25.pdf">IGETC</a>
|
|
||||||
<a href="https://www.norcocollege.edu/services/counseling/index.html">Counseling</a>
|
|
||||||
<a href="https://mvc.edu/_resources/files/transfer/rccd-associate-degree-requirements.pdf ">RCCD GE (Graduation Requirements)</a>
|
|
||||||
</div>
|
|
||||||
<div className="">
|
|
||||||
<div className="flex space-x-1">
|
|
||||||
<button onClick={() => setOpenModal(true)} className=" bg-blue-700 p-2 text-yellow-50 rounded-lg flex flex-row">
|
|
||||||
<span className="flex justify-center items-center aspect-square flex-1"><i className="material-symbols-rounded">attach_file</i></span>
|
|
||||||
<span className="flex flex-col"> <span className="text-sm">add a syllabus</span>
|
|
||||||
<span className="text-xs">1 volunteer hours</span></span>
|
|
||||||
</button>
|
|
||||||
<div className="flex-1 flex bg-white rounded-lg">
|
|
||||||
<input type="search" onKeyDown={handleKeyDown} placeholder="search, hit enter or the search button to enter query" className="focus:outline-none bg-transparent border-none flex-1 text-black" value={query} onChange={(e) => setQuery(e.target.value)}>
|
|
||||||
|
|
||||||
</input>
|
<TabPanel>
|
||||||
<button className="aspect-square flex justify-center items-center" onClick={handleSearch}>
|
<div className="place-items-center justify-center flex w-full flex-col">
|
||||||
<span className="material-symbols-rounded text-black">
|
<div className="w-full flex space-y-2 flex-col">
|
||||||
search
|
<div className="flex pt-2">
|
||||||
</span>
|
<div className="flex-1 flex border border-zinc-800 focus:outline-none rounded-lg">
|
||||||
</button></div>
|
<input type="search" onKeyDown={handleKeyDown} placeholder="search, hit enter or the search button to enter query" className="focus:outline-none focus:ring-0 bg-transparent border-none focus:bg-black rounded-lg flex-1 text-white" value={query} onChange={(e) => setQuery(e.target.value)}>
|
||||||
|
|
||||||
|
</input>
|
||||||
|
<button className="aspect-square flex justify-center items-center" onClick={handleSearch}>
|
||||||
|
<span className="material-symbols-rounded text-white">
|
||||||
|
search
|
||||||
|
</span>
|
||||||
|
</button></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<div className="text-left">
|
||||||
|
<text>Filters:</text>
|
||||||
|
<div className="">
|
||||||
|
<select className="w-full mb-2 mt-2 bg-black p-1 rounded-lg">
|
||||||
|
<option>10 results</option>
|
||||||
|
<option>15 results</option>
|
||||||
|
<option>20 results</option>
|
||||||
|
</select>
|
||||||
|
<span>
|
||||||
|
<select className="w-full bg-black p-1 rounded-lg" >
|
||||||
|
<option>all results (unverified too)</option>
|
||||||
|
<option>verified results only</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
|
||||||
|
|
||||||
|
<div className="listing">
|
||||||
|
{syllabi.map((syllabus, i) => {
|
||||||
|
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.class?.className} description={syllabus.description} fullClassName={syllabus.class?.fullClassName} textbookCost={syllabus.textbookCost} classLength={syllabus.classLength} professor={syllabus.professor} />
|
||||||
|
})}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="listing">
|
</TabPanel>
|
||||||
{syllabi.map((syllabus, i) => {
|
</Tabs>
|
||||||
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} description={syllabus.description} fullClassName={syllabus.fullClassName} textbookCost={syllabus.textbookCost} classLength={syllabus.classLength} professor={syllabus.professor} />
|
|
||||||
})}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>}
|
</div>}
|
||||||
{isMobile === true && <div className="p-1 pt-2">
|
{isMobile === true && <div className="p-1 pt-2">
|
||||||
<h3 className="text-2xl font-bold mb-2">Share Syllabus</h3>
|
<h3 className="text-2xl font-bold mb-2">Share Syllabus</h3>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import Marquee from "react-fast-marquee";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
function Layout({children}: {children: React.ReactNode}) {
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const uploadSyllabi = () => {
|
||||||
|
navigate('/upload')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="place-items-center justify-center flex border-b-2 pt-10 border-purple-500">
|
||||||
|
<div className="w-1/2 ">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<h3 onClick={() => {navigate("/")}} className="text-4xl hover:text-yellow-400 w-min cursor-pointer text-yellow-300 font-extrabold text-left flex-1">📚 Share Syllabus</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-left ">
|
||||||
|
A platform for sharing and exploring course syllabi, helping students compare class structures, assignments, and topics while earning community service hours by contributing syllabuses to the knowledge base.</p>
|
||||||
|
<div className="pt-1 text-left flex flex-row space-x-2">
|
||||||
|
<p className="text-xs font-bold">Contacts:</p>
|
||||||
|
<Marquee className="text-xs flex-1" speed={25}>
|
||||||
|
<p className=""> lwen3@student.rccd.edu 336722@students.cnusd.k12.ca.us College Success Club @college_success_club | here to assist those who wish to know how hard classes are before taking them at Norco College | do you have a computer running linux 24/7 with port forwarding? contact us to help host this website! |</p>
|
||||||
|
</Marquee>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full p-4 flex place-items-center justify-between text-sm">
|
||||||
|
<a href="https://portal.rcc.edu">MyPortal</a>
|
||||||
|
<a href="https://www.norcocollege.edu/articulation/documents/norco-igetc-2024-25.pdf">IGETC</a>
|
||||||
|
<a href="https://www.norcocollege.edu/services/counseling/index.html">Counseling</a>
|
||||||
|
<a href="https://mvc.edu/_resources/files/transfer/rccd-associate-degree-requirements.pdf ">RCCD GE (Graduation Requirements)</a>
|
||||||
|
<button onClick={uploadSyllabi} className="bg-blue-600 hover:bg-blue-700 border-yellow-300 border-2 flex place-items-center justify-center text-white rounded-lg font-bold p-2"><span className="pr-1 material-symbols-rounded">add_circle</span>Upload a Syllabus</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { ClassListing } from "./syllabus_comp";
|
||||||
|
import View from "./View";
|
||||||
|
|
||||||
|
|
||||||
|
function Syllabus() {
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [syllabus, getSyllabus] = useState<ClassListing | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`https://api.sharesyllabus.me/syllabus/${id}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => getSyllabus(data))
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-16 h-full text-left justify-center flex">
|
||||||
|
<div className="text-left place-items-center justify-center flex w-3/4">
|
||||||
|
<div className="w-1/4 h-full flex flex-col">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<text className="text-3xl text-yellow-300 font-black">{syllabus?.className}</text>
|
||||||
|
<text className="">{syllabus?.fullClassName}</text>
|
||||||
|
<text className="italic underline">{syllabus?.professor}</text>
|
||||||
|
</div>
|
||||||
|
<text className="italic font-light pt-6 font-serif">"{syllabus?.description}"</text>
|
||||||
|
<text className="text-blue-600">{syllabus?.classLength} weeked class</text>
|
||||||
|
<text className="font-bold text-lime-500">{syllabus?.textbookCost} textbook cost</text>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{syllabus ? <View passedFile={syllabus?.fileName}></View> : <div></div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Syllabus;
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export interface Class {
|
||||||
|
id: number;
|
||||||
|
className: string;
|
||||||
|
fullClassName: string;
|
||||||
|
disciplineId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface School {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Professor {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UploadSyllabi() {
|
||||||
|
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [college, setCollege] = useState("");
|
||||||
|
const [professor, setProfessor] = useState("");
|
||||||
|
const [classLength, setClassLength] = useState(0);
|
||||||
|
const [textbookCost, setTextbookCost] = useState("free");
|
||||||
|
const [classDescription, setClassDescription] = useState("");
|
||||||
|
const [classAbrvOptions, setClassAbrvOptions] = useState<Class[] | null>(null);
|
||||||
|
const [classAbrvQuery, setClassAbrvQuery] = useState("");
|
||||||
|
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
|
||||||
|
const [collegeOptions, setCollegeOptions] = useState<School[]>([]);
|
||||||
|
const [professorOptions, setProfessorOptions] = useState<Professor[]>([]);
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("https://api.sharesyllabus.me/schools", {
|
||||||
|
method: "GET",
|
||||||
|
}).then((res) => res.json()).then((data) => {
|
||||||
|
setCollegeOptions(data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
if (event.target.files) {
|
||||||
|
setFile(event.target.files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchProfessor(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
setProfessor(e.target.value);
|
||||||
|
fetch(`https://api.sharesyllabus.me/search/professor/?q=${professor}&s=${college}`, {
|
||||||
|
method: "POST",
|
||||||
|
}).then((res) => res.json()).then((data) => {
|
||||||
|
setProfessorOptions(data);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchClassAbrv(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
setClassAbrvQuery(e.target.value);
|
||||||
|
|
||||||
|
|
||||||
|
fetch(`https://api.sharesyllabus.me/search/class/?q=${classAbrvQuery}&s=${college}`, {
|
||||||
|
method: "POST",
|
||||||
|
}).then((res) => res.json()).then((data) => {
|
||||||
|
setClassAbrvOptions(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit(e: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
setError("Please select a file");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!college || !classAbrvQuery || !professor) {
|
||||||
|
setError("Please fill in all required fields");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("upload", file);
|
||||||
|
formData.append("className", classAbrvQuery);
|
||||||
|
formData.append("schoolName", college);
|
||||||
|
formData.append("professor", professor);
|
||||||
|
formData.append("classLength", classLength.toString());
|
||||||
|
formData.append("textbookCost", textbookCost);
|
||||||
|
formData.append("description", classDescription);
|
||||||
|
formData.append("createdByName", name);
|
||||||
|
formData.append("createdByEmail", email);
|
||||||
|
|
||||||
|
fetch("https://api.sharesyllabus.me/create", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorData = await res.json();
|
||||||
|
throw new Error(errorData.msg || 'Upload failed');
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Upload successful:', data);
|
||||||
|
// Add success handling here (e.g., show success message, redirect)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error:", error);
|
||||||
|
setError(error.message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full flex place-items-center justify-center p-10">
|
||||||
|
|
||||||
|
<div className="w-1/2 text-left flex flex-col">
|
||||||
|
<text className="text-3xl font-bold">Upload a Syllabus!</text>
|
||||||
|
<text className="italic">If you complete all the information you can receive 2 hrs in community service. Just send us your email so we can give you an E-signature of your hours. If you dont want to do the community service hours, feel free to leave some fields blank.</text>
|
||||||
|
<div className="flex flex-col pt-2 gap-2">
|
||||||
|
<text>Syllabus (pdf, docx, etc.)</text>
|
||||||
|
<input type="file" className="bg-zinc-800 w-min rounded-md" onChange={handleFileChange}></input>
|
||||||
|
<text>College District (not school!):</text>
|
||||||
|
<input
|
||||||
|
list="collegeOptions"
|
||||||
|
value={college}
|
||||||
|
onChange={(e) => setCollege(e.target.value)}
|
||||||
|
placeholder="Search or select college district"
|
||||||
|
required
|
||||||
|
className="p-1 rounded-md"
|
||||||
|
/>
|
||||||
|
<datalist id="collegeOptions">
|
||||||
|
{collegeOptions.map((college: School, index: number) => (
|
||||||
|
<option key={index} value={college.name} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
<text>Class Name:</text>
|
||||||
|
<input list="classAbrvOptions" className="p-1 rounded-md" placeholder="i.e.: ENG-1A" value={classAbrvQuery} disabled={!college} onChange={(e) => searchClassAbrv(e)} required onInput={(e) => setClassAbrvQuery(e.currentTarget.value)} />
|
||||||
|
<datalist id="classAbrvOptions" className="w-full bg-white">
|
||||||
|
{classAbrvOptions?.map((classOption, index) => (
|
||||||
|
<option key={index} value={classOption.className} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
<text>Professor:</text>
|
||||||
|
<input placeholder="enter or search professor" className="p-1 rounded-md" list="professorOptions" value={professor} disabled={!college} required onChange={searchProfessor}></input>
|
||||||
|
<datalist id="professorOptions">
|
||||||
|
{professorOptions.map((professor, index) => (
|
||||||
|
<option key={index} value={professor.name} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
<text>Duration of class in weeks: [OPTIONAL: community service hours]</text>
|
||||||
|
<input type="number" className="bg-zinc-800 p-1 rounded-md" value={classLength} onChange={(e) => setClassLength(Number(e.target.value))}></input>
|
||||||
|
<text>Textbook Cost: [OPTIONAL: community service hours]</text>
|
||||||
|
<select className="bg-zinc-800 p-1 rounded-md" value={textbookCost} onChange={(e) => setTextbookCost(e.target.value)}>
|
||||||
|
<option>free</option>
|
||||||
|
<option>cheap</option>
|
||||||
|
<option>moderate</option>
|
||||||
|
<option>expensive</option>
|
||||||
|
</select>
|
||||||
|
<text>Class description in at most 2 sentences (NOT a description of the teacher) [OPTIONAL: community service hours]:</text>
|
||||||
|
<textarea className="bg-zinc-800 p-1 rounded-md" value={classDescription} onChange={(e) => setClassDescription(e.target.value)}></textarea>
|
||||||
|
<text>Your Name [OPTIONAL: community service hours]</text>
|
||||||
|
<input placeholder="John Doe" className="bg-zinc-800 p-1 rounded-md" value={name} onChange={(e) => setName(e.target.value)}></input>
|
||||||
|
<text>Your Email [OPTIONAL: community service hours]</text>
|
||||||
|
<input placeholder="example@company.com" className="bg-zinc-800 p-1 rounded-md" value={email} onChange={(e) => setEmail(e.target.value)}></input>
|
||||||
|
<div className="text-right">
|
||||||
|
<button
|
||||||
|
className="bg-blue-600 rounded-lg p-2 disabled:opacity-50"
|
||||||
|
onClick={submit}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isLoading ? 'Uploading...' : 'Submit'}
|
||||||
|
</button>
|
||||||
|
{error && <div className="text-red-500 mt-2">{error}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadSyllabi;
|
||||||
+18
-24
@@ -1,20 +1,27 @@
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import DocViewer, { DocViewerRenderers } from "react-doc-viewer";
|
import DocViewer, { DocViewerRenderers } from "react-doc-viewer";
|
||||||
|
|
||||||
function findIfMobile() {
|
|
||||||
//return true;
|
|
||||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
interface ViewParams {
|
||||||
|
passedFile?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function View() {
|
function View({passedFile, className}: ViewParams) {
|
||||||
|
|
||||||
|
|
||||||
const isMobile = findIfMobile();
|
|
||||||
|
|
||||||
function checkIfPdf(file: string) {
|
function checkIfPdf(file: string) {
|
||||||
return file.endsWith(".pdf");
|
return file.endsWith(".pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { file } = useParams();
|
|
||||||
|
let { file } = useParams();
|
||||||
|
|
||||||
|
if (file === undefined) {
|
||||||
|
file = passedFile;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: make it work on mobile (pdf viewer doesen't work)
|
// TODO: make it work on mobile (pdf viewer doesen't work)
|
||||||
|
|
||||||
@@ -24,34 +31,21 @@ function View() {
|
|||||||
{ uri: `https://api.sharesyllabus.me/files/${file}` }
|
{ uri: `https://api.sharesyllabus.me/files/${file}` }
|
||||||
]
|
]
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
console.log(docs[0].uri);
|
console.log(docs[0].uri);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-2 justify-center align-middle w-full h-full m-0 p-4">
|
<div className={`flex flex-col space-y-2 justify-center align-middle flex-1 w-full h-full m-0 ${className}`}>
|
||||||
<nav className="w-full flex text-left pb-1 space-x-4">
|
<nav className="w-full flex text-right place-items-end pb-1 space-x-4">
|
||||||
<button onClick={() => navigate("/")} className="p-2 bg-white rounded-xl text-black aspect-square">
|
|
||||||
<i className="material-symbols-rounded">arrow_back</i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="flex space-x-1 h-full items-center p-2 bg-blue-700 text-white rounded-xl">
|
<div className="flex-1"></div>
|
||||||
|
<div className="flex space-x-1 items-center p-2 bg-blue-700 text-white rounded-xl">
|
||||||
<button onClick={() => window.location.replace(docs[0].uri)} className=" aspect-square">
|
<button onClick={() => window.location.replace(docs[0].uri)} className=" aspect-square">
|
||||||
<i className="material-symbols-rounded">download</i>
|
<i className="material-symbols-rounded">download</i>
|
||||||
</button>
|
</button>
|
||||||
<text className=" font-medium pr-1">download</text>
|
<text className=" font-medium pr-1">download</text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={() => {
|
|
||||||
navigator.clipboard.writeText(docs[0].uri);
|
|
||||||
alert("Link Copied to Clipboard!");
|
|
||||||
}} className="p-2 bg-white rounded-xl text-black aspect-square">
|
|
||||||
<i className="material-symbols-rounded">share</i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isMobile ? <></> : <div className="h-full flex items-center justify-start align-middle">
|
|
||||||
<text className="text-xl text-left font-bold">Preview & Or Download</text>
|
|
||||||
</div>}
|
|
||||||
</nav>
|
</nav>
|
||||||
{isPDF
|
{isPDF
|
||||||
? <iframe src={`https://api.sharesyllabus.me/files/${file}`} className="flex-1 w-full rounded-xl"></iframe>
|
? <iframe src={`https://api.sharesyllabus.me/files/${file}`} className="flex-1 w-full rounded-xl"></iframe>
|
||||||
|
|||||||
+7
-2
@@ -6,16 +6,21 @@ import View from './View.tsx';
|
|||||||
import Admin from './Admin.tsx';
|
import Admin from './Admin.tsx';
|
||||||
import Flag from './Flag.tsx';
|
import Flag from './Flag.tsx';
|
||||||
import Reports from './Reports.tsx';
|
import Reports from './Reports.tsx';
|
||||||
|
import Syllabus from './Syllabus.tsx';
|
||||||
|
import Layout from './Layout.tsx';
|
||||||
|
import UploadSyllabi from './UploadSyllabi.tsx';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<Layout><App/></Layout>} />
|
||||||
<Route path="/view/:file" element={<View />} />
|
<Route path="/view/:file" element={<View />} />
|
||||||
<Route path="/admin" element={<Admin />} />
|
<Route path="/admin" element={<Admin />} />
|
||||||
<Route path="/admin/:highlightedSyllabusId" element={<Admin />} />
|
<Route path="/admin/:highlightedSyllabusId" element={<Admin />} />
|
||||||
<Route path="/flag/:id" element={<Flag />} />
|
<Route path="/flag/:id" element={<Layout><Flag /></Layout>} />
|
||||||
<Route path="/reports" element={<Reports />} />
|
<Route path="/reports" element={<Reports />} />
|
||||||
|
<Route path="/syllabus/:id" element={<Layout><Syllabus/></Layout>} />
|
||||||
|
<Route path="/upload" element={<Layout><UploadSyllabi/></Layout>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
|
|||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
declare module 'react-autocomplete' {
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export interface AutocompleteProps {
|
||||||
|
items: string[];
|
||||||
|
getItemValue(item: string): string;
|
||||||
|
renderItem(item: string, isHighlighted: boolean): React.ReactNode;
|
||||||
|
value: string;
|
||||||
|
onChange(e: React.ChangeEvent<HTMLInputElement>): void;
|
||||||
|
onSelect(value: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Autocomplete: React.FC<AutocompleteProps>;
|
||||||
|
export default Autocomplete;
|
||||||
|
}
|
||||||
|
|
||||||
+15
-8
@@ -14,7 +14,7 @@ export enum TextbookCost {
|
|||||||
// TODO: implement fullClassName and textbook cost in mobile
|
// TODO: implement fullClassName and textbook cost in mobile
|
||||||
|
|
||||||
export interface ClassListing {
|
export interface ClassListing {
|
||||||
className: string;
|
className?: string;
|
||||||
professor: string;
|
professor: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -25,6 +25,12 @@ export interface ClassListing {
|
|||||||
mimeType?: string;
|
mimeType?: string;
|
||||||
textbookCost?: string;
|
textbookCost?: string;
|
||||||
fullClassName?: string;
|
fullClassName?: string;
|
||||||
|
class?: {
|
||||||
|
className?: string;
|
||||||
|
fullClassName?: string;
|
||||||
|
discipline?: string;
|
||||||
|
}
|
||||||
|
professorId?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
function findIfMobile() {
|
function findIfMobile() {
|
||||||
@@ -55,7 +61,11 @@ function SyllabusListing({ className, professor, fileName, description, classLen
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
async function handleView() {
|
async function handleView() {
|
||||||
navigate(`/view/${encodeURIComponent(`${fileName}`)}`);
|
if (findIfMobile()) {
|
||||||
|
navigate(`/view/${encodeURIComponent(`${fileName}`)}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/syllabus/${encodeURIComponent(`${id}`)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFlag() {
|
async function handleFlag() {
|
||||||
@@ -79,10 +89,10 @@ function SyllabusListing({ className, professor, fileName, description, classLen
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex space-x-4 bg-white text-black rounded-lg p-3" >
|
<div className="w-full border-zinc-800 border hover:bg-zinc-900 flex space-x-4 cursor-pointer text-white rounded-lg p-3" onClick={handleView} >
|
||||||
<div className="flex text-left flex-col w-1/4">
|
<div className="flex text-left flex-col w-1/4">
|
||||||
<span className="font-extrabold">{className}</span>
|
<span className="font-extrabold">{className}</span>
|
||||||
<span className="text-xs text-gray-600">{fullClassName}</span>
|
<span className="text-xs text-gray-300">{fullClassName}</span>
|
||||||
{textbookCostElement()}
|
{textbookCostElement()}
|
||||||
<span className="flex-1 italic">{professor}</span>
|
<span className="flex-1 italic">{professor}</span>
|
||||||
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
||||||
@@ -91,11 +101,8 @@ function SyllabusListing({ className, professor, fileName, description, classLen
|
|||||||
<span className="w-3/4 font-serif">"{description}"</span>
|
<span className="w-3/4 font-serif">"{description}"</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<button className="aspect-square flex items-center align-middle justify-center hover:bg-slate-200 rounded-sm text-black" onClick={handleView}>
|
|
||||||
<span className="material-symbols-rounded flex-1 text-2xl">open_in_new</span>
|
|
||||||
</button>
|
|
||||||
<span className="flex-1"></span>
|
<span className="flex-1"></span>
|
||||||
<button className="aspect-square flex items-center align-middle justify-center hover:bg-slate-200 rounded-sm text-black" onClick={() => setHideMore(!hideMore)}>
|
<button className="aspect-square flex items-center align-middle justify-center hover:bg-slate-200 rounded-sm text-white" onClick={() => setHideMore(!hideMore)}>
|
||||||
<span className="material-symbols-rounded text-2xl">more_horiz</span>
|
<span className="material-symbols-rounded text-2xl">more_horiz</span>
|
||||||
<div className="relative" hidden={hideMore}>
|
<div className="relative" hidden={hideMore}>
|
||||||
<button ref={buttonRef} onClick={handleFlag} className="absolute border-black border-2 -left-14 top-2 drop-shadow-lg bg-white rounded-lg aspect-square p-4">
|
<button ref={buttonRef} onClick={handleFlag} className="absolute border-black border-2 -left-14 top-2 drop-shadow-lg bg-white rounded-lg aspect-square p-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user