DashCaddy.net all files

This commit is contained in:
Krystie
2026-04-15 20:55:30 -07:00
commit c5d7d99852
36 changed files with 9611 additions and 0 deletions

197
src/app/about/page.tsx Normal file
View File

@@ -0,0 +1,197 @@
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import Link from "next/link";
export default function AboutPage() {
return (
<>
<Navbar />
<main className="flex-1">
{/* Hero */}
<section className="relative pt-32 pb-20 px-4">
<div className="absolute inset-0 hero-glow pointer-events-none" />
<div className="max-w-4xl mx-auto text-center relative z-10">
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
Built for the{" "}
<span className="gradient-text">Self-Hosting Community</span>
</h1>
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
DashCaddy was born from the frustration of managing dozens of
Docker containers, SSL certificates, and DNS records by hand. We
built the tool we wished existed.
</p>
</div>
</section>
{/* Story */}
<section className="py-20 px-4">
<div className="max-w-3xl mx-auto">
<h2 className="text-2xl font-bold text-white mb-6">Our Story</h2>
<div className="space-y-4 text-surface-300 leading-relaxed">
<p>
Self-hosting is powerful. You own your data, you control your
infrastructure, and you&apos;re not at the mercy of SaaS
providers who can change their terms, raise prices, or shut down
overnight. But let&apos;s be honest &mdash; it can also be a
pain.
</p>
<p>
Every new service means editing Caddyfiles, creating DNS
records, configuring SSL certificates, writing Docker Compose
files, and hoping everything plays nicely together. Multiply that
by 20, 30, or 50 services, and you&apos;ve got a full-time
operations job on your hands.
</p>
<p>
DashCaddy was built to solve this. One click to deploy an app.
SSL, DNS, and reverse proxy configuration happen automatically.
A beautiful dashboard to monitor everything. And when something
goes wrong, you know about it immediately &mdash; not when a
family member texts you that Plex is down.
</p>
<p>
We believe self-hosting should be accessible to everyone, not
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
beautiful and effortless.
</p>
</div>
</div>
</section>
{/* Values */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl font-bold text-white mb-12 text-center">
What We Believe In
</h2>
<div className="grid md:grid-cols-3 gap-8">
{[
{
icon: "🔓",
title: "Open Core",
description:
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
},
{
icon: "🏠",
title: "Your Data, Your Server",
description:
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
},
{
icon: "🛠️",
title: "Built to Last",
description:
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
},
].map((value) => (
<div
key={value.title}
className="glass-card rounded-xl p-8 text-center"
>
<div className="text-4xl mb-4">{value.icon}</div>
<h3 className="text-lg font-semibold text-white mb-3">
{value.title}
</h3>
<p className="text-surface-400">{value.description}</p>
</div>
))}
</div>
</div>
</section>
{/* Tech Stack */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl font-bold text-white mb-12 text-center">
Built With
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{[
{
name: "Caddy",
role: "Reverse Proxy & SSL",
icon: "🔒",
},
{
name: "Docker",
role: "Container Runtime",
icon: "🐳",
},
{
name: "Node.js",
role: "API Backend",
icon: "🟢",
},
{
name: "Technitium",
role: "DNS Server",
icon: "🌐",
},
].map((tech) => (
<div
key={tech.name}
className="glass-card rounded-xl p-6 text-center hover:border-brand-500/30 transition-colors"
>
<div className="text-3xl mb-3">{tech.icon}</div>
<div className="font-semibold text-white">{tech.name}</div>
<div className="text-sm text-surface-400">{tech.role}</div>
</div>
))}
</div>
</div>
</section>
{/* Contact / Support */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
<p className="text-surface-300 mb-8">
Have questions, feedback, or want to contribute? We&apos;d love to
hear from you.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="mailto:support@dashcaddy.net"
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
>
Email Us
</a>
<a
href="#"
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
>
Join Discord
</a>
<a
href="#"
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
>
GitHub
</a>
</div>
</div>
</section>
{/* CTA */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl font-bold text-white mb-4">
Ready to simplify your homelab?
</h2>
<p className="text-surface-300 mb-8">
Start with the free tier. Upgrade when you&apos;re ready.
</p>
<Link
href="/pricing"
className="inline-block px-8 py-4 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-semibold text-lg transition-colors"
>
View Pricing
</Link>
</div>
</section>
</main>
<Footer />
</>
);
}

View File

@@ -0,0 +1,74 @@
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2026-03-25.dahlia",
});
const PRICE_IDS: Record<string, string | undefined> = {
monthly: process.env.STRIPE_PRICE_MONTHLY,
yearly: process.env.STRIPE_PRICE_YEARLY,
};
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { plan, email } = body;
if (!plan || !PRICE_IDS[plan]) {
return NextResponse.json(
{ error: "Invalid plan. Choose 'monthly' or 'yearly'." },
{ status: 400 }
);
}
const priceId = PRICE_IDS[plan];
if (!priceId) {
return NextResponse.json(
{ error: "Price not configured. Please contact support." },
{ status: 500 }
);
}
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "https://dashcaddy.net";
const sessionParams: Stripe.Checkout.SessionCreateParams = {
mode: "subscription",
payment_method_types: ["card"],
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${appUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${appUrl}/pricing`,
allow_promotion_codes: true,
billing_address_collection: "required",
subscription_data: {
trial_period_days: 14,
metadata: {
plan,
source: "dashcaddy-website",
},
},
metadata: {
plan,
},
};
// Pre-fill email if provided
if (email) {
sessionParams.customer_email = email;
}
const session = await stripe.checkout.sessions.create(sessionParams);
return NextResponse.json({ url: session.url });
} catch (error) {
console.error("Stripe checkout error:", error);
const message =
error instanceof Error ? error.message : "Internal server error";
return NextResponse.json({ error: message }, { status: 500 });
}
}

View File

@@ -0,0 +1,99 @@
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2026-03-25.dahlia",
});
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get("stripe-signature");
if (!signature) {
return NextResponse.json(
{ error: "Missing stripe-signature header" },
{ status: 400 }
);
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
console.error(`Webhook signature verification failed: ${message}`);
return NextResponse.json({ error: message }, { status: 400 });
}
try {
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
console.log("Checkout completed:", {
sessionId: session.id,
customerEmail: session.customer_email,
plan: session.metadata?.plan,
subscriptionId: session.subscription,
});
// TODO: Generate and deliver license key to customer
// This is where you'd:
// 1. Generate a DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX license code
// 2. Store it in your database
// 3. Email it to the customer
// 4. Associate it with the Stripe subscription ID
break;
}
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
console.log("Subscription updated:", {
subscriptionId: subscription.id,
status: subscription.status,
});
// TODO: Update license expiration based on subscription status
break;
}
case "customer.subscription.deleted": {
const subscription = event.data.object as Stripe.Subscription;
console.log("Subscription cancelled:", {
subscriptionId: subscription.id,
status: subscription.status,
});
// TODO: Deactivate/expire the license key
// The DashCaddy instance will gracefully downgrade to free tier
break;
}
case "invoice.payment_failed": {
const invoice = event.data.object as Stripe.Invoice;
console.log("Payment failed:", {
invoiceId: invoice.id,
customerEmail: invoice.customer_email,
});
// TODO: Notify customer about failed payment
// Consider a grace period before deactivating license
break;
}
default:
console.log(`Unhandled event type: ${event.type}`);
}
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook handler error:", error);
return NextResponse.json(
{ error: "Webhook handler failed" },
{ status: 500 }
);
}
}

389
src/app/docs/page.tsx Normal file
View File

@@ -0,0 +1,389 @@
'use client';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function DocsPage() {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Getting <span className="text-brand-400">Started</span>
</h1>
<p className="text-xl text-surface-300 max-w-2xl">
Get DashCaddy up and running on your server in just a few minutes.
</p>
</div>
</section>
{/* Main Content */}
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
{/* Table of Contents */}
<div className="mb-16 rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">Table of Contents</h2>
<ul className="space-y-3">
<li>
<a href="#prerequisites" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Prerequisites
</a>
</li>
<li>
<a href="#installation" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Installation
</a>
</li>
<li>
<a href="#configuration" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Configuration
</a>
</li>
<li>
<a href="#first-run" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> First Run
</a>
</li>
<li>
<a href="#troubleshooting" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Troubleshooting
</a>
</li>
</ul>
</div>
{/* Prerequisites Section */}
<section id="prerequisites" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">📋</span> Prerequisites
</h2>
<p className="text-surface-300 mb-6 leading-relaxed">
Before you begin, ensure you have the following installed on your server:
</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🐳</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Docker</h3>
<p className="text-sm text-surface-400">Version 20.10+ required. <a href="https://docs.docker.com/get-docker/" className="text-brand-400 hover:text-brand-300">Install Docker</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0"></div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Caddy</h3>
<p className="text-sm text-surface-400">Version 2.7+ required. <a href="https://caddyserver.com/docs/install" className="text-brand-400 hover:text-brand-300">Install Caddy</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🟢</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Node.js</h3>
<p className="text-sm text-surface-400">Version 18+ required. <a href="https://nodejs.org/" className="text-brand-400 hover:text-brand-300">Install Node.js</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🔧</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Git</h3>
<p className="text-sm text-surface-400">For cloning the repository. <a href="https://git-scm.com/" className="text-brand-400 hover:text-brand-300">Install Git</a></p>
</div>
</div>
<div className="border-t border-surface-700/30 pt-4 mt-4">
<h3 className="font-semibold text-surface-50 mb-2">Optional</h3>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🌐</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Technitium DNS</h3>
<p className="text-sm text-surface-400">For automatic DNS management. If not installed, you can still manage DNS manually.</p>
</div>
</div>
</div>
</div>
</section>
{/* Installation Section */}
<section id="installation" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">📦</span> Installation
</h2>
<p className="text-surface-300 mb-8 leading-relaxed">
Follow these steps to install DashCaddy on your server:
</p>
{/* Step 1 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 1: Clone the Repository</h3>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
git clone https://github.com/dashcaddy/dashcaddy.git
<br />
cd dashcaddy
</code>
</div>
</div>
{/* Step 2 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 2: Install Dependencies</h3>
<p className="text-surface-300 mb-4">Install Node.js dependencies:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
npm install
</code>
</div>
</div>
{/* Step 3 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 3: Configure Environment Variables</h3>
<p className="text-surface-300 mb-4">Copy the example environment file and customize it:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-4">
<code className="text-sm text-green-400 font-mono block">
cp .env.example .env
</code>
</div>
<p className="text-surface-300 mb-4">Then edit <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">.env</code> with your configuration:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
{`# Server
PORT=3000
NODE_ENV=production
# Database (optional - defaults to SQLite)
DATABASE_URL=sqlite:./data/dashcaddy.db
# Security
JWT_SECRET=your-secure-random-secret-here
SESSION_SECRET=another-secure-random-secret
# Caddy
CADDY_PORT=80
CADDY_HTTPS_PORT=443
CADDY_ADMIN_LISTEN=localhost:2019
# Technitium DNS (optional)
TECHNITIUM_API_URL=http://localhost:5380/api
TECHNITIUM_API_KEY=your-api-key
# Email (optional - for notifications)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-password`}
</code>
</div>
</div>
{/* Step 4 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 4: Build and Start</h3>
<p className="text-surface-300 mb-4">Build the application:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-6">
<code className="text-sm text-green-400 font-mono block">
npm run build
</code>
</div>
<p className="text-surface-300 mb-4">Start DashCaddy:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
npm start
</code>
</div>
<p className="text-surface-300 mt-4 text-sm">
The application will be available at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://localhost:3000</code>
</p>
</div>
{/* Step 5 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 5: Configure Caddy</h3>
<p className="text-surface-300 mb-4">Update your Caddy configuration to proxy requests to DashCaddy:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
{`dashcaddy.local {
reverse_proxy localhost:3000
# Enable automatic HTTPS
encode gzip
# Security headers
header Strict-Transport-Security "max-age=31536000"
header X-Content-Type-Options "nosniff"
header X-Frame-Options "DENY"
}`}
</code>
</div>
</div>
</section>
{/* Configuration Section */}
<section id="configuration" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400"></span> Configuration
</h2>
<p className="text-surface-300 mb-8 leading-relaxed">
Key environment variables for DashCaddy configuration:
</p>
<div className="space-y-6">
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">PORT</h3>
<p className="text-surface-300 text-sm mb-2">The port DashCaddy runs on. Default: <code className="bg-surface-800 px-1 rounded">3000</code></p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">JWT_SECRET</h3>
<p className="text-surface-300 text-sm mb-2">Secret key for JWT tokens. Generate a secure random string:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-brand-400 text-xs">openssl rand -hex 32</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">DATABASE_URL</h3>
<p className="text-surface-300 text-sm">Connection string for your database. Defaults to SQLite if not provided.</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">CADDY_ADMIN_LISTEN</h3>
<p className="text-surface-300 text-sm mb-2">Caddy admin API endpoint. Default: <code className="bg-surface-800 px-1 rounded">localhost:2019</code></p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">TECHNITIUM_API_URL</h3>
<p className="text-surface-300 text-sm">URL to your Technitium DNS API. Optional for DNS management features.</p>
</div>
</div>
</section>
{/* First Run Section */}
<section id="first-run" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">🚀</span> First Run
</h2>
<div className="rounded-lg border border-brand-500/30 bg-brand-950/50 p-8">
<ol className="space-y-4 list-decimal list-inside text-surface-300">
<li>Access the dashboard at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://dashcaddy.local</code></li>
<li>Create your admin account with a strong password</li>
<li>Enable TOTP 2FA for enhanced security</li>
<li>Configure your Technitium DNS API key (optional)</li>
<li>Deploy your first application from the app templates library</li>
<li>Monitor your apps in real-time from the dashboard</li>
</ol>
</div>
</section>
{/* Troubleshooting Section */}
<section id="troubleshooting" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">🔧</span> Troubleshooting
</h2>
<div className="space-y-6">
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Docker daemon not running
</h3>
<p className="text-surface-300 text-sm mb-3">Make sure the Docker daemon is started:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">sudo systemctl start docker</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Port 3000 already in use
</h3>
<p className="text-surface-300 text-sm mb-3">Change the PORT in your .env file or stop the process using that port:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">lsof -i :3000</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Cannot connect to Caddy admin API
</h3>
<p className="text-surface-300 text-sm mb-3">Verify Caddy is running and the admin API is accessible:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">curl http://localhost:2019/config/</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Database connection errors
</h3>
<p className="text-surface-300 text-sm mb-3">Check that your DATABASE_URL is correct and the database is accessible. For SQLite, ensure the data directory exists:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">mkdir -p ./data</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> SSL certificate issues
</h3>
<p className="text-surface-300 text-sm mb-3">DashCaddy uses Caddy's internal CA for certificate generation. If you have issues, check the Caddy logs:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">journalctl -u caddy -f</code>
</div>
</div>
</section>
{/* Next Steps */}
<section className="mb-16">
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">Next Steps</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Link
href="/features"
className="flex items-start gap-4 p-4 rounded-lg border border-brand-500/20 bg-brand-950/30 hover:bg-brand-950/50 transition-colors"
>
<span className="text-2xl flex-shrink-0"></span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Explore Features</h3>
<p className="text-sm text-surface-400">Learn about all the powerful features DashCaddy offers.</p>
</div>
</Link>
<a
href="#"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">📚</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">API Reference</h3>
<p className="text-sm text-surface-400">Full API documentation for developers.</p>
</div>
</a>
<Link
href="/pricing"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">💎</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">View Pricing</h3>
<p className="text-sm text-surface-400">Check out our free and premium plans.</p>
</div>
</Link>
<a
href="mailto:support@dashcaddy.net"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">💬</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Get Support</h3>
<p className="text-sm text-surface-400">Contact our support team for help.</p>
</div>
</a>
</div>
</div>
</section>
</div>
</section>
<Footer />
</div>
);
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

336
src/app/features/page.tsx Normal file
View File

@@ -0,0 +1,336 @@
'use client';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import FeatureCard from '@/components/FeatureCard';
interface FeatureItem {
icon: string;
label: string;
premium?: boolean;
}
interface FeatureSection {
id: string;
title: string;
description: string;
icon: string;
features: FeatureItem[];
}
export default function FeaturesPage() {
const features: FeatureSection[] = [
{
id: 'deployment',
title: 'App Deployment',
description: 'Deploy your favorite applications instantly from our library of 50+ pre-configured Docker templates. No manual configuration needed—just click, deploy, and go live.',
icon: '🚀',
features: [
{ icon: '⚡', label: 'One-click deployment' },
{ icon: '📦', label: '50+ app templates' },
{ icon: '⚙️', label: 'Auto-configuration' },
],
},
{
id: 'ssl-security',
title: 'SSL & Security',
description: 'Enterprise-grade security built in. Automatic SSL certificates, TOTP 2FA, encrypted credentials, and comprehensive audit logs to track every action.',
icon: '🔒',
features: [
{ icon: '🔐', label: 'Automatic SSL certificates' },
{ icon: '📱', label: 'TOTP 2FA authentication' },
{ icon: '🔑', label: 'Encrypted credentials' },
{ icon: '📝', label: 'Audit logs' },
],
},
{
id: 'dns',
title: 'DNS Management',
description: 'Manage all your app subdomains from one dashboard. Automatic DNS record creation with Technitium DNS integration makes domain management effortless.',
icon: '🌐',
features: [
{ icon: '✨', label: 'Automatic DNS records' },
{ icon: '🔗', label: 'Technitium integration' },
{ icon: '📋', label: 'Subdomain management' },
{ icon: '🎯', label: 'Zero DNS config' },
],
},
{
id: 'monitoring',
title: 'Monitoring & Health',
description: 'Real-time visibility into your infrastructure. Monitor container health, response times, and resource usage with detailed metrics and alerts.',
icon: '📊',
features: [
{ icon: '🟢', label: 'Real-time status' },
{ icon: '⏱️', label: 'Response time tracking' },
{ icon: '💾', label: 'Resource monitoring' },
{ icon: '📈', label: 'Performance metrics' },
],
},
{
id: 'docker',
title: 'Docker Management',
description: 'Control your entire Docker environment visually. View, manage, and scale containers, access logs, and perform updates without touching the command line.',
icon: '🐳',
features: [
{ icon: '🎮', label: 'Container control' },
{ icon: '📜', label: 'Live logs access' },
{ icon: '🔄', label: 'Auto-updates' },
{ icon: '📊', label: 'Resource monitoring' },
],
},
{
id: 'premium',
title: 'Premium Features',
description: 'Advanced features for power users and production deployments. SSO integration, multi-container recipes, and Docker Swarm orchestration.',
icon: '⭐',
features: [
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
{ icon: '📚', label: 'Recipes (stack deployment)', premium: true },
{ icon: '🚀', label: 'Docker Swarm orchestration', premium: true },
{ icon: '🚀', label: 'Priority support', premium: true },
],
},
];
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Powerful <span className="text-brand-400">Features</span> Built In
</h1>
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
Everything you need to manage Docker applications professionally. From deployment to monitoring, SSL to securityit's all included.
</p>
</div>
</section>
{/* Feature Sections */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="space-y-24">
{features.map((feature, idx) => (
<div key={feature.id} className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}>
{/* Content Side */}
<div className={idx % 2 === 1 ? 'lg:order-2' : ''}>
<div className="mb-6 inline-flex rounded-lg bg-brand-500/10 p-4 text-brand-400">
<div className="text-3xl">{feature.icon}</div>
</div>
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-surface-50">
{feature.title}
</h2>
<p className="text-lg text-surface-300 mb-8 leading-relaxed">
{feature.description}
</p>
{/* Feature Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{feature.features.map((item, itemIdx) => (
<div
key={itemIdx}
className={`flex items-center gap-3 p-3 rounded-lg border transition-all ${
item.premium
? 'border-brand-500/30 bg-brand-500/5 hover:bg-brand-500/10'
: 'border-surface-700/30 bg-surface-800/30 hover:border-surface-700/50'
}`}
>
<span className="text-xl flex-shrink-0">{item.icon}</span>
<span className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}>
{item.label}
{item.premium && (
<span className="ml-1 inline-block text-xs px-2 py-0.5 bg-brand-500/20 text-brand-300 rounded font-semibold">
Premium
</span>
)}
</span>
</div>
))}
</div>
</div>
{/* Visual Side */}
<div className={idx % 2 === 1 ? 'lg:order-1' : ''}>
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/10 to-brand-600/5 rounded-2xl blur-xl" />
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
<div className="space-y-4">
{feature.features.slice(0, 3).map((item, itemIdx) => (
<div key={itemIdx} className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30">
<div className="w-2 h-2 rounded-full bg-brand-400" />
<span className="text-sm text-surface-300">{item.label}</span>
</div>
))}
</div>
<div className="mt-6 pt-6 border-t border-surface-700/30">
<div className="text-sm text-surface-500 text-center">
<span className="text-brand-400 font-semibold">{feature.features.length} features</span> included
</div>
</div>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Feature Comparison Grid */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-900 to-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
What's Included in <span className="text-brand-400">Free</span>
</h2>
<p className="text-lg text-surface-400">
Start with the free tier and upgrade anytime when you need advanced features.
</p>
</div>
{/* Feature Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
icon="🚀"
title="One-Click Deployment"
description="Deploy from 50+ pre-configured templates with zero configuration."
/>
<FeatureCard
icon="🔒"
title="Automatic SSL"
description="Secure your apps with automatically renewed SSL certificates."
/>
<FeatureCard
icon="🌐"
title="DNS Management"
description="Automatic DNS record creation with Technitium integration."
/>
<FeatureCard
icon="📊"
title="Real-Time Monitoring"
description="Track container health, response times, and resource usage."
/>
<FeatureCard
icon="🐳"
title="Docker Control"
description="Manage containers, access logs, and updates from one dashboard."
/>
<FeatureCard
icon="🔐"
title="Security Built-In"
description="TOTP 2FA, encrypted credentials, and audit logging included."
/>
</div>
</div>
</section>
{/* Premium Section */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 right-0 w-96 h-96 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="rounded-2xl border border-brand-500/30 bg-gradient-to-br from-brand-950/50 to-surface-900 p-12">
<div className="max-w-3xl">
<div className="mb-6 inline-block">
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
Premium Features
</span>
</div>
<h2 className="text-3xl sm:text-4xl font-bold mb-6 text-surface-50">
Level Up With <span className="text-brand-400">Premium</span>
</h2>
<p className="text-lg text-surface-300 mb-8">
Get advanced features designed for power users and production deployments. Auto-Login SSO, stack recipes, Docker Swarm orchestration, and priority support.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🔑</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Auto-Login SSO</h3>
<p className="text-sm text-surface-400">Deploy apps with automatic single sign-on integration.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">📚</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Recipes</h3>
<p className="text-sm text-surface-400">Deploy multi-container stacks with one click.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🚀</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Docker Swarm</h3>
<p className="text-sm text-surface-400">Orchestrate multi-node clusters effortlessly.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0"></span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Priority Support</h3>
<p className="text-sm text-surface-400">Get faster responses from our support team.</p>
</div>
</div>
</div>
<Link
href="/pricing"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Explore Premium Plans
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</Link>
</div>
</div>
</div>
</section>
{/* FAQ Section */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
Questions About <span className="text-brand-400">Features?</span>
</h2>
<p className="text-lg text-surface-400">
Check our documentation or contact support.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/docs"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 font-semibold text-white hover:bg-brand-600 transition-all"
>
Read Documentation
</Link>
<a
href="mailto:support@dashcaddy.net"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 font-semibold text-surface-50 hover:border-brand-400 transition-colors"
>
Contact Support
</a>
</div>
</div>
</section>
<Footer />
</div>
);
}

73
src/app/globals.css Normal file
View File

@@ -0,0 +1,73 @@
@import "tailwindcss";
@theme inline {
--color-brand-50: #eef6ff;
--color-brand-100: #d9eaff;
--color-brand-200: #bcdbff;
--color-brand-300: #8ec5ff;
--color-brand-400: #59a4ff;
--color-brand-500: #3381ff;
--color-brand-600: #1a5ff5;
--color-brand-700: #1349e1;
--color-brand-800: #163cb6;
--color-brand-900: #18378f;
--color-brand-950: #142357;
--color-surface-50: #f8fafc;
--color-surface-100: #f1f5f9;
--color-surface-200: #e2e8f0;
--color-surface-300: #cbd5e1;
--color-surface-400: #94a3b8;
--color-surface-500: #64748b;
--color-surface-600: #475569;
--color-surface-700: #334155;
--color-surface-800: #1e293b;
--color-surface-900: #0f172a;
--color-surface-950: #020617;
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background-color: var(--color-surface-950);
color: var(--color-surface-100);
font-family: var(--font-sans);
}
::selection {
background-color: var(--color-brand-500);
color: white;
}
.gradient-text {
background: linear-gradient(135deg, var(--color-brand-400), var(--color-brand-300), #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass-card {
background: rgba(30, 41, 59, 0.5);
backdrop-filter: blur(16px);
border: 1px solid rgba(148, 163, 184, 0.1);
}
.glow-border {
box-shadow: 0 0 0 1px rgba(51, 129, 255, 0.3),
0 0 20px rgba(51, 129, 255, 0.1);
}
.hero-glow {
background: radial-gradient(
ellipse 80% 60% at 50% -20%,
rgba(51, 129, 255, 0.15),
transparent
);
}

46
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,46 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration. The all-in-one self-hosting dashboard.",
keywords: [
"self-hosting",
"docker",
"dashboard",
"caddy",
"reverse proxy",
"ssl",
"dns",
"homelab",
],
openGraph: {
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration.",
url: "https://dashcaddy.net",
siteName: "DashCaddy",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy.",
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="h-full antialiased">
<body className="min-h-full flex flex-col bg-surface-950 text-surface-100" style={{ fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif" }}>
{children}
</body>
</html>
);
}

511
src/app/page.tsx Normal file
View File

@@ -0,0 +1,511 @@
'use client';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import FeatureCard from '@/components/FeatureCard';
import AppShowcase from '@/components/AppShowcase';
export default function Home() {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
{/* Navigation */}
<Navbar />
{/* Hero Section */}
<section id="hero" className="relative w-full overflow-hidden py-20 sm:py-32 lg:py-40">
{/* Background gradient effects */}
<div className="absolute inset-0 -z-10">
{/* Radial gradient glow */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse-slow" />
<div className="absolute top-1/3 right-0 w-72 h-72 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
<div className="absolute bottom-0 left-1/4 w-80 h-80 bg-brand-400/10 rounded-full blur-3xl opacity-20" />
</div>
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Left Content */}
<div className="flex flex-col justify-center space-y-8">
<div className="space-y-4">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight">
<span className="text-brand-400">Self-Hosting</span>
<br />
<span>Made Beautiful</span>
</h1>
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
Deploy Docker apps with one click. Automatic SSL certificates, DNS management, and reverse proxy configurationeverything you need for self-hosted perfection.
</p>
</div>
{/* CTA Buttons */}
<div className="flex flex-col sm:flex-row gap-4">
<Link
href="/pricing"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
>
<span>Get Started Free</span>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</Link>
<a
href="#"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
View on GitHub
</a>
</div>
{/* Trust indicators */}
<div className="flex flex-col sm:flex-row gap-6 pt-4">
<div className="flex items-center gap-3">
<div className="flex -space-x-2">
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
</div>
<p className="text-sm text-surface-400">Trusted by self-hosting enthusiasts</p>
</div>
</div>
</div>
{/* Right - Dashboard Mockup */}
<div className="relative hidden lg:block">
<div className="relative mx-auto aspect-square max-w-md">
{/* Outer glow */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-brand-400/20 to-brand-600/20 blur-2xl" />
{/* Dashboard mockup container */}
<div className="relative rounded-2xl border border-surface-700/50 bg-gradient-to-br from-surface-800 to-surface-900 p-6 overflow-hidden">
{/* Terminal-like dashboard header */}
<div className="space-y-4">
{/* Top bar */}
<div className="flex items-center justify-between bg-surface-900/50 rounded-lg p-3 border border-surface-700/30">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/60" />
<div className="w-3 h-3 rounded-full bg-yellow-500/60" />
<div className="w-3 h-3 rounded-full bg-green-500/60" />
</div>
<span className="text-xs font-mono text-surface-500">dashcaddy.local</span>
</div>
{/* Content area with animated elements */}
<div className="space-y-3">
{/* Container item 1 */}
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-mono text-brand-400">🐳 plex</span>
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
</div>
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-2/3 rounded-full" />
</div>
</div>
{/* Container item 2 */}
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in" style={{ animationDelay: '0.1s' }}>
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-mono text-brand-400"> nextcloud</span>
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
</div>
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-3/4 rounded-full" />
</div>
</div>
{/* Container item 3 */}
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in" style={{ animationDelay: '0.2s' }}>
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-mono text-brand-400">📊 prometheus</span>
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
</div>
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-1/2 rounded-full" />
</div>
</div>
</div>
{/* Stats row */}
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-surface-700/30">
<div className="text-center py-2">
<div className="text-lg font-bold text-brand-400">3</div>
<div className="text-xs text-surface-500">Containers</div>
</div>
<div className="text-center py-2 border-l border-r border-surface-700/30">
<div className="text-lg font-bold text-brand-400">100%</div>
<div className="text-xs text-surface-500">Uptime</div>
</div>
<div className="text-center py-2">
<div className="text-lg font-bold text-brand-400">42GB</div>
<div className="text-xs text-surface-500">Storage</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Key Features Grid */}
<section id="features" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
Powerful Features
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Everything you need to manage your self-hosted Docker applications professionally.
</p>
</div>
{/* Feature Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
icon="🚀"
title="One-Click App Deployment"
description="Choose from 50+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
/>
<FeatureCard
icon="🔒"
title="Automatic SSL Certificates"
description="Caddy internal CA automatically generates and renews SSL certificates. Secure your apps with zero configuration overhead."
/>
<FeatureCard
icon="🌐"
title="DNS Integration"
description="Automatic DNS record creation with Technitium DNS. Manage all your app subdomains effortlessly from one dashboard."
/>
<FeatureCard
icon="📊"
title="Real-Time Monitoring"
description="Monitor container health, response times, and resource usage. Get instant insights into your application performance."
/>
<FeatureCard
icon="🐳"
title="Docker Management"
description="View, manage, and scale your containers. Access logs, manage volumes, and handle Docker operations visually."
/>
<FeatureCard
icon="🛡️"
title="Security First"
description="TOTP 2FA, audit logs, encrypted credentials, and role-based access control built in."
/>
</div>
</div>
</section>
{/* How It Works Section */}
<section id="how-it-works" className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
How It Works
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Get up and running in minutes, not hours.
</p>
</div>
{/* Steps */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{/* Step 1 */}
<div className="relative">
<div className="flex flex-col items-center space-y-4">
{/* Number Circle */}
<div className="relative">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
<span className="text-2xl font-bold text-brand-400">1</span>
</div>
{/* Connector line (hidden on mobile, visible on larger screens) */}
<div className="hidden md:block absolute top-8 left-16 w-full h-0.5 bg-gradient-to-r from-brand-500/50 to-transparent" />
</div>
{/* Content */}
<div className="text-center pt-4">
<h3 className="text-xl font-semibold text-surface-50 mb-2">
Install
</h3>
<p className="text-surface-400">
Clone the DashCaddy repository and run the setup script. Takes just a few minutes on your server.
</p>
</div>
</div>
</div>
{/* Step 2 */}
<div className="relative">
<div className="flex flex-col items-center space-y-4">
{/* Number Circle */}
<div className="relative">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
<span className="text-2xl font-bold text-brand-400">2</span>
</div>
{/* Connector line */}
<div className="hidden md:block absolute top-8 left-16 w-full h-0.5 bg-gradient-to-r from-brand-500/50 to-transparent" />
</div>
{/* Content */}
<div className="text-center pt-4">
<h3 className="text-xl font-semibold text-surface-50 mb-2">
Deploy
</h3>
<p className="text-surface-400">
Click "Deploy" on any app template. SSL certificates and DNS records are automatically configured.
</p>
</div>
</div>
</div>
{/* Step 3 */}
<div className="relative">
<div className="flex flex-col items-center space-y-4">
{/* Number Circle */}
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
<span className="text-2xl font-bold text-brand-400">3</span>
</div>
{/* Content */}
<div className="text-center pt-4">
<h3 className="text-xl font-semibold text-surface-50 mb-2">
Done
</h3>
<p className="text-surface-400">
Your app is live with SSL and DNS configured. Monitor health, manage containers, and backup from the dashboard.
</p>
</div>
</div>
</div>
</div>
</div>
</section>
{/* App Showcase */}
<AppShowcase />
{/* Why DashCaddy Section */}
<section id="why-dashcaddy" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
Why DashCaddy?
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Compare the pain of manual setup versus the simplicity of DashCaddy.
</p>
</div>
{/* Comparison Table */}
<div className="overflow-hidden rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-surface-700/50">
<th className="px-6 py-4 text-left text-sm font-semibold text-surface-300 bg-surface-900/50">
Feature
</th>
<th className="px-6 py-4 text-center text-sm font-semibold text-surface-300 bg-surface-900/50">
Manual Docker Setup
</th>
<th className="px-6 py-4 text-center text-sm font-semibold text-brand-400 bg-surface-900/70">
DashCaddy
</th>
</tr>
</thead>
<tbody className="divide-y divide-surface-700/30">
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">SSL Certificates</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual configuration via Let's Encrypt</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Automatic</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">DNS Management</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual DNS records</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Automatic</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Reverse Proxy Setup</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Complex Nginx/Caddy config</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ One-click</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">App Templates</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Write your own compose files</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ 50+ pre-configured</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Health Monitoring</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Third-party tools required</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Built-in</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Backup & Restore</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual scripts and planning</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ One-click backup</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Audit Logs</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Not included</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Built-in</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Additional Benefits */}
<div className="mt-16 grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-2xl">⏱️</div>
<div>
<h3 className="text-lg font-semibold text-surface-50 mb-2">
Save Countless Hours
</h3>
<p className="text-surface-400 text-sm">
What takes days of manual configuration takes minutes with DashCaddy's automated setup and configuration.
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-2xl">🔐</div>
<div>
<h3 className="text-lg font-semibold text-surface-50 mb-2">
Enterprise Security
</h3>
<p className="text-surface-400 text-sm">
TOTP 2FA, encrypted credentials, audit logging, and role-based access control built in.
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-2xl">📈</div>
<div>
<h3 className="text-lg font-semibold text-surface-50 mb-2">
Scale Confidently
</h3>
<p className="text-surface-400 text-sm">
Real-time monitoring and health checks give you visibility into your infrastructure 24/7.
</p>
</div>
</div>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-2xl">🛠</div>
<div>
<h3 className="text-lg font-semibold text-surface-50 mb-2">
Never Lose Data
</h3>
<p className="text-surface-400 text-sm">
One-click backup and restore functionality ensures your data is always protected and recoverable.
</p>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Final CTA Section */}
<section id="get-started" className="relative py-20 sm:py-24 lg:py-32 bg-gradient-to-b from-surface-900 to-surface-950 overflow-hidden">
{/* Background effects */}
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/10 rounded-full blur-3xl opacity-20" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6 text-surface-50">
Ready to Transform Your
<br />
<span className="text-brand-400">Self-Hosting Setup?</span>
</h2>
<p className="text-xl text-surface-300 mb-12 max-w-2xl mx-auto leading-relaxed">
Join developers and self-hosting enthusiasts who've simplified their infrastructure management with DashCaddy.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/pricing"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-10 py-4 text-lg font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
>
Start Free Today
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</Link>
<a
href="#"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-10 py-4 text-lg font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
>
View Documentation
</a>
</div>
{/* Trust badges */}
<div className="mt-12 flex flex-wrap justify-center gap-8 text-center">
<div>
<div className="text-2xl font-bold text-brand-400">100%</div>
<p className="text-sm text-surface-400">Open Source</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
<div className="text-2xl font-bold text-brand-400">Self-Hosted</div>
<p className="text-sm text-surface-400">Your Data, Your Rules</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
<div className="text-2xl font-bold text-brand-400">24/7</div>
<p className="text-sm text-surface-400">Community Support</p>
</div>
</div>
</div>
</section>
{/* Footer */}
<Footer />
</div>
);
}

280
src/app/pricing/page.tsx Normal file
View File

@@ -0,0 +1,280 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function PricingPage() {
const [isAnnual, setIsAnnual] = useState(false);
const plans = [
{
name: 'Free',
price: '0',
period: 'forever',
description: 'Perfect for getting started with self-hosting',
features: [
'Dashboard & monitoring',
'Up to 10 services',
'50+ app templates',
'Automatic SSL & DNS',
'TOTP 2FA',
'Community support',
],
cta: {
text: 'Get Started Free',
href: '/docs',
},
highlighted: false,
},
{
name: 'Premium',
price: isAnnual ? '99' : '20',
period: isAnnual ? 'per year' : 'per month',
savings: isAnnual ? 'Save 58%' : null,
description: 'For power users and production deployments',
features: [
'Everything in Free, plus:',
'Unlimited services',
'Auto-Login SSO for deployed apps',
'Recipes (multi-container stack deployment)',
'Docker Swarm (multi-node cluster orchestration)',
'Priority email support',
'Early access to new features',
],
cta: {
text: 'Start 14-Day Free Trial',
href: '/api/checkout?plan=premium',
},
highlighted: true,
},
];
const faqs = [
{
question: 'Can I try Premium for free?',
answer: 'Yes! Premium includes a 14-day free trial. No credit card required. You can cancel anytime.',
},
{
question: 'What happens when my subscription ends?',
answer: 'Your subscription gracefully downgrades to the Free tier. All your data remains intact—no data loss. You can resubscribe at any time.',
},
{
question: 'Can I self-host the license server?',
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
},
{
question: 'Do you offer refunds?',
answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with Premium, contact support for a full refund.',
},
{
question: 'Is my data safe?',
answer: '100% self-hosted means your data never leaves your server. DashCaddy runs entirely on your infrastructure. We have no access to your applications, configurations, or data.',
},
];
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Simple, Transparent <span className="text-brand-400">Pricing</span>
</h1>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
</p>
{/* Toggle for Monthly/Yearly */}
<div className="flex items-center justify-center gap-4 mb-12">
<span className={`text-sm font-medium ${!isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
Monthly
</span>
<button
onClick={() => setIsAnnual(!isAnnual)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
isAnnual ? 'bg-brand-500' : 'bg-surface-700'
}`}
aria-label="Toggle annual pricing"
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
isAnnual ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
<span className={`text-sm font-medium ${isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
Annual
</span>
{isAnnual && (
<span className="ml-2 inline-block rounded-full bg-brand-500/20 px-3 py-1 text-sm font-semibold text-brand-300">
Best Value
</span>
)}
</div>
</div>
</section>
{/* Pricing Cards */}
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
{plans.map((plan, idx) => (
<div
key={idx}
className={`relative rounded-2xl border transition-all duration-300 ${
plan.highlighted
? 'border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20 scale-105 md:scale-105'
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
}`}
>
{/* Popular Badge */}
{plan.highlighted && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
Most Popular
</span>
</div>
)}
<div className="p-8 sm:p-10">
{/* Header */}
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">{plan.name}</h3>
<p className="text-surface-400 text-sm mb-6">{plan.description}</p>
{/* Price */}
<div className="flex items-baseline gap-2 mb-2">
<span className="text-5xl font-bold text-surface-50">${plan.price}</span>
<span className="text-surface-400">/{plan.period}</span>
</div>
{plan.savings && (
<p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>
)}
</div>
{/* CTA Button */}
<Link
href={plan.cta.href}
className={`block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 ${
plan.highlighted
? 'bg-brand-500 text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30'
: 'border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400'
}`}
>
{plan.cta.text}
</Link>
{/* Features List */}
<div className="border-t border-surface-700/50 pt-8">
<ul className="space-y-4">
{plan.features.map((feature, featureIdx) => (
<li key={featureIdx} className="flex items-start gap-3">
{feature.startsWith('Everything in') ? (
<span className="text-sm font-semibold text-surface-300">{feature}</span>
) : (
<>
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-surface-300 text-sm">{feature}</span>
</>
)}
</li>
))}
</ul>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* FAQ Section */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Frequently Asked <span className="text-brand-400">Questions</span>
</h2>
<p className="text-lg text-surface-400">
Have a question? We've got answers.
</p>
</div>
{/* FAQ Accordion */}
<div className="space-y-4">
{faqs.map((faq, idx) => (
<div
key={idx}
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
>
<button
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
>
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
<svg
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
expandedFaq === idx ? 'rotate-180' : ''
}`}
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</button>
{expandedFaq === idx && (
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* Final CTA */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl sm:text-4xl font-bold mb-6">
Ready to Get Started?
</h2>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
Try DashCaddy free forever or upgrade to Premium for advanced features.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/docs"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
View Documentation
</Link>
<Link
href="/"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
>
Back to Home
</Link>
</div>
</div>
</section>
<Footer />
</div>
);
}

121
src/app/success/page.tsx Normal file
View File

@@ -0,0 +1,121 @@
"use client";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { Suspense } from "react";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
function SuccessContent() {
const searchParams = useSearchParams();
const sessionId = searchParams.get("session_id");
return (
<>
<Navbar />
<main className="flex-1 flex items-center justify-center px-4 py-24">
<div className="max-w-lg w-full text-center">
{/* Success icon */}
<div className="mx-auto w-20 h-20 rounded-full bg-green-500/20 flex items-center justify-center mb-8">
<svg
className="w-10 h-10 text-green-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 13l4 4L19 7"
/>
</svg>
</div>
<h1 className="text-4xl font-bold text-white mb-4">
Welcome to DashCaddy Premium!
</h1>
<p className="text-lg text-surface-300 mb-8">
Your subscription is active. Check your email for your license key
and setup instructions. Your 14-day free trial has started.
</p>
<div className="glass-card rounded-xl p-6 mb-8 text-left">
<h2 className="text-lg font-semibold text-white mb-4">
Next Steps
</h2>
<ol className="space-y-3 text-surface-300">
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
1
</span>
<span>
Check your email for your license key (DC-XXXXX-...)
</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
2
</span>
<span>
Open your DashCaddy dashboard and go to Admin &rarr; License
</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
3
</span>
<span>Paste your license key and click Activate</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
4
</span>
<span>
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
</span>
</li>
</ol>
</div>
{sessionId && (
<p className="text-sm text-surface-500 mb-6">
Session ID: {sessionId.substring(0, 20)}...
</p>
)}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/docs"
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
>
View Setup Guide
</Link>
<Link
href="/"
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
>
Back to Home
</Link>
</div>
</div>
</main>
<Footer />
</>
);
}
export default function SuccessPage() {
return (
<Suspense
fallback={
<div className="flex-1 flex items-center justify-center">
<div className="text-surface-400">Loading...</div>
</div>
}
>
<SuccessContent />
</Suspense>
);
}

View File

@@ -0,0 +1,181 @@
'use client';
import { useState } from 'react';
interface App {
name: string;
icon: string;
category: string;
}
const apps: App[] = [
// Media
{ name: 'Plex', icon: '🎬', category: 'Media' },
{ name: 'Jellyfin', icon: '📽️', category: 'Media' },
{ name: 'Kaleidescape', icon: '🎞️', category: 'Media' },
{ name: 'Emby', icon: '🎥', category: 'Media' },
{ name: 'Subsonic', icon: '🎵', category: 'Media' },
{ name: 'Synology Photos', icon: '📸', category: 'Media' },
// Downloads
{ name: 'Sonarr', icon: '📺', category: 'Downloads' },
{ name: 'Radarr', icon: '🎬', category: 'Downloads' },
{ name: 'Lidarr', icon: '🎶', category: 'Downloads' },
{ name: 'qBittorrent', icon: '⚡', category: 'Downloads' },
{ name: 'Transmission', icon: '📤', category: 'Downloads' },
{ name: 'SABnzbd', icon: '📥', category: 'Downloads' },
// Productivity
{ name: 'Nextcloud', icon: '☁️', category: 'Productivity' },
{ name: 'Vikunja', icon: '✓', category: 'Productivity' },
{ name: 'OpenProject', icon: '📋', category: 'Productivity' },
{ name: 'Plane', icon: '🚀', category: 'Productivity' },
{ name: 'Actual Budget', icon: '💰', category: 'Productivity' },
{ name: 'HedgeDoc', icon: '📝', category: 'Productivity' },
// Management
{ name: 'Portainer', icon: '🐋', category: 'Management' },
{ name: 'Homelabs', icon: '🏠', category: 'Management' },
{ name: 'Yacht', icon: '⛵', category: 'Management' },
{ name: 'DockSTARTer', icon: '⭐', category: 'Management' },
{ name: 'Unraid', icon: '📦', category: 'Management' },
{ name: 'TrueNAS', icon: '💾', category: 'Management' },
// Security
{ name: 'Vaultwarden', icon: '🔐', category: 'Security' },
{ name: 'Bitwarden', icon: '🗝️', category: 'Security' },
{ name: 'Keycloak', icon: '🔑', category: 'Security' },
{ name: 'Authelia', icon: '🛡️', category: 'Security' },
{ name: 'OpenVPN', icon: '🌐', category: 'Security' },
{ name: 'WireGuard', icon: '📡', category: 'Security' },
// Development
{ name: 'Gitea', icon: '🐙', category: 'Development' },
{ name: 'GitLab', icon: '🦊', category: 'Development' },
{ name: 'Forgejo', icon: '🔧', category: 'Development' },
{ name: 'Jenkins', icon: '🤖', category: 'Development' },
{ name: 'Drone', icon: '🚁', category: 'Development' },
{ name: 'Code Server', icon: '💻', category: 'Development' },
// Monitoring
{ name: 'Grafana', icon: '📊', category: 'Monitoring' },
{ name: 'Prometheus', icon: '⚙️', category: 'Monitoring' },
{ name: 'Uptime Kuma', icon: '📈', category: 'Monitoring' },
{ name: 'Netdata', icon: '🔍', category: 'Monitoring' },
{ name: 'New Relic', icon: '👁️', category: 'Monitoring' },
{ name: 'Kibana', icon: '📉', category: 'Monitoring' },
// Additional
{ name: 'Home Assistant', icon: '🏡', category: 'Smart Home' },
{ name: 'Node-RED', icon: '🔴', category: 'Smart Home' },
{ name: 'OpenHAB', icon: '⚙️', category: 'Smart Home' },
{ name: 'Immich', icon: '📷', category: 'Media' },
{ name: 'Calibre', icon: '📚', category: 'Productivity' },
{ name: 'Paperless', icon: '📄', category: 'Productivity' },
];
const categories = [
'All',
'Media',
'Downloads',
'Productivity',
'Management',
'Security',
'Development',
'Monitoring',
'Smart Home',
];
export default function AppShowcase() {
const [activeCategory, setActiveCategory] = useState('All');
const filteredApps =
activeCategory === 'All'
? apps
: apps.filter((app) => app.category === activeCategory);
return (
<section className="relative py-12 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-7xl">
{/* Header */}
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
50+ One-Click App Templates
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
</p>
</div>
{/* Category Filter */}
<div className="mb-10 flex flex-wrap justify-center gap-2">
{categories.map((category) => (
<button
key={category}
onClick={() => setActiveCategory(category)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
activeCategory === category
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/30'
: 'bg-surface-800 text-surface-300 hover:bg-surface-700 hover:text-surface-200'
}`}
>
{category}
</button>
))}
</div>
{/* App Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
{filteredApps.map((app) => (
<div
key={`${app.name}-${app.category}`}
className="group relative flex flex-col items-center justify-center rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-4 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10 hover:-translate-y-1"
>
{/* Background gradient on hover */}
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 rounded-lg transition-all duration-300 pointer-events-none" />
<div className="relative z-10 flex flex-col items-center justify-center text-center">
{/* Icon */}
<div className="text-4xl mb-2 transition-transform duration-300 group-hover:scale-110">
{app.icon}
</div>
{/* App Name */}
<h3 className="text-sm font-semibold text-surface-50 line-clamp-2 group-hover:text-brand-400 transition-colors">
{app.name}
</h3>
{/* Category Tag */}
<span className="text-xs text-surface-500 mt-1 group-hover:text-brand-400/70">
{app.category}
</span>
</div>
</div>
))}
</div>
{/* Stats */}
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
50+
</div>
<p className="text-sm text-surface-400">App Templates</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
0
</div>
<p className="text-sm text-surface-400">Configuration</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
1-Click
</div>
<p className="text-sm text-surface-400">Deploy</p>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,37 @@
'use client';
interface FeatureCardProps {
icon: React.ReactNode;
title: string;
description: string;
}
export default function FeatureCard({
icon,
title,
description,
}: FeatureCardProps) {
return (
<div className="group relative overflow-hidden rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-6 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10">
{/* Background gradient effect on hover */}
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
<div className="relative z-10">
{/* Icon */}
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
<div className="text-2xl">{icon}</div>
</div>
{/* Title */}
<h3 className="mb-2 text-lg font-semibold text-surface-50">
{title}
</h3>
{/* Description */}
<p className="text-surface-300 text-sm leading-relaxed">
{description}
</p>
</div>
</div>
);
}

139
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,139 @@
import Link from 'next/link';
import Image from 'next/image';
export default function Footer() {
const currentYear = new Date().getFullYear();
const footerSections = [
{
title: 'Product',
links: [
{ label: 'Features', href: '/features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Documentation', href: '/docs' },
{ label: 'About', href: '/about' },
],
},
{
title: 'Resources',
links: [
{ label: 'Getting Started', href: '/docs' },
{ label: 'API Reference', href: '/docs#api' },
{ label: 'GitHub', href: 'https://git.dashcaddy.net/sami7777/dashcaddy' },
{ label: 'Community', href: '#' },
],
},
{
title: 'Support',
links: [
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
{ label: 'Discord', href: '#' },
{ label: 'Privacy Policy', href: '#' },
{ label: 'Terms of Service', href: '#' },
],
},
];
const socialLinks = [
{
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
),
label: 'GitHub',
href: 'https://git.dashcaddy.net/sami7777/dashcaddy',
},
{
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
</svg>
),
label: 'Discord',
href: '#',
},
];
return (
<footer className="border-t border-surface-700/50 bg-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
{/* Brand Section */}
<div>
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
<span>DashCaddy</span>
</Link>
<p className="text-surface-400 text-sm leading-relaxed mb-4">
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
</p>
<div className="flex items-center gap-4">
{socialLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-surface-400 hover:text-brand-400 transition-colors"
aria-label={link.label}
target="_blank"
rel="noopener noreferrer"
>
{link.icon}
</a>
))}
</div>
</div>
{/* Link Sections */}
{footerSections.map((section) => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-surface-50 mb-4">
{section.title}
</h3>
<ul className="space-y-3">
{section.links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
{/* Support Link */}
<div className="border-t border-surface-700/50 pt-8 mb-8">
<p className="text-sm text-surface-400">
Need help? Email us at{' '}
<a
href="mailto:support@dashcaddy.net"
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
>
support@dashcaddy.net
</a>
</p>
</div>
{/* Copyright with samiahmed7777 logo */}
<div className="border-t border-surface-700/50 pt-8">
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<p className="text-sm text-surface-500">
&copy; {currentYear} DashCaddy. All rights reserved. A product by
</p>
<Image
src="/images/samiahmed7777-logo.png"
alt="samiahmed7777"
width={160}
height={32}
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
/>
</div>
</div>
</div>
</footer>
);
}

98
src/components/Navbar.tsx Normal file
View File

@@ -0,0 +1,98 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const navLinks = [
{ href: '#features', label: 'Features' },
{ href: '#pricing', label: 'Pricing' },
{ href: '#docs', label: 'Docs' },
{ href: '#about', label: 'About' },
];
return (
<nav className="sticky top-0 z-50 border-b border-surface-700/50 bg-surface-950/95 backdrop-blur supports-[backdrop-filter]:bg-surface-950/75">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 font-bold text-xl text-brand-400 hover:text-brand-300 transition-colors">
<span>🚀</span>
<span>DashCaddy</span>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-8">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-surface-300 hover:text-brand-400 transition-colors text-sm font-medium"
>
{link.label}
</Link>
))}
</div>
{/* CTA Button (Desktop) */}
<div className="hidden md:block">
<Link
href="#get-started"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Get Started
</Link>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="md:hidden inline-flex items-center justify-center rounded-lg p-2 text-surface-400 hover:bg-surface-800 hover:text-surface-200 transition-colors"
aria-label="Toggle menu"
>
<svg
className={`h-6 w-6 transition-transform duration-300 ${isOpen ? 'rotate-90' : ''}`}
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</button>
</div>
{/* Mobile Navigation */}
{isOpen && (
<div className="border-t border-surface-700/50 bg-surface-900/50 backdrop-blur md:hidden">
<div className="space-y-1 px-2 py-4">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="block rounded-lg px-3 py-2 text-base font-medium text-surface-300 hover:bg-surface-800 hover:text-brand-400 transition-colors"
onClick={() => setIsOpen(false)}
>
{link.label}
</Link>
))}
<Link
href="#get-started"
className="block rounded-lg bg-brand-500 px-3 py-2 text-base font-medium text-white hover:bg-brand-600 transition-colors mt-4"
onClick={() => setIsOpen(false)}
>
Get Started
</Link>
</div>
</div>
)}
</div>
</nav>
);
}