WIP: pending changes — docs, webhook, nav/footer (committed per Sami request, not yet reviewed)
This commit is contained in:
+3
-5
@@ -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
@@ -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'd love to
|
||||
hear from you.
|
||||
Have questions or feedback? We'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're ready.
|
||||
Install DashCaddy and start deploying services today.
|
||||
</p>
|
||||
<Link
|
||||
href="/pricing"
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
DashCaddy’s app-side licensing is already built around an external validation and deactivation service, rather than unlimited static license reuse.
|
||||
</p>
|
||||
</DocsLayout>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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="/"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user