Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.90.2",
"@trpc/client": "^11.6.0",
Expand All @@ -27,15 +28,18 @@
"dompurify": "^3.3.0",
"framer-motion": "^11.15.0",
"geist": "^1.5.1",
"gray-matter": "^4.0.3",
"lucide-react": "^0.456.0",
"next": "15.5.3",
"next-auth": "^4.24.11",
"next-themes": "^0.4.3",
"posthog-js": "^1.203.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^10.1.0",
"react-qr-code": "^2.0.18",
"react-tweet": "^3.2.1",
"remark-gfm": "^4.0.1",
"superjson": "^2.2.5",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
Expand Down
15 changes: 15 additions & 0 deletions apps/web/src/app/(main)/dashboard/newsletter/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NewsletterView } from "@/components/dashboard/NewsletterView";
import { getNewsletter } from "@/utils/getMarkdownNewsletter";

export default async function NewsletterViewPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const newsletter = getNewsletter(slug);

if (!newsletter) return <div className="text-white p-10">Not Found</div>;

return <NewsletterView newsletter={newsletter} />;
}
15 changes: 15 additions & 0 deletions apps/web/src/app/(main)/dashboard/newsletter/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import NewsLetterContainer from "@/components/dashboard/NewsLetterContainer";
import { getAllNewsletters } from "@/utils/getMarkdownNewsletter";

import React from "react";

async function NewsletterPage() {
const newsletters = await getAllNewsletters();
return (
<div className=" ">
<NewsLetterContainer newsletters={newsletters} />
</div>
);
}

export default NewsletterPage;
114 changes: 114 additions & 0 deletions apps/web/src/components/dashboard/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";

import React from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

interface MarkdownViewerProps {
content: string;
}

export function MarkdownViewer({ content }: MarkdownViewerProps) {
return (
<div className="prose prose-invert prose-zinc max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ node, ...props }) => (
<h1 className="text-3xl font-bold mb-4 text-white" {...props} />
),
h2: ({ node, ...props }) => (
<h2
className="text-2xl font-semibold mb-3 mt-8 text-white"
{...props}
/>
),
h3: ({ node, ...props }) => (
<h3
className="text-xl font-semibold mb-2 mt-6 text-white"
{...props}
/>
),
p: ({ node, ...props }) => (
<p className="mb-4 text-zinc-300 leading-relaxed" {...props} />
),
ul: ({ node, ...props }) => (
<ul
className="list-disc list-inside mb-4 text-zinc-300 space-y-2"
{...props}
/>
),
ol: ({ node, ...props }) => (
<ol
className="list-decimal list-inside mb-4 text-zinc-300 space-y-2"
{...props}
/>
),
li: ({ node, ...props }) => (
<li className="text-zinc-300" {...props} />
),
a: ({ node, ...props }) => (
<a
className="text-ox-purple hover:text-ox-purple/80 underline transition-colors"
target="_blank"
rel="noopener noreferrer"
{...props}
/>
),
blockquote: ({ node, ...props }) => (
<blockquote
className="border-l-4 border-ox-purple pl-4 py-2 my-4 italic text-zinc-400 bg-[#15161a]"
{...props}
/>
),
code: ({ node, inline, ...props }: any) =>
inline ? (
<code
className="bg-[#15161a] text-ox-purple px-1.5 py-0.5 rounded text-sm font-mono"
{...props}
/>
) : (
<code
className="block bg-[#15161a] text-zinc-300 p-4 rounded-lg overflow-x-auto my-4 font-mono text-sm"
{...props}
/>
),
pre: ({ node, ...props }) => (
<pre
className="bg-[#15161a] rounded-lg overflow-x-auto my-4"
{...props}
/>
),
img: ({ node, ...props }) => (
<img className="rounded-lg my-4 max-w-full h-auto" {...props} />
),
table: ({ node, ...props }) => (
<div className="overflow-x-auto my-4">
<table
className="min-w-full border border-[#1d1d21]"
{...props}
/>
</div>
),
th: ({ node, ...props }) => (
<th
className="border border-[#1d1d21] bg-[#15161a] px-4 py-2 text-left text-white font-semibold"
{...props}
/>
),
td: ({ node, ...props }) => (
<td
className="border border-[#1d1d21] px-4 py-2 text-zinc-300"
{...props}
/>
),
hr: ({ node, ...props }) => (
<hr className="my-8 border-[#1d1d21]" {...props} />
),
}}
>
{content}
</ReactMarkdown>
</div>
);
}
59 changes: 59 additions & 0 deletions apps/web/src/components/dashboard/NewsLetterCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { NewsletterProps } from "@/types/newsletter";
import { Calendar, ArrowRight } from "lucide-react";
import Link from "next/link";

interface NewsletterCardProps {
newsletter: NewsletterProps;
}

export function NewsletterCard({ newsletter }: NewsletterCardProps) {
const formattedDate = newsletter.date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});

return (
<Link
href={`/dashboard/newsletter/${newsletter.slug}`}
className="block h-full "
>
<Card className="h-full transition-all duration-300 bg-[#15161a] border border-[#1a1a1d] hover:border-ox-purple/50 hover:shadow-xl hover:shadow-ox-purple/10 hover:-translate-y-2 group relative overflow-hidden rounded-xl">
{/* Accent Glow */}
<div className="absolute top-0 right-0 w-32 h-32 bg-ox-purple/10 rounded-full blur-3xl group-hover:bg-ox-purple/20 transition-colors duration-300 -mr-16 -mt-16" />

<CardHeader className="relative">
<div className="inline-flex items-center gap-2 text-xs text-zinc-400 mb-3 bg-[#1a1a1d] px-3 py-1 rounded-full w-fit border border-[#242529]">
<Calendar className="w-3.5 h-3.5 text-ox-purple" />
<time dateTime={newsletter.date.toISOString()}>
{formattedDate}
</time>
</div>
<h2 className="text-xl font-bold text-white group-hover:text-ox-purple transition-colors duration-300 leading-tight">
{newsletter.title}
</h2>
</CardHeader>

<CardContent className="relative">
<p className="text-zinc-400 leading-relaxed mb-4 line-clamp-3">
{newsletter.excerpt}
</p>
<div className="flex items-center gap-2 text-ox-purple font-medium text-sm group-hover:gap-3 transition-all duration-300">
Read newsletter
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform duration-300" />
</div>
</CardContent>
</Card>
</Link>
);
}

// Grid Wrapper for Newsletter Cards
export function NewsletterGrid({ children }: { children: React.ReactNode }) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-fr py-2">
{children}
</div>
);
}
Loading