Skip to content

Commit 928b699

Browse files
huamanrajapsinghdev
authored andcommitted
feat: OSS programs added with data
1 parent 6e70c37 commit 928b699

45 files changed

Lines changed: 3713 additions & 22 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"framer-motion": "^11.15.0",
2929
"geist": "^1.5.1",
3030
"gray-matter": "^4.0.3",
31+
"jsdom": "^27.2.0",
3132
"lucide-react": "^0.456.0",
3233
"marked": "^17.0.0",
3334
"next": "15.5.3",
@@ -44,8 +45,8 @@
4445
"zustand": "^5.0.1"
4546
},
4647
"devDependencies": {
47-
"@types/dompurify": "^3.2.0",
4848
"@tailwindcss/line-clamp": "^0.4.4",
49+
"@types/jsdom": "^27.0.0",
4950
"@types/node": "^20",
5051
"@types/react": "^18",
5152
"@types/react-dom": "^18",
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"use client";
2+
3+
import { useState, useMemo } from "react";
4+
import { Program } from "@/data/oss-programs/types";
5+
import { SearchInput, TagFilter, ProgramCard } from "@/components/oss-programs";
6+
7+
interface ProgramsListProps {
8+
programs: Program[];
9+
tags: string[];
10+
}
11+
12+
export default function ProgramsList({ programs, tags }: ProgramsListProps) {
13+
const [searchQuery, setSearchQuery] = useState("");
14+
const [selectedTags, setSelectedTags] = useState<string[]>([]);
15+
16+
const filteredPrograms = useMemo(() => {
17+
return programs.filter((program) => {
18+
const matchesSearch =
19+
program.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
20+
program.tags.some((tag) =>
21+
tag.toLowerCase().includes(searchQuery.toLowerCase())
22+
);
23+
24+
const matchesTags =
25+
selectedTags.length === 0 ||
26+
selectedTags.every((tag) => program.tags.includes(tag));
27+
28+
return matchesSearch && matchesTags;
29+
});
30+
}, [programs, searchQuery, selectedTags]);
31+
32+
return (
33+
<div className="min-h-screen w-full bg-[#1e1e1e] text-white p-4 md:p-8 lg:p-12">
34+
<div className="max-w-6xl mx-auto">
35+
{/* Header Section */}
36+
<div className="flex flex-col gap-8 mb-12">
37+
<h1 className="text-4xl font-bold text-white">OSS Programs</h1>
38+
39+
<div className="flex flex-col md:flex-row gap-4 w-full">
40+
<SearchInput
41+
value={searchQuery}
42+
onChange={setSearchQuery}
43+
placeholder="Search programs..."
44+
/>
45+
<TagFilter
46+
tags={tags}
47+
selectedTags={selectedTags}
48+
onTagsChange={setSelectedTags}
49+
/>
50+
</div>
51+
</div>
52+
53+
{/* List Section */}
54+
<div className="flex flex-col gap-2 md:gap-3">
55+
{filteredPrograms.length === 0 ? (
56+
<div className="text-center py-20 text-gray-500">
57+
No programs found matching your criteria.
58+
</div>
59+
) : (
60+
filteredPrograms.map((program) => (
61+
<ProgramCard key={program.slug} program={program} />
62+
))
63+
)}
64+
</div>
65+
</div>
66+
</div>
67+
);
68+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { getProgramBySlug, getAllPrograms } from "@/data/oss-programs";
2+
import { notFound } from "next/navigation";
3+
import { marked } from "marked";
4+
import DOMPurify from "dompurify";
5+
import { JSDOM } from "jsdom";
6+
import { ProgramHeader, ProgramMetadata, ProgramSection } from "@/components/oss-programs";
7+
import "./program-styles.css";
8+
9+
export const revalidate = 3600;
10+
11+
export async function generateStaticParams() {
12+
const programs = getAllPrograms();
13+
return programs.map((program) => ({
14+
slug: program.slug,
15+
}));
16+
}
17+
18+
export default async function ProgramPage({
19+
params,
20+
}: {
21+
params: Promise<{ slug: string }>;
22+
}) {
23+
const { slug } = await params;
24+
const program = getProgramBySlug(slug);
25+
26+
if (!program) {
27+
notFound();
28+
}
29+
30+
marked.setOptions({
31+
gfm: true,
32+
breaks: true,
33+
});
34+
35+
const renderMarkdown = (markdown: string) => {
36+
const html = marked.parse(markdown) as string;
37+
const window = new JSDOM("").window;
38+
const purify = DOMPurify(window);
39+
return purify.sanitize(html);
40+
};
41+
42+
return (
43+
<main className="min-h-screen w-full bg-[#1e1e1e] text-white overflow-x-hidden">
44+
<div className="max-w-6xl mx-auto px-4 md:px-8 py-8 md:py-12 w-full">
45+
<ProgramHeader program={program} />
46+
<ProgramMetadata program={program} />
47+
48+
<div className="space-y-10">
49+
{program.sections.map((section) => (
50+
<ProgramSection
51+
key={section.id}
52+
id={section.id}
53+
title={section.title}
54+
contentHtml={renderMarkdown(section.bodyMarkdown)}
55+
/>
56+
))}
57+
</div>
58+
</div>
59+
</main>
60+
);
61+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* Base list styling */
2+
.program-content li {
3+
position: relative;
4+
padding-left: 1.5rem;
5+
margin: 0.5rem 0;
6+
}
7+
8+
.program-content ul li::before {
9+
content: "•";
10+
position: absolute;
11+
left: 0;
12+
color: #9455f4;
13+
font-weight: bold;
14+
}
15+
16+
.program-content ol {
17+
counter-reset: item;
18+
}
19+
20+
.program-content ol li {
21+
counter-increment: item;
22+
}
23+
24+
.program-content ol li::before {
25+
content: counter(item) ".";
26+
position: absolute;
27+
left: 0;
28+
color: #9455f4;
29+
font-weight: 600;
30+
font-size: 0.875rem;
31+
}
32+
33+
.program-content ul li::marker,
34+
.program-content ol li::marker {
35+
content: "";
36+
}
37+
38+
/* Eligibility section - checkmarks for "good match" list */
39+
.eligibility-section ul:first-of-type li::before {
40+
content: "✓";
41+
color: #22c55e;
42+
}
43+
44+
/* Keep in mind - simple muted styling */
45+
.eligibility-section p:last-of-type {
46+
color: #9ca3af;
47+
font-style: italic;
48+
padding-left: 1rem;
49+
border-left: 2px solid #444;
50+
margin-top: 1rem;
51+
}
52+
53+
/* Preparation checklist - numbered steps with subtle background */
54+
.preparation-checklist ol li {
55+
background: #252525;
56+
padding: 0.75rem 1rem 0.75rem 2.25rem;
57+
margin: 0.5rem 0;
58+
border-radius: 0.375rem;
59+
border: 1px solid transparent;
60+
transition: border-color 0.2s;
61+
}
62+
63+
.preparation-checklist ol li:hover {
64+
border-color: #333;
65+
}
66+
67+
.preparation-checklist ol li::before {
68+
left: 0.75rem;
69+
top: 0.75rem;
70+
color: #9455f4;
71+
font-weight: 700;
72+
}
73+
74+
/* Application process - step indicators */
75+
.application-timeline ul {
76+
padding-left: 0.5rem;
77+
border-left: 2px solid #333;
78+
margin-left: 0.5rem;
79+
}
80+
81+
.application-timeline ul li {
82+
padding: 0.5rem 0 0.5rem 1.25rem;
83+
margin: 0;
84+
}
85+
86+
.application-timeline ul li::before {
87+
content: "";
88+
position: absolute;
89+
left: -0.85rem;
90+
top: 0.85rem;
91+
width: 8px;
92+
height: 8px;
93+
background: #9455f4;
94+
border-radius: 50%;
95+
}
96+
97+
.application-timeline ul li:first-child::before {
98+
background: #22c55e;
99+
}
100+
101+
.application-timeline ul li:last-child::before {
102+
background: #f59e0b;
103+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getAllPrograms, getAllTags } from "@/data/oss-programs";
2+
import ProgramsList from "./ProgramsList";
3+
4+
export const revalidate = 3600;
5+
6+
export default function Page() {
7+
const programs = getAllPrograms();
8+
const tags = getAllTags();
9+
10+
return <ProgramsList programs={programs} tags={tags} />;
11+
}

apps/web/src/components/dashboard/Sidebar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
Squares2X2Icon,
2020
ChevronDownIcon,
2121
LockClosedIcon,
22+
AcademicCapIcon
2223
} from "@heroicons/react/24/outline";
2324
import { useShowSidebar } from "@/store/useShowSidebar";
2425
import { signOut, useSession } from "next-auth/react";
@@ -51,6 +52,11 @@ const FREE_ROUTES: RouteConfig[] = [
5152
label: "OSS Sheet",
5253
icon: <DocumentTextIcon className="size-5" />,
5354
},
55+
{
56+
path: "/dashboard/oss-programs",
57+
label: "OSS Programs",
58+
icon: <AcademicCapIcon className="size-5" />,
59+
},
5460
];
5561

5662
// premium features under Opensox Pro
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Link from "next/link";
2+
import { ChevronRight } from "lucide-react";
3+
import { Program } from "@/data/oss-programs/types";
4+
5+
interface ProgramCardProps {
6+
program: Program;
7+
}
8+
9+
export default function ProgramCard({ program }: ProgramCardProps) {
10+
return (
11+
<Link
12+
href={`/dashboard/oss-programs/${program.slug}`}
13+
className="group block"
14+
>
15+
<div className="bg-[#252525] border border-[#333] rounded-xl px-4 py-3 md:px-5 md:py-4 hover:border-[#9455f4]/50 hover:bg-[#2a2a2a] transition-all duration-200 flex items-center justify-between gap-3 md:gap-4">
16+
<div className="flex-1 min-w-0">
17+
<h2 className="text-sm md:text-base font-semibold text-white group-hover:text-[#dcb8ff] transition-colors truncate">
18+
{program.name}
19+
</h2>
20+
<p className="text-gray-500 text-xs md:text-sm mt-0.5 truncate">
21+
{program.shortDescription}
22+
</p>
23+
</div>
24+
25+
<div className="hidden md:flex items-center gap-6 flex-shrink-0">
26+
<div className="text-right">
27+
<p className="text-xs text-gray-500 uppercase tracking-wide">Region</p>
28+
<p className="text-sm text-gray-300 capitalize">{program.region}</p>
29+
</div>
30+
</div>
31+
32+
<div className="flex-shrink-0 text-gray-500 group-hover:text-[#9455f4] transition-all duration-200 group-hover:translate-x-1">
33+
<ChevronRight className="w-4 h-4 md:w-5 md:h-5" />
34+
</div>
35+
</div>
36+
</Link>
37+
);
38+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Link from "next/link";
2+
import { ChevronLeft, Globe } from "lucide-react";
3+
import { Program } from "@/data/oss-programs/types";
4+
5+
interface ProgramHeaderProps {
6+
program: Program;
7+
}
8+
9+
export default function ProgramHeader({ program }: ProgramHeaderProps) {
10+
return (
11+
<>
12+
<Link
13+
href="/dashboard/oss-programs"
14+
className="inline-flex items-center text-sm text-gray-400 hover:text-[#9455f4] transition-colors group mb-10"
15+
>
16+
<ChevronLeft className="w-4 h-4 mr-1 group-hover:-translate-x-1 transition-transform" />
17+
<span className="font-medium">Back to Programs</span>
18+
</Link>
19+
20+
<div className="mb-12">
21+
<h1 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-3 leading-tight tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white via-gray-200 to-gray-400 break-words">
22+
{program.name}
23+
</h1>
24+
<p className="text-sm md:text-base lg:text-lg text-gray-400 mb-5 leading-relaxed font-light max-w-2xl">
25+
{program.tagline}
26+
</p>
27+
28+
{program.websiteUrl && (
29+
<a
30+
href={program.websiteUrl}
31+
target="_blank"
32+
rel="noopener noreferrer"
33+
className="inline-flex items-center gap-2 text-sm text-gray-400 hover:text-white transition-colors group"
34+
>
35+
<Globe className="w-4 h-4 flex-shrink-0 group-hover:text-[#9455f4] transition-colors" />
36+
<span className="border-b border-gray-700 group-hover:border-[#9455f4] transition-colors truncate">
37+
Visit Website
38+
</span>
39+
</a>
40+
)}
41+
</div>
42+
</>
43+
);
44+
}

0 commit comments

Comments
 (0)