Skip to content

Commit 13c7b77

Browse files
committed
feat: add codebase section
1 parent cf2c60a commit 13c7b77

5 files changed

Lines changed: 546 additions & 0 deletions

File tree

src/App.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const NotFound = lazy(() => import("./components/NotFound"));
1717
const ProjectDetail = lazy(() => import("./components/ProjectDetail"));
1818
const ProblemsHome = lazy(() => import("./problems/ProblemsHome"));
1919
const ProblemDetailPage = lazy(() => import("./problems/ProblemDetailPage"));
20+
const CodebasePage = lazy(() => import("./codebase/CodebasePage"));
21+
const CodebaseDetailPage = lazy(() => import("./codebase/CodebaseDetailPage"));
2022

2123
function RouteFallback() {
2224
return (
@@ -48,6 +50,8 @@ function App() {
4850
<Route path="/contact" element={<Contact />} />
4951
<Route path="/problems" element={<ProblemsHome />} />
5052
<Route path="/problems/:platform/:slug" element={<ProblemDetailPage />} />
53+
<Route path="/codebase" element={<CodebasePage />} />
54+
<Route path="/codebase/:platform/:slug" element={<CodebaseDetailPage />} />
5155
<Route path="*" element={<NotFound />} />
5256
</Routes>
5357
</Suspense>
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import React, { useEffect, useMemo, useState } from "react";
2+
import { Link, useParams } from "react-router-dom";
3+
import {
4+
getCachedText,
5+
getJournalProblemFolderListing,
6+
getJournalProblemFolderWebUrl,
7+
getJournalProblems,
8+
toPlatformSegment,
9+
} from "../lib/codingJournal";
10+
11+
function extractSection(markdown, heading) {
12+
if (!markdown) return "";
13+
14+
const pattern = new RegExp(`^##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=^##\\s+|$)`, "im");
15+
const match = markdown.match(pattern);
16+
return match ? match[1].trim() : "";
17+
}
18+
19+
function stripMarkdown(text) {
20+
return text
21+
.replace(/```[\s\S]*?```/g, "")
22+
.replace(/`([^`]+)`/g, "$1")
23+
.replace(/^\s*[-*]\s+/gm, "")
24+
.trim();
25+
}
26+
27+
function extractCodeBlock(markdown) {
28+
const match = markdown.match(/```(?:\w+)?\n([\s\S]*?)```/);
29+
return match ? match[1].trim() : "";
30+
}
31+
32+
function copyText(text) {
33+
if (!text) return Promise.resolve();
34+
return navigator.clipboard.writeText(text);
35+
}
36+
37+
export default function CodebaseDetailPage() {
38+
const { platform, slug } = useParams();
39+
const [problems, setProblems] = useState([]);
40+
const [detail, setDetail] = useState(null);
41+
const [loading, setLoading] = useState(true);
42+
const [error, setError] = useState("");
43+
const [copyState, setCopyState] = useState("");
44+
45+
useEffect(() => {
46+
let ignore = false;
47+
48+
async function loadDetail() {
49+
setLoading(true);
50+
setError("");
51+
52+
try {
53+
const problemsData = await getJournalProblems();
54+
if (ignore) return;
55+
56+
const allProblems = Array.isArray(problemsData) ? problemsData : [];
57+
setProblems(allProblems);
58+
59+
const problem = allProblems.find(
60+
(item) => item.slug === slug && toPlatformSegment(item.platform) === platform
61+
);
62+
63+
if (!problem) {
64+
setDetail(null);
65+
setLoading(false);
66+
return;
67+
}
68+
69+
const folderListing = await getJournalProblemFolderListing(problem.platform, problem.slug);
70+
const files = Array.isArray(folderListing) ? folderListing : [];
71+
const problemJsonFile = files.find((file) => file.name === "problem.json");
72+
const explanationFile = files.find((file) => file.name === "explanation.md");
73+
const solutionFile = files.find((file) => file.name.startsWith("solution."));
74+
75+
const [problemJsonText, explanationText, solutionCode] = await Promise.all([
76+
problemJsonFile?.download_url ? getCachedText(problemJsonFile.download_url) : Promise.resolve(""),
77+
explanationFile?.download_url ? getCachedText(explanationFile.download_url) : Promise.resolve(""),
78+
solutionFile?.download_url ? getCachedText(solutionFile.download_url) : Promise.resolve(""),
79+
]);
80+
81+
if (ignore) return;
82+
83+
const parsedJson = problemJsonText ? JSON.parse(problemJsonText) : {};
84+
const approachSection = extractSection(explanationText, "Approach") || extractSection(explanationText, "Hash Map Approach");
85+
const explanationSection = extractSection(explanationText, "Step-By-Step Example");
86+
const timeSection = stripMarkdown(extractSection(explanationText, "Time Complexity"));
87+
const spaceSection = stripMarkdown(extractSection(explanationText, "Space Complexity"));
88+
const questionText = parsedJson.question || stripMarkdown(extractSection(explanationText, "Question"));
89+
90+
setDetail({
91+
...problem,
92+
...parsedJson,
93+
question: questionText,
94+
solutionCode: parsedJson.solutionCode || solutionCode || extractCodeBlock(explanationText),
95+
explanation: parsedJson.explanation || stripMarkdown(explanationSection),
96+
approach: parsedJson.approach || stripMarkdown(approachSection),
97+
timeComplexity: parsedJson.timeComplexity || timeSection || problem.timeComplexity,
98+
spaceComplexity: parsedJson.spaceComplexity || spaceSection || problem.spaceComplexity,
99+
githubSourceUrl: solutionFile?.html_url || getJournalProblemFolderWebUrl(problem.platform, problem.slug),
100+
});
101+
} catch (fetchError) {
102+
if (ignore) return;
103+
setError(fetchError.message || "Unable to load codebase detail from coding-journal.");
104+
setDetail(null);
105+
} finally {
106+
if (!ignore) {
107+
setLoading(false);
108+
}
109+
}
110+
}
111+
112+
loadDetail();
113+
114+
return () => {
115+
ignore = true;
116+
};
117+
}, [platform, slug]);
118+
119+
const problem = useMemo(
120+
() =>
121+
problems.find(
122+
(item) => item.slug === slug && toPlatformSegment(item.platform) === platform
123+
) || null,
124+
[platform, problems, slug]
125+
);
126+
127+
return (
128+
<main className="page-shell">
129+
<div className="page-header">
130+
<span className="section-eyebrow">Codebase Detail</span>
131+
<h1>{detail?.title || problem?.title || "Code Entry"}</h1>
132+
<p>
133+
Review live question notes, implementation, explanation, and complexity data pulled from
134+
the coding-journal repository.
135+
</p>
136+
</div>
137+
138+
<div className="problem-toolbar problem-toolbar-wrap">
139+
<Link className="page-button compact" to="/codebase">
140+
← Back to Codebase
141+
</Link>
142+
{detail?.url ? (
143+
<a className="page-button compact" href={detail.url} target="_blank" rel="noreferrer">
144+
View Original Problem
145+
</a>
146+
) : null}
147+
{detail?.githubSourceUrl ? (
148+
<a className="page-button compact" href={detail.githubSourceUrl} target="_blank" rel="noreferrer">
149+
View GitHub Source
150+
</a>
151+
) : null}
152+
</div>
153+
154+
{loading ? (
155+
<section className="section-panel">
156+
<article className="glass-card">
157+
<h3>Loading code entry</h3>
158+
<p>Fetching problem files, solution code, and explanation data from coding-journal.</p>
159+
</article>
160+
</section>
161+
) : error ? (
162+
<section className="section-panel">
163+
<article className="glass-card">
164+
<h3>Unable to load code entry</h3>
165+
<p>{error}</p>
166+
</article>
167+
</section>
168+
) : !detail ? (
169+
<section className="section-panel">
170+
<article className="glass-card">
171+
<h3>Code entry not found</h3>
172+
<p>No coding-journal code entry matched this platform and slug.</p>
173+
</article>
174+
</section>
175+
) : (
176+
<>
177+
<div className="problem-grid">
178+
<article className="problem-card">
179+
<span className="problem-stat">Platform</span>
180+
<h2>{detail.platform}</h2>
181+
<p>Difficulty: {detail.difficulty || "Unknown"}</p>
182+
</article>
183+
<article className="problem-card">
184+
<span className="problem-stat">Verification</span>
185+
<h2>{detail.verified ? "Verified" : "Unverified"}</h2>
186+
<p>Language: {detail.language || "Unknown"}</p>
187+
</article>
188+
<article className="problem-card">
189+
<span className="problem-stat">Tags</span>
190+
<h2>{(detail.tags || []).length}</h2>
191+
{(detail.tags || []).length ? <p>{detail.tags.join(", ")}</p> : null}
192+
</article>
193+
<article className="problem-card">
194+
<span className="problem-stat">Complexity</span>
195+
<h2>{detail.timeComplexity || "Unavailable"}</h2>
196+
<p>{detail.spaceComplexity ? `Space: ${detail.spaceComplexity}` : "Space complexity unavailable."}</p>
197+
</article>
198+
</div>
199+
200+
<section className="section-panel">
201+
<span className="section-eyebrow">Question</span>
202+
<h2>{detail.title}</h2>
203+
<div className="feature-grid">
204+
{detail.question ? (
205+
<article className="glass-card">
206+
<h3>Question</h3>
207+
<p>{detail.question}</p>
208+
</article>
209+
) : null}
210+
{detail.approach ? (
211+
<article className="glass-card">
212+
<h3>Approach</h3>
213+
<p>{detail.approach}</p>
214+
</article>
215+
) : null}
216+
{detail.explanation ? (
217+
<article className="glass-card">
218+
<h3>Explanation</h3>
219+
<p>{detail.explanation}</p>
220+
</article>
221+
) : null}
222+
<article className="glass-card">
223+
<h3>Verification Status</h3>
224+
<p>{detail.verified ? "Verified solution" : "Unverified solution"}</p>
225+
</article>
226+
</div>
227+
</section>
228+
229+
{detail.solutionCode ? (
230+
<section className="section-panel">
231+
<span className="section-eyebrow">Implementation</span>
232+
<h2>Solution Code</h2>
233+
<div className="problem-actions-row">
234+
<button
235+
type="button"
236+
onClick={() => {
237+
copyText(detail.solutionCode).then(() => {
238+
setCopyState("Code copied");
239+
window.setTimeout(() => setCopyState(""), 1800);
240+
});
241+
}}
242+
>
243+
Copy Code
244+
</button>
245+
{copyState ? <span className="problem-copy-state">{copyState}</span> : null}
246+
</div>
247+
<article className="problem-detail-card">
248+
<pre>
249+
<code>{detail.solutionCode}</code>
250+
</pre>
251+
</article>
252+
</section>
253+
) : null}
254+
</>
255+
)}
256+
</main>
257+
);
258+
}

0 commit comments

Comments
 (0)