Skip to content
Open
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
90 changes: 31 additions & 59 deletions app/(home)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,85 +4,57 @@ import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { blog } from '@/lib/source';
import { Button } from '@/components/ui/button';
import { FiArrowLeft, FiCalendar, FiUser } from 'react-icons/fi';

import { ArrowLeft, Calendar, UserCircle2 } from 'lucide-react';

export default async function Page(props: {
params: Promise<{ slug: string }>;
}) {
const params = await props.params;
const page = blog.getPage([params.slug]);

if (!page) notFound();
const Mdx = page.data.body;

return (
<div className="container py-8 md:py-12 px-4">
<Button
variant="ghost"
asChild
className="mb-8 group hover:bg-primary/10 transition-colors"
>
<Link href="/blog" className="flex items-center gap-2">
<FiArrowLeft className="h-4 w-4 transition-transform group-hover:-translate-x-1" />
Back to Blog
<main className="container mx-auto max-w-6xl px-4 pb-16 pt-8 md:pt-12">
<Button variant="ghost" asChild className="mb-6">
<Link href="/blog" className="inline-flex items-center gap-2">
<ArrowLeft className="h-4 w-4" /> Back to Blog
</Link>
</Button>

<article className="relative">
{/* Article Header */}
<div className="mb-8 space-y-6">
<h1 className="text-4xl font-bold tracking-tight bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
{page.data.title}
</h1>
<p className="text-xl text-muted-foreground">
{page.data.description}
</p>

{/* Metadata */}
<div className="flex flex-wrap gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-2 bg-accent/20 px-4 py-2 rounded-full">
<FiUser className="h-4 w-4" />
<span>{page.data.author}</span>
</div>
<div className="flex items-center gap-2 bg-accent/20 px-4 py-2 rounded-full">
<FiCalendar className="h-4 w-4" />
<span>{new Date(page.data.date).toLocaleDateString()}</span>
</div>
<article className="rounded-2xl border bg-background p-6 md:p-10">
<header className="mb-10 border-b pb-8">
<h1 className="mb-4 text-3xl font-bold tracking-tight md:text-5xl">{page.data.title}</h1>
<p className="max-w-3xl text-lg text-muted-foreground">{page.data.description}</p>

<div className="mt-5 flex flex-wrap gap-2 text-sm">
<span className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-primary">
<UserCircle2 className="h-4 w-4" /> {page.data.author}
</span>
<span className="inline-flex items-center gap-2 rounded-full bg-muted px-3 py-1 text-muted-foreground">
<Calendar className="h-4 w-4" /> {new Date(page.data.date).toLocaleDateString()}
</span>
</div>
</div>
</header>

{/* Content Layout */}
<div className="grid md:grid-cols-[240px_1fr] gap-8">
{/* Sticky TOC */}
<div className="hidden md:block sticky top-24 self-start">
<div className="border-l-2 pl-4">
<h3 className="text-sm font-semibold mb-4">Table of Contents</h3>
<InlineTOC
items={page.data.toc}
/>
<div className="grid gap-10 md:grid-cols-[250px_1fr]">
<aside className="hidden md:block">
<div className="sticky top-24 rounded-xl border bg-card p-4">
<h3 className="mb-3 text-sm font-semibold">On this page</h3>
<InlineTOC items={page.data.toc} />
</div>
</div>
</aside>

{/* Main Content */}
<div className="prose prose-lg dark:prose-invert max-w-none">
<div className="prose prose-neutral max-w-none dark:prose-invert prose-headings:scroll-mt-24 prose-a:text-primary">
<Mdx components={defaultMdxComponents} />
</div>
</div>

{/* Footer */}
<div className="mt-12 pt-8 border-t">
<Button variant="outline" asChild>
<Link href="/blog" className="gap-2">
<FiArrowLeft className="h-4 w-4" />
Back to All Articles
</Link>
</Button>
</div>
</article>
</div>
</main>
);
}
}

