Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions apps/searchneu/app/faq/faq-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { FAQDropDown } from "@/components/ui/accordion";

interface FAQ {
id: number;
title: string;
description: string;
sections?: { heading: string; body: string }[];
}

export interface FAQTab {
label: string;
faqs: FAQ[];
}

export function FAQTabs({ tabs }: { tabs: FAQTab[] }) {
const defaultValue = tabs[0]?.label.toLowerCase() ?? "";

return (
<Tabs defaultValue={defaultValue}>
<TabsList className="mb-4 ml-4 h-auto gap-4 rounded-none border-b border-neutral-300 bg-transparent p-0">
{tabs.map((tab) => (
<TabsTrigger
key={tab.label}
value={tab.label.toLowerCase()}
className="text-neu4 data-[state=active]:text-neu7 -mb-px h-auto cursor-pointer rounded-none border-b border-transparent px-0 py-1 text-xs font-bold tracking-normal uppercase data-[state=active]:border-neutral-500 data-[state=active]:bg-transparent"
>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.label} value={tab.label.toLowerCase()}>
<div className="pl-4">
{tab.faqs.map((faq) => (
<FAQDropDown
key={faq.id}
title={faq.title}
description={faq.description}
sections={faq.sections}
/>
))}
</div>
</TabsContent>
))}
</Tabs>
);
}
17 changes: 7 additions & 10 deletions apps/searchneu/app/faq/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import faqData from "../../faqs.json";
import howToData from "../../howto.json";
import { FAQDropDown } from "@/components/ui/accordion";
import { HowToCard } from "@/components/ui/how-to-card";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { faqHowToFlag } from "@/lib/flags";
import { FAQTabs } from "./faq-tabs";

export default async function Page() {
const showHowTo = await faqHowToFlag();
Expand All @@ -27,15 +27,12 @@ export default async function Page() {
</>
)}
<h2 className="text-neu7 mb-4 pl-4 text-2xl font-bold">FAQs</h2>
<div className="pl-4">
{faqData.faqs.map((faq) => (
<FAQDropDown
key={faq.id}
title={faq.title}
description={faq.description}
/>
))}
</div>
<FAQTabs
tabs={[
{ label: "Catalog", faqs: faqData.catalog },
{ label: "Scheduler", faqs: faqData.scheduler },
]}
/>
</div>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions apps/searchneu/app/scheduler/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Header } from "@/components/navigation/Header";
import { FullScreenRequired } from "@/components/scheduler/FullScreenRequired";

