DashCaddy.net all files
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
5
AGENTS.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<!-- BEGIN:nextjs-agent-rules -->
|
||||||
|
# This is NOT the Next.js you know
|
||||||
|
|
||||||
|
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||||
|
<!-- END:nextjs-agent-rules -->
|
||||||
66
FILELIST.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# DashCaddy.net — File List
|
||||||
|
|
||||||
|
**37 files total** (excluding `node_modules/`, `.next/`, `.git/`)
|
||||||
|
|
||||||
|
## Root Config & Docs
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.env.example` | Template for Stripe keys and app config |
|
||||||
|
| `.gitignore` | Git ignore rules |
|
||||||
|
| `AGENTS.md` | AI assistant guidelines |
|
||||||
|
| `CLAUDE.md` | Project instructions for Claude |
|
||||||
|
| `FILELIST.md` | This file |
|
||||||
|
| `README.md` | Project overview and setup guide |
|
||||||
|
| `STRIPE_SETUP.md` | Step-by-step Stripe configuration guide |
|
||||||
|
| `eslint.config.mjs` | ESLint configuration |
|
||||||
|
| `next-env.d.ts` | Next.js TypeScript declarations |
|
||||||
|
| `next.config.ts` | Next.js configuration |
|
||||||
|
| `package.json` | Dependencies and scripts |
|
||||||
|
| `package-lock.json` | Locked dependency versions |
|
||||||
|
| `postcss.config.mjs` | PostCSS configuration (Tailwind) |
|
||||||
|
| `tailwind.config.ts` | Tailwind CSS theme and color tokens |
|
||||||
|
| `tsconfig.json` | TypeScript configuration |
|
||||||
|
|
||||||
|
## Public Assets
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `public/favicon.ico` | *(in src/app)* Site favicon |
|
||||||
|
| `public/file.svg` | Default Next.js icon |
|
||||||
|
| `public/globe.svg` | Default Next.js icon |
|
||||||
|
| `public/next.svg` | Default Next.js icon |
|
||||||
|
| `public/vercel.svg` | Default Next.js icon |
|
||||||
|
| `public/window.svg` | Default Next.js icon |
|
||||||
|
| `public/images/samiahmed7777-logo.png` | samiahmed7777 brand logo (footer) |
|
||||||
|
| `public/images/.gitkeep` | Keeps images directory in git |
|
||||||
|
| `public/images/README.txt` | Logo placement instructions |
|
||||||
|
|
||||||
|
## Pages (`src/app/`)
|
||||||
|
|
||||||
|
| File | Route | Purpose |
|
||||||
|
|------|-------|---------|
|
||||||
|
| `src/app/layout.tsx` | — | Root layout (metadata, fonts, body wrapper) |
|
||||||
|
| `src/app/globals.css` | — | Global styles, Tailwind theme, custom classes |
|
||||||
|
| `src/app/page.tsx` | `/` | Landing page (hero, features, app showcase, CTA) |
|
||||||
|
| `src/app/features/page.tsx` | `/features` | Detailed feature breakdown by category |
|
||||||
|
| `src/app/pricing/page.tsx` | `/pricing` | Pricing plans with monthly/yearly toggle and FAQ |
|
||||||
|
| `src/app/docs/page.tsx` | `/docs` | Getting started guide and documentation |
|
||||||
|
| `src/app/about/page.tsx` | `/about` | Company story, values, tech stack, contact |
|
||||||
|
| `src/app/success/page.tsx` | `/success` | Post-checkout confirmation with license setup steps |
|
||||||
|
|
||||||
|
## API Routes (`src/app/api/`)
|
||||||
|
|
||||||
|
| File | Endpoint | Purpose |
|
||||||
|
|------|----------|---------|
|
||||||
|
| `src/app/api/checkout/route.ts` | `POST /api/checkout` | Creates Stripe Checkout session with 14-day trial |
|
||||||
|
| `src/app/api/webhooks/stripe/route.ts` | `POST /api/webhooks/stripe` | Handles Stripe subscription lifecycle events |
|
||||||
|
|
||||||
|
## Components (`src/components/`)
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `src/components/Navbar.tsx` | Sticky top navigation with mobile hamburger menu |
|
||||||
|
| `src/components/Footer.tsx` | Site footer with links, socials, and samiahmed7777 logo |
|
||||||
|
| `src/components/AppShowcase.tsx` | Filterable grid of 50+ supported app templates |
|
||||||
|
| `src/components/FeatureCard.tsx` | Reusable glass-style feature card with hover effects |
|
||||||
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# DashCaddy.net - Marketing Website
|
||||||
|
|
||||||
|
Marketing and sales website for [DashCaddy](https://git.dashcaddy.net/sami7777/dashcaddy), the self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy configuration.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Next.js 16** with App Router and TypeScript
|
||||||
|
- **Tailwind CSS** for styling
|
||||||
|
- **Stripe** for subscription payments ($20/mo or $99/yr)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Copy environment file and configure
|
||||||
|
cp .env.example .env.local
|
||||||
|
|
||||||
|
# Run development server
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to see the site.
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
| Route | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `/` | Landing page with hero, features, app showcase |
|
||||||
|
| `/features` | Detailed feature breakdown |
|
||||||
|
| `/pricing` | Pricing plans with Stripe Checkout |
|
||||||
|
| `/docs` | Getting started guide and documentation |
|
||||||
|
| `/about` | About page and company info |
|
||||||
|
| `/success` | Post-checkout success page |
|
||||||
|
|
||||||
|
## Stripe Integration
|
||||||
|
|
||||||
|
See [STRIPE_SETUP.md](./STRIPE_SETUP.md) for detailed setup instructions.
|
||||||
|
|
||||||
|
**API Routes:**
|
||||||
|
- `POST /api/checkout` - Creates Stripe Checkout session
|
||||||
|
- `POST /api/webhooks/stripe` - Handles Stripe webhook events
|
||||||
|
|
||||||
|
## Logo Setup
|
||||||
|
|
||||||
|
Place your `samiahmed7777-logo.png` file in `public/images/` for the footer copyright branding.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Start production server
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The site can be deployed to Vercel, your own server (with Node.js), or any platform that supports Next.js.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Proprietary - DashCaddy
|
||||||
83
STRIPE_SETUP.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Stripe Setup Guide for DashCaddy.net
|
||||||
|
|
||||||
|
## 1. Create a Stripe Account
|
||||||
|
|
||||||
|
Go to [stripe.com](https://stripe.com) and create an account (or log in).
|
||||||
|
|
||||||
|
## 2. Create Your Product and Prices
|
||||||
|
|
||||||
|
In the Stripe Dashboard:
|
||||||
|
|
||||||
|
1. Go to **Products** > **Add Product**
|
||||||
|
2. Name: `DashCaddy Premium`
|
||||||
|
3. Description: `Premium license for DashCaddy - Self-hosting dashboard`
|
||||||
|
4. Create two prices:
|
||||||
|
- **Monthly**: $20.00 USD / month (recurring)
|
||||||
|
- **Yearly**: $99.00 USD / year (recurring)
|
||||||
|
5. Note down both **Price IDs** (they look like `price_1234...`)
|
||||||
|
|
||||||
|
## 3. Get Your API Keys
|
||||||
|
|
||||||
|
1. Go to **Developers** > **API Keys**
|
||||||
|
2. Copy your **Publishable key** (`pk_test_...` or `pk_live_...`)
|
||||||
|
3. Copy your **Secret key** (`sk_test_...` or `sk_live_...`)
|
||||||
|
|
||||||
|
## 4. Set Up Webhooks
|
||||||
|
|
||||||
|
1. Go to **Developers** > **Webhooks**
|
||||||
|
2. Click **Add endpoint**
|
||||||
|
3. URL: `https://dashcaddy.net/api/webhooks/stripe`
|
||||||
|
4. Select these events:
|
||||||
|
- `checkout.session.completed`
|
||||||
|
- `customer.subscription.updated`
|
||||||
|
- `customer.subscription.deleted`
|
||||||
|
- `invoice.payment_failed`
|
||||||
|
5. Copy the **Webhook signing secret** (`whsec_...`)
|
||||||
|
|
||||||
|
## 5. Configure Environment Variables
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env.local` and fill in your values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env.local`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
STRIPE_SECRET_KEY=sk_live_your_actual_secret_key
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_actual_publishable_key
|
||||||
|
STRIPE_WEBHOOK_SECRET=whsec_your_actual_webhook_secret
|
||||||
|
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
|
||||||
|
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
|
||||||
|
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Test with Stripe CLI (Optional)
|
||||||
|
|
||||||
|
For local development, use Stripe CLI to forward webhooks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
||||||
|
```
|
||||||
|
|
||||||
|
Use test card `4242 4242 4242 4242` with any future date and any CVC.
|
||||||
|
|
||||||
|
## 7. License Key Delivery
|
||||||
|
|
||||||
|
The webhook handler at `src/app/api/webhooks/stripe/route.ts` has TODO comments
|
||||||
|
where you need to implement:
|
||||||
|
|
||||||
|
1. **Generate license key** using the same format as DashCaddy's license-keygen
|
||||||
|
(DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)
|
||||||
|
2. **Store it** in a database (Stripe metadata can also hold it)
|
||||||
|
3. **Email it** to the customer (use Stripe's receipt email or a service like
|
||||||
|
SendGrid/Resend)
|
||||||
|
4. **Link it** to the Stripe subscription ID so you can manage renewals/cancellations
|
||||||
|
|
||||||
|
## Pricing Strategy Notes
|
||||||
|
|
||||||
|
- **Monthly ($20/mo)**: Positioned as the flexibility option
|
||||||
|
- **Yearly ($99/yr)**: ~$8.25/mo — 58% savings, this will be the primary seller
|
||||||
|
- **14-day free trial**: Enabled on both plans via `trial_period_days: 14`
|
||||||
|
- **Promotion codes**: Enabled via `allow_promotion_codes: true`
|
||||||
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Override default ignores of eslint-config-next.
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
6599
package-lock.json
generated
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "dashcaddy-site",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@stripe/stripe-js": "^9.2.0",
|
||||||
|
"next": "16.2.4",
|
||||||
|
"react": "19.2.4",
|
||||||
|
"react-dom": "19.2.4",
|
||||||
|
"stripe": "^22.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "16.2.4",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
1
public/file.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
0
public/images/.gitkeep
Normal file
1
public/images/README.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Place your samiahmed7777-logo.png file in this directory
|
||||||
BIN
public/images/samiahmed7777-logo.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
197
src/app/about/page.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import Navbar from "@/components/Navbar";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function AboutPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-1">
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="relative pt-32 pb-20 px-4">
|
||||||
|
<div className="absolute inset-0 hero-glow pointer-events-none" />
|
||||||
|
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||||
|
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
|
||||||
|
Built for the{" "}
|
||||||
|
<span className="gradient-text">Self-Hosting Community</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
||||||
|
DashCaddy was born from the frustration of managing dozens of
|
||||||
|
Docker containers, SSL certificates, and DNS records by hand. We
|
||||||
|
built the tool we wished existed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Story */}
|
||||||
|
<section className="py-20 px-4">
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-6">Our Story</h2>
|
||||||
|
<div className="space-y-4 text-surface-300 leading-relaxed">
|
||||||
|
<p>
|
||||||
|
Self-hosting is powerful. You own your data, you control your
|
||||||
|
infrastructure, and you're not at the mercy of SaaS
|
||||||
|
providers who can change their terms, raise prices, or shut down
|
||||||
|
overnight. But let's be honest — it can also be a
|
||||||
|
pain.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Every new service means editing Caddyfiles, creating DNS
|
||||||
|
records, configuring SSL certificates, writing Docker Compose
|
||||||
|
files, and hoping everything plays nicely together. Multiply that
|
||||||
|
by 20, 30, or 50 services, and you've got a full-time
|
||||||
|
operations job on your hands.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
DashCaddy was built to solve this. One click to deploy an app.
|
||||||
|
SSL, DNS, and reverse proxy configuration happen automatically.
|
||||||
|
A beautiful dashboard to monitor everything. And when something
|
||||||
|
goes wrong, you know about it immediately — not when a
|
||||||
|
family member texts you that Plex is down.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We believe self-hosting should be accessible to everyone, not
|
||||||
|
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
|
||||||
|
beautiful and effortless.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Values */}
|
||||||
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
|
<div className="max-w-5xl mx-auto">
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
||||||
|
What We Believe In
|
||||||
|
</h2>
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: "🔓",
|
||||||
|
title: "Open Core",
|
||||||
|
description:
|
||||||
|
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🏠",
|
||||||
|
title: "Your Data, Your Server",
|
||||||
|
description:
|
||||||
|
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🛠️",
|
||||||
|
title: "Built to Last",
|
||||||
|
description:
|
||||||
|
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
|
||||||
|
},
|
||||||
|
].map((value) => (
|
||||||
|
<div
|
||||||
|
key={value.title}
|
||||||
|
className="glass-card rounded-xl p-8 text-center"
|
||||||
|
>
|
||||||
|
<div className="text-4xl mb-4">{value.icon}</div>
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">
|
||||||
|
{value.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400">{value.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Tech Stack */}
|
||||||
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
|
<div className="max-w-5xl mx-auto">
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
||||||
|
Built With
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
name: "Caddy",
|
||||||
|
role: "Reverse Proxy & SSL",
|
||||||
|
icon: "🔒",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Docker",
|
||||||
|
role: "Container Runtime",
|
||||||
|
icon: "🐳",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Node.js",
|
||||||
|
role: "API Backend",
|
||||||
|
icon: "🟢",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Technitium",
|
||||||
|
role: "DNS Server",
|
||||||
|
icon: "🌐",
|
||||||
|
},
|
||||||
|
].map((tech) => (
|
||||||
|
<div
|
||||||
|
key={tech.name}
|
||||||
|
className="glass-card rounded-xl p-6 text-center hover:border-brand-500/30 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="text-3xl mb-3">{tech.icon}</div>
|
||||||
|
<div className="font-semibold text-white">{tech.name}</div>
|
||||||
|
<div className="text-sm text-surface-400">{tech.role}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact / Support */}
|
||||||
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
||||||
|
<p className="text-surface-300 mb-8">
|
||||||
|
Have questions, feedback, or want to contribute? We'd love to
|
||||||
|
hear from you.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a
|
||||||
|
href="mailto:support@dashcaddy.net"
|
||||||
|
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
||||||
|
>
|
||||||
|
Email Us
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
|
>
|
||||||
|
Join Discord
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl font-bold text-white mb-4">
|
||||||
|
Ready to simplify your homelab?
|
||||||
|
</h2>
|
||||||
|
<p className="text-surface-300 mb-8">
|
||||||
|
Start with the free tier. Upgrade when you're ready.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-block px-8 py-4 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-semibold text-lg transition-colors"
|
||||||
|
>
|
||||||
|
View Pricing
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
src/app/api/checkout/route.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||||
|
apiVersion: "2026-03-25.dahlia",
|
||||||
|
});
|
||||||
|
|
||||||
|
const PRICE_IDS: Record<string, string | undefined> = {
|
||||||
|
monthly: process.env.STRIPE_PRICE_MONTHLY,
|
||||||
|
yearly: process.env.STRIPE_PRICE_YEARLY,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { plan, email } = body;
|
||||||
|
|
||||||
|
if (!plan || !PRICE_IDS[plan]) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Invalid plan. Choose 'monthly' or 'yearly'." },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceId = PRICE_IDS[plan];
|
||||||
|
if (!priceId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Price not configured. Please contact support." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "https://dashcaddy.net";
|
||||||
|
|
||||||
|
const sessionParams: Stripe.Checkout.SessionCreateParams = {
|
||||||
|
mode: "subscription",
|
||||||
|
payment_method_types: ["card"],
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price: priceId,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
success_url: `${appUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
|
cancel_url: `${appUrl}/pricing`,
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
billing_address_collection: "required",
|
||||||
|
subscription_data: {
|
||||||
|
trial_period_days: 14,
|
||||||
|
metadata: {
|
||||||
|
plan,
|
||||||
|
source: "dashcaddy-website",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
plan,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pre-fill email if provided
|
||||||
|
if (email) {
|
||||||
|
sessionParams.customer_email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await stripe.checkout.sessions.create(sessionParams);
|
||||||
|
|
||||||
|
return NextResponse.json({ url: session.url });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Stripe checkout error:", error);
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : "Internal server error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/app/api/webhooks/stripe/route.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||||
|
apiVersion: "2026-03-25.dahlia",
|
||||||
|
});
|
||||||
|
|
||||||
|
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const body = await request.text();
|
||||||
|
const signature = request.headers.get("stripe-signature");
|
||||||
|
|
||||||
|
if (!signature) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Missing stripe-signature header" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let event: Stripe.Event;
|
||||||
|
|
||||||
|
try {
|
||||||
|
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : "Unknown error";
|
||||||
|
console.error(`Webhook signature verification failed: ${message}`);
|
||||||
|
return NextResponse.json({ error: message }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (event.type) {
|
||||||
|
case "checkout.session.completed": {
|
||||||
|
const session = event.data.object as Stripe.Checkout.Session;
|
||||||
|
console.log("Checkout completed:", {
|
||||||
|
sessionId: session.id,
|
||||||
|
customerEmail: session.customer_email,
|
||||||
|
plan: session.metadata?.plan,
|
||||||
|
subscriptionId: session.subscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Generate and deliver license key to customer
|
||||||
|
// This is where you'd:
|
||||||
|
// 1. Generate a DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX license code
|
||||||
|
// 2. Store it in your database
|
||||||
|
// 3. Email it to the customer
|
||||||
|
// 4. Associate it with the Stripe subscription ID
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "customer.subscription.updated": {
|
||||||
|
const subscription = event.data.object as Stripe.Subscription;
|
||||||
|
console.log("Subscription updated:", {
|
||||||
|
subscriptionId: subscription.id,
|
||||||
|
status: subscription.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Update license expiration based on subscription status
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "customer.subscription.deleted": {
|
||||||
|
const subscription = event.data.object as Stripe.Subscription;
|
||||||
|
console.log("Subscription cancelled:", {
|
||||||
|
subscriptionId: subscription.id,
|
||||||
|
status: subscription.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Deactivate/expire the license key
|
||||||
|
// The DashCaddy instance will gracefully downgrade to free tier
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "invoice.payment_failed": {
|
||||||
|
const invoice = event.data.object as Stripe.Invoice;
|
||||||
|
console.log("Payment failed:", {
|
||||||
|
invoiceId: invoice.id,
|
||||||
|
customerEmail: invoice.customer_email,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Notify customer about failed payment
|
||||||
|
// Consider a grace period before deactivating license
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`Unhandled event type: ${event.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ received: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Webhook handler error:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Webhook handler failed" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
389
src/app/docs/page.tsx
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
|
export default function DocsPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
|
Getting <span className="text-brand-400">Started</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-surface-300 max-w-2xl">
|
||||||
|
Get DashCaddy up and running on your server in just a few minutes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<section className="relative py-12 sm:py-16 lg:py-20">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Table of Contents */}
|
||||||
|
<div className="mb-16 rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
|
||||||
|
<h2 className="text-2xl font-bold text-surface-50 mb-6">Table of Contents</h2>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li>
|
||||||
|
<a href="#prerequisites" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||||
|
<span>→</span> Prerequisites
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#installation" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||||
|
<span>→</span> Installation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#configuration" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||||
|
<span>→</span> Configuration
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#first-run" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||||
|
<span>→</span> First Run
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#troubleshooting" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||||
|
<span>→</span> Troubleshooting
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Prerequisites Section */}
|
||||||
|
<section id="prerequisites" className="mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||||
|
<span className="text-brand-400">📋</span> Prerequisites
|
||||||
|
</h2>
|
||||||
|
<p className="text-surface-300 mb-6 leading-relaxed">
|
||||||
|
Before you begin, ensure you have the following installed on your server:
|
||||||
|
</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6 space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-xl flex-shrink-0">🐳</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Docker</h3>
|
||||||
|
<p className="text-sm text-surface-400">Version 20.10+ required. <a href="https://docs.docker.com/get-docker/" className="text-brand-400 hover:text-brand-300">Install Docker</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-xl flex-shrink-0">⚙️</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Caddy</h3>
|
||||||
|
<p className="text-sm text-surface-400">Version 2.7+ required. <a href="https://caddyserver.com/docs/install" className="text-brand-400 hover:text-brand-300">Install Caddy</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-xl flex-shrink-0">🟢</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Node.js</h3>
|
||||||
|
<p className="text-sm text-surface-400">Version 18+ required. <a href="https://nodejs.org/" className="text-brand-400 hover:text-brand-300">Install Node.js</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-xl flex-shrink-0">🔧</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Git</h3>
|
||||||
|
<p className="text-sm text-surface-400">For cloning the repository. <a href="https://git-scm.com/" className="text-brand-400 hover:text-brand-300">Install Git</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-surface-700/30 pt-4 mt-4">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-2">Optional</h3>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-xl flex-shrink-0">🌐</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Technitium DNS</h3>
|
||||||
|
<p className="text-sm text-surface-400">For automatic DNS management. If not installed, you can still manage DNS manually.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Installation Section */}
|
||||||
|
<section id="installation" className="mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||||
|
<span className="text-brand-400">📦</span> Installation
|
||||||
|
</h2>
|
||||||
|
<p className="text-surface-300 mb-8 leading-relaxed">
|
||||||
|
Follow these steps to install DashCaddy on your server:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Step 1 */}
|
||||||
|
<div className="mb-10">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 1: Clone the Repository</h3>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||||
|
<code className="text-sm text-green-400 font-mono block">
|
||||||
|
git clone https://github.com/dashcaddy/dashcaddy.git
|
||||||
|
<br />
|
||||||
|
cd dashcaddy
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 2 */}
|
||||||
|
<div className="mb-10">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 2: Install Dependencies</h3>
|
||||||
|
<p className="text-surface-300 mb-4">Install Node.js dependencies:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||||
|
<code className="text-sm text-green-400 font-mono block">
|
||||||
|
npm install
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 3 */}
|
||||||
|
<div className="mb-10">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 3: Configure Environment Variables</h3>
|
||||||
|
<p className="text-surface-300 mb-4">Copy the example environment file and customize it:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-4">
|
||||||
|
<code className="text-sm text-green-400 font-mono block">
|
||||||
|
cp .env.example .env
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 mb-4">Then edit <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">.env</code> with your configuration:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||||
|
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
||||||
|
{`# Server
|
||||||
|
PORT=3000
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# Database (optional - defaults to SQLite)
|
||||||
|
DATABASE_URL=sqlite:./data/dashcaddy.db
|
||||||
|
|
||||||
|
# Security
|
||||||
|
JWT_SECRET=your-secure-random-secret-here
|
||||||
|
SESSION_SECRET=another-secure-random-secret
|
||||||
|
|
||||||
|
# Caddy
|
||||||
|
CADDY_PORT=80
|
||||||
|
CADDY_HTTPS_PORT=443
|
||||||
|
CADDY_ADMIN_LISTEN=localhost:2019
|
||||||
|
|
||||||
|
# Technitium DNS (optional)
|
||||||
|
TECHNITIUM_API_URL=http://localhost:5380/api
|
||||||
|
TECHNITIUM_API_KEY=your-api-key
|
||||||
|
|
||||||
|
# Email (optional - for notifications)
|
||||||
|
SMTP_HOST=smtp.example.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=your-email@example.com
|
||||||
|
SMTP_PASSWORD=your-password`}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 4 */}
|
||||||
|
<div className="mb-10">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 4: Build and Start</h3>
|
||||||
|
<p className="text-surface-300 mb-4">Build the application:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-6">
|
||||||
|
<code className="text-sm text-green-400 font-mono block">
|
||||||
|
npm run build
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 mb-4">Start DashCaddy:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||||
|
<code className="text-sm text-green-400 font-mono block">
|
||||||
|
npm start
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 mt-4 text-sm">
|
||||||
|
The application will be available at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://localhost:3000</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 5 */}
|
||||||
|
<div className="mb-10">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 5: Configure Caddy</h3>
|
||||||
|
<p className="text-surface-300 mb-4">Update your Caddy configuration to proxy requests to DashCaddy:</p>
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||||
|
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
||||||
|
{`dashcaddy.local {
|
||||||
|
reverse_proxy localhost:3000
|
||||||
|
|
||||||
|
# Enable automatic HTTPS
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header Strict-Transport-Security "max-age=31536000"
|
||||||
|
header X-Content-Type-Options "nosniff"
|
||||||
|
header X-Frame-Options "DENY"
|
||||||
|
}`}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Configuration Section */}
|
||||||
|
<section id="configuration" className="mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||||
|
<span className="text-brand-400">⚙️</span> Configuration
|
||||||
|
</h2>
|
||||||
|
<p className="text-surface-300 mb-8 leading-relaxed">
|
||||||
|
Key environment variables for DashCaddy configuration:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-brand-400 mb-2 font-mono">PORT</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-2">The port DashCaddy runs on. Default: <code className="bg-surface-800 px-1 rounded">3000</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-brand-400 mb-2 font-mono">JWT_SECRET</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-2">Secret key for JWT tokens. Generate a secure random string:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-brand-400 text-xs">openssl rand -hex 32</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-brand-400 mb-2 font-mono">DATABASE_URL</h3>
|
||||||
|
<p className="text-surface-300 text-sm">Connection string for your database. Defaults to SQLite if not provided.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-brand-400 mb-2 font-mono">CADDY_ADMIN_LISTEN</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-2">Caddy admin API endpoint. Default: <code className="bg-surface-800 px-1 rounded">localhost:2019</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-brand-400 mb-2 font-mono">TECHNITIUM_API_URL</h3>
|
||||||
|
<p className="text-surface-300 text-sm">URL to your Technitium DNS API. Optional for DNS management features.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* First Run Section */}
|
||||||
|
<section id="first-run" className="mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||||
|
<span className="text-brand-400">🚀</span> First Run
|
||||||
|
</h2>
|
||||||
|
<div className="rounded-lg border border-brand-500/30 bg-brand-950/50 p-8">
|
||||||
|
<ol className="space-y-4 list-decimal list-inside text-surface-300">
|
||||||
|
<li>Access the dashboard at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://dashcaddy.local</code></li>
|
||||||
|
<li>Create your admin account with a strong password</li>
|
||||||
|
<li>Enable TOTP 2FA for enhanced security</li>
|
||||||
|
<li>Configure your Technitium DNS API key (optional)</li>
|
||||||
|
<li>Deploy your first application from the app templates library</li>
|
||||||
|
<li>Monitor your apps in real-time from the dashboard</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Troubleshooting Section */}
|
||||||
|
<section id="troubleshooting" className="mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||||
|
<span className="text-brand-400">🔧</span> Troubleshooting
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||||
|
<span className="text-red-400">❌</span> Docker daemon not running
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-3">Make sure the Docker daemon is started:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">sudo systemctl start docker</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||||
|
<span className="text-red-400">❌</span> Port 3000 already in use
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-3">Change the PORT in your .env file or stop the process using that port:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">lsof -i :3000</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||||
|
<span className="text-red-400">❌</span> Cannot connect to Caddy admin API
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-3">Verify Caddy is running and the admin API is accessible:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">curl http://localhost:2019/config/</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||||
|
<span className="text-red-400">❌</span> Database connection errors
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-3">Check that your DATABASE_URL is correct and the database is accessible. For SQLite, ensure the data directory exists:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">mkdir -p ./data</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||||
|
<span className="text-red-400">❌</span> SSL certificate issues
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-300 text-sm mb-3">DashCaddy uses Caddy's internal CA for certificate generation. If you have issues, check the Caddy logs:</p>
|
||||||
|
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">journalctl -u caddy -f</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Next Steps */}
|
||||||
|
<section className="mb-16">
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
|
||||||
|
<h2 className="text-2xl font-bold text-surface-50 mb-6">Next Steps</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<Link
|
||||||
|
href="/features"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-brand-500/20 bg-brand-950/30 hover:bg-brand-950/50 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-2xl flex-shrink-0">✨</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Explore Features</h3>
|
||||||
|
<p className="text-sm text-surface-400">Learn about all the powerful features DashCaddy offers.</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-2xl flex-shrink-0">📚</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">API Reference</h3>
|
||||||
|
<p className="text-sm text-surface-400">Full API documentation for developers.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-2xl flex-shrink-0">💎</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">View Pricing</h3>
|
||||||
|
<p className="text-sm text-surface-400">Check out our free and premium plans.</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="mailto:support@dashcaddy.net"
|
||||||
|
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-2xl flex-shrink-0">💬</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Get Support</h3>
|
||||||
|
<p className="text-sm text-surface-400">Contact our support team for help.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
336
src/app/features/page.tsx
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import FeatureCard from '@/components/FeatureCard';
|
||||||
|
|
||||||
|
interface FeatureItem {
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
premium?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FeatureSection {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
features: FeatureItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeaturesPage() {
|
||||||
|
const features: FeatureSection[] = [
|
||||||
|
{
|
||||||
|
id: 'deployment',
|
||||||
|
title: 'App Deployment',
|
||||||
|
description: 'Deploy your favorite applications instantly from our library of 50+ pre-configured Docker templates. No manual configuration needed—just click, deploy, and go live.',
|
||||||
|
icon: '🚀',
|
||||||
|
features: [
|
||||||
|
{ icon: '⚡', label: 'One-click deployment' },
|
||||||
|
{ icon: '📦', label: '50+ app templates' },
|
||||||
|
{ icon: '⚙️', label: 'Auto-configuration' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssl-security',
|
||||||
|
title: 'SSL & Security',
|
||||||
|
description: 'Enterprise-grade security built in. Automatic SSL certificates, TOTP 2FA, encrypted credentials, and comprehensive audit logs to track every action.',
|
||||||
|
icon: '🔒',
|
||||||
|
features: [
|
||||||
|
{ icon: '🔐', label: 'Automatic SSL certificates' },
|
||||||
|
{ icon: '📱', label: 'TOTP 2FA authentication' },
|
||||||
|
{ icon: '🔑', label: 'Encrypted credentials' },
|
||||||
|
{ icon: '📝', label: 'Audit logs' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dns',
|
||||||
|
title: 'DNS Management',
|
||||||
|
description: 'Manage all your app subdomains from one dashboard. Automatic DNS record creation with Technitium DNS integration makes domain management effortless.',
|
||||||
|
icon: '🌐',
|
||||||
|
features: [
|
||||||
|
{ icon: '✨', label: 'Automatic DNS records' },
|
||||||
|
{ icon: '🔗', label: 'Technitium integration' },
|
||||||
|
{ icon: '📋', label: 'Subdomain management' },
|
||||||
|
{ icon: '🎯', label: 'Zero DNS config' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'monitoring',
|
||||||
|
title: 'Monitoring & Health',
|
||||||
|
description: 'Real-time visibility into your infrastructure. Monitor container health, response times, and resource usage with detailed metrics and alerts.',
|
||||||
|
icon: '📊',
|
||||||
|
features: [
|
||||||
|
{ icon: '🟢', label: 'Real-time status' },
|
||||||
|
{ icon: '⏱️', label: 'Response time tracking' },
|
||||||
|
{ icon: '💾', label: 'Resource monitoring' },
|
||||||
|
{ icon: '📈', label: 'Performance metrics' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'docker',
|
||||||
|
title: 'Docker Management',
|
||||||
|
description: 'Control your entire Docker environment visually. View, manage, and scale containers, access logs, and perform updates without touching the command line.',
|
||||||
|
icon: '🐳',
|
||||||
|
features: [
|
||||||
|
{ icon: '🎮', label: 'Container control' },
|
||||||
|
{ icon: '📜', label: 'Live logs access' },
|
||||||
|
{ icon: '🔄', label: 'Auto-updates' },
|
||||||
|
{ icon: '📊', label: 'Resource monitoring' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'premium',
|
||||||
|
title: 'Premium Features',
|
||||||
|
description: 'Advanced features for power users and production deployments. SSO integration, multi-container recipes, and Docker Swarm orchestration.',
|
||||||
|
icon: '⭐',
|
||||||
|
features: [
|
||||||
|
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
|
||||||
|
{ icon: '📚', label: 'Recipes (stack deployment)', premium: true },
|
||||||
|
{ icon: '🚀', label: 'Docker Swarm orchestration', premium: true },
|
||||||
|
{ icon: '🚀', label: 'Priority support', premium: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
|
Powerful <span className="text-brand-400">Features</span> Built In
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
||||||
|
Everything you need to manage Docker applications professionally. From deployment to monitoring, SSL to security—it's all included.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Feature Sections */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="space-y-24">
|
||||||
|
{features.map((feature, idx) => (
|
||||||
|
<div key={feature.id} className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}>
|
||||||
|
{/* Content Side */}
|
||||||
|
<div className={idx % 2 === 1 ? 'lg:order-2' : ''}>
|
||||||
|
<div className="mb-6 inline-flex rounded-lg bg-brand-500/10 p-4 text-brand-400">
|
||||||
|
<div className="text-3xl">{feature.icon}</div>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-surface-50">
|
||||||
|
{feature.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-300 mb-8 leading-relaxed">
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Feature Grid */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{feature.features.map((item, itemIdx) => (
|
||||||
|
<div
|
||||||
|
key={itemIdx}
|
||||||
|
className={`flex items-center gap-3 p-3 rounded-lg border transition-all ${
|
||||||
|
item.premium
|
||||||
|
? 'border-brand-500/30 bg-brand-500/5 hover:bg-brand-500/10'
|
||||||
|
: 'border-surface-700/30 bg-surface-800/30 hover:border-surface-700/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-xl flex-shrink-0">{item.icon}</span>
|
||||||
|
<span className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}>
|
||||||
|
{item.label}
|
||||||
|
{item.premium && (
|
||||||
|
<span className="ml-1 inline-block text-xs px-2 py-0.5 bg-brand-500/20 text-brand-300 rounded font-semibold">
|
||||||
|
Premium
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Visual Side */}
|
||||||
|
<div className={idx % 2 === 1 ? 'lg:order-1' : ''}>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/10 to-brand-600/5 rounded-2xl blur-xl" />
|
||||||
|
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{feature.features.slice(0, 3).map((item, itemIdx) => (
|
||||||
|
<div key={itemIdx} className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-brand-400" />
|
||||||
|
<span className="text-sm text-surface-300">{item.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 pt-6 border-t border-surface-700/30">
|
||||||
|
<div className="text-sm text-surface-500 text-center">
|
||||||
|
<span className="text-brand-400 font-semibold">{feature.features.length} features</span> included
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Feature Comparison Grid */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-900 to-surface-950">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-16 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||||
|
What's Included in <span className="text-brand-400">Free</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400">
|
||||||
|
Start with the free tier and upgrade anytime when you need advanced features.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<FeatureCard
|
||||||
|
icon="🚀"
|
||||||
|
title="One-Click Deployment"
|
||||||
|
description="Deploy from 50+ pre-configured templates with zero configuration."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🔒"
|
||||||
|
title="Automatic SSL"
|
||||||
|
description="Secure your apps with automatically renewed SSL certificates."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🌐"
|
||||||
|
title="DNS Management"
|
||||||
|
description="Automatic DNS record creation with Technitium integration."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="📊"
|
||||||
|
title="Real-Time Monitoring"
|
||||||
|
description="Track container health, response times, and resource usage."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🐳"
|
||||||
|
title="Docker Control"
|
||||||
|
description="Manage containers, access logs, and updates from one dashboard."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🔐"
|
||||||
|
title="Security Built-In"
|
||||||
|
description="TOTP 2FA, encrypted credentials, and audit logging included."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Premium Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute top-1/2 right-0 w-96 h-96 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="rounded-2xl border border-brand-500/30 bg-gradient-to-br from-brand-950/50 to-surface-900 p-12">
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<div className="mb-6 inline-block">
|
||||||
|
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
|
||||||
|
Premium Features
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-6 text-surface-50">
|
||||||
|
Level Up With <span className="text-brand-400">Premium</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-300 mb-8">
|
||||||
|
Get advanced features designed for power users and production deployments. Auto-Login SSO, stack recipes, Docker Swarm orchestration, and priority support.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
|
<span className="text-2xl flex-shrink-0">🔑</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Auto-Login SSO</h3>
|
||||||
|
<p className="text-sm text-surface-400">Deploy apps with automatic single sign-on integration.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
|
<span className="text-2xl flex-shrink-0">📚</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Recipes</h3>
|
||||||
|
<p className="text-sm text-surface-400">Deploy multi-container stacks with one click.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
|
<span className="text-2xl flex-shrink-0">🚀</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Docker Swarm</h3>
|
||||||
|
<p className="text-sm text-surface-400">Orchestrate multi-node clusters effortlessly.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
|
<span className="text-2xl flex-shrink-0">⚡</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-surface-50 mb-1">Priority Support</h3>
|
||||||
|
<p className="text-sm text-surface-400">Get faster responses from our support team.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
|
||||||
|
>
|
||||||
|
Explore Premium Plans
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||||
|
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-12 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
|
||||||
|
Questions About <span className="text-brand-400">Features?</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400">
|
||||||
|
Check our documentation or contact support.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Link
|
||||||
|
href="/docs"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 font-semibold text-white hover:bg-brand-600 transition-all"
|
||||||
|
>
|
||||||
|
Read Documentation
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="mailto:support@dashcaddy.net"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 font-semibold text-surface-50 hover:border-brand-400 transition-colors"
|
||||||
|
>
|
||||||
|
Contact Support
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/app/globals.css
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-brand-50: #eef6ff;
|
||||||
|
--color-brand-100: #d9eaff;
|
||||||
|
--color-brand-200: #bcdbff;
|
||||||
|
--color-brand-300: #8ec5ff;
|
||||||
|
--color-brand-400: #59a4ff;
|
||||||
|
--color-brand-500: #3381ff;
|
||||||
|
--color-brand-600: #1a5ff5;
|
||||||
|
--color-brand-700: #1349e1;
|
||||||
|
--color-brand-800: #163cb6;
|
||||||
|
--color-brand-900: #18378f;
|
||||||
|
--color-brand-950: #142357;
|
||||||
|
|
||||||
|
--color-surface-50: #f8fafc;
|
||||||
|
--color-surface-100: #f1f5f9;
|
||||||
|
--color-surface-200: #e2e8f0;
|
||||||
|
--color-surface-300: #cbd5e1;
|
||||||
|
--color-surface-400: #94a3b8;
|
||||||
|
--color-surface-500: #64748b;
|
||||||
|
--color-surface-600: #475569;
|
||||||
|
--color-surface-700: #334155;
|
||||||
|
--color-surface-800: #1e293b;
|
||||||
|
--color-surface-900: #0f172a;
|
||||||
|
--color-surface-950: #020617;
|
||||||
|
|
||||||
|
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
|
||||||
|
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-surface-950);
|
||||||
|
color: var(--color-surface-100);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: var(--color-brand-500);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text {
|
||||||
|
background: linear-gradient(135deg, var(--color-brand-400), var(--color-brand-300), #a78bfa);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card {
|
||||||
|
background: rgba(30, 41, 59, 0.5);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-border {
|
||||||
|
box-shadow: 0 0 0 1px rgba(51, 129, 255, 0.3),
|
||||||
|
0 0 20px rgba(51, 129, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-glow {
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse 80% 60% at 50% -20%,
|
||||||
|
rgba(51, 129, 255, 0.15),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
46
src/app/layout.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||||
|
description:
|
||||||
|
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration. The all-in-one self-hosting dashboard.",
|
||||||
|
keywords: [
|
||||||
|
"self-hosting",
|
||||||
|
"docker",
|
||||||
|
"dashboard",
|
||||||
|
"caddy",
|
||||||
|
"reverse proxy",
|
||||||
|
"ssl",
|
||||||
|
"dns",
|
||||||
|
"homelab",
|
||||||
|
],
|
||||||
|
openGraph: {
|
||||||
|
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||||
|
description:
|
||||||
|
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration.",
|
||||||
|
url: "https://dashcaddy.net",
|
||||||
|
siteName: "DashCaddy",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||||
|
description:
|
||||||
|
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className="h-full antialiased">
|
||||||
|
<body className="min-h-full flex flex-col bg-surface-950 text-surface-100" style={{ fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif" }}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
511
src/app/page.tsx
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import FeatureCard from '@/components/FeatureCard';
|
||||||
|
import AppShowcase from '@/components/AppShowcase';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
|
{/* Navigation */}
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section id="hero" className="relative w-full overflow-hidden py-20 sm:py-32 lg:py-40">
|
||||||
|
{/* Background gradient effects */}
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
{/* Radial gradient glow */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse-slow" />
|
||||||
|
<div className="absolute top-1/3 right-0 w-72 h-72 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
|
||||||
|
<div className="absolute bottom-0 left-1/4 w-80 h-80 bg-brand-400/10 rounded-full blur-3xl opacity-20" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||||
|
{/* Left Content */}
|
||||||
|
<div className="flex flex-col justify-center space-y-8">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight">
|
||||||
|
<span className="text-brand-400">Self-Hosting</span>
|
||||||
|
<br />
|
||||||
|
<span>Made Beautiful</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
|
||||||
|
Deploy Docker apps with one click. Automatic SSL certificates, DNS management, and reverse proxy configuration—everything you need for self-hosted perfection.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Buttons */}
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
|
||||||
|
>
|
||||||
|
<span>Get Started Free</span>
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust indicators */}
|
||||||
|
<div className="flex flex-col sm:flex-row gap-6 pt-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex -space-x-2">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-brand-400/20 border border-brand-400 flex items-center justify-center text-xs font-semibold">👤</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-surface-400">Trusted by self-hosting enthusiasts</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right - Dashboard Mockup */}
|
||||||
|
<div className="relative hidden lg:block">
|
||||||
|
<div className="relative mx-auto aspect-square max-w-md">
|
||||||
|
{/* Outer glow */}
|
||||||
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-brand-400/20 to-brand-600/20 blur-2xl" />
|
||||||
|
|
||||||
|
{/* Dashboard mockup container */}
|
||||||
|
<div className="relative rounded-2xl border border-surface-700/50 bg-gradient-to-br from-surface-800 to-surface-900 p-6 overflow-hidden">
|
||||||
|
{/* Terminal-like dashboard header */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Top bar */}
|
||||||
|
<div className="flex items-center justify-between bg-surface-900/50 rounded-lg p-3 border border-surface-700/30">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-red-500/60" />
|
||||||
|
<div className="w-3 h-3 rounded-full bg-yellow-500/60" />
|
||||||
|
<div className="w-3 h-3 rounded-full bg-green-500/60" />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-mono text-surface-500">dashcaddy.local</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content area with animated elements */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Container item 1 */}
|
||||||
|
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-mono text-brand-400">🐳 plex</span>
|
||||||
|
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-2/3 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Container item 2 */}
|
||||||
|
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in" style={{ animationDelay: '0.1s' }}>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-mono text-brand-400">☁️ nextcloud</span>
|
||||||
|
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-3/4 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Container item 3 */}
|
||||||
|
<div className="bg-surface-900/70 border border-surface-700/30 rounded p-3 animate-fade-in" style={{ animationDelay: '0.2s' }}>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-mono text-brand-400">📊 prometheus</span>
|
||||||
|
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">running</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-surface-700/30 rounded-full h-1.5 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-brand-400 to-brand-500 h-full w-1/2 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats row */}
|
||||||
|
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-surface-700/30">
|
||||||
|
<div className="text-center py-2">
|
||||||
|
<div className="text-lg font-bold text-brand-400">3</div>
|
||||||
|
<div className="text-xs text-surface-500">Containers</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-2 border-l border-r border-surface-700/30">
|
||||||
|
<div className="text-lg font-bold text-brand-400">100%</div>
|
||||||
|
<div className="text-xs text-surface-500">Uptime</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-2">
|
||||||
|
<div className="text-lg font-bold text-brand-400">42GB</div>
|
||||||
|
<div className="text-xs text-surface-500">Storage</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Key Features Grid */}
|
||||||
|
<section id="features" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="mb-16 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
|
||||||
|
Powerful Features
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
|
Everything you need to manage your self-hosted Docker applications professionally.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<FeatureCard
|
||||||
|
icon="🚀"
|
||||||
|
title="One-Click App Deployment"
|
||||||
|
description="Choose from 50+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🔒"
|
||||||
|
title="Automatic SSL Certificates"
|
||||||
|
description="Caddy internal CA automatically generates and renews SSL certificates. Secure your apps with zero configuration overhead."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🌐"
|
||||||
|
title="DNS Integration"
|
||||||
|
description="Automatic DNS record creation with Technitium DNS. Manage all your app subdomains effortlessly from one dashboard."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="📊"
|
||||||
|
title="Real-Time Monitoring"
|
||||||
|
description="Monitor container health, response times, and resource usage. Get instant insights into your application performance."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🐳"
|
||||||
|
title="Docker Management"
|
||||||
|
description="View, manage, and scale your containers. Access logs, manage volumes, and handle Docker operations visually."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🛡️"
|
||||||
|
title="Security First"
|
||||||
|
description="TOTP 2FA, audit logs, encrypted credentials, and role-based access control built in."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* How It Works Section */}
|
||||||
|
<section id="how-it-works" className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="mb-16 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
|
||||||
|
How It Works
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
|
Get up and running in minutes, not hours.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Steps */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{/* Step 1 */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
{/* Number Circle */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
|
||||||
|
<span className="text-2xl font-bold text-brand-400">1</span>
|
||||||
|
</div>
|
||||||
|
{/* Connector line (hidden on mobile, visible on larger screens) */}
|
||||||
|
<div className="hidden md:block absolute top-8 left-16 w-full h-0.5 bg-gradient-to-r from-brand-500/50 to-transparent" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="text-center pt-4">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-2">
|
||||||
|
Install
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400">
|
||||||
|
Clone the DashCaddy repository and run the setup script. Takes just a few minutes on your server.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 2 */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
{/* Number Circle */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
|
||||||
|
<span className="text-2xl font-bold text-brand-400">2</span>
|
||||||
|
</div>
|
||||||
|
{/* Connector line */}
|
||||||
|
<div className="hidden md:block absolute top-8 left-16 w-full h-0.5 bg-gradient-to-r from-brand-500/50 to-transparent" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="text-center pt-4">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-2">
|
||||||
|
Deploy
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400">
|
||||||
|
Click "Deploy" on any app template. SSL certificates and DNS records are automatically configured.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 3 */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
{/* Number Circle */}
|
||||||
|
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-brand-400/20 to-brand-600/20 border border-brand-500/30 flex items-center justify-center">
|
||||||
|
<span className="text-2xl font-bold text-brand-400">3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="text-center pt-4">
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-2">
|
||||||
|
Done
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400">
|
||||||
|
Your app is live with SSL and DNS configured. Monitor health, manage containers, and backup from the dashboard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* App Showcase */}
|
||||||
|
<AppShowcase />
|
||||||
|
|
||||||
|
{/* Why DashCaddy Section */}
|
||||||
|
<section id="why-dashcaddy" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="mb-16 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
|
||||||
|
Why DashCaddy?
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
|
Compare the pain of manual setup versus the simplicity of DashCaddy.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Comparison Table */}
|
||||||
|
<div className="overflow-hidden rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-surface-700/50">
|
||||||
|
<th className="px-6 py-4 text-left text-sm font-semibold text-surface-300 bg-surface-900/50">
|
||||||
|
Feature
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-4 text-center text-sm font-semibold text-surface-300 bg-surface-900/50">
|
||||||
|
Manual Docker Setup
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-4 text-center text-sm font-semibold text-brand-400 bg-surface-900/70">
|
||||||
|
DashCaddy
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-surface-700/30">
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">SSL Certificates</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Manual configuration via Let's Encrypt</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ Automatic</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">DNS Management</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Manual DNS records</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ Automatic</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">Reverse Proxy Setup</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Complex Nginx/Caddy config</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ One-click</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">App Templates</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Write your own compose files</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ 50+ pre-configured</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">Health Monitoring</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Third-party tools required</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ Built-in</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">Backup & Restore</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Manual scripts and planning</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ One-click backup</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
|
<td className="px-6 py-4 text-sm text-surface-300">Audit Logs</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-surface-500">Not included</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
|
<span className="text-green-400 font-semibold">✓ Built-in</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Additional Benefits */}
|
||||||
|
<div className="mt-16 grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-2xl">⏱️</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50 mb-2">
|
||||||
|
Save Countless Hours
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
What takes days of manual configuration takes minutes with DashCaddy's automated setup and configuration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-2xl">🔐</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50 mb-2">
|
||||||
|
Enterprise Security
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
TOTP 2FA, encrypted credentials, audit logging, and role-based access control built in.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-2xl">📈</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50 mb-2">
|
||||||
|
Scale Confidently
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
Real-time monitoring and health checks give you visibility into your infrastructure 24/7.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 backdrop-blur-sm p-6 space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="text-2xl">🛠️</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50 mb-2">
|
||||||
|
Never Lose Data
|
||||||
|
</h3>
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
One-click backup and restore functionality ensures your data is always protected and recoverable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Final CTA Section */}
|
||||||
|
<section id="get-started" className="relative py-20 sm:py-24 lg:py-32 bg-gradient-to-b from-surface-900 to-surface-950 overflow-hidden">
|
||||||
|
{/* Background effects */}
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/10 rounded-full blur-3xl opacity-20" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h2 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6 text-surface-50">
|
||||||
|
Ready to Transform Your
|
||||||
|
<br />
|
||||||
|
<span className="text-brand-400">Self-Hosting Setup?</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-surface-300 mb-12 max-w-2xl mx-auto leading-relaxed">
|
||||||
|
Join developers and self-hosting enthusiasts who've simplified their infrastructure management with DashCaddy.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-10 py-4 text-lg font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
|
||||||
|
>
|
||||||
|
Start Free Today
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-10 py-4 text-lg font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
|
||||||
|
>
|
||||||
|
View Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="mt-12 flex flex-wrap justify-center gap-8 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-brand-400">100%</div>
|
||||||
|
<p className="text-sm text-surface-400">Open Source</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-l border-surface-700/50" />
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-brand-400">Self-Hosted</div>
|
||||||
|
<p className="text-sm text-surface-400">Your Data, Your Rules</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-l border-surface-700/50" />
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-brand-400">24/7</div>
|
||||||
|
<p className="text-sm text-surface-400">Community Support</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
280
src/app/pricing/page.tsx
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
|
export default function PricingPage() {
|
||||||
|
const [isAnnual, setIsAnnual] = useState(false);
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
name: 'Free',
|
||||||
|
price: '0',
|
||||||
|
period: 'forever',
|
||||||
|
description: 'Perfect for getting started with self-hosting',
|
||||||
|
features: [
|
||||||
|
'Dashboard & monitoring',
|
||||||
|
'Up to 10 services',
|
||||||
|
'50+ app templates',
|
||||||
|
'Automatic SSL & DNS',
|
||||||
|
'TOTP 2FA',
|
||||||
|
'Community support',
|
||||||
|
],
|
||||||
|
cta: {
|
||||||
|
text: 'Get Started Free',
|
||||||
|
href: '/docs',
|
||||||
|
},
|
||||||
|
highlighted: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Premium',
|
||||||
|
price: isAnnual ? '99' : '20',
|
||||||
|
period: isAnnual ? 'per year' : 'per month',
|
||||||
|
savings: isAnnual ? 'Save 58%' : null,
|
||||||
|
description: 'For power users and production deployments',
|
||||||
|
features: [
|
||||||
|
'Everything in Free, plus:',
|
||||||
|
'Unlimited services',
|
||||||
|
'Auto-Login SSO for deployed apps',
|
||||||
|
'Recipes (multi-container stack deployment)',
|
||||||
|
'Docker Swarm (multi-node cluster orchestration)',
|
||||||
|
'Priority email support',
|
||||||
|
'Early access to new features',
|
||||||
|
],
|
||||||
|
cta: {
|
||||||
|
text: 'Start 14-Day Free Trial',
|
||||||
|
href: '/api/checkout?plan=premium',
|
||||||
|
},
|
||||||
|
highlighted: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
question: 'Can I try Premium for free?',
|
||||||
|
answer: 'Yes! Premium includes a 14-day free trial. No credit card required. You can cancel anytime.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'What happens when my subscription ends?',
|
||||||
|
answer: 'Your subscription gracefully downgrades to the Free tier. All your data remains intact—no data loss. You can resubscribe at any time.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Can I self-host the license server?',
|
||||||
|
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Do you offer refunds?',
|
||||||
|
answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with Premium, contact support for a full refund.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Is my data safe?',
|
||||||
|
answer: '100% self-hosted means your data never leaves your server. DashCaddy runs entirely on your infrastructure. We have no access to your applications, configurations, or data.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
|
Simple, Transparent <span className="text-brand-400">Pricing</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||||
|
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Toggle for Monthly/Yearly */}
|
||||||
|
<div className="flex items-center justify-center gap-4 mb-12">
|
||||||
|
<span className={`text-sm font-medium ${!isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||||
|
Monthly
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsAnnual(!isAnnual)}
|
||||||
|
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
|
||||||
|
isAnnual ? 'bg-brand-500' : 'bg-surface-700'
|
||||||
|
}`}
|
||||||
|
aria-label="Toggle annual pricing"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
|
||||||
|
isAnnual ? 'translate-x-7' : 'translate-x-1'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className={`text-sm font-medium ${isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||||
|
Annual
|
||||||
|
</span>
|
||||||
|
{isAnnual && (
|
||||||
|
<span className="ml-2 inline-block rounded-full bg-brand-500/20 px-3 py-1 text-sm font-semibold text-brand-300">
|
||||||
|
Best Value
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing Cards */}
|
||||||
|
<section className="relative py-12 sm:py-16 lg:py-20">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||||
|
{plans.map((plan, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className={`relative rounded-2xl border transition-all duration-300 ${
|
||||||
|
plan.highlighted
|
||||||
|
? 'border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20 scale-105 md:scale-105'
|
||||||
|
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Popular Badge */}
|
||||||
|
{plan.highlighted && (
|
||||||
|
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
||||||
|
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
|
||||||
|
Most Popular
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="p-8 sm:p-10">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h3 className="text-2xl font-bold text-surface-50 mb-2">{plan.name}</h3>
|
||||||
|
<p className="text-surface-400 text-sm mb-6">{plan.description}</p>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div className="flex items-baseline gap-2 mb-2">
|
||||||
|
<span className="text-5xl font-bold text-surface-50">${plan.price}</span>
|
||||||
|
<span className="text-surface-400">/{plan.period}</span>
|
||||||
|
</div>
|
||||||
|
{plan.savings && (
|
||||||
|
<p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<Link
|
||||||
|
href={plan.cta.href}
|
||||||
|
className={`block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 ${
|
||||||
|
plan.highlighted
|
||||||
|
? 'bg-brand-500 text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30'
|
||||||
|
: 'border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{plan.cta.text}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Features List */}
|
||||||
|
<div className="border-t border-surface-700/50 pt-8">
|
||||||
|
<ul className="space-y-4">
|
||||||
|
{plan.features.map((feature, featureIdx) => (
|
||||||
|
<li key={featureIdx} className="flex items-start gap-3">
|
||||||
|
{feature.startsWith('Everything in') ? (
|
||||||
|
<span className="text-sm font-semibold text-surface-300">{feature}</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span className="text-surface-300 text-sm">{feature}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ Section */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||||
|
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-12 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||||
|
Frequently Asked <span className="text-brand-400">Questions</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400">
|
||||||
|
Have a question? We've got answers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FAQ Accordion */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{faqs.map((faq, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
|
||||||
|
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
|
||||||
|
>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
|
||||||
|
<svg
|
||||||
|
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
|
||||||
|
expandedFaq === idx ? 'rotate-180' : ''
|
||||||
|
}`}
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={2}
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{expandedFaq === idx && (
|
||||||
|
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
|
||||||
|
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Final CTA */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-6">
|
||||||
|
Ready to Get Started?
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||||
|
Try DashCaddy free forever or upgrade to Premium for advanced features.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Link
|
||||||
|
href="/docs"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
|
||||||
|
>
|
||||||
|
View Documentation
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
|
||||||
|
>
|
||||||
|
Back to Home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
src/app/success/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import Navbar from "@/components/Navbar";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
|
function SuccessContent() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const sessionId = searchParams.get("session_id");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-1 flex items-center justify-center px-4 py-24">
|
||||||
|
<div className="max-w-lg w-full text-center">
|
||||||
|
{/* Success icon */}
|
||||||
|
<div className="mx-auto w-20 h-20 rounded-full bg-green-500/20 flex items-center justify-center mb-8">
|
||||||
|
<svg
|
||||||
|
className="w-10 h-10 text-green-400"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={2}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-4">
|
||||||
|
Welcome to DashCaddy Premium!
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-lg text-surface-300 mb-8">
|
||||||
|
Your subscription is active. Check your email for your license key
|
||||||
|
and setup instructions. Your 14-day free trial has started.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="glass-card rounded-xl p-6 mb-8 text-left">
|
||||||
|
<h2 className="text-lg font-semibold text-white mb-4">
|
||||||
|
Next Steps
|
||||||
|
</h2>
|
||||||
|
<ol className="space-y-3 text-surface-300">
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||||
|
1
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Check your email for your license key (DC-XXXXX-...)
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||||
|
2
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Open your DashCaddy dashboard and go to Admin → License
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||||
|
3
|
||||||
|
</span>
|
||||||
|
<span>Paste your license key and click Activate</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||||
|
4
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{sessionId && (
|
||||||
|
<p className="text-sm text-surface-500 mb-6">
|
||||||
|
Session ID: {sessionId.substring(0, 20)}...
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Link
|
||||||
|
href="/docs"
|
||||||
|
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
||||||
|
>
|
||||||
|
View Setup Guide
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
|
>
|
||||||
|
Back to Home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SuccessPage() {
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="flex-1 flex items-center justify-center">
|
||||||
|
<div className="text-surface-400">Loading...</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SuccessContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
181
src/components/AppShowcase.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface App {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apps: App[] = [
|
||||||
|
// Media
|
||||||
|
{ name: 'Plex', icon: '🎬', category: 'Media' },
|
||||||
|
{ name: 'Jellyfin', icon: '📽️', category: 'Media' },
|
||||||
|
{ name: 'Kaleidescape', icon: '🎞️', category: 'Media' },
|
||||||
|
{ name: 'Emby', icon: '🎥', category: 'Media' },
|
||||||
|
{ name: 'Subsonic', icon: '🎵', category: 'Media' },
|
||||||
|
{ name: 'Synology Photos', icon: '📸', category: 'Media' },
|
||||||
|
|
||||||
|
// Downloads
|
||||||
|
{ name: 'Sonarr', icon: '📺', category: 'Downloads' },
|
||||||
|
{ name: 'Radarr', icon: '🎬', category: 'Downloads' },
|
||||||
|
{ name: 'Lidarr', icon: '🎶', category: 'Downloads' },
|
||||||
|
{ name: 'qBittorrent', icon: '⚡', category: 'Downloads' },
|
||||||
|
{ name: 'Transmission', icon: '📤', category: 'Downloads' },
|
||||||
|
{ name: 'SABnzbd', icon: '📥', category: 'Downloads' },
|
||||||
|
|
||||||
|
// Productivity
|
||||||
|
{ name: 'Nextcloud', icon: '☁️', category: 'Productivity' },
|
||||||
|
{ name: 'Vikunja', icon: '✓', category: 'Productivity' },
|
||||||
|
{ name: 'OpenProject', icon: '📋', category: 'Productivity' },
|
||||||
|
{ name: 'Plane', icon: '🚀', category: 'Productivity' },
|
||||||
|
{ name: 'Actual Budget', icon: '💰', category: 'Productivity' },
|
||||||
|
{ name: 'HedgeDoc', icon: '📝', category: 'Productivity' },
|
||||||
|
|
||||||
|
// Management
|
||||||
|
{ name: 'Portainer', icon: '🐋', category: 'Management' },
|
||||||
|
{ name: 'Homelabs', icon: '🏠', category: 'Management' },
|
||||||
|
{ name: 'Yacht', icon: '⛵', category: 'Management' },
|
||||||
|
{ name: 'DockSTARTer', icon: '⭐', category: 'Management' },
|
||||||
|
{ name: 'Unraid', icon: '📦', category: 'Management' },
|
||||||
|
{ name: 'TrueNAS', icon: '💾', category: 'Management' },
|
||||||
|
|
||||||
|
// Security
|
||||||
|
{ name: 'Vaultwarden', icon: '🔐', category: 'Security' },
|
||||||
|
{ name: 'Bitwarden', icon: '🗝️', category: 'Security' },
|
||||||
|
{ name: 'Keycloak', icon: '🔑', category: 'Security' },
|
||||||
|
{ name: 'Authelia', icon: '🛡️', category: 'Security' },
|
||||||
|
{ name: 'OpenVPN', icon: '🌐', category: 'Security' },
|
||||||
|
{ name: 'WireGuard', icon: '📡', category: 'Security' },
|
||||||
|
|
||||||
|
// Development
|
||||||
|
{ name: 'Gitea', icon: '🐙', category: 'Development' },
|
||||||
|
{ name: 'GitLab', icon: '🦊', category: 'Development' },
|
||||||
|
{ name: 'Forgejo', icon: '🔧', category: 'Development' },
|
||||||
|
{ name: 'Jenkins', icon: '🤖', category: 'Development' },
|
||||||
|
{ name: 'Drone', icon: '🚁', category: 'Development' },
|
||||||
|
{ name: 'Code Server', icon: '💻', category: 'Development' },
|
||||||
|
|
||||||
|
// Monitoring
|
||||||
|
{ name: 'Grafana', icon: '📊', category: 'Monitoring' },
|
||||||
|
{ name: 'Prometheus', icon: '⚙️', category: 'Monitoring' },
|
||||||
|
{ name: 'Uptime Kuma', icon: '📈', category: 'Monitoring' },
|
||||||
|
{ name: 'Netdata', icon: '🔍', category: 'Monitoring' },
|
||||||
|
{ name: 'New Relic', icon: '👁️', category: 'Monitoring' },
|
||||||
|
{ name: 'Kibana', icon: '📉', category: 'Monitoring' },
|
||||||
|
|
||||||
|
// Additional
|
||||||
|
{ name: 'Home Assistant', icon: '🏡', category: 'Smart Home' },
|
||||||
|
{ name: 'Node-RED', icon: '🔴', category: 'Smart Home' },
|
||||||
|
{ name: 'OpenHAB', icon: '⚙️', category: 'Smart Home' },
|
||||||
|
{ name: 'Immich', icon: '📷', category: 'Media' },
|
||||||
|
{ name: 'Calibre', icon: '📚', category: 'Productivity' },
|
||||||
|
{ name: 'Paperless', icon: '📄', category: 'Productivity' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
'All',
|
||||||
|
'Media',
|
||||||
|
'Downloads',
|
||||||
|
'Productivity',
|
||||||
|
'Management',
|
||||||
|
'Security',
|
||||||
|
'Development',
|
||||||
|
'Monitoring',
|
||||||
|
'Smart Home',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function AppShowcase() {
|
||||||
|
const [activeCategory, setActiveCategory] = useState('All');
|
||||||
|
|
||||||
|
const filteredApps =
|
||||||
|
activeCategory === 'All'
|
||||||
|
? apps
|
||||||
|
: apps.filter((app) => app.category === activeCategory);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative py-12 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-12 text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
|
||||||
|
50+ One-Click App Templates
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
|
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category Filter */}
|
||||||
|
<div className="mb-10 flex flex-wrap justify-center gap-2">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<button
|
||||||
|
key={category}
|
||||||
|
onClick={() => setActiveCategory(category)}
|
||||||
|
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
||||||
|
activeCategory === category
|
||||||
|
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/30'
|
||||||
|
: 'bg-surface-800 text-surface-300 hover:bg-surface-700 hover:text-surface-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{category}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* App Grid */}
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
||||||
|
{filteredApps.map((app) => (
|
||||||
|
<div
|
||||||
|
key={`${app.name}-${app.category}`}
|
||||||
|
className="group relative flex flex-col items-center justify-center rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-4 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10 hover:-translate-y-1"
|
||||||
|
>
|
||||||
|
{/* Background gradient on hover */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 rounded-lg transition-all duration-300 pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="relative z-10 flex flex-col items-center justify-center text-center">
|
||||||
|
{/* Icon */}
|
||||||
|
<div className="text-4xl mb-2 transition-transform duration-300 group-hover:scale-110">
|
||||||
|
{app.icon}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* App Name */}
|
||||||
|
<h3 className="text-sm font-semibold text-surface-50 line-clamp-2 group-hover:text-brand-400 transition-colors">
|
||||||
|
{app.name}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Category Tag */}
|
||||||
|
<span className="text-xs text-surface-500 mt-1 group-hover:text-brand-400/70">
|
||||||
|
{app.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
||||||
|
50+
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-surface-400">App Templates</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-surface-400">Configuration</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
||||||
|
1-Click
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-surface-400">Deploy</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/components/FeatureCard.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
interface FeatureCardProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeatureCard({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}: FeatureCardProps) {
|
||||||
|
return (
|
||||||
|
<div className="group relative overflow-hidden rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-6 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10">
|
||||||
|
{/* Background gradient effect on hover */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="relative z-10">
|
||||||
|
{/* Icon */}
|
||||||
|
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
|
||||||
|
<div className="text-2xl">{icon}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h3 className="mb-2 text-lg font-semibold text-surface-50">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="text-surface-300 text-sm leading-relaxed">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
139
src/components/Footer.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
const footerSections = [
|
||||||
|
{
|
||||||
|
title: 'Product',
|
||||||
|
links: [
|
||||||
|
{ label: 'Features', href: '/features' },
|
||||||
|
{ label: 'Pricing', href: '/pricing' },
|
||||||
|
{ label: 'Documentation', href: '/docs' },
|
||||||
|
{ label: 'About', href: '/about' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Resources',
|
||||||
|
links: [
|
||||||
|
{ label: 'Getting Started', href: '/docs' },
|
||||||
|
{ label: 'API Reference', href: '/docs#api' },
|
||||||
|
{ label: 'GitHub', href: 'https://git.dashcaddy.net/sami7777/dashcaddy' },
|
||||||
|
{ label: 'Community', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Support',
|
||||||
|
links: [
|
||||||
|
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
||||||
|
{ label: 'Discord', href: '#' },
|
||||||
|
{ label: 'Privacy Policy', href: '#' },
|
||||||
|
{ label: 'Terms of Service', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const socialLinks = [
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
label: 'GitHub',
|
||||||
|
href: 'https://git.dashcaddy.net/sami7777/dashcaddy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
label: 'Discord',
|
||||||
|
href: '#',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-surface-700/50 bg-surface-950">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||||
|
{/* Brand Section */}
|
||||||
|
<div>
|
||||||
|
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
|
||||||
|
<span>DashCaddy</span>
|
||||||
|
</Link>
|
||||||
|
<p className="text-surface-400 text-sm leading-relaxed mb-4">
|
||||||
|
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{socialLinks.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.label}
|
||||||
|
href={link.href}
|
||||||
|
className="text-surface-400 hover:text-brand-400 transition-colors"
|
||||||
|
aria-label={link.label}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{link.icon}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Link Sections */}
|
||||||
|
{footerSections.map((section) => (
|
||||||
|
<div key={section.title}>
|
||||||
|
<h3 className="text-sm font-semibold text-surface-50 mb-4">
|
||||||
|
{section.title}
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{section.links.map((link) => (
|
||||||
|
<li key={link.label}>
|
||||||
|
<Link
|
||||||
|
href={link.href}
|
||||||
|
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Support Link */}
|
||||||
|
<div className="border-t border-surface-700/50 pt-8 mb-8">
|
||||||
|
<p className="text-sm text-surface-400">
|
||||||
|
Need help? Email us at{' '}
|
||||||
|
<a
|
||||||
|
href="mailto:support@dashcaddy.net"
|
||||||
|
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
|
||||||
|
>
|
||||||
|
support@dashcaddy.net
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Copyright with samiahmed7777 logo */}
|
||||||
|
<div className="border-t border-surface-700/50 pt-8">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||||
|
<p className="text-sm text-surface-500">
|
||||||
|
© {currentYear} DashCaddy. All rights reserved. A product by
|
||||||
|
</p>
|
||||||
|
<Image
|
||||||
|
src="/images/samiahmed7777-logo.png"
|
||||||
|
alt="samiahmed7777"
|
||||||
|
width={160}
|
||||||
|
height={32}
|
||||||
|
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
98
src/components/Navbar.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ href: '#features', label: 'Features' },
|
||||||
|
{ href: '#pricing', label: 'Pricing' },
|
||||||
|
{ href: '#docs', label: 'Docs' },
|
||||||
|
{ href: '#about', label: 'About' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="sticky top-0 z-50 border-b border-surface-700/50 bg-surface-950/95 backdrop-blur supports-[backdrop-filter]:bg-surface-950/75">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex h-16 items-center justify-between">
|
||||||
|
{/* Logo */}
|
||||||
|
<Link href="/" className="flex items-center gap-2 font-bold text-xl text-brand-400 hover:text-brand-300 transition-colors">
|
||||||
|
<span>🚀</span>
|
||||||
|
<span>DashCaddy</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Navigation */}
|
||||||
|
<div className="hidden md:flex items-center gap-8">
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="text-surface-300 hover:text-brand-400 transition-colors text-sm font-medium"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Button (Desktop) */}
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<Link
|
||||||
|
href="#get-started"
|
||||||
|
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="md:hidden inline-flex items-center justify-center rounded-lg p-2 text-surface-400 hover:bg-surface-800 hover:text-surface-200 transition-colors"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className={`h-6 w-6 transition-transform duration-300 ${isOpen ? 'rotate-90' : ''}`}
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Navigation */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="border-t border-surface-700/50 bg-surface-900/50 backdrop-blur md:hidden">
|
||||||
|
<div className="space-y-1 px-2 py-4">
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="block rounded-lg px-3 py-2 text-base font-medium text-surface-300 hover:bg-surface-800 hover:text-brand-400 transition-colors"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link
|
||||||
|
href="#get-started"
|
||||||
|
className="block rounded-lg bg-brand-500 px-3 py-2 text-base font-medium text-white hover:bg-brand-600 transition-colors mt-4"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
72
tailwind.config.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: {
|
||||||
|
50: "#eef6ff",
|
||||||
|
100: "#d9eaff",
|
||||||
|
200: "#bcdbff",
|
||||||
|
300: "#8ec5ff",
|
||||||
|
400: "#59a4ff",
|
||||||
|
500: "#3381ff",
|
||||||
|
600: "#1a5ff5",
|
||||||
|
700: "#1349e1",
|
||||||
|
800: "#163cb6",
|
||||||
|
900: "#18378f",
|
||||||
|
950: "#142357",
|
||||||
|
},
|
||||||
|
surface: {
|
||||||
|
50: "#f8fafc",
|
||||||
|
100: "#f1f5f9",
|
||||||
|
200: "#e2e8f0",
|
||||||
|
300: "#cbd5e1",
|
||||||
|
400: "#94a3b8",
|
||||||
|
500: "#64748b",
|
||||||
|
600: "#475569",
|
||||||
|
700: "#334155",
|
||||||
|
800: "#1e293b",
|
||||||
|
900: "#0f172a",
|
||||||
|
950: "#020617",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Inter", "system-ui", "-apple-system", "sans-serif"],
|
||||||
|
mono: ["JetBrains Mono", "Fira Code", "monospace"],
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"fade-in": "fadeIn 0.5s ease-out",
|
||||||
|
"slide-up": "slideUp 0.6s ease-out",
|
||||||
|
"slide-in-left": "slideInLeft 0.6s ease-out",
|
||||||
|
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
||||||
|
float: "float 6s ease-in-out infinite",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
fadeIn: {
|
||||||
|
"0%": { opacity: "0" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
slideUp: {
|
||||||
|
"0%": { opacity: "0", transform: "translateY(20px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||||
|
},
|
||||||
|
slideInLeft: {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(-20px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateX(0)" },
|
||||||
|
},
|
||||||
|
float: {
|
||||||
|
"0%, 100%": { transform: "translateY(0)" },
|
||||||
|
"50%": { transform: "translateY(-10px)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts",
|
||||||
|
"**/*.mts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||