This commit is contained in:
Leo Wen
2025-04-03 21:55:11 -07:00
parent d1520e1e5d
commit 5a08aa2ce0
398 changed files with 313241 additions and 203 deletions
+24 -10
View File
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react'
import './App.css'
import './pages/App.css'
import SyllabusListing, { ClassListing } from '../components/syllabus_comp';
import SyllabusListing, { ClassListing } from './components/syllabus_comp';
import server from '../main'
import server from './main'
import Select from "react-select"
import AsyncSelect from "react-select/async";
@@ -12,8 +12,13 @@ import AsyncSelect from "react-select/async";
import { useLocation, useNavigate } from 'react-router-dom';
import { isMobile } from "is-mobile";
import { useParams } from 'react-router-dom';
import { School } from './UploadSyllabi';
import { AmbiguousClass } from '../welcome';
import { School } from './pages/UploadSyllabi';
import { AmbiguousClass } from './welcome';
export var initialQuery = "";
export function setInitialQuery(value: string) {
initialQuery = value;
}
interface Filter {
school?: string;
@@ -27,6 +32,8 @@ function App() {
const [querying, setQuerying] = useState(false);
const [initialized, setIntialized] = useState(false);
const [filter, setFilter] = useState<Filter>({});
@@ -74,6 +81,7 @@ function App() {
}, [filter]);
async function handleSearch() {
if (!initialized) return;
if (query === "admin") {
navigate('admin');
}
@@ -115,6 +123,12 @@ function App() {
},
[location])
useEffect(() => {
setQuery(initialQuery);
setIntialized(true);
handleSearch();
console.log("initialQuery set to" + initialQuery)
}, [])
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
@@ -133,22 +147,22 @@ function App() {
<div className="place-items-center justify-center flex w-full flex-col">
<div className="w-full flex space-y-2 flex-col">
<div className="flex pt-2">
<div className="flex-1 flex border-2 border-black focus:outline-none rounded-lg">
<div className="flex-1 flex border border-gray-400 focus:outline-none rounded-lg">
<input
type="search"
onKeyDown={handleKeyDown}
placeholder="search, hit enter or the search button to enter query"
className="focus:outline-none placeholder:text-black font-[Parkinsans] font-bold focus:ring-0 bg-transparent border-none rounded-lg flex-1 text-black"
className="focus:outline-none placeholder:text-gray-500 font-normal focus:ring-0 bg-transparent border-none rounded-lg flex-1 text-black"
value={query}
onChange={(e) => setQuery(e.target.value)}>
</input>
<button className="aspect-square flex justify-center items-center" onClick={() => { setQuery(''); handleSearch() }}>
<button className="aspect-square p-2 pr-0 flex justify-center items-center" onClick={() => { setQuery(''); handleSearch() }}>
<span className="material-symbols-rounded hover:text-black text-gray-500">
close
</span>
</button>
<button className="aspect-square flex justify-center items-center" onClick={handleSearch}>
<span className="material-symbols-rounded text-black">
<button className="aspect-square p-2 flex justify-center items-center" onClick={handleSearch}>
<span className="material-symbols-rounded hover:text-black text-gray-500">
search
</span>
</button></div>
+11 -11
View File
@@ -16,10 +16,10 @@ function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="bg-[#fcfffd] text-[#051008] w-full h-full">
<div className={`w-full text-left p-1 flex place-items-center justify-center border-b border-gray-300`}>
<div className={`flex-1 gap-4 place-items-center ${!isMobile() ? "flex" : "flex flex-col"}`}>
<div className={`w-full sticky shadow-sm top-0 left-0 backdrop-blur-md z-50 text-left p-2 flex place-items-center justify-center border-b border-gray-300`}>
<div className={`flex-1 gap-4 place-items-center ${!isMobile() ? "flex" : "flex flex-col"}`}>
{isMobile() ?
<button onClick={() => navigate("/upload")} className="p-1 flex-1 rounded-full hover:drop-shadow-xl hover:bg-slate-950 font-[Parkinsans] pl-2 pr-2 font-bold bg-black">
<button onClick={() => navigate("/upload")} className="p-1 flex-1 rounded-full hover:drop-shadow-xl hover:bg-slate-950 pl-2 pr-2 font-bold bg-black">
<Marquee className="anim-enter-blur max-w-screen" delay={1} gradient={true} gradientWidth="10%" gradientColor="black" speed={15}>
<div className="flex gap-1">
<span className="bg-blue-800 pl-2 gap-2 pr-2 rounded-md flex">
@@ -34,20 +34,20 @@ function Layout({ children }: { children: React.ReactNode }) {
</div>
</Marquee>
</button> :
<div className="flex-1 grid grid-cols-3 gap-4 place-items-center">
<text onClick={() => navigate("/")} className="text-xl text-[#29a847] cursor-pointer hover:drop-shadow-md font-extrabold flex-1 font-bol">📚 Share My Syllabus <span className="font-light text-purple-600">beta</span></text>
<div className="flex-1 flex place-items-center text-center">
<div className="flex-1 grid grid-cols-3 gap-4 justify-center place-items-center">
<text onClick={() => navigate("/")} className="text-xl bg-clip-text flex place-items-center text-transparent bg-gradient-to-tl from-purple-600 to-blue-700 cursor-pointer gap-1 hover:drop-shadow-md font-extrabold flex-1 font-bol"><img className="h-6 drop-shadow-lg" src="/logo_borderless.png"></img>Share Syllabus <span className="font-light text-purple-600"> beta</span></text>
<div onClick={() => navigate("/upload")} className="flex-1 cursor-pointer hover:backdrop-blur-md rounded-full flex place-items-center text-center">
<span className="material-symbols-rounded p-1 text-black">web_traffic</span>
<button onClick={() => navigate("/upload")} className="p-1 rounded-full anim-enter-width hover:drop-shadow-xl hover:bg-slate-950 font-[Parkinsans] pl-2 pr-2 font-bold bg-black">
<button onClick={() => navigate("/upload")} className="p-1 rounded-full anim-enter-width hover:drop-shadow-xl hover:bg-slate-950 pl-2 pr-2 font-bold bg-black">
<Marquee className="anim-enter-blur w-40" delay={1} gradient={true} gradientWidth="10%" gradientColor="black" speed={15}>
<div className="flex gap-1">
<span className="bg-blue-800 pl-2 gap-2 pr-2 rounded-md flex">
<span className="text-white font-sans">Click to Share A Syllabus!</span>
<span className="text-white">Click to Share A Syllabus!</span>
</span>
<span className="material-symbols-rounded text-white">web_traffic</span>
<span className="bg-purple-700 pl-2 gap-2 pr-2 place-items-center rounded-md flex">
<span className="text-white font-bold font-sans">Sharying a syllabus awards 2 volunteer hours</span>
<span className="text-white font-bold">Sharying a syllabus awards 2 volunteer hours</span>
</span>
<span className="material-symbols-rounded pr-1 text-white">web_traffic</span>
</div>
@@ -55,8 +55,8 @@ function Layout({ children }: { children: React.ReactNode }) {
</button>
<span className="material-symbols-rounded p-1 text-black">add</span>
</div>
<div className="flex gap-4 font-light text-gray-600">
<Link to="/moderator" className=" text-gray-600">Moderator Panel</Link>
<div className="flex flex-row gap-4 font-light text-gray-600 items-center">
<Link to="/moderator" className="text-gray-600">Moderator Panel</Link>
<Link to="/blog" className="text-gray-600">Blog</Link>
</div>
</div>
+3 -3
View File
@@ -1,5 +1,5 @@
import "../syllabuslisting.css";
import "../pages/App.tsx";
import "../App.tsx";
import { useNavigate } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
@@ -99,7 +99,7 @@ function SyllabusListing({ className, school, professor, professorId, fileName,
return (
<>
<div className="w-full border-gray-400 border flex gap-4 cursor-pointer text-[#051008] rounded-lg p-3" onClick={handleView} >
<div className="w-full border-gray-400 bg-[rgba(255,255,255,0.7)] border flex gap-4 cursor-pointer text-[#051008] rounded-lg p-3 backdrop-blur-md" onClick={handleView} >
<div className="flex text-left flex-col w-1/4">
<span className="font-extrabold">{className}</span>
<span className="text-xs text-gray-800">{fullClassName}</span>
@@ -111,7 +111,7 @@ function SyllabusListing({ className, school, professor, professorId, fileName,
{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>}
</div>
<div className="flex-1 flex flex-col text-center">
<span className="w-3/4 font-serif">"{description}"</span>
<span className="font-serif">"{description}"</span>
</div>
<div className="flex flex-col">
<span className="flex-1"></span>
+2 -6
View File
@@ -1,7 +1,7 @@
import { createRoot } from 'react-dom/client';
import { Route, Routes, HashRouter } from 'react-router-dom';
import './index.css';
import App from './pages/App.tsx';
import App from './App.tsx';
import View from './components/FileView.tsx';
import Admin from './pages/Admin.tsx';
import Flag from './pages/Flag.tsx';
@@ -11,11 +11,9 @@ import Layout from './Layout.tsx';
import UploadSyllabi from './pages/UploadSyllabi.tsx';
import Professor from './pages/Professor.tsx';
import Welcome from './welcome.tsx';
import Blog from './pages/Blog.tsx';
import Moderator from './pages/Moderator/Moderator.tsx';
const server = "https://api.sharesyllabus.me";
export default server
export default server;
createRoot(document.getElementById('root')!).render(
<HashRouter>
@@ -30,8 +28,6 @@ createRoot(document.getElementById('root')!).render(
<Route path="/syllabus/:id" element={<Layout><Syllabus /></Layout>} />
<Route path="/upload" element={<Layout><UploadSyllabi /></Layout>} />
<Route path="/professor/:id" element={<Layout><Professor /></Layout>} />
<Route path="/blog/:file" element={<Blog/>}/>
<Route path="/moderator" element={<Moderator></Moderator>} />
</Routes>
</HashRouter>
);
+360
View File
@@ -0,0 +1,360 @@
import { useEffect, useState, useCallback } from 'react'
import SyllabusListing, { ClassListing } from '../../components/syllabus_comp';
import { Accordion, FileInput, } from "flowbite-react";
import Select from "react-select"
import AsyncSelect from "react-select/async";
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
import { useLocation, useNavigate } from 'react-router-dom';
import { isMobile } from "is-mobile";
import { useParams } from 'react-router-dom';
import { School } from '../UploadSyllabi';
import { AmbiguousClass } from '../../welcome';
interface Filter {
school?: string;
professor?: string;
class?: string;
}
function App() {
const navigate = useNavigate();
const [openModal, setOpenModal] = useState(false);
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [accordionOpen, setAccordionOpen] = useState(false);
const [tempFileId, setTempFileId] = useState<string>("");
const [file, setFile] = useState<File | null>(null);
const [filter, setFilter] = useState<Filter>({});
const [syllabi, setSyllabi] = useState<ClassListing[]>([]);
const [query, setQuery] = useState<string>("");
const [debouncedQuery, setDebouncedQuery] = useState(query);
const { college, professor, class: classParam } = useParams();
const [collegeOptions, setCollegeOptions] = useState<School[]>([]);
const [initialSearchPerformed, setInitialSearchPerformed] = useState(false);
useEffect(() => {
if (college) {
setFilter({ ...filter, school: college });
}
if (professor) {
setFilter({ ...filter, professor: professor });
}
if (classParam) {
setFilter({ ...filter, class: classParam });
}
}, [college, professor, classParam])
// Handle query changes with debounce
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
}, 100);
return () => clearTimeout(timer);
}, [query]);
// Trigger search when debouncedQuery changes
useEffect(() => {
if (initialSearchPerformed) {
handleSearch();
}
}, [debouncedQuery]);
useEffect(() => {
handleSearch();
setInitialSearchPerformed(true);
}, [filter]);
async function handleSearch() {
if (query === "admin") {
navigate('admin');
}
let end = ``;
if (filter.school) end += `&s=${filter.school}`;
if (filter.professor) end += `&p=${filter.professor}`;
if (filter.class) end += `&c=${filter.class}`;
try {
var response = await fetch(`${import.meta.env.VITE_SERVER_ADDRESS}/search/?q=${debouncedQuery}${end}`);
var data = await response.json();
setSyllabi(data);
} catch {
console.error("error")
}
}
function onCloseModal() {
setOpenModal(false);
setEmail('');
setName('');
setFile(null);
setAccordionOpen(false);
}
function uploadFile() {
let formdata = new FormData();
if (file === null) {
alert("File not uploaded");
throw new Error("File not uploaded");
}
formdata.append("upload", file);
formdata.append("email", email);
formdata.append("name", name);
fetch(`${import.meta.env.VITE_SERVER_ADDRESS}/create`, {
method: 'POST',
body: formdata,
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`Server error: ${response.statusText}`);
}
setAccordionOpen(true);
let data = await response.json();
setTempFileId(data.fileId);
setOpenModal(false);
alert("Upload successful! You will receive an email with your volunteer hours within 3 days.");
})
.catch((error) => {
console.error("Upload failed:", error);
alert("Upload failed: " + error.message);
});
}
useEffect(() => {
}, [tempFileId])
const location = useLocation();
useEffect(() => {
fetch(`${import.meta.env.VITE_SERVER_ADDRESS}/schools`)
.then(async (response) => {
if (!response.ok) {
throw new Error(`Server error: ${response.statusText}`);
}
let data = await response.json();
setCollegeOptions(data);
})
.catch((error) => {
console.error("Failed to fetch schools:", error);
});
},
[location])
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files === null) return
if (e.target.files[0] === null) return
setFile(e.target.files[0]);
setAccordionOpen(true);
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === "Enter") {
handleSearch();
}
}
return (
<>
<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="w-full flex place-items-center justify-center pt-1">
<div className="w-3/4 flex justify-center items-center">
<div className="place-items-center justify-center flex w-full flex-col">
<div className="w-full flex space-y-2 flex-col">
<div className="flex pt-2">
<div className="flex-1 flex border border-gray-400 focus:outline-none rounded-lg">
<input
type="search"
onKeyDown={handleKeyDown}
placeholder="search, hit enter or the search button to enter query"
className="focus:outline-none placeholder:text-gray-500 font-normal focus:ring-0 bg-transparent border-none rounded-lg flex-1 text-black"
value={query}
onChange={(e) => setQuery(e.target.value)}>
</input>
<button className="aspect-square p-2 flex justify-center items-center" onClick={handleSearch}>
<span className="material-symbols-rounded text-black">
search
</span>
</button></div>
</div>
<div className="flex flex-1 h-max gap-2">
<div className="text-left w-1/4 rounded-b-none rounded-lg min-h-screen">
<div className="flex gap-2 flex-col">
<Select
className="p-1 text-md bg-[#fafdfb] border-gray-400 border rounded-md"
placeholder="School District"
options={collegeOptions}
getOptionLabel={(option: School) => option.name}
getOptionValue={(option: School) => option.name}
value={filter.school ? collegeOptions.find(option => option.name === filter.school) : undefined}
onChange={(selectedOption: School | null) => {
if (selectedOption) {
setFilter({ ...filter, school: selectedOption.name });
}
}}
isSearchable={true}
components={{
DropdownIndicator: (props) => (
<div {...props.innerProps} onClick={() => {
props.selectOption({ id: "", name: "No Filter", fullName: "No Filter" });
setFilter({ ...filter, school: undefined });
}}>
<span className="material-symbols-rounded text-red-700">close</span>
</div>
),
}}
styles={{
control: (provided) => ({
...provided,
backgroundColor: '#fafdfb',
border: 'none',
boxShadow: 'none',
}),
input: (provided) => ({
...provided,
boxShadow: 'none',
}),
placeholder: (provided) => ({
...provided,
color: '#888',
}),
}}
/>
<AsyncSelect
className="p-1 text-md bg-[#fafdfb] border-gray-400 border rounded-md "
placeholder="Course Name: ENG-1A, General Psych, etc."
value={filter.class ? { className: filter.class, school: { name: '' } } as AmbiguousClass : undefined}
styles={{
control: (provided) => ({
...provided,
backgroundColor: '#fafdfb',
border: 'none',
boxShadow: 'none',
}),
input: (provided) => ({
...provided,
boxShadow: 'none',
}),
placeholder: (provided) => ({
...provided,
color: '#888',
}),
}}
loadOptions={async (inputValue) => {
if (!inputValue) return [];
let responses = await Promise.all(
collegeOptions.map(async (college) => {
const res = await fetch(`${import.meta.env.VITE_SERVER_ADDRESS}/search/class/?q=${inputValue}&s=${college.name}`, {
method: "POST",
});
const data = await res.json();
return data.map((item: AmbiguousClass) => ({
...item,
school: college,
}));
})
);
// Add an option with no class
responses.unshift([{ className: "", school: { name: "No Class" } }]);
return responses.flat();
}}
formatOptionLabel={(option: AmbiguousClass) => (
<div>
<div>{option.className}</div>
<div style={{ fontSize: '0.8em', color: '#888' }}>{option.school.name}</div>
</div>
)}
getOptionValue={(option: AmbiguousClass) => option.className}
onChange={(selectedOption: AmbiguousClass | null) => {
if (selectedOption) {
setFilter({ ...filter, class: selectedOption.className });
}
}}
isSearchable={true}
cacheOptions
defaultOptions
components={{
DropdownIndicator: (props) => (
<div {...props.innerProps} onClick={() => {
setFilter({ ...filter, class: "" });
props.selectOption({ id: 0, className: "", fullClassName: "", disciplineId: "", school: { id: "", name: "No Class", fullName: "No Class" } });
}}>
<span className="material-symbols-rounded text-red-700">close</span>
</div>
),
}}
/>
{filter.professor && <div className="flex p-2 bg-black text-white rounded-lg gap-2">
<span className="text-md font-bold">Professor:</span>
<span className="text-md">{filter.professor}</span>
<button className="text-md text-red-700" onClick={() => setFilter({ ...filter, professor: undefined })}>Clear</button>
</div>}
</div>
</div>
<div className="flex-1">
<div className="flex flex-col gap-2">
{syllabi.map((syllabus, i) => {
return <SyllabusListing key={i} school={syllabus.school} fileName={syllabus.fileName} professorId={syllabus.professorId} 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>}
{isMobile() === true && <div className="p-1 pt-2">
<div className="">
<div className="flex space-x-1">
<div className="flex-1 flex bg-white rounded-lg">
<input type="search" onKeyDown={handleKeyDown} placeholder="search class or professor" className="focus:outline-none w-4/5 bg-transparent border-none flex-1 text-black" value={query} onChange={(e) => setQuery(e.target.value)}>
</input>
<button className="aspect-square flex justify-center bg-white rounded-lg items-center" onClick={handleSearch}>
<span className="material-symbols-rounded text-black">
search
</span>
</button></div>
</div>
<div className="listing">
{syllabi.map((syllabus, i) => {
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} fullClassName={syllabus.fullClassName} textbookCost={syllabus.textbookCost} description={syllabus.description} classLength={syllabus.classLength} professor={syllabus.professor} />
})}
</div>
</div>
</div>}
</>
)
}
export default App
+1 -1
View File
@@ -25,7 +25,7 @@ function Syllabus() {
<div className="text-left place-items-center justify-center flex w-3/4">
<div className="w-2/5 h-full flex flex-col">
<div className="flex flex-col">
<text className="text-3xl text-red-400 font-[Parkinsans] font-black">{syllabus?.className}</text>
<text className="text-3xl text-red-400 font-black">{syllabus?.className}</text>
<text className="">{syllabus?.fullClassName}</text>
<text className="italic underline">{syllabus?.professor}</text>
</div>
+145 -164
View File
@@ -1,71 +1,52 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
import type { Class, Professor, School } from "./types";
import type { Class, School } from "./types";
import Select from "react-select";
import AsyncSelect from "react-select/async";
import server from "./main";
import { useNavigate } from "react-router-dom";
import isMobile from "is-mobile";
import { setInitialQuery } from "./App";
import SyllabusListing, { ClassListing } from "./components/syllabus_comp";
// Removed initialQuery import as it cannot be reassigned
export interface AmbiguousClass extends Class {
school: School;
}
function Welcome() {
const navigate = useNavigate();
// State for tracking user selections
const [selectedCollege, setSelectedCollege] = useState<string>("");
const [selectedClass, setSelectedClass] = useState<string>("");
const [selectedProfessor, setSelectedProfessor] = useState<string>("");
const [shouldNavigate, setShouldNavigate] = useState(false);
const [selectedOption, setSelectedOption] = useState<ClassListing | string>("");
const [collegeOptions, setCollegeOptions] = React.useState<School[]>([]);
const [defaultClassOptions, setDefaultClassOptions] = useState<AmbiguousClass[]>([]);
const [defaultProfessorOptions, setDefaultProfessorOptions] = useState<Professor[]>([]);
const [query] = useState<string>("");
const [syllabi, setSyllabi] = useState<ClassListing[]>([]);
// Fetch syllabi for an empty search
useEffect(() => {
fetch(`${server}/schools`, {
method: "GET",
}).then((res) => res.json()).then((data) => {
setCollegeOptions(data);
console.log('data', data);
});
}, []);
useEffect(() => {
const loadDefaultOptions = async () => {
const classResponses = await Promise.all(
collegeOptions.map(async (college) => {
const res = await fetch(`https://api.sharesyllabus.me/search/class/&q=A&s=${college.name}`, {
method: "POST",
});
const data = await res.json();
return data.map((item: AmbiguousClass) => ({
...item,
school: college,
}));
})
);
setDefaultClassOptions(classResponses.flat());
const professorResponses = await Promise.all(
collegeOptions.map(async (college) => {
const res = await fetch(`${server}/search/professor/&s=${college.name}`, {
method: "POST",
});
const data = await res.json();
return data;
})
);
setDefaultProfessorOptions(professorResponses.flat());
const fetchSyllabi = async () => {
try {
const response = await fetch(`${server}/search/?q=`, {
method: "GET",
});
const data = await response.json();
setSyllabi(data);
} catch (error) {
console.error("Failed to fetch syllabi:", error);
}
};
if (collegeOptions.length > 0) {
loadDefaultOptions();
}
}, [collegeOptions]);
fetchSyllabi();
}, []);
// Navigation logic in useEffect to ensure we have the latest state
useEffect(() => {
@@ -75,152 +56,152 @@ function Welcome() {
let path = "/app";
// Add college parameter (if present)
if (selectedCollege) {
path += `/${encodeURIComponent(selectedCollege)}`;
} else if (selectedProfessor || selectedClass) {
// Need placeholder if we have later params but no college
path += "/_";
}
// Add professor parameter (if present)
if (selectedProfessor) {
path += `/${encodeURIComponent(selectedProfessor)}`;
} else if (selectedClass && (selectedCollege || path.includes("/_"))) {
// Need placeholder if we have class but no professor
path += "/_";
}
// Add class parameter (if present)
if (selectedClass) {
path += `/${encodeURIComponent(selectedClass)}`;
}
console.log("Navigating to:", path);
setInitialQuery(query);
navigate(path);
// Reset the navigation flag
setShouldNavigate(false);
}, [selectedCollege, selectedProfessor, selectedClass, shouldNavigate, navigate]);
}, [shouldNavigate, navigate]);
return (
<div className={`w-full text-left h-full bg-[#fafdfb] gap-2 text-[#051008] place-items-center ${!isMobile() ? "p-32" : "p-4"} flex flex-col`}>
<div className={`w-full flex-1 text-left h-full bg-[#fafdfb] bg-no-repeat bg-center gap-2 text-[#051008] place-items-center ${!isMobile() ? " bg-[url(/logo_big.png)] pt-32" : "p-4"} flex flex-col`}>
{!isMobile() &&
<div className="flex-1 flex flex-col place-items-center gap-2">
<h1 className="font-[Parkinsans] text-5xl w-2/3 font-extrabold">📚 Enter your <span className="text-[#47c068]"> School District, Course Name, or Professor's Name, </span> to begin!</h1>
<Select
className="p-1 text-lg bg-[#fafdfb] w-2/3 rounded-md border-gray-300"
placeholder="School District: i.e. RCCD, Chaffey, SBVC, etc."
options={collegeOptions}
getOptionLabel={(option: School) => option.name}
getOptionValue={(option: School) => option.name}
onChange={(selectedOption: School | null) => {
if (selectedOption) {
setSelectedCollege(selectedOption.name);
setShouldNavigate(true);
}
}}
isSearchable={true}
/>
<div className="flex flex-col place-items-center z-50 gap-2">
<div className="w-2/3 p-2 bg-[rgba(0,0,0,0.75)] shadow-md gap-2 flex flex-col place-items-center backdrop-blur-md rounded-md border border-gray-400 bg-origin-content">
<h1 className=" text-5xl font-black text-white">📚 Enter your <span className=" bg-clip-text font-black text-transparent bg-gradient-to-tl from-yellow-400 to-orange-200"> School District, Course Name, or Professor's Name, </span> to begin!</h1>
<AsyncSelect
className="p-1 text-lg bg-[#fafdfb] w-2/3 rounded-md border-gray-300"
placeholder="Course Name: ENG-1A, General Psych, etc."
value={selectedOption}
className="w-full text-md bg-[#fafdfb] border rounded-lg border-gray-400"
placeholder="Search for school, course, or professor"
loadOptions={async (inputValue) => {
if (!inputValue) {
inputValue = "A";
console.log('inputValue', inputValue);
if (!inputValue) return [];
try {
const response = await fetch(`${server}/search/?q=${inputValue}`, {
method: "GET",
});
const data = await response.json();
// Map the fetched data into options
const options = data.map((item: ClassListing) => ({
label: `${item.class?.className || "Unknown Class"} - ${item.professor || "Unknown Professor"}`,
value: item,
}));
// Prepend a custom option based on the input value
const customOption = {
label: `Search for "${inputValue}"`,
value: { customSearch: inputValue },
};
return [customOption, ...options]; // Prepend the custom option
} catch (error) {
console.error("Failed to fetch options:", error);
return [];
}
const responses = await Promise.all(
collegeOptions.map(async (college) => {
const res = await fetch(`${server}/search/class/?q=${inputValue}&s=${college.name}`, {
method: "POST",
});
const data = await res.json();
return data.map((item: AmbiguousClass) => ({
...item,
school: college,
}));
})
);
return responses.flat();
}}
defaultOptions={defaultClassOptions}
formatOptionLabel={(option: AmbiguousClass) => (
<div>
<div>{option.className}</div>
<div style={{ fontSize: '0.8em', color: '#888' }}>{option.school.name}</div>
</div>
)}
getOptionValue={(option: AmbiguousClass) => option.className}
onChange={(selectedOption: AmbiguousClass | null) => {
if (selectedOption) {
setSelectedClass(selectedOption.className);
setShouldNavigate(true);
onChange={(option: any) => {
if (option) {
console.log("Option selected:", option);
// Handle the custom prepended option differently if needed
if (option.value.customSearch) {
console.log(`Custom search triggered for: ${option.value.customSearch}`);
setInitialQuery(option.value.customSearch)
navigate(`/app/`);
return;
}
setSelectedOption(option); // Update the selected option state
let path = "/app";
setInitialQuery(option.label);
navigate(path); // Redirect to the desired path
}
}}
isSearchable={true}
cacheOptions
/>
<AsyncSelect
className="p-1 text-lg bg-[#fafdfb] w-2/3 rounded-md border-gray-300"
placeholder="Professor Name: John Doe, Jane Smith, etc."
loadOptions={async (inputValue) => {
const responses = await Promise.all(
collegeOptions.map(async (college) => {
const res = await fetch(`${server}/search/professor/?q=${inputValue}&s=${college.name}`, {
method: "POST",
});
const data = await res.json();
return data;
})
);
return responses.flat();
styles={{
control: (provided) => ({
...provided,
border: 'none',
boxShadow: 'none',
backgroundColor: '#fafdfb',
borderRadius: '8px',
}),
input: (provided) => ({
...provided,
boxShadow: 'none',
}),
placeholder: (provided) => ({
...provided,
color: '#888',
}),
menu: (provided) => ({
...provided,
zIndex: 99999,
pointerEvents: "all"
}),
menuPortal: (provided) => ({
...provided,
zIndex: 99999,
pointerEvents: "all"
}),
}}
defaultOptions={defaultProfessorOptions}
getOptionLabel={(option: Professor) => option.name}
getOptionValue={(option: Professor) => option.name}
onChange={(selectedOption: Professor | null) => {
if (selectedOption) {
setSelectedProfessor(selectedOption.name);
setShouldNavigate(true);
}
}}
isSearchable={true}
cacheOptions
/>
</div>
</div>
}
{isMobile() &&
<div className="text-center flex place-items-center flex-col gap-4 pt-4">
{isMobile() &&
<div className="text-center z-10 flex place-items-center flex-col gap-4 pt-4">
<img className="w-1/2 text-center" src="https://akns-images.eonline.com/eol_images/Entire_Site/201363/rs_600x600-130703124143-600.Gru.mh.070313.jpg?fit=around%7C1080:1080&output-quality=90&crop=1080:1080;center,top"></img>
<h1 className="font-sans font-black text-2xl text-blue-600">Search Syllabuses in Your School!</h1>
{/* Search button as fallback */}
<div className="flex flex-col text-center gap-2">
{!isMobile() && <text className="relative">or </text>}
<button
className="p-2 px-4 bg-black text-white rounded-xl drop-shadow-md hover:drop-shadow-xl cursor-pointer transition-colors"
onClick={() => setShouldNavigate(true)}
>
Just Open the Search 🚀
</button>
</div>
</div>
}
{/* Search button as fallback */}
<div className="flex flex-col text-center gap-2">
{!isMobile() && <text className="relative">or </text>}
<button
className="p-2 px-4 bg-black text-white rounded-xl drop-shadow-md hover:drop-shadow-xl cursor-pointer transition-colors"
onClick={() => setShouldNavigate(true)}
>
Just Open the Search 🚀
</button>
{!isMobile() ? <Marquee speed={10} gradient={true} className="w-full relative flex-1 gap-2">
{syllabi.map((syllabus) => (
<div className="mx-1">
<SyllabusListing
key={syllabus.id || syllabus.fileName}
className={syllabus.class?.className}
school={syllabus.school}
professor={syllabus.professor}
professorId={syllabus.professorId}
fileName={syllabus.fileName}
description={syllabus.description}
classLength={syllabus.classLength}
fullClassName={syllabus.class?.fullClassName}
textbookCost={syllabus.textbookCost}
/>
</div>
))}
</Marquee> : <div className="h-1/3"></div>}
<div className="text-center ">
<text className="text-xl ">Made By <span className=" font-bold rounded text-[#47c068]">the College Success Club of JFKMCHS</span>. Gain up to 2 hrs of community service hours by contributing syllabuses into our index. Visit our blog for free resources for college students. &nbsp;</text>
</div>
{!isMobile() ? <div className="flex-1"></div> : <div className="h-1/3"></div>}
<div className="relative w-full overflow-hidden">
<div className="absolute top-0 bottom-0 left-0 w-8 bg-gradient-to-r from-[#fafdfb] to-transparent z-10"></div>
<div className="absolute top-0 bottom-0 right-0 w-8 bg-gradient-to-l from-[#fafdfb] to-transparent z-10"></div>
<Marquee speed={10} gradient={false}>
<text className="text-xl font-[Parkinsans]">Made By <span className=" font-bold rounded text-[#47c068]">the College Success Club of JFKMCHS</span>. Gain up to 2 hrs of community service hours by contributing syllabuses into our index. Visit our blog for free resources for college students. &nbsp;</text>
</Marquee>
</div>
</div>
)
}