Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions app/projects/[slug]/[[...tab]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ProjectAnalytics from "@/components/projects/project-analytics";
import { PROJECT_TABS } from "@/components/projects/project-constants";
import ProjectContentWrapper from "@/components/projects/project-content-wrapper";
import ProjectReadme from "@/components/projects/project-readme";
import ProjectTeam from "@/components/projects/project-team";
import { getProject } from "@/lib/actions/get-project";
import { notFound } from "next/navigation";
Expand Down Expand Up @@ -30,7 +32,12 @@ export default async function Project({
}

if (!tab) {
return <ProjectAnalytics project={project} />;
return (
<div className="flex flex-col space-y-8">
<ProjectAnalytics project={project} />
{project.readme && <ProjectReadme project={project} />}
</div>
);
}

if (tab[0] === "team") {
Expand All @@ -39,13 +46,15 @@ export default async function Project({

if (tab[0] === "contributors") {
return (
<a href={project.githubLink.shortLink} target="_blank">
<img
src={`https://contrib.rocks/image?repo=${
project.githubLink.url.split("https://github.com/")[1]
}`}
/>
</a>
<ProjectContentWrapper className="min-h-[22rem]">
<a href={project.githubLink.shortLink} target="_blank">
<img
src={`https://contrib.rocks/image?repo=${
project.githubLink.url.split("https://github.com/")[1]
}`}
/>
</a>
</ProjectContentWrapper>
);
}
}
22 changes: 4 additions & 18 deletions app/projects/[slug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import ProjectLayoutTabs from "@/components/projects/project-layout-tabs";
import ProjectProvider from "@/components/projects/project-provider";
import { buttonLinkVariants } from "@/components/ui/button-link";
import { getProject } from "@/lib/actions/get-project";
import { getRepo } from "@/lib/github";
import prisma from "@/lib/prisma";
import { constructMetadata } from "@/lib/utils";
import { cn, nFormatter } from "@dub/utils";
Expand Down Expand Up @@ -57,19 +56,6 @@ export default async function ProjectLayout({
notFound();
}

const { stars } = await getRepo(project.githubLink.url);

if (stars !== project.stars) {
await prisma.project.update({
where: {
slug,
},
data: {
stars,
},
});
}

return (
<ProjectProvider props={project}>
<div
Expand Down Expand Up @@ -100,7 +86,9 @@ export default async function ProjectLayout({
className={buttonLinkVariants({ variant: "secondary" })}
>
<Star className="h-4 w-4" />
<p className="text-sm">{nFormatter(stars, { full: true })}</p>
<p className="text-sm">
{nFormatter(project.stars, { full: true })}
</p>
</a>
{project.websiteLink && (
<a
Expand All @@ -126,9 +114,7 @@ export default async function ProjectLayout({

<ProjectLayoutTabs />

<div className="relative mx-4 flex min-h-[22rem] items-center justify-center rounded-xl border border-gray-200 bg-white p-4">
{children}
</div>
{children}
</ProjectProvider>
);
}
5 changes: 4 additions & 1 deletion components/projects/project-analytics-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ export default function ProjectAnalyticsClient({

return (
<div className="w-full">
<div className="mb-2 flex justify-end">
<div className="flex items-center justify-between p-4">
<h2 className="text-lg font-medium text-gray-700">
Real-time click analytics
</h2>
<button
onClick={refreshData}
disabled={isPending}
Expand Down
11 changes: 6 additions & 5 deletions components/projects/project-analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { EnrichedProjectProps } from "@/lib/types";
import { LoadingSpinner } from "@dub/ui";
import { Suspense } from "react";
import ProjectAnalyticsClient from "./project-analytics-client";
import ProjectContentWrapper from "./project-content-wrapper";

export default function ProjectAnalytics({
project,
}: {
project: EnrichedProjectProps;
}) {
return (
<Suspense fallback={<LoadingSpinner />}>
<ProjectAnalyticsRSC project={project} />
</Suspense>
<ProjectContentWrapper className="min-h-[22rem]">
<Suspense fallback={<LoadingSpinner />}>
<ProjectAnalyticsRSC project={project} />
</Suspense>
</ProjectContentWrapper>
);
}

Expand Down Expand Up @@ -42,8 +45,6 @@ async function ProjectAnalyticsRSC({
}),
);

console.log("Refreshed analytics data");

const chartData = analytics[0].map(
(data: { start: string; clicks: number }, i) => {
return {
Expand Down
20 changes: 20 additions & 0 deletions components/projects/project-content-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cn } from "@dub/utils";

export default function ProjectContentWrapper({
className,
children,
}: {
className?: string;
children: React.ReactNode;
}) {
return (
<div
className={cn(
"relative mx-4 flex items-center justify-center rounded-xl border border-gray-200 bg-white p-4",
className,
)}
>
{children}
</div>
);
}
2 changes: 1 addition & 1 deletion components/projects/project-layout-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function ProjectLayoutTabs() {
return (
<div className="my-4 flex flex-col space-y-6 p-4">
<div className="flex items-center">
<TabLink title="Analytics" href={`/projects/${slug}`} active={!tab} />
<TabLink title="Overview" href={`/projects/${slug}`} active={!tab} />
{PROJECT_TABS.map((t) => (
<TabLink
key={t.tab}
Expand Down
15 changes: 15 additions & 0 deletions components/projects/project-readme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { EnrichedProjectProps } from "@/lib/types";
import ReactMarkdown from "../ui/react-markdown";
import ProjectContentWrapper from "./project-content-wrapper";

export default function ProjectReadme({
project,
}: {
project: EnrichedProjectProps;
}) {
return (
<ProjectContentWrapper className="py-8">
<ReactMarkdown>{project.readme}</ReactMarkdown>
</ProjectContentWrapper>
);
}
17 changes: 2 additions & 15 deletions components/projects/project-tab-note.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cn } from "@dub/utils";
import { Info } from "lucide-react";
import ReactMarkdown from "react-markdown";
import ReactMarkdown from "../ui/react-markdown";

export default function ProjectTabNote(props: {
className?: string;
Expand All @@ -15,20 +15,7 @@ export default function ProjectTabNote(props: {
>
<Info className="h-5 w-5 text-blue-500" />
{typeof props.children === "string" ? (
<ReactMarkdown
className="prose text-[0.95rem] text-gray-600"
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
{...props}
className="font-medium text-gray-500 underline underline-offset-4 transition-colors hover:text-gray-800"
/>
),
}}
>
{props.children}
</ReactMarkdown>
<ReactMarkdown>{props.children}</ReactMarkdown>
) : (
props.children
)}
Expand Down
5 changes: 3 additions & 2 deletions components/projects/project-team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { EnrichedProjectProps } from "@/lib/types";
import { cn } from "@dub/utils";
import Link from "next/link";
import EditTeamButton from "./edit-team-button";
import ProjectContentWrapper from "./project-content-wrapper";

export default function ProjectTeam({
project,
Expand All @@ -11,7 +12,7 @@ export default function ProjectTeam({
const { users } = project;

return (
<>
<ProjectContentWrapper className="min-h-[22rem]">
<div className="absolute right-4 top-4">
<EditTeamButton project={project} />
</div>
Expand Down Expand Up @@ -42,6 +43,6 @@ export default function ProjectTeam({
</div>
))}
</div>
</>
</ProjectContentWrapper>
);
}
79 changes: 79 additions & 0 deletions components/ui/react-markdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { cn } from "@dub/utils";
import ReactMarkdownHelper from "react-markdown";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeRaw from "rehype-raw";

import rehypeSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
import { visit } from "unist-util-visit";

export default function ReactMarkdown({
className,
children,
}: {
className?: string;
children: string;
}) {
return (
<ReactMarkdownHelper
className={cn("prose text-[0.95rem] text-gray-600", className)}
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
{...props}
className="font-medium text-gray-500 underline underline-offset-4 transition-colors hover:text-gray-800"
/>
),
}}
remarkPlugins={[remarkGfm]}
rehypePlugins={[
rehypeRaw,
rehypeSlug,
() => (tree) => {
visit(tree, (node) => {
if (node?.type === "element" && node?.tagName === "pre") {
const [codeEl] = node.children;

if (codeEl.tagName !== "code") return;

node.raw = codeEl.children?.[0].value;
}
});
},
// [
// rehypePrettyCode,
// {
// theme: "github-light",
// },
// ],
() => (tree) => {
visit(tree, (node) => {
if (node?.type === "element" && node?.tagName === "div") {
if (!("data-rehype-pretty-code-fragment" in node.properties)) {
return;
}

for (const child of node.children) {
if (child.tagName === "pre") {
child.properties["raw"] = node.raw;
}
}
}
});
},
[
rehypeAutolinkHeadings,
{
properties: {
className: ["anchor"],
"data-mdx-heading": "",
},
},
],
]}
>
{children}
</ReactMarkdownHelper>
);
}
27 changes: 27 additions & 0 deletions lib/actions/get-project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cache } from "react";
import { getRepo } from "../github";
import prisma from "../prisma";
import { EnrichedProjectProps } from "../types";

Expand Down Expand Up @@ -27,12 +28,38 @@ export const getProject = cache(
},
});
if (!project) return null;

const githubLink = project.links.find((link) => link.type === "GITHUB")!;
const websiteLink = project.links.find((link) => link.type === "WEBSITE");

const { stars, default_branch } = await getRepo(githubLink.url);

if (stars !== project.stars) {
await prisma.project.update({
where: {
slug,
},
data: {
stars,
},
});
}

const readmeUrl =
githubLink.url
.replace("github.com", "raw.githubusercontent.com")
.replace(/\/$/, "") + `/${default_branch}/README.md`;

const readme = await fetch(readmeUrl)
.then((res) => res.text())
.catch(() => "");

return {
...project,
stars,
githubLink,
websiteLink,
readme,
users: project.users.map(({ userId, role, user }) => ({
id: userId,
role,
Expand Down
2 changes: 2 additions & 0 deletions lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function getRepo(url: string) {
owner,
stargazers_count: stars,
forks,
default_branch,
} = data;

return {
Expand All @@ -32,5 +33,6 @@ export async function getRepo(url: string) {
homepage,
stars,
forks,
default_branch,
};
}
1 change: 1 addition & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Link, Project } from "@prisma/client";
export interface EnrichedProjectProps extends Project {
links: Link[];
githubLink: Link;
readme: string;
websiteLink: Link | null;
users: {
id: string;
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"react-markdown": "^9.0.1",
"react-textarea-autosize": "^8.5.3",
"react@latest": "link:@@types/react@latest",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.13.1",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0",
"sonner": "^1.4.41",
"typescript": "5.4.5",
"use-debounce": "^9.0.4",
Expand Down
Loading