diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa9765192..857b0feab 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -677,6 +677,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1083,6 +1084,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001489", "electron-to-chromium": "^1.4.411", @@ -1777,6 +1779,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2068,6 +2071,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -2485,6 +2489,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3659,9 +3677,10 @@ } }, "node_modules/next-auth": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.22.1.tgz", - "integrity": "sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", + "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "license": "ISC", "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -3674,7 +3693,7 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "next": "^12.2.5 || ^13", + "next": "^12.2.5 || ^13 || ^14", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" @@ -4174,6 +4193,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -4256,6 +4276,7 @@ "version": "10.15.1", "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz", "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -4344,6 +4365,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4355,6 +4377,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -4676,6 +4699,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.2.tgz", "integrity": "sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==", + "peer": true, "peerDependencies": { "typescript": ">=4.1.0" } @@ -5123,6 +5147,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -5364,6 +5389,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/frontend/src/app/NEW-course-library-page/page.tsx b/frontend/src/app/NEW-course-library-page/page.tsx new file mode 100644 index 000000000..41bcb37cf --- /dev/null +++ b/frontend/src/app/NEW-course-library-page/page.tsx @@ -0,0 +1,29 @@ +import Image from "next/image"; +import LandingPageContent from "@/components/LandingPageContent/LandingPageContent"; +import navbar from "@/assets/navbar.svg"; +import Sponsorships from "@/components/SponsorshipsSection/Sponsorships"; +import { Metadata } from "next"; +import { ItemList, WithContext } from "schema-dts"; +import { get } from "@/utils/request"; +import { Course, Courses } from "@/types/api"; + +export default async function CourseLibraryPage() { + const { courses: initialCourses } = (await get( + "/courses?offset=0", + )) as Courses; + + return ( +
+ {/* TOP OF PAGE */} + {/* Title here */} +

+ Search courses +

+ {/* Course cards */} +
+ +
+ {/* BOTTOM OF PAGE */} +
+ ); +} diff --git a/frontend/src/app/NEW-landing-page/Features.tsx b/frontend/src/app/NEW-landing-page/Features.tsx new file mode 100644 index 000000000..5e37b9985 --- /dev/null +++ b/frontend/src/app/NEW-landing-page/Features.tsx @@ -0,0 +1,95 @@ +"use client"; +import Image, { StaticImageData } from "next/image"; +import { useState, useEffect } from "react"; +import feature_box_one from "../../assets/features/feature_box_one.svg"; +import feature_box_two from "../../assets/features/feature_box_two.svg"; +import feature_box_three from "../../assets/features/feature_box_three.svg"; +import Star from "@/assets/star.png"; +import Account from "@/assets/account.png"; +import Comment from "@/assets/comment.png"; +import { ReactNode } from "react"; + +interface FeatureBoxProps { + title: string; + description: string; + image: StaticImageData; // new prop for the image +} + +const FeatureBox: React.FC = ({ + title, + description, + image, +}) => { + const [windowWidth, setWindowWidth] = useState(0); + + useEffect(() => { + setWindowWidth(window.innerWidth); + const handleResize = () => setWindowWidth(window.innerWidth); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const showImage = windowWidth > 645; + + return ( +
+ {showImage && ( +
+ {title} +
+ )} + + {/* Text */} +

+ {title} +

+ +

+ {description} +

+
+ ); +}; + +export default function Features() { + return ( +
+

Our Features

+ +
+ + + +
+
+ ); +} diff --git a/frontend/src/app/NEW-landing-page/Header.tsx b/frontend/src/app/NEW-landing-page/Header.tsx new file mode 100644 index 000000000..b6bd498f0 --- /dev/null +++ b/frontend/src/app/NEW-landing-page/Header.tsx @@ -0,0 +1,53 @@ +import UnilectivesLogo from "@/assets/unilectives-logo.png"; +import Image from "next/image"; + +export default function Header() { + return ( +
+
+ {/* Left side text */} +
+

+ DevSoc presents +

+ + {/* Smaller size on very small screens */} +

+ unilectives +

+ +

+ Your one-stop shop for UNSW course reviews. +

+ + + + +
+ + {/* Hide the logo on small screens */} + Unilectives Logo +
+
+ ); +} diff --git a/frontend/src/app/NEW-landing-page/Sponsors.tsx b/frontend/src/app/NEW-landing-page/Sponsors.tsx new file mode 100644 index 000000000..734ed01d6 --- /dev/null +++ b/frontend/src/app/NEW-landing-page/Sponsors.tsx @@ -0,0 +1,7 @@ +export default function Sponsors() { + return ( +
+

Sponsors...

+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/NEW-landing-page/page.tsx b/frontend/src/app/NEW-landing-page/page.tsx new file mode 100644 index 000000000..9986c7923 --- /dev/null +++ b/frontend/src/app/NEW-landing-page/page.tsx @@ -0,0 +1,97 @@ +import Image from "next/image"; +import LandingPageContent from "@/components/LandingPageContent/LandingPageContent"; +import navbar from "@/assets/navbar.svg"; +import OldSponsorships from "@/components/SponsorshipsSection/Sponsorships"; +import { Metadata } from "next"; +import { ItemList, WithContext } from "schema-dts"; +import { get } from "@/utils/request"; +import { Course, Courses } from "@/types/api"; +import NewSponsorships from "@/components/SponsorshipsSection/NewSponsorships"; +import Header from "./Header"; +import Features from "./Features"; + +// Metadata to assist SEO - provies metadata for HTML head section +export async function generateMetadata(): Promise { + return { + title: `Home | Unilectives - UNSW Course Reviews`, + description: `UNSW course reviews, ratings, and study tips. Unilectives is your one-stop shop for making informed course choices at UNSW.`, + }; +} + +export default async function LandingPage() { + // GET request for all courses + const { courses: initialCourses } = (await get( + "/courses?offset=0", + )) as Courses; + + // Generate metadata to help with SEO (inject via script in return) + const metaLD: WithContext = { + "@context": "https://schema.org", + "@type": "ItemList", + itemListElement: initialCourses.map((course: Course, index: number) => ({ + "@type": "ListItem", + position: index + 1, + item: { + "@type": "Course", + url: `//www.handbook.unsw.edu.au/undergraduate/courses/${new Date().getFullYear()}/${ + course.courseCode + }`, + name: course.title, + description: course.description, + provider: { + "@type": "CollegeOrUniversity", + name: "University of New South Wales", + sameAs: "https://www.unsw.edu.au/", + }, + aggregateRating: { + "@type": "AggregateRating", + ratingCount: course.reviewCount, + ratingValue: course.reviewCount === 0 ? 0 : course.overallRating, + bestRating: 5, + }, + offers: [ + { + "@type": "Offer", + category: "Paid", + }, + ], + hasCourseInstance: course.terms.map((term: number) => ({ + "@type": "CourseInstance", + courseMode: "Blended", + courseSchedule: { + "@type": "Schedule", + repeatCount: term === 0 ? 5 : 10, + repeatFrequency: "Weekly", + }, + })), + }, + })), + }; + + return ( +
+ {/* SCRIPT FOR SEO - do not touch*/} +