WIP: pending changes — docs, webhook, nav/footer (committed per Sami request, not yet reviewed)

This commit is contained in:
Krystie
2026-06-13 21:29:36 -07:00
parent 69c2179a43
commit 3b2023e97a
19 changed files with 7584 additions and 7717 deletions
+3 -5
View File
@@ -1,7 +1,5 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
const nextConfig = {
output: "export",
images: { unoptimized: true }
};
export default nextConfig;
+9 -16
View File
@@ -67,10 +67,10 @@ export default function AboutPage() {
<div className="grid md:grid-cols-3 gap-8">
{[
{
icon: "🔓",
title: "Open Core",
icon: "\U0001f512",
title: "Proprietary & Polished",
description:
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
"DashCaddy is proprietary software built with care. The core platform is free to use, with Premium features for advanced orchestration.",
},
{
icon: "🏠",
@@ -147,8 +147,7 @@ export default function AboutPage() {
<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.
Have questions or feedback? We&apos;d love to hear from you.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
@@ -157,18 +156,12 @@ export default function AboutPage() {
>
Email Us
</a>
<a
href="#"
<Link
href="/docs"
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>
Read the Docs
</Link>
</div>
</div>
</section>
@@ -180,7 +173,7 @@ export default function AboutPage() {
Ready to simplify your homelab?
</h2>
<p className="text-surface-300 mb-8">
Start with the free tier. Upgrade when you&apos;re ready.
Install DashCaddy and start deploying services today.
</p>
<Link
href="/pricing"
+2 -71
View File
@@ -1,74 +1,5 @@
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,
};
import { NextRequest, NextResponse } from 'next/server';
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 });
}
return NextResponse.json({ error: 'Checkout disabled for static export' }, { status: 503 });
}
+5
View File
@@ -0,0 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Webhooks disabled for static export' }, { status: 503 });
}
+2 -97
View File
@@ -1,99 +1,4 @@
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!;
import { NextRequest, NextResponse } from 'next/server';
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 }
);
}
return NextResponse.json({ error: 'disabled' }, { status: 503 });
}
+36
View File
@@ -0,0 +1,36 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsApiPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="API and Automation"
intro="DashCaddy includes a real API surface for service management, deployment, DNS/proxy automation, certificates, and operational visibility."
>
<h2>What the API is for</h2>
<ul>
<li>service management</li>
<li>app deployment</li>
<li>DNS automation</li>
<li>Caddy integration</li>
<li>certificate-related workflows</li>
<li>health and status reporting</li>
</ul>
<h2>Why automation matters</h2>
<p>
DashCaddy is more than a dashboard because it can execute the infrastructure chain around a service, not just show service state after the fact.
</p>
<h2>Reference direction</h2>
<p>
The repo already includes an OpenAPI file, which means the public API docs can mature into a proper reference section as the external contract is stabilized.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsFirstServicePage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Deploy Your First Service"
intro="This is where DashCaddy becomes real: you take a service from container or target port to DNS-backed, reverse-proxied, HTTPS-enabled application visible in one dashboard."
>
<h2>What DashCaddy handles for you</h2>
<ul>
<li>service records</li>
<li>app template deployment or manual service definition</li>
<li>DNS creation when configured</li>
<li>Caddy reverse proxy updates</li>
<li>service visibility and health tracking</li>
</ul>
<h2>Typical first deployment flow</h2>
<ol>
<li>Open the dashboard.</li>
<li>Choose a template or define a service manually.</li>
<li>Provide the service name, hostname/subdomain, and backend target.</li>
<li>Let DashCaddy wire DNS and Caddy where configured.</li>
<li>Wait for readiness, then verify the service URL.</li>
</ol>
<h2>If the service is internal-only</h2>
<p>
Verify the client trusts the DashCA root certificate, the internal domain resolves properly, and the route is reachable on the network you intend to use.
</p>
<h2>If the service does not come up correctly</h2>
<p>
Debug in order: backend process, reverse proxy, DNS, TLS trust, and finally dashboard/API state.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+53
View File
@@ -0,0 +1,53 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsInstallationPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Installation Guide"
intro="DashCaddy supports both guided installation and manual operator-controlled setup. This guide covers the dependencies and the two practical setup paths."
>
<h2>Prerequisites</h2>
<ul>
<li>Docker and Docker Compose</li>
<li>Caddy with Admin API access</li>
<li>Node.js 18+ for the API server</li>
<li>Technitium DNS if you want DNS automation</li>
</ul>
<h2>Installer-based setup</h2>
<p>
DashCaddy includes a dedicated installer intended to walk users through dependency checks, file deployment,
configuration generation, and first launch.
</p>
<h2>Manual setup</h2>
<p>
Manual setup is best when you want direct control over paths, services, Caddy, DNS integration, and deployment layout.
</p>
<ol>
<li>Clone the software repo.</li>
<li>Install the API dependencies.</li>
<li>Prepare Caddy and confirm the Admin API is reachable.</li>
<li>Prepare Technitium DNS if using automatic DNS changes.</li>
<li>Configure environment and state paths.</li>
<li>Start the DashCaddy API.</li>
<li>Serve the dashboard through Caddy.</li>
</ol>
<h2>Post-install checks</h2>
<ul>
<li>Dashboard loads in browser</li>
<li>API responds correctly</li>
<li>Caddy Admin API is reachable</li>
<li>Service management UI loads</li>
<li>DNS/certificate integration is healthy if configured</li>
</ul>
</DocsLayout>
<Footer />
</div>
);
}
+41
View File
@@ -0,0 +1,41 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsIntegrationsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Infrastructure Integrations"
intro="DashCaddy is most valuable when its supporting integrations are healthy. This guide explains the layers it expects to work with and how they fit together."
>
<h2>Docker</h2>
<p>
Docker is the runtime foundation for deployment workflows, container lifecycle actions, and app-template based launches.
</p>
<h2>Caddy</h2>
<p>
Caddy is the reverse proxy and HTTPS publication layer. DashCaddy relies on the Caddy Admin API to manage routes and exposure.
</p>
<h2>Technitium DNS</h2>
<p>
Technitium DNS is the documented DNS automation target for record creation and removal.
</p>
<h2>DashCA</h2>
<p>
DashCA is the certificate distribution system that makes internal HTTPS practical by letting users trust the local CA across devices.
</p>
<h2>Private access layers</h2>
<p>
DashCaddy also fits well into private/internal access patterns where services should not be directly exposed to the public internet.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+47
View File
@@ -0,0 +1,47 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsOverviewPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Product Overview"
intro="DashCaddy is a self-hosted control plane for deploying, exposing, and managing Docker applications with DNS automation, reverse proxy integration, internal HTTPS, and centralized service visibility."
>
<h2>What DashCaddy is</h2>
<p>
DashCaddy brings together the layers that self-hosters usually wire by hand: Docker deployment,
reverse proxy management through Caddy, DNS automation through Technitium DNS, internal certificate
distribution through DashCA, service monitoring, and operational controls.
</p>
<p>
The goal is simple: make running self-hosted services feel cohesive instead of fragmented.
</p>
<h2>Core product components</h2>
<ul>
<li><strong>Dashboard</strong>: the main operator interface for service visibility and management.</li>
<li><strong>DashCaddy API</strong>: the orchestration engine for deployments, DNS, reverse proxy, certificates, and operational tooling.</li>
<li><strong>Installer</strong>: a guided installation path for users who want faster setup.</li>
<li><strong>DashCA</strong>: the internal certificate authority distribution surface for trusted internal HTTPS.</li>
<li><strong>Licensing</strong>: Premium feature gating tied to external validation/deactivation flows.</li>
</ul>
<h2>Who DashCaddy is for</h2>
<p>
DashCaddy is built for self-hosters, home lab operators, small teams, and administrators who want one place to deploy apps,
publish them cleanly, trust internal HTTPS, and keep service infrastructure under control.
</p>
<h2>Ownership and licensing</h2>
<p>
DashCaddy is proprietary software and intellectual property of <strong>samiahmed7777</strong>. Public-facing documentation and branding
should reflect that commercial/proprietary positioning rather than an open-source default.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+61 -368
View File
@@ -1,385 +1,78 @@
'use client';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function DocsPage() {
const docs = [
{
href: '/docs/overview',
title: 'Product Overview',
description: 'What DashCaddy is, who it is for, and how the platform fits together.',
},
{
href: '/docs/installation',
title: 'Installation Guide',
description: 'Installer-based and manual setup paths, prerequisites, and first launch expectations.',
},
{
href: '/docs/first-service',
title: 'Deploy Your First Service',
description: 'Learn the actual deployment flow and how DashCaddy wires Docker, DNS, and Caddy together.',
},
{
href: '/docs/integrations',
title: 'Infrastructure Integrations',
description: 'How DashCaddy works with Docker, Caddy, Technitium DNS, DashCA, and private access workflows.',
},
{
href: '/docs/premium',
title: 'Premium Features',
description: 'Free vs Premium, current plan model, and the exact premium-gated feature set.',
},
{
href: '/docs/api',
title: 'API and Automation',
description: 'How the API fits into service management, deployment workflows, and automation.',
},
{
href: '/docs/troubleshooting',
title: 'Troubleshooting',
description: 'A practical debugging guide for DNS, TLS, reverse proxy, certificates, and service health.',
},
];
export default function DocsHomePage() {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<section className="relative overflow-hidden 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 className="absolute left-1/2 top-1/2 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-brand-500/20 blur-3xl opacity-30" />
</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.
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
<p className="mb-4 text-sm font-semibold uppercase tracking-[0.25em] text-brand-400">Documentation</p>
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">DashCaddy Docs</h1>
<p className="mt-6 max-w-3xl text-lg leading-8 text-surface-300">
Everything you need to understand, install, operate, and extend DashCaddy as a real self-hosting platform.
</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>
<section className="pb-20">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
{docs.map((doc) => (
<Link
key={doc.href}
href={doc.href}
className="rounded-2xl border border-surface-700/50 bg-surface-900/50 p-6 transition-all hover:border-brand-500/50 hover:bg-surface-900"
>
<h2 className="text-xl font-semibold text-surface-50">{doc.title}</h2>
<p className="mt-3 text-sm leading-6 text-surface-300">{doc.description}</p>
<p className="mt-5 text-sm font-medium text-brand-400">Read guide </p>
</Link>
))}
</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>
+39
View File
@@ -0,0 +1,39 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsPremiumPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Premium Features"
intro="DashCaddy keeps its Premium model intentionally narrow. The core platform remains useful without a license, while Premium unlocks a small set of advanced orchestration features."
>
<h2>Premium-gated features</h2>
<ul>
<li><strong>SSO</strong>: Auto-Login SSO</li>
<li><strong>Recipes</strong>: multi-container stack deployment</li>
<li><strong>Swarm</strong>: Docker Swarm multi-node orchestration</li>
</ul>
<h2>Current plan model</h2>
<ul>
<li>1 month $25</li>
<li>3 months $50</li>
<li>6 months $65</li>
<li>12 months $99</li>
</ul>
<p>
Subscriptions only, one Premium tier, one active machine at a time, 7-day grace period, cancel at period end, and no free trial.
</p>
<h2>License behavior</h2>
<p>
DashCaddys app-side licensing is already built around an external validation and deactivation service, rather than unlimited static license reuse.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+40
View File
@@ -0,0 +1,40 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsTroubleshootingPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Troubleshooting"
intro="Because DashCaddy sits across runtime, DNS, reverse proxy, certificates, and dashboard state, the fastest way to debug it is layer by layer instead of guessing."
>
<h2>Debug order</h2>
<ol>
<li>backend process or container</li>
<li>service port reachability</li>
<li>reverse proxy route</li>
<li>DNS</li>
<li>certificate trust</li>
<li>dashboard/API visibility</li>
</ol>
<h2>Common failures</h2>
<ul>
<li>service is down even though the dashboard is reachable</li>
<li>internal HTTPS shows warnings because DashCA trust is missing</li>
<li>DNS automation fails due to API token or zone issues</li>
<li>Caddy changes are not applying because Admin API is unavailable</li>
<li>Premium features do not unlock because license validation is failing</li>
</ul>
<h2>Mindset</h2>
<p>
Most DashCaddy problems are really one dependency layer failing while the others are healthy. Debugging that dependency chain is the right way to recover quickly.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+16 -16
View File
@@ -43,20 +43,20 @@ export default function Home() {
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>
<span>View Plans</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="#"
<Link
href="/docs"
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" />
<span>Read the Docs</span>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
</svg>
View on GitHub
</a>
</Link>
</div>
{/* Trust indicators */}
@@ -233,7 +233,7 @@ export default function Home() {
Install
</h3>
<p className="text-surface-400">
Clone the DashCaddy repository and run the setup script. Takes just a few minutes on your server.
Install DashCaddy on your server using the guided installer or manual setup. Takes just a few minutes.
</p>
</div>
</div>
@@ -471,29 +471,29 @@ export default function Home() {
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
Get Started
<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="#"
<Link
href="/docs"
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>
</Link>
</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 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">Self-Hosted</div>
<p className="text-sm text-surface-400">Your Data, Your Rules</p>
<div className="text-2xl font-bold text-brand-400">Proprietary</div>
<p className="text-sm text-surface-400">Built With Care</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
+160 -172
View File
@@ -6,71 +6,63 @@ import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function PricingPage() {
const [isAnnual, setIsAnnual] = useState(false);
const [selectedPlan, setSelectedPlan] = useState<'monthly' | 'quarterly' | 'semiannual' | 'annual'>('annual');
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 planOptions = [
{ key: 'monthly', label: '1 Month', price: 25, period: '/mo', perMonth: undefined },
{ key: 'quarterly', label: '3 Months', price: 50, period: '/3mo', perMonth: '$16.67/mo' },
{ key: 'semiannual', label: '6 Months', price: 65, period: '/6mo', perMonth: '$10.83/mo' },
{ key: 'annual', label: '12 Months', price: 99, period: '/yr', perMonth: '$8.25/mo' },
] as const;
const selected = planOptions.find(p => p.key === selectedPlan)!;
const coreFeatures = [
'Dashboard & service monitoring',
'50+ pre-configured app templates',
'Automatic SSL via Caddy internal CA',
'DNS automation via Technitium DNS',
'Reverse proxy management',
'DashCA internal HTTPS trust distribution',
'Container lifecycle management',
'TOTP 2FA authentication',
'Audit logging',
];
const premiumFeatures = [
'Everything in Core, plus:',
'Auto-Login SSO for deployed apps',
'Recipes — multi-container stack deployment',
'Swarm — Docker Swarm multi-node orchestration',
'One active machine per license',
'7-day grace period on expiry',
'Cancel at period end, no lock-in',
];
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.',
answer: 'Premium features gracefully deactivate after a 7-day grace period. Your services keep running — only the premium-gated features (SSO, Recipes, Swarm) become unavailable. Resubscribe at any time to re-enable them.',
},
{
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: 'Can I use DashCaddy without Premium?',
answer: 'Yes. The core platform is fully functional without a license. Premium unlocks SSO, Recipes, and Swarm — advanced orchestration features that most individual self-hosters may not need initially.',
},
{
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: 'How many machines can I activate?',
answer: 'One active machine at a time per license. You can deactivate and move to a new machine when needed.',
},
{
question: 'Is there a free trial?',
answer: 'Not currently. The core platform is available without a license, so you can evaluate DashCaddy before purchasing Premium.',
},
{
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.',
answer: 'DashCaddy runs entirely on your infrastructure. Your data never leaves your server. We have no access to your applications, configurations, or data.',
},
{
question: 'How does license validation work?',
answer: 'DashCaddy validates licenses through an external license server. The app checks your subscription status and activates or deactivates premium features accordingly.',
},
];
@@ -80,144 +72,148 @@ export default function PricingPage() {
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
{/* Hero */}
<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.
The core platform works without a license. Premium unlocks advanced orchestration when you need it.
</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 */}
{/* Two-column: Core vs Premium */}
<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 className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Core Plan */}
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80 transition-all">
<div className="p-8 sm:p-10">
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">Core</h3>
<p className="text-surface-400 text-sm mb-6">Everything you need to deploy and manage self-hosted services.</p>
<div className="flex items-baseline gap-2">
<span className="text-5xl font-bold text-surface-50">Free</span>
</div>
)}
<p className="text-surface-500 text-sm mt-2">No license required</p>
</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>
<Link
href="/docs/installation"
className="block w-full rounded-lg border border-surface-700 bg-surface-700/50 px-6 py-3 text-center font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400 transition-all"
>
Read Install Guide
</Link>
{/* 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 className="border-t border-surface-700/50 pt-8 mt-8">
<p className="text-xs font-semibold uppercase tracking-wide text-surface-500 mb-4">Includes</p>
<ul className="space-y-4">
{coreFeatures.map((feature, i) => (
<li key={i} className="flex items-start gap-3">
<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>
{/* Premium Plan */}
<div className="relative rounded-2xl border border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20">
<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">
Premium
</span>
</div>
<div className="p-8 sm:p-10">
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">Premium</h3>
<p className="text-surface-400 text-sm mb-6">Advanced orchestration for power users and production setups.</p>
{/* Plan selector */}
<div className="mb-4">
<div className="grid grid-cols-2 gap-2">
{planOptions.map((opt) => (
<button
key={opt.key}
onClick={() => setSelectedPlan(opt.key)}
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all ${
selectedPlan === opt.key
? 'bg-brand-500 text-white'
: 'bg-surface-700/50 text-surface-400 hover:bg-surface-700 hover:text-surface-300'
}`}
>
{opt.label}
</button>
))}
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-5xl font-bold text-surface-50">${selected.price}</span>
<span className="text-surface-400">{selected.period}</span>
</div>
{selected.perMonth && (
<p className="text-brand-400 text-sm font-semibold mt-1">{selected.perMonth}</p>
)}
</div>
<Link
href={`/api/checkout?plan=premium&duration=${selectedPlan}`}
className="block w-full rounded-lg bg-brand-500 px-6 py-3 text-center font-semibold text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30 transition-all"
>
Subscribe to Premium
</Link>
<div className="border-t border-surface-700/50 pt-8 mt-8">
<p className="text-xs font-semibold uppercase tracking-wide text-surface-500 mb-4">Everything in Core, plus</p>
<ul className="space-y-4">
{premiumFeatures.map((feature, i) => (
<li key={i} className="flex items-start gap-3">
{feature.startsWith('Everything') ? (
<span className="text-sm font-semibold text-surface-300">{feature}</span>
) : (
<>
<svg className="h-5 w-5 flex-shrink-0 text-brand-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 */}
{/* FAQ */}
<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"
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all hover:border-surface-700"
>
<button
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
@@ -225,18 +221,12 @@ export default function PricingPage() {
>
<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"
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform ${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>
@@ -248,21 +238,19 @@ export default function PricingPage() {
</div>
</section>
{/* Final CTA */}
{/* 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>
<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.
Install DashCaddy and start deploying services in minutes.
</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"
href="/docs/installation"
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 hover:shadow-lg hover:shadow-brand-500/30"
>
View Documentation
Installation Guide
</Link>
<Link
href="/"
+6 -16
View File
@@ -17,9 +17,8 @@ export default function Footer() {
{
title: 'Resources',
links: [
{ label: 'Getting Started', href: '/docs' },
{ label: 'API Reference', href: '/docs#api' },
{ label: 'GitHub', href: 'https://git.dashcaddy.net/sami7777/dashcaddy' },
{ label: 'Getting Started', href: '/docs/installation' },
{ label: 'API Reference', href: '/docs/api' },
{ label: 'Community', href: '#' },
],
},
@@ -27,7 +26,7 @@ export default function Footer() {
title: 'Support',
links: [
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
{ label: 'Discord', href: '#' },
{ label: 'Docs', href: '/docs' },
{ label: 'Privacy Policy', href: '#' },
{ label: 'Terms of Service', href: '#' },
],
@@ -35,23 +34,14 @@ export default function Footer() {
];
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: '#',
label: 'Contact',
href: 'mailto:support@dashcaddy.net',
},
];
@@ -68,7 +58,7 @@ export default function Footer() {
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) => (
{socialLinks.map((link: any) => (
<a
key={link.label}
href={link.href}
+3 -3
View File
@@ -9,7 +9,7 @@ export default function Navbar() {
const navLinks = [
{ href: '#features', label: 'Features' },
{ href: '#pricing', label: 'Pricing' },
{ href: '#docs', label: 'Docs' },
{ href: '/docs', label: 'Docs' },
{ href: '#about', label: 'About' },
];
@@ -39,7 +39,7 @@ export default function Navbar() {
{/* CTA Button (Desktop) */}
<div className="hidden md:block">
<Link
href="#get-started"
href="/pricing"
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
@@ -83,7 +83,7 @@ export default function Navbar() {
</Link>
))}
<Link
href="#get-started"
href="/pricing"
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)}
>
+64
View File
@@ -0,0 +1,64 @@
import Link from 'next/link';
const docsLinks = [
{ href: '/docs/overview', label: 'Product Overview' },
{ href: '/docs/installation', label: 'Installation Guide' },
{ href: '/docs/first-service', label: 'Deploy Your First Service' },
{ href: '/docs/integrations', label: 'Infrastructure Integrations' },
{ href: '/docs/premium', label: 'Premium Features' },
{ href: '/docs/api', label: 'API and Automation' },
{ href: '/docs/troubleshooting', label: 'Troubleshooting' },
];
export default function DocsLayout({
title,
intro,
children,
}: {
title: string;
intro: string;
children: React.ReactNode;
}) {
return (
<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 gap-10 lg:grid-cols-[260px_minmax(0,1fr)]">
<aside className="h-fit rounded-2xl border border-surface-700/50 bg-surface-900/50 p-5 lg:sticky lg:top-24">
<div className="mb-4">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-brand-400">Documentation</p>
<h2 className="mt-2 text-lg font-semibold text-surface-50">DashCaddy Docs</h2>
</div>
<nav className="space-y-2">
<Link
href="/docs"
className="block rounded-lg px-3 py-2 text-sm font-medium text-surface-300 transition-colors hover:bg-surface-800 hover:text-brand-400"
>
Docs Home
</Link>
{docsLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="block rounded-lg px-3 py-2 text-sm font-medium text-surface-300 transition-colors hover:bg-surface-800 hover:text-brand-400"
>
{link.label}
</Link>
))}
</nav>
</aside>
<article className="min-w-0 rounded-2xl border border-surface-700/50 bg-surface-900/40 p-6 sm:p-8 lg:p-10">
<header className="mb-10 border-b border-surface-700/50 pb-6">
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.2em] text-brand-400">DashCaddy Documentation</p>
<h1 className="text-3xl font-bold text-surface-50 sm:text-4xl">{title}</h1>
<p className="mt-4 max-w-3xl text-base leading-7 text-surface-300 sm:text-lg">{intro}</p>
</header>
<div className="prose prose-invert max-w-none prose-headings:text-surface-50 prose-p:text-surface-300 prose-li:text-surface-300 prose-strong:text-surface-100">
{children}
</div>
</article>
</div>
</div>
</section>
);
}