diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts index 4a4098d6..ce0b4938 100644 --- a/src/shared/types/Course.ts +++ b/src/shared/types/Course.ts @@ -6,7 +6,7 @@ import { CourseSchedule } from './CourseSchedule'; * Also includes a link to their RateMyProfessor page */ export type Instructor = { - name: string; + fullName: string; firstName?: string; lastName?: string; middleInitial?: string; @@ -75,6 +75,32 @@ export class Course { constructor(course: Course | Serialized) { Object.assign(this, course); } + + getInstructorString(options: { + /** The maximum number of instructors to show */ + max?: number; + format: 'abbr' | 'first_last' | 'last' | 'full_name'; + }): string { + const { max = 3, format } = options; + + if (!this.instructors) { + return 'Undecided'; + } + + const instructors = this.instructors.slice(0, max); + switch (format) { + case 'abbr': + return instructors.map(instructor => `${instructor.firstName?.[0]}. ${instructor.lastName}`).join(', '); + case 'full_name': + return instructors.map(instructor => instructor.fullName).join(', '); + case 'first_last': + return instructors.map(instructor => `${instructor.firstName} ${instructor.lastName}`).join(', '); + case 'last': + return instructors.map(instructor => instructor.lastName).join(', '); + default: + throw new Error(`Invalid Instructor String format: ${format}`); + } + } } /** diff --git a/src/views/components/common/Card/Card.module.scss b/src/views/components/common/Card/Card.module.scss new file mode 100644 index 00000000..0d3f9862 --- /dev/null +++ b/src/views/components/common/Card/Card.module.scss @@ -0,0 +1,8 @@ +@import 'src/views/styles/base.module.scss'; + +.card { + background: $white; + border: 1px solid #c3cee0; + box-sizing: border-box; + border-radius: 8px; +} diff --git a/src/views/components/common/Card/Card.tsx b/src/views/components/common/Card/Card.tsx new file mode 100644 index 00000000..f063c4b8 --- /dev/null +++ b/src/views/components/common/Card/Card.tsx @@ -0,0 +1,27 @@ +import classNames from 'classnames'; +import React, { Component } from 'react'; +import styles from './Card.module.scss'; + +export type Props = { + style?: React.CSSProperties; + className?: string; + onClick?: (...args) => void; + children?: React.ReactNode; + testId?: string; +}; + +/** + * A reusable Card component that can be used to wrap other components + */ +export default function Card(props: Props) { + return ( +
+ {props.children} +
+ ); +} diff --git a/src/views/components/common/Link/Link.module.scss b/src/views/components/common/Link/Link.module.scss new file mode 100644 index 00000000..5cc8a0c3 --- /dev/null +++ b/src/views/components/common/Link/Link.module.scss @@ -0,0 +1,5 @@ +.link { + font-family: 'Inter', sans-serif; + text-decoration: underline; + cursor: pointer; +} diff --git a/src/views/components/common/Link/Link.tsx b/src/views/components/common/Link/Link.tsx new file mode 100644 index 00000000..9b4ccd83 --- /dev/null +++ b/src/views/components/common/Link/Link.tsx @@ -0,0 +1,25 @@ +import classNames from 'classnames'; +import React, { PropsWithChildren } from 'react'; +import { bMessenger } from 'src/shared/messages'; +import Text, { TextProps } from '../Text/Text'; +import styles from './Link.module.scss'; + +type Props = TextProps & { + url?: string; +}; + +/** + * A reusable Text component with props that build on top of the design system for the extension + */ +export default function Link(props: PropsWithChildren) { + let passedProps = { + ...props, + }; + const { url } = props; + + if (url && !props.onClick) { + passedProps.onClick = () => bMessenger.openNewTab({ url }); + } + + return ; +} diff --git a/src/views/components/common/Popup/Popup.tsx b/src/views/components/common/Popup/Popup.tsx index 745b6cb5..a74f27f9 100644 --- a/src/views/components/common/Popup/Popup.tsx +++ b/src/views/components/common/Popup/Popup.tsx @@ -17,16 +17,12 @@ export default function Popup(props: PropsWithChildren) { return (
-
{props.children}
+
{props.children}
); } diff --git a/src/views/components/common/Text/Text.tsx b/src/views/components/common/Text/Text.tsx index 1dae0e06..b726e88a 100644 --- a/src/views/components/common/Text/Text.tsx +++ b/src/views/components/common/Text/Text.tsx @@ -4,9 +4,9 @@ import colors, { ISassColors } from 'src/views/styles/colors.module.scss'; import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss'; import styles from './Text.module.scss'; -type Props = { +export type TextProps = { color?: keyof ISassColors; - weight: Weight; + weight?: Weight; size: Size; span?: boolean; className?: string; @@ -18,7 +18,7 @@ type Props = { /** * A reusable Text component with props that build on top of the design system for the extension */ -export default function Text(props: PropsWithChildren) { +export default function Text(props: PropsWithChildren) { const style = props.style || {}; style.textAlign ??= props.align; diff --git a/src/views/components/injected/CoursePopup/CoursePopup.module.scss b/src/views/components/injected/CoursePopup/CoursePopup.module.scss index 8644dc21..0e3ad49b 100644 --- a/src/views/components/injected/CoursePopup/CoursePopup.module.scss +++ b/src/views/components/injected/CoursePopup/CoursePopup.module.scss @@ -1,35 +1,30 @@ -.popupBody { - height: auto; - color: white; - padding: 10px; - display: flex; - align-items: center; - justify-content: center; -} +.popup { + border-radius: 12px; + position: relative; -.coursePopupBase { - position: fixed; - transform: translateY(-50%); - top: 15px; - right: 15px; - z-index: 2147483647; -} + .close { + position: absolute; + top: 12px; + right: 12px; + cursor: pointer; + } -.coursePopupHeader { - display: flex; - height: 50; - background-color: #29465b; - width: 100%; - - .closePopupButton { - display: flex; + .body { + height: auto; + color: white; + padding: 12px; + margin: 40px; align-items: center; justify-content: center; - width: 20px; - height: 20px; - margin: 5px; - margin-left: auto; - cursor: pointer; - color: white; + + .title { + display: flex; + align-items: center; + justify-content: center; + + .uniqueId { + margin-left: 8px; + } + } } } diff --git a/src/views/components/injected/CoursePopup/CoursePopup.tsx b/src/views/components/injected/CoursePopup/CoursePopup.tsx index 5bc62ffa..6702d0c8 100644 --- a/src/views/components/injected/CoursePopup/CoursePopup.tsx +++ b/src/views/components/injected/CoursePopup/CoursePopup.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { Course } from 'src/shared/types/Course'; +import Card from '../../common/Card/Card'; import Icon from '../../common/Icon/Icon'; +import Link from '../../common/Link/Link'; import Popup from '../../common/Popup/Popup'; +import Text from '../../common/Text/Text'; import styles from './CoursePopup.module.scss'; interface Props { @@ -14,11 +17,24 @@ interface Props { */ export default function CoursePopup({ course, onClose }: Props) { return ( - -
-
{course.courseName}
-
{course.uniqueId}
-
+ + + + + {course.courseName} ({course.department} {course.number}) + + #{course.uniqueId} + + + + ); } diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts index 20d9a68e..6cbc85ec 100644 --- a/src/views/lib/CourseCatalogScraper.ts +++ b/src/views/lib/CourseCatalogScraper.ts @@ -159,12 +159,12 @@ export class CourseCatalogScraper { .map(name => name.trim()) .filter(Boolean); - return names.map(name => { - const [lastName, rest] = name.split(',').map(s => s.trim()); + return names.map(fullName => { + const [lastName, rest] = fullName.split(',').map(s => s.trim()); const [firstName, middleInitial] = rest.split(' '); return { - name, + fullName, firstName, lastName, middleInitial,