export default async function Layout({ children }: LayoutProps<"/rooms">) {
return (
<div className="flex h-screen flex-col overflow-hidden pt-4">
<FullScreenRequired />
<Header />
<div className="min-h-0 flex-1">{children}</div>
</div>
Expand Down
17 changes: 17 additions & 0 deletions apps/searchneu/components/icons/Desktop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function DesktopIcon({ className }: { className?: string }) {
return (
<svg
className={className}
width="128"
height="128"
viewBox="0 0 128 128"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M104 20H24C20.8174 20 17.7652 21.2643 15.5147 23.5147C13.2643 25.7652 12 28.8174 12 32V88C12 91.1826 13.2643 94.2348 15.5147 96.4853C17.7652 98.7357 20.8174 100 24 100H60V108H48C46.9391 108 45.9217 108.421 45.1716 109.172C44.4214 109.922 44 110.939 44 112C44 113.061 44.4214 114.078 45.1716 114.828C45.9217 115.579 46.9391 116 48 116H80C81.0609 116 82.0783 115.579 82.8284 114.828C83.5786 114.078 84 113.061 84 112C84 110.939 83.5786 109.922 82.8284 109.172C82.0783 108.421 81.0609 108 80 108H68V100H104C107.183 100 110.235 98.7357 112.485 96.4853C114.736 94.2348 116 91.1826 116 88V32C116 28.8174 114.736 25.7652 112.485 23.5147C110.235 21.2643 107.183 20 104 20ZM24 28H104C105.061 28 106.078 28.4214 106.828 29.1716C107.579 29.9217 108 30.9391 108 32V72H20V32C20 30.9391 20.4214 29.9217 21.1716 29.1716C21.9217 28.4214 22.9391 28 24 28ZM104 92H24C22.9391 92 21.9217 91.5786 21.1716 90.8284C20.4214 90.0783 20 89.0609 20 88V80H108V88C108 89.0609 107.579 90.0783 106.828 90.8284C106.078 91.5786 105.061 92 104 92Z"
fill="#F9F9F9"
/>
</svg>
);
}
24 changes: 24 additions & 0 deletions apps/searchneu/components/scheduler/FullScreenRequired.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";

import { DesktopIcon } from "@/components/icons/Desktop";

export function FullScreenRequired() {
return (
<div className="fixed inset-0 z-50 flex min-h-screen flex-col items-center justify-center bg-[#333] min-[1180px]:hidden">
<DesktopIcon className="mb-3 h-[100px] w-[100px]" />
<h2 className="mb-3 font-[Lato] text-[18px] leading-[13px] font-semibold text-white">
Full Screen Required
</h2>
<p className="mb-3 text-center font-[Lato] text-[16px] leading-[20.8px] font-normal text-white">
For the best experience, please use SearchNEU Scheduler on a larger
screen.
</p>
<button
onClick={() => window.location.reload()}
className="flex cursor-pointer items-center justify-center gap-[10px] rounded-lg bg-[#E63946] px-[20px] py-[12px] font-[Lato] text-[16px] leading-[12px] font-bold text-white hover:bg-[#d32f3c]"
>
Reload
</button>
</div>
);
}
24 changes: 14 additions & 10 deletions apps/searchneu/components/scheduler/dashboard/PlanCard/PlanCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,25 @@ export function PlanCard({ plan, onDelete, campuses, nupaths }: PlanCardProps) {
return (
<div
key={course.courseId}
className="text-neu8 relative flex items-center gap-2 overflow-hidden rounded-lg bg-[#F8F9F9] px-3 py-2.5 text-sm"
className="relative flex min-w-0 items-center gap-1.5 overflow-hidden rounded-sm py-1 pr-3 pl-1 text-sm"
style={{
backgroundColor: color.fill,
}}
>
{/* Thin vertical color bar on left border */}
<div
className="absolute top-0 left-0 h-full w-2"
className="h-full w-1 shrink-0 rounded-full"
style={{ backgroundColor: color.accent }}
/>
<span className="text-base font-bold">
{course.courseSubject} {course.courseNumber}
</span>
<span className="text-neu6 flex-1 truncate">
{course.courseName}
</span>
<div className="flex items-center gap-1.5 pt-1 pl-1.5">
<span className="shrink-0 text-sm font-bold text-[#333]">
{course.courseSubject} {course.courseNumber}
</span>
<span className="min-w-0 flex-1 truncate text-sm text-[#858585]">
{course.courseName}
</span>
</div>
{course.isLocked && (
<Lock className="text-red h-4 w-4 shrink-0" />
<Lock className="h-3.5 w-3.5 shrink-0 text-red-500" />
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
CourseSearchResult,
Term,
} from "@/lib/catalog/types";
import { CircleAlert } from "lucide-react";
import {
extractCoreqReqs,
fetchCoreqCourses,
Expand Down Expand Up @@ -220,7 +221,7 @@
};

syncInitialCourses();
}, [activeTerm, initialExistingPlan]);

Check warning on line 224 in apps/searchneu/components/scheduler/shared/modal/AddCoursesModal.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has a missing dependency: 'fetchCoreqs'. Either include it or remove the dependency array

const handleSelectCourse = async (course: CourseSearchResult) => {
if (
Expand Down Expand Up @@ -292,9 +293,7 @@
key={num}
onClick={() => setNumCourses(num)}
className={`flex h-5.5 w-10.5 cursor-pointer items-center justify-center rounded-[46px] text-xs font-semibold ${
numCourses === num
? "bg-red-500 text-white"
: "hover:bg-muted"
numCourses === num ? "bg-neu text-white" : "hover:bg-muted"
}`}
>
{num}
Expand Down Expand Up @@ -354,6 +353,17 @@
>
{isGenerating ? "Generating Schedules..." : "Generate Schedules"}
</Button>
{props.planId && (
<div className="flex w-full flex-col">
<div className="border-yellow text-yellow flex items-start gap-1 rounded-sm border bg-[#FFF6EB] py-2 pl-2 text-xs">
<CircleAlert className="h-4 w-4 shrink-0" />
<p>
Editing courses may remove favorited schedules that are no
longer possible
</p>
</div>
</div>
)}
</div>
</div>
</DialogContent>
Expand Down
28 changes: 23 additions & 5 deletions apps/searchneu/components/ui/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
import { ChevronDownIcon } from "lucide-react";
import { Accordion } from "radix-ui";

export interface FAQSection {
heading: string;
body: string;
}

export interface AccordionType {
title: string;
description: string;
sections?: FAQSection[];
}

export function FAQDropDown({ title, description }: AccordionType) {
export function FAQDropDown({ title, description, sections }: AccordionType) {
return (
<div className="mb-4 flex flex-col items-start self-stretch rounded-lg bg-[#FFF] p-6">
<Accordion.Root type="single" collapsible className="w-full">
<Accordion.Item value="item1">
<Accordion.Header>
<Accordion.Trigger className="data-[state=closed]:[&>span]:text-neu4 data-[state=open]:[&>span]:text-neu5 data-[state=closed]:text-neu7 data-[state=open]:text-neu8 data-[state=open]:text-neu8 flex w-full items-center justify-between data-[state=closed]:[&>span]:rotate-0 data-[state=closed]:[&>span]:duration-300 data-[state=open]:[&>span]:rotate-180 data-[state=open]:[&>span]:duration-300">
<Accordion.Trigger className="data-[state=closed]:[&>span]:text-neu4 data-[state=open]:[&>span]:text-neu5 data-[state=closed]:text-neu7 data-[state=open]:text-neu8 data-[state=open]:text-neu8 flex w-full cursor-pointer items-center justify-between data-[state=closed]:[&>span]:rotate-0 data-[state=closed]:[&>span]:duration-300 data-[state=open]:[&>span]:rotate-180 data-[state=open]:[&>span]:duration-300">
<h4 className="text-left text-xl leading-6 font-medium">
{title}
</h4>
Expand All @@ -24,9 +30,21 @@ export function FAQDropDown({ title, description }: AccordionType) {
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content className="data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up fade-in fade-out mt-4">
<p className="text-neu7 text-base leading-[19.2px] font-normal">
{description}
</p>
{description && (
<p className="text-neu7 text-base leading-[19.2px] font-normal">
{description}
</p>
)}
{sections && (
<div className="text-neu7 space-y-4 text-base leading-[19.2px] font-normal">
{sections.map((section, i) => (
<div key={i}>
<p className="font-bold">{section.heading}</p>
{section.body && <p>{section.body}</p>}
</div>
))}
</div>
)}
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
Expand Down
54 changes: 53 additions & 1 deletion apps/searchneu/faqs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"faqs": [
"catalog": [
{
"id": 1,
"title": "What is SearchNEU?",
Expand Down Expand Up @@ -40,5 +40,57 @@
"title": "What's next for SearchNEU?",
"description": "We're always looking for ways to enhance the experience of using SearchNEU. Right now, we're exploring new features and potential integrations between SearchNEU and GraduateNU. We'd love to hear your ideas—share your suggestions with us through the SearchNEU Contact Form!"
}
],
"scheduler": [
{
"id": 1,
"title": "What is SearchNEU Scheduler? How does it work?",
"description": "SearchNEU Scheduler is a schedule generator designed to help students efficiently find their ideal course schedule for an upcoming semester. To get started, students enter the courses they are considering. The tool then generates all possible schedule combinations. From there, students can refine their options using a range of filters, including hiding specific sections, setting preferred start and end times, or choosing desired days off."
},
{
"id": 2,
"title": "How many courses can I add to the schedule generator?",
"description": "Each user can add up to 6 courses to the generator, not including co-reqs. Additionally, users can add more than the number of courses they are planning to take for the semester to the generator. For example, if the user selects they are taking 4 courses for Fall 2026 but is still deciding between whether to take two electives, they can add both of these electives to the generator (with total 5 courses added to the generator)."
},
{
"id": 3,
"title": "What filters are in SearchNEU Scheduler?",
"description": "",
"sections": [
{
"heading": "Locking and Unlocking Classes",
"body": "All courses added to the generator are default \u201cunlocked.\u201d But if a user knows they need a specific course in their schedule (have to take that class), then they can \u201clock\u201d that class, ensuring it shows up in all schedules generated."
},
{
"heading": "Hiding and Unhiding Sections",
"body": "All sections of each course added to the generator are default shown in the generated schedules. To narrow down schedules, users can \u201chide\u201d certain sections with professors they don\u2019t want to take."
},
{
"heading": "Campuses",
"body": "The default campus course shown is Boston. But students on satellite campuses can select their campus location to ensure their schedules have accurate course sections and times."
},
{
"heading": "Time",
"body": "Users who do not want classes to start or end before a specific time can add a \u201cstart after\u201d and \u201cend before\u201d time, filtering down schedules that only have courses that start after the specified time and before the specified time."
},
{
"heading": "Free Days",
"body": "If users want certain days off could select specific days they don\u2019t want to have class, filtering down schedules that exclude classes on those days."
},
{
"heading": "NU Path",
"body": "If users want to ensure they take courses that satisfy specific NU path requirements, they can add those NU path requirements to filter down schedules that only meet those requirements."
},
{
"heading": "Include Honors Sections",
"body": "By default, all sections, including honors sections will be shown. If a user does not want to see honors sections, they can toggle this off, ensuring their schedules only have non-honors course sections."
}
]
},
{
"id": 4,
"title": "Where can I find my saved schedules?",
"description": "Users can find both their plan (courses entered into the generator) and any saved schedules from that plan on the dashboard. The dashboard can be found by clicking the \u201cscheduler\u201d in the top navigation bar."
}
]
}
Loading