Skip to content

Commit 9297e20

Browse files
committed
add user/org profile readme's
1 parent 70d9b49 commit 9297e20

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

gitarena-frontend/app/[user]/page.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { ErrorDisplay } from "@/components/error-display";
1212
import { Skeleton } from "@/components/ui/skeleton";
1313
import { Badge } from "@/components/ui/badge";
1414
import { useAuth } from "@/hooks/use-auth";
15-
import { jsonFetcher } from "@/lib/fetchers";
15+
import { jsonFetcher, nullOn404Fetcher } from "@/lib/fetchers";
16+
import { MarkdownRenderer } from "@/components/markdown-renderer";
1617
import { ActivityEvent, type EventResponse } from "@/components/activity-event";
1718
import { ContributionGraph } from "@/components/contribution-graph";
1819
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -58,6 +59,11 @@ interface OrgMemberRaw {
5859
role: "owner" | "admin" | "member";
5960
}
6061

62+
interface ProfileReadmeResponse {
63+
file_name: string;
64+
content: string;
65+
}
66+
6167
interface OrgRepo {
6268
id: string;
6369
name: string;
@@ -267,6 +273,11 @@ function OrgProfilePage({ name, authUserId }: { name: string; authUserId: string
267273
activeTab === "repos" ? `/api/orgs/${name}/repos` : null,
268274
jsonFetcher
269275
);
276+
const { data: readme, isLoading: readmeLoading } = useSWR<ProfileReadmeResponse | null>(
277+
`/api/repo/${name}/${name}/tree/HEAD/readme`,
278+
nullOn404Fetcher,
279+
{ shouldRetryOnError: false }
280+
);
270281

271282
if (isLoading || membersLoading) {
272283
return <ProfileSkeleton username={name} />;
@@ -375,8 +386,20 @@ function OrgProfilePage({ name, authUserId }: { name: string; authUserId: string
375386
<div className="p-6 space-y-8">
376387
{/* ── Overview tab ── */}
377388
{activeTab === "overview" && (
378-
<div className="text-sm text-muted-foreground">
379-
<p>No recent activity to display yet.</p>
389+
<div>
390+
{readmeLoading && <Skeleton className="h-48 w-full rounded-md" />}
391+
{!readmeLoading && readme && (
392+
<div className="border border-border rounded-md p-5">
393+
<MarkdownRenderer
394+
content={readme.content}
395+
fileName={readme.file_name}
396+
user={name}
397+
repo={name}
398+
branch="HEAD"
399+
/>
400+
</div>
401+
)}
402+
{!readmeLoading && !readme && <p className="text-sm text-muted-foreground">No overview yet.</p>}
380403
</div>
381404
)}
382405

@@ -508,6 +531,11 @@ export default function NamespacePage() {
508531

509532
const { data: orgs, isLoading: orgsLoading } = useSWR<UserOrgEntry[]>(`/api/users/${namespace}/orgs`, jsonFetcher);
510533
const { data: activityFeed, isLoading: activityLoading } = useSWR<EventResponse[]>(`/api/users/${namespace}/events`, jsonFetcher);
534+
const { data: readme, isLoading: readmeLoading } = useSWR<ProfileReadmeResponse | null>(
535+
`/api/repo/${namespace}/${namespace}/tree/HEAD/readme`,
536+
nullOn404Fetcher,
537+
{ shouldRetryOnError: false }
538+
);
511539

512540
const [pinnedKeys, setPinnedKeys] = useState<Set<string>>(new Set());
513541
const [contributionYear, setContributionYear] = useState<number | null>(null);
@@ -647,6 +675,18 @@ export default function NamespacePage() {
647675
</aside>
648676

649677
<main className="flex-1 min-w-0 overflow-y-auto p-4 lg:p-6 space-y-8">
678+
{readmeLoading && <Skeleton className="h-48 w-full rounded-md" />}
679+
{!readmeLoading && readme && (
680+
<section className="border border-border rounded-md p-5">
681+
<MarkdownRenderer
682+
content={readme.content}
683+
fileName={readme.file_name}
684+
user={namespace}
685+
repo={namespace}
686+
branch="HEAD"
687+
/>
688+
</section>
689+
)}
650690
<section>
651691
<div className="flex items-center justify-between mb-3">
652692
<h2 className="text-xs font-medium uppercase tracking-widest text-muted-foreground">Contributions</h2>

gitarena-frontend/app/new/page.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
AlertCircle,
2727
Loader2,
2828
Building2,
29+
BookOpen,
2930
} from "lucide-react";
3031
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
3132
import { useAuth } from "@/hooks/use-auth";
@@ -300,6 +301,17 @@ function NewRepositoryForm() {
300301
</span>
301302
</p>
302303
)}
304+
{nameValid && selectedNamespace && repoName === selectedNamespace && (
305+
<div className="flex items-start gap-3 p-3 rounded-md bg-blue-500/10 border border-blue-500/30">
306+
<BookOpen className="h-4 w-4 text-blue-500 mt-0.5 shrink-0" />
307+
<p className="text-sm text-blue-600 dark:text-blue-400">
308+
<span className="font-medium">
309+
{selectedNamespace}/{repoName}
310+
</span>{" "}
311+
is a special repository — its README will appear on your profile page.
312+
</p>
313+
</div>
314+
)}
303315
</div>
304316

305317
<div className="space-y-3">

gitarena-frontend/lib/fetchers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,21 @@ export async function putJsonVoidFetcher<TArg>(url: string, { arg }: { arg: TArg
161161
}
162162
}
163163

164+
/**
165+
* Fetcher that returns null on 404 instead of throwing.
166+
* Useful for optional resources like profile READMEs.
167+
*/
168+
export async function nullOn404Fetcher<T>(url: string): Promise<T | null> {
169+
const res = await fetch(url);
170+
if (res.status === 404) {
171+
return null;
172+
}
173+
if (!res.ok) {
174+
throw new Error(res.statusText);
175+
}
176+
return res.json();
177+
}
178+
164179
/**
165180
* Fetcher for DELETE requests.
166181
*/

0 commit comments

Comments
 (0)