Skip to content

Commit

Permalink
Updated designs for the unit page (#1325)
Browse files Browse the repository at this point in the history
* Design updates

* Refactor out UnitCard. Apply styles for desktop

* Mobile breakpoint for logo container

* Migration for updated copy

* Remove log line, fix migration

* Apply loading styles
  • Loading branch information
jonkafton authored Jul 30, 2024
1 parent 00bf0f3 commit fafe842
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 165 deletions.
35 changes: 35 additions & 0 deletions data_fixtures/migrations/0005_unit_page_copy_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.db import migrations

fixtures = [
{
"name": "mitpe",
"channel_configuration": {
"heading": (
"Offering lifelong learning opportunities that prepare engineering, "
"science, and technology professionals to address complex industry "
"challenges."
),
},
},
]


def update_copy(apps, schema_editor):
Channel = apps.get_model("channels", "Channel")
for fixture in fixtures:
channel_configuration_updates = fixture["channel_configuration"]
channel = Channel.objects.get(name=fixture["name"])
if Channel.objects.filter(name=fixture["name"]).exists():
for key, val in channel_configuration_updates.items():
channel.configuration[key] = val
channel.save()


class Migration(migrations.Migration):
dependencies = [
("data_fixtures", "0004_upsert_initial_topic_data"),
]

operations = [
migrations.RunPython(update_copy, migrations.RunPython.noop),
]
215 changes: 215 additions & 0 deletions frontends/mit-open/src/pages/UnitsListingPage/UnitCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import React from "react"
import { LearningResourceOfferorDetail, OfferedByEnum } from "api"
import { Card, Skeleton, Typography, styled, theme } from "ol-components"
import { useChannelDetail } from "api/hooks/channels"

const CardStyled = styled(Card)({
height: "100%",
})

const UnitCardContainer = styled.div({
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
backgroundColor: "rgba(243, 244, 248, 0.50)",
[theme.breakpoints.down("md")]: {
backgroundColor: theme.custom.colors.white,
},
})

const UnitCardContent = styled.div({
display: "flex",
flexDirection: "column",
flexGrow: 1,
width: "100%",
})

const LogoContainer = styled.div({
padding: "40px 32px",
backgroundColor: theme.custom.colors.white,
[theme.breakpoints.down("md")]: {
padding: "34px 0 14px",
".MuiSkeleton-root": {
margin: "0 auto",
},
},
})

const UnitLogo = styled.img({
height: "50px",
display: "block",
[theme.breakpoints.down("md")]: {
height: "40px",
margin: "0 auto",
},
})

const CardBottom = styled.div({
padding: "24px",
borderTop: `1px solid ${theme.custom.colors.lightGray2}`,
display: "flex",
flexGrow: 1,
flexDirection: "column",
gap: "24px",
[theme.breakpoints.down("md")]: {
padding: "16px",
gap: "10px",
borderTop: "none",
},
})

const ValuePropContainer = styled.div({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-start",
flexGrow: 1,
paddingBottom: "16px",
})

const LoadingContent = styled.div({
padding: "24px",
})

const HeadingText = styled(Typography)(({ theme }) => ({
alignSelf: "stretch",
color: theme.custom.colors.darkGray2,
...theme.typography.body2,
[theme.breakpoints.down("md")]: {
display: "none",
},
}))

const SubHeadingText = styled(HeadingText)(({ theme }) => ({
alignSelf: "stretch",
color: theme.custom.colors.darkGray2,
...theme.typography.body2,
display: "none",
[theme.breakpoints.down("md")]: {
display: "block",
},
}))

const CountsTextContainer = styled.div({
display: "flex",
gap: "10px",
[theme.breakpoints.down("md")]: {
justifyContent: "flex-end",
},
})

const CountsText = styled(Typography)(({ theme }) => ({
color: theme.custom.colors.darkGray2,
...theme.typography.body2,
[theme.breakpoints.down("md")]: {
...theme.typography.body3,
color: theme.custom.colors.silverGrayDark,
},
}))

const unitLogos = {
[OfferedByEnum.Mitx]: "/static/images/unit_logos/mitx.svg",
[OfferedByEnum.Ocw]: "/static/images/unit_logos/ocw.svg",
[OfferedByEnum.Bootcamps]: "/static/images/unit_logos/bootcamps.svg",
[OfferedByEnum.Xpro]: "/static/images/unit_logos/xpro.svg",
[OfferedByEnum.Mitpe]: "/static/images/unit_logos/mitpe.svg",
[OfferedByEnum.See]: "/static/images/unit_logos/see.svg",
}

interface UnitCardsProps {
units: LearningResourceOfferorDetail[] | undefined
courseCounts: Record<string, number>
programCounts: Record<string, number>
}

interface UnitCardProps {
unit: LearningResourceOfferorDetail
logo: string
courseCount: number
programCount: number
}

const UnitCard: React.FC<UnitCardProps> = (props) => {
const { unit, logo, courseCount, programCount } = props
const channelDetailQuery = useChannelDetail("unit", unit.code)
const channelDetail = channelDetailQuery.data
const unitUrl = channelDetail?.channel_url

return channelDetailQuery.isLoading ? (
<UnitCardLoading />
) : (
<CardStyled href={unitUrl}>
<Card.Content>
<UnitCardContainer>
<UnitCardContent>
<LogoContainer>
<UnitLogo src={logo} alt={unit.name} />
</LogoContainer>
<CardBottom>
<ValuePropContainer>
<HeadingText>
{channelDetail?.configuration?.heading}
</HeadingText>
<SubHeadingText>
{channelDetail?.configuration?.sub_heading}
</SubHeadingText>
</ValuePropContainer>
<CountsTextContainer>
<CountsText data-testid={`course-count-${unit.code}`}>
{courseCount > 0 ? `Courses: ${courseCount}` : ""}
</CountsText>
<CountsText data-testid={`program-count-${unit.code}`}>
{programCount > 0 ? `Programs: ${programCount}` : ""}
</CountsText>
</CountsTextContainer>
</CardBottom>
</UnitCardContent>
</UnitCardContainer>
</Card.Content>
</CardStyled>
)
}

export const UnitCardLoading = () => {
return (
<Card>
<Card.Content>
<UnitCardContainer>
<UnitCardContent>
<LogoContainer>
<Skeleton variant="rectangular" width="60%" height={50} />
</LogoContainer>
<LoadingContent>
<Skeleton variant="text" height={100} />
</LoadingContent>
</UnitCardContent>
</UnitCardContainer>
</Card.Content>
</Card>
)
}

export const UnitCards: React.FC<UnitCardsProps> = (props) => {
const { units, courseCounts, programCounts } = props
return (
<>
{units?.map((unit) => {
const courseCount = courseCounts[unit.code] || 0
const programCount = programCounts[unit.code] || 0
const logo =
unitLogos[unit.code as OfferedByEnum] ||
`/static/images/unit_logos/${unit.code}.svg`
return unit.value_prop ? (
<UnitCard
key={unit.code}
unit={unit}
logo={logo}
courseCount={courseCount}
programCount={programCount}
/>
) : null
})}
</>
)
}
Loading

0 comments on commit fafe842

Please sign in to comment.