Skip to content
Merged
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
10 changes: 10 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Release Notes
=============

Version 0.13.8
--------------

- add is_learning_material filter show courses and programs first in default sort (#1104)
- dashboard my lists style fixes (#1107)
- Updates to learning resource price display (#1108)
- Add profile edit page (#1029)
- Append `/static` to the front of the testimonial marketing card image (#1115)
- two separate search inputs (#1111)

Version 0.13.7 (Released June 18, 2024)
--------------

Expand Down
97 changes: 92 additions & 5 deletions frontends/api/src/generated/v1/api.ts

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion frontends/api/src/test-utils/factories/learningResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ const learningResourceCourseNumber: Factory<CourseNumber> = (
const _learningResourceShared = (): Partial<
Omit<LearningResource, "resource_type">
> => {
const free = Math.random() < 0.5
return {
id: uniqueEnforcerId.enforce(() => faker.number.int()),
professional: faker.datatype.boolean(),
Expand All @@ -233,7 +234,8 @@ const _learningResourceShared = (): Partial<
image: learningResourceImage(),
offered_by: maybe(learningResourceOfferor) ?? null,
platform: maybe(learningResourcePlatform) ?? null,
prices: ["0.00"],
free,
prices: free ? ["0"] : [faker.finance.amount({ min: 0, max: 100 })],
readable_id: faker.lorem.slug(),
course_feature: repeat(faker.lorem.word),
runs: [],
Expand Down
123 changes: 123 additions & 0 deletions frontends/mit-open/src/page-components/Profile/CertificateChoice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from "react"

import {
styled,
Grid,
Container,
ChoiceBox,
RadioChoiceField,
} from "ol-components"
import {
CertificateDesiredEnum,
CertificateDesiredEnumDescriptions,
} from "api/v0"

import type { ProfileFieldUpdateProps, ProfileFieldStateHook } from "./types"

const CHOICES = [
CertificateDesiredEnum.Yes,
CertificateDesiredEnum.No,
CertificateDesiredEnum.NotSureYet,
].map((value) => ({
value,
label: CertificateDesiredEnumDescriptions[value],
}))

type State = CertificateDesiredEnum | ""
type Props = ProfileFieldUpdateProps<"certificate_desired">

const useCertificateChoiceState: ProfileFieldStateHook<
"certificate_desired"
> = (value, onUpdate) => {
const [certificateDesired, setCertificateDesired] = React.useState<State>(
value || "",
)

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCertificateDesired(() => {
return event.target.value as CertificateDesiredEnum
})
}

React.useEffect(() => {
onUpdate("certificate_desired", certificateDesired)
}, [certificateDesired, onUpdate])

return [certificateDesired, handleChange]
}

const CertificateChoiceBoxField: React.FC<Props> = ({
value,
label,
onUpdate,
}) => {
const [certificateDesired, handleChange] = useCertificateChoiceState(
value,
onUpdate,
)

return (
<>
{label}
<Container maxWidth="md">
<Grid
container
spacing={2}
justifyContent="center"
columns={{
md: 12,
xs: 4,
}}
>
{Object.values(CertificateDesiredEnum).map((value, index) => {
const checked = value === certificateDesired
return (
<Grid item xs={4} key={index}>
<ChoiceBox
type="radio"
label={CertificateDesiredEnumDescriptions[value]}
value={value}
onChange={handleChange}
checked={checked}
/>
</Grid>
)
})}
</Grid>
</Container>
</>
)
}

const RadioContainer = styled.div(({ theme }) => ({
[theme.breakpoints.up("md")]: {
"& .MuiFormGroup-root": {
flexDirection: "row",
},
},
}))

const CertificateRadioChoiceField: React.FC<Props> = ({
value,
label,
onUpdate,
}) => {
const [certificateDesired, handleChange] = useCertificateChoiceState(
value,
onUpdate,
)

return (
<RadioContainer>
<RadioChoiceField
name="certificate_desired"
choices={CHOICES}
label={label}
value={certificateDesired}
onChange={handleChange}
/>
</RadioContainer>
)
}

export { CertificateChoiceBoxField, CertificateRadioChoiceField }
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react"

import {
Select,
MenuItem,
FormControl,
FormLabel,
SelectChangeEvent,
} from "ol-components"
import { CurrentEducationEnum, CurrentEducationEnumDescriptions } from "api/v0"

import { ProfileFieldUpdateProps } from "./types"

const EducationLevelSelect: React.FC<
ProfileFieldUpdateProps<"current_education">
> = ({ label, value, onUpdate }) => {
const [educationLevel, setEducationLevel] = React.useState<
CurrentEducationEnum | ""
>(value || "")

const handleChange = (event: SelectChangeEvent<typeof educationLevel>) => {
setEducationLevel(event.target.value as CurrentEducationEnum)
}

React.useEffect(() => {
onUpdate("current_education", educationLevel)
}, [educationLevel, onUpdate])

return (
<FormControl component="fieldset" fullWidth>
<FormLabel component="label">{label}</FormLabel>
<Select displayEmpty onChange={handleChange} value={educationLevel}>
<MenuItem disabled value="">
<em>Please select</em>
</MenuItem>
{Object.values(CurrentEducationEnum).map((value, index) => {
return (
<MenuItem value={value} key={index}>
{CurrentEducationEnumDescriptions[value]}
</MenuItem>
)
})}
</Select>
</FormControl>
)
}

export { EducationLevelSelect }
137 changes: 137 additions & 0 deletions frontends/mit-open/src/page-components/Profile/GoalsChoice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from "react"
import {
styled,
Checkbox,
CheckboxChoiceBoxField,
ChoiceBoxGridProps,
FormLabel,
FormControl,
} from "ol-components"
import { GoalsEnum, GoalsEnumDescriptions, PatchedProfileRequest } from "api/v0"

import type { ProfileFieldUpdateProps, ProfileFieldUpdateFunc } from "./types"

const CHOICES = [
{
value: GoalsEnum.CareerGrowth,
label: GoalsEnumDescriptions[GoalsEnum.CareerGrowth],
description: "Looking for career growth through new skills & certification",
},
{
value: GoalsEnum.SupplementalLearning,
label: GoalsEnumDescriptions[GoalsEnum.SupplementalLearning],
description: "Additional learning to integrate with degree work",
},
{
value: GoalsEnum.JustToLearn,
label: GoalsEnumDescriptions[GoalsEnum.JustToLearn],
description: "I just want more knowledge",
},
]

type State = GoalsEnum[]

const useGoalsChoice = (
value: PatchedProfileRequest["goals"],
onUpdate: ProfileFieldUpdateFunc<"goals">,
): [State, React.ChangeEventHandler] => {
const [goals, setGoals] = React.useState<State>(value || [])

const handleToggle = (event: React.SyntheticEvent) => {
setGoals((prevGoals) => {
const target = event.target as HTMLInputElement
if (target.checked) {
return [...prevGoals, target.value as GoalsEnum]
} else {
return prevGoals.filter((goal) => goal !== target.value)
}
})
}

React.useEffect(() => {
onUpdate("goals", goals)
}, [goals, onUpdate])

return [goals, handleToggle]
}

function GoalsChoiceBoxField({
value,
label,
gridProps,
gridItemProps,
onUpdate,
}: ProfileFieldUpdateProps<"goals"> & ChoiceBoxGridProps) {
const [goals, handleToggle] = useGoalsChoice(value, onUpdate)

return (
<CheckboxChoiceBoxField
label={label}
choices={CHOICES}
values={goals}
onChange={handleToggle}
gridProps={Object.assign(
{
justifyContent: "center",
columns: {
lg: 12,
xs: 4,
},
maxWidth: "md",
margin: "0 auto",
},
gridProps,
)}
gridItemProps={Object.assign({ xs: 4 }, gridItemProps)}
/>
)
}

const CheckboxContainer = styled.div(({ theme }) => ({
display: "flex",
flexDirection: "column",
flexWrap: "wrap",
"& label": {
flexShrink: 0,
},
[theme.breakpoints.up("md")]: {
display: "flex",
flexDirection: "row",
"& label": {
marginRight: theme.spacing(3),
},
},
}))

function GoalsCheckboxChoiceField({
label,
value,
onUpdate,
}: ProfileFieldUpdateProps<"goals">) {
const [goals, handleToggle] = useGoalsChoice(value, onUpdate)

return (
<FormControl component="fieldset" sx={{ width: "100%" }}>
<FormLabel component="legend" sx={{ width: "100%" }}>
{label}
</FormLabel>
<CheckboxContainer>
{CHOICES.map((choice) => {
return (
<label key={choice.value}>
<Checkbox
edge="start"
checked={goals.indexOf(choice.value) !== -1}
onChange={handleToggle}
value={choice.value}
disableRipple
/>
{choice.label}
</label>
)
})}
</CheckboxContainer>
</FormControl>
)
}
export { GoalsChoiceBoxField, GoalsCheckboxChoiceField }
Loading