export function generateStaticParams(): { slug: string }[] {
return blog.getPages().map((page) => ({
slug: page.slugs[0],
Expand All @@ -94,9 +66,9 @@ export async function generateMetadata(props: {
}) {
const params = await props.params;
const page = blog.getPage([params.slug]);

if (!page) notFound();

return {
title: page.data.title,
description: page.data.description,
Expand Down
81 changes: 35 additions & 46 deletions app/(home)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,59 @@
import Link from 'next/link';
import Image from 'next/image';

import { blog } from '@/lib/source';
import { FiArrowRight, FiClock, FiTag } from 'react-icons/fi';
import { ArrowRight, Clock3, Sparkles, Tag } from 'lucide-react';

export default function Home() {
export default function BlogHome() {
const posts = blog.getPages();

return (
<main className="grow container mx-auto px-4 py-12">
<h1 className="text-4xl md:text-5xl font-bold mb-12 bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">
Latest Insights
</h1>

<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<main className="container mx-auto max-w-6xl px-4 pb-16 pt-12">
<section className="mb-10 rounded-2xl border bg-gradient-to-r from-primary/10 via-background to-pink-500/10 p-8">
<p className="mb-2 inline-flex items-center gap-2 rounded-full border bg-background/80 px-3 py-1 text-xs font-medium text-primary">
<Sparkles className="h-3.5 w-3.5" /> Fresh insights for builders
</p>
<h1 className="mb-3 text-4xl font-bold tracking-tight md:text-5xl">Dcup Blog</h1>
<p className="max-w-2xl text-muted-foreground">
Product, AI retrieval, infrastructure, and growth playbooks to help you ship faster and convert better.
</p>
</section>

<section className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post, idx) => (
<Link
key={post.url}
href={post.url}
className="group relative overflow-hidden rounded-xl bg-background/50 backdrop-blur-lg border border-gray-200/20 hover:border-primary/30 transition-all duration-300 ease-out hover:shadow-xl hover:-translate-y-2"
style={{ viewTransitionName: 'blog-card' }}
className="group overflow-hidden rounded-2xl border bg-card transition-all hover:-translate-y-1 hover:border-primary/40 hover:shadow-xl"
style={{ animation: `fadeSlideIn 0.55s ease-out ${idx * 0.07}s both` }}
>
{/* Optional image container - add if you have cover images */}
<div className="h-48 bg-gray-800/30 overflow-hidden">
<div className="h-52 overflow-hidden border-b">
<Image
src={`/${post.file.name}.jpg`}
alt={post.data.title}
width={200}
height={10}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
width={640}
height={320}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
</div>

<div className="p-6">
{/* Category and date */}
<div className="flex items-center gap-4 text-sm text-muted-foreground mb-4">
<div className="flex items-center gap-2 bg-primary/10 text-primary px-3 py-1 rounded-full">
<FiTag className="h-4 w-4" />
<span>{'General'}</span>
</div>
<div className="flex items-center gap-2">
<FiClock className="h-4 w-4" />
<span>{'5 min read'}</span>
</div>
</div>

{/* Content */}
<h2 className="text-xl font-semibold mb-3 line-clamp-2">
{post.data.title}
</h2>
<p className="text-muted-foreground line-clamp-3 mb-6">
{post.data.description}
</p>

{/* Read more */}
<div className="flex items-center gap-2 text-primary font-medium">
<span>Read article</span>
<FiArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
<div className="p-5">
<div className="mb-4 flex flex-wrap gap-2 text-xs">
<span className="inline-flex items-center gap-1 rounded-full bg-primary/10 px-2.5 py-1 text-primary">
<Tag className="h-3 w-3" /> General
</span>
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2.5 py-1 text-muted-foreground">
<Clock3 className="h-3 w-3" /> 5 min read
</span>
</div>
<h2 className="mb-2 text-xl font-semibold leading-tight">{post.data.title}</h2>
<p className="mb-5 line-clamp-3 text-sm text-muted-foreground">{post.data.description}</p>
<span className="inline-flex items-center gap-2 text-sm font-medium text-primary">
Read article <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
</span>
</div>

{/* Hover effect */}
<div className="absolute inset-0 border-2 border-transparent group-hover:border-primary/20 rounded-xl pointer-events-none transition-all duration-300" />
</Link>
))}
</div>
</section>
</main>
);
}
137 changes: 137 additions & 0 deletions app/(home)/coming-soon/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use client';

import { FormEvent, useState } from 'react';
import Link from 'next/link';
import { ArrowRight, CheckCircle2, Sparkles } from 'lucide-react';
import { Button } from '@/components/ui/button';

export default function ComingSoonPage() {
const [email, setEmail] = useState('');
const [thoughts, setThoughts] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [message, setMessage] = useState('');

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setStatus('loading');
setMessage('Submitting...');

try {
const response = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, thoughts }),
});

const data = await response.json();

if (!response.ok) {
throw new Error(data?.message || 'Unable to submit right now.');
}

setStatus('success');
setMessage('You are on the list! We will reach out before launch.');
setEmail('');
setThoughts('');
} catch (error) {
setStatus('error');
setMessage(error instanceof Error ? error.message : 'Something went wrong. Please try again.');
}
}

