Skip to content
Closed
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
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