Skip to content

Commit 6667c7d

Browse files
committed
feat: add course card design
1 parent e6098a8 commit 6667c7d

File tree

7 files changed

+109
-52
lines changed

7 files changed

+109
-52
lines changed

src/components/cards/challenge/Badges.tsx renamed to src/components/badges/Badges.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,26 @@ import { useTranslation } from "react-i18next";
1313
* @returns {*}
1414
*/
1515
interface BadgeProps {
16-
challenge: Challenge;
16+
challenge?: Challenge;
1717
className?: string;
18+
courseLevel?: number;
1819
}
19-
export default function Badges({ challenge, className }: BadgeProps) {
20+
export default function Badges({ challenge, className, courseLevel }: BadgeProps) {
2021
const { t } = useTranslation();
2122
const [challengeLevel, setChallengeLevel] = useState("");
2223

24+
const level = challenge?.level || courseLevel;
25+
2326
useEffect(() => {
24-
if (challenge.level === 0 || challenge.level === 1) return setChallengeLevel("course.challenge.level-0");
27+
if (level === 0 || level === 1) return setChallengeLevel("course.challenge.level-0");
2528
return setChallengeLevel("course.challenge.level-2");
26-
}, [challenge.level]);
29+
}, [level]);
2730

28-
if (!challenge?.level && !challenge?.isTeamChallenge) return <></>;
31+
if (!level && !challenge?.isTeamChallenge) return <></>;
2932

3033
return (
3134
<div className={`uppercase flex flex-wrap gap-2 mb-3 ${className}`}>
32-
{challenge?.level && <Tag>{t(challengeLevel)}</Tag>}
35+
{level && <Tag>{t(challengeLevel)}</Tag>}
3336
{challenge?.isTeamChallenge && <Tag type="light">{challenge?.isHackathon ? "Hackathon" : "Team"} challenge</Tag>}
3437
</div>
3538
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import DateManager from "@/utilities/DateManager";
2+
import classNames from "classnames";
3+
import { useRouter } from "next/router";
4+
import React, { useMemo } from "react";
5+
6+
export default function DurationBadge({ value, type = "gray" }: { value: number; type?: string }) {
7+
const router = useRouter();
8+
const duration = useMemo(() => {
9+
return (value: number) => {
10+
if (!value) {
11+
return 0;
12+
}
13+
return DateManager.humanize(value, router.locale as string);
14+
};
15+
}, [router.locale]);
16+
return (
17+
<span
18+
className={classNames("text-xxs uppercase font-semibold px-2 rounded-3xl inline-block text-gray-500", {
19+
"bg-gray-200": type === "gray",
20+
"border border-gray-200": type === "bordered",
21+
})}
22+
>
23+
{duration(value)}
24+
</span>
25+
);
26+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from "react";
2+
import ArrowButton from "@/components/ui/button/Arrow";
3+
import Link from "next/link";
4+
import { useTranslation } from "next-i18next";
5+
import { useSelector } from "react-redux";
6+
import { IRootState } from "@/store";
7+
import Badges from "@/components/badges/Badges";
8+
import DurationBadge from "@/components/badges/durationBadge";
9+
10+
/**
11+
* Props for the Learning component.
12+
*/
13+
interface LearningProps {
14+
title: string;
15+
description: string;
16+
link: string;
17+
level: number;
18+
learningModulesCount: number;
19+
duration: number
20+
}
21+
22+
/**
23+
* Learning component.
24+
*
25+
* @param {LearningProps} props - The props for the Learning component.
26+
* @returns {JSX.Element} The Learning component JSX element.
27+
*/
28+
export default function CourseCard({ title, description, link, level, learningModulesCount, duration}: LearningProps): JSX.Element {
29+
const { t } = useTranslation();
30+
const colors = useSelector((state: IRootState)=>state.ui.colors);
31+
32+
return (
33+
<div className="flex flex-col gap-3 relative p-6 divide-y sm:divide-y-0 sm:divide-x divide-gray-200 rounded-3xl group text-gray-700 sm:p-8 border-solid border border-gray-200">
34+
<div className="flex flex-col w-full">
35+
<div className="flex items-center justify-between mb-6">
36+
<div className="flex gap-2 items-center">
37+
<div className="h-5.5 w-5.5 rounded-sm clip-polygon" style={{ backgroundColor: colors?.primary }} />
38+
<span className="capitalize font-semibold text-[#4B5563] text-sm">COURSE</span>
39+
</div>
40+
<div className="flex items-start gap-2">
41+
<Badges courseLevel={level} />
42+
<DurationBadge value={duration} type="bordered" />
43+
</div>
44+
</div>
45+
<div className="flex flex-col gap-6">
46+
<div className="text-lg font-medium leading-normal text-gray-900">{title}</div>
47+
<div className="text-sm font-normal text-gray-700 max-w-xxs">{description}</div>
48+
{learningModulesCount && (
49+
<p className="text-sm pb-6 font-medium text-gray-400 border-b-2 border-gray-200 border-dotted">{`${learningModulesCount} Learning ${
50+
learningModulesCount === 1 ? "material" : "materials"
51+
} included`}</p>
52+
)}
53+
</div>
54+
<div className="bottom-0 mt-6">
55+
<Link href={link}>
56+
<ArrowButton communityStyles={true} variant="outline-primary">
57+
{t("communities.overview.challenge.learning.start")}
58+
</ArrowButton>
59+
</Link>
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
}

src/components/cards/challenge/Challenge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ArrowButton from "@/components/ui/button/Arrow";
22
import { Community } from "@/types/community";
33
import { Challenge } from "@/types/course";
44
import Link from "next/link";
5-
import Badges from "./Badges";
5+
import Badges from "../../badges/Badges";
66
import { useMemo } from "react";
77
import { useTranslation } from "next-i18next";
88
import Image from "next/image";

src/components/cards/challenge/_partials/Learning.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/components/sections/challenges/Learning.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import Accordion from "@/components/ui/accordion/Accordion";
33
import Section from "@/components/sections/communities/_partials/Section";
4-
import LearningCard from "@/components/cards/challenge/_partials/Learning";
4+
import CourseCard from "@/components/cards/CourseCard";
55
import RelatedLearningCard from "@/components/cards/challenge/_partials/RelatedLearning";
66
import { Course, LearningModule } from "@/types/course";
77
import { Community } from "@/types/community";
@@ -25,13 +25,16 @@ export default function Learning({ courses, learningModules, community }: { cour
2525
content={
2626
<>
2727
<div className="text-base font-normal text-slate-700 py-6">{t("communities.overview.challenge.learning.title")}</div>
28-
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
28+
<div className={`grid grid-cols-1 gap-3 mb-3 ${courses.length > 1 && "md:grid-cols-2"}`}>
2929
{courses?.map((course) => (
30-
<LearningCard
30+
<CourseCard
3131
key={`learning-card-data-${course.id}`}
3232
title={course.name}
3333
description={course.description}
3434
link={`/communities/${community.slug}/courses/${course.slug}`}
35+
duration={course.duration}
36+
level={course.level}
37+
learningModulesCount={course.learningModules?.length}
3538
/>
3639
))}
3740
</div>

src/styles/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ button,
130130
.floating-input > input::placeholder {
131131
color: transparent;
132132
}
133+
.clip-polygon {
134+
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
135+
}
133136

134137
.floating-input > input:focus,
135138
.floating-input > input:not(:placeholder-shown) {

0 commit comments

Comments
 (0)