return (
<main className="relative overflow-hidden">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_10%_20%,rgba(59,130,246,0.16),transparent_35%),radial-gradient(circle_at_85%_15%,rgba(236,72,153,0.15),transparent_30%),linear-gradient(to_bottom,rgba(99,102,241,0.08),transparent_35%)]" />

<section className="container mx-auto max-w-6xl px-4 pb-20 pt-14 md:pt-20">
<div className="grid items-start gap-10 lg:grid-cols-[1fr_1fr]">
<div className="space-y-6">
<p className="inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1 text-xs font-semibold uppercase tracking-wider text-primary">
<Sparkles className="h-3.5 w-3.5" /> Coming Soon
</p>

<h1 className="text-4xl font-extrabold tracking-tight md:text-6xl md:leading-tight">
dcup – Your Data, Smarter. <span className="bg-gradient-to-r from-blue-500 to-pink-500 bg-clip-text text-transparent">Coming Soon.</span>
</h1>

<p className="max-w-xl text-base text-muted-foreground md:text-lg">
Unlock the power of your data with dcup – the open-source AI assistant platform built for developers. Connect PDFs, web pages, Notion notes, and more in minutes, and turn them into searchable, actionable intelligence.
</p>

<div className="space-y-3 rounded-2xl border bg-background/80 p-5 backdrop-blur">
{[
'Connect docs, sites, and notes in minutes',
'Search and reason over your data with confidence',
'Built open-source for developer control and flexibility',
].map((item) => (
<p key={item} className="flex items-start gap-2 text-sm text-foreground/90">
<CheckCircle2 className="mt-0.5 h-4 w-4 text-primary" />
<span>{item}</span>
</p>
))}
</div>

<Button asChild variant="outline">
<Link href="/docs" className="inline-flex items-center gap-2">
Explore existing docs <ArrowRight className="h-4 w-4" />
</Link>
</Button>
</div>

<div className="rounded-3xl border bg-card p-6 shadow-2xl md:p-8">
<h2 className="mb-2 text-2xl font-bold">Join the early-access waitlist</h2>
<p className="mb-6 text-sm text-muted-foreground">
Leave your email and thoughts. We are collecting interest to prioritize launch features.
</p>

<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-1.5">
<label htmlFor="email" className="text-sm font-medium">Email address</label>
<input
id="email"
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
placeholder="you@company.com"
required
className="h-11 w-full rounded-lg border bg-background px-3 text-sm outline-none ring-offset-background transition focus:border-primary focus:ring-2 focus:ring-primary/30"
/>
</div>

<div className="space-y-1.5">
<label htmlFor="thoughts" className="text-sm font-medium">What would you like dcup to solve for you?</label>
<textarea
id="thoughts"
value={thoughts}
onChange={(event) => setThoughts(event.target.value)}
placeholder="Tell us your data challenges, integrations, or ideal workflow..."
required
rows={6}
className="w-full rounded-lg border bg-background px-3 py-2 text-sm outline-none ring-offset-background transition focus:border-primary focus:ring-2 focus:ring-primary/30"
/>
</div>

<Button type="submit" size="lg" disabled={status === 'loading'} className="w-full bg-gradient-to-r from-blue-600 to-pink-600 text-white">
{status === 'loading' ? 'Submitting...' : 'Join Waitlist'}
</Button>

{status !== 'idle' && (
<p
className={`rounded-md px-3 py-2 text-sm ${
status === 'success'
? 'border border-green-500/30 bg-green-500/10 text-green-700 dark:text-green-400'
: status === 'error'
? 'border border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-400'
: 'border border-primary/30 bg-primary/10 text-primary'
}`}
>
{message}
</p>
)}
</form>
</div>
</div>
</section>
</main>
);
}
Loading