mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 08:06:42 +02:00
feat(todo): prepare for production deployment
- Fix type-check errors (subtask id, duplicate currentLocale) - Add complete Astro landing page with Hero, Features, Pricing, CTA - Add production environment templates (.env.example, .env.production.example) - Add docker-compose.prod.yml for production deployment - Add deploy.sh script for server deployment - Add /health endpoint for web app health checks - Improve docker-entrypoint.sh with database wait logic - Remove references to deleted statistics and session-tasks stores Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
99fdf1d17f
commit
1dc4f58edb
24 changed files with 1511 additions and 129 deletions
25
apps/todo/.env.example
Normal file
25
apps/todo/.env.example
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Todo Production Environment Variables
|
||||
# Copy this file to .env and fill in the values for docker-compose.prod.yml
|
||||
|
||||
# =============================================================================
|
||||
# Backend Configuration
|
||||
# =============================================================================
|
||||
|
||||
# PostgreSQL connection string
|
||||
DATABASE_URL=postgresql://todo_user:CHANGE_ME@postgres:5432/todo
|
||||
|
||||
# Mana Core Auth URL for JWT validation
|
||||
MANA_CORE_AUTH_URL=https://auth.mana.how
|
||||
|
||||
# Allowed CORS origins (comma-separated)
|
||||
CORS_ORIGINS=https://todo.mana.how
|
||||
|
||||
# =============================================================================
|
||||
# Web Configuration
|
||||
# =============================================================================
|
||||
|
||||
# Backend API URL (as seen from browser)
|
||||
PUBLIC_BACKEND_URL=https://todo-api.mana.how
|
||||
|
||||
# Auth URL (as seen from browser)
|
||||
PUBLIC_MANA_CORE_AUTH_URL=https://auth.mana.how
|
||||
36
apps/todo/apps/backend/.env.production.example
Normal file
36
apps/todo/apps/backend/.env.production.example
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Todo Backend - Production Environment Variables
|
||||
# Copy this file to .env.production and fill in the values
|
||||
|
||||
# =============================================================================
|
||||
# REQUIRED
|
||||
# =============================================================================
|
||||
|
||||
# Database connection string (PostgreSQL)
|
||||
DATABASE_URL=postgresql://todo_user:CHANGE_ME@localhost:5432/todo
|
||||
|
||||
# Mana Core Auth URL for JWT validation
|
||||
MANA_CORE_AUTH_URL=https://auth.mana.how
|
||||
|
||||
# Allowed CORS origins (comma-separated)
|
||||
# Include your frontend domains here
|
||||
CORS_ORIGINS=https://todo.mana.how,https://todo-landing.mana.how
|
||||
|
||||
# =============================================================================
|
||||
# OPTIONAL
|
||||
# =============================================================================
|
||||
|
||||
# Server port (default: 3018)
|
||||
PORT=3018
|
||||
|
||||
# Node environment (MUST be 'production' for security)
|
||||
NODE_ENV=production
|
||||
|
||||
# Logging level (default: info)
|
||||
LOG_LEVEL=info
|
||||
|
||||
# =============================================================================
|
||||
# SECURITY NOTES
|
||||
# =============================================================================
|
||||
# - NEVER set DEV_BYPASS_AUTH=true in production
|
||||
# - NEVER commit this file with real credentials
|
||||
# - Use Docker secrets or environment injection for sensitive values
|
||||
|
|
@ -1,8 +1,46 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Starting Todo Backend..."
|
||||
echo "=========================================="
|
||||
echo " Todo Backend Startup"
|
||||
echo "=========================================="
|
||||
echo "Environment: ${NODE_ENV:-development}"
|
||||
echo "Port: ${PORT:-3018}"
|
||||
|
||||
# Wait for database to be ready
|
||||
if [ -n "$DATABASE_URL" ]; then
|
||||
echo "Waiting for database..."
|
||||
|
||||
# Extract host and port from DATABASE_URL
|
||||
DB_HOST=$(echo $DATABASE_URL | sed -n 's/.*@\([^:]*\):.*/\1/p')
|
||||
DB_PORT=$(echo $DATABASE_URL | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
|
||||
|
||||
# Default port if not found
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
|
||||
# Wait for database to accept connections
|
||||
max_attempts=30
|
||||
attempt=1
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if pg_isready -h "$DB_HOST" -p "$DB_PORT" > /dev/null 2>&1; then
|
||||
echo "Database is ready!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for database... (attempt $attempt/$max_attempts)"
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if [ $attempt -gt $max_attempts ]; then
|
||||
echo "Warning: Could not connect to database after $max_attempts attempts"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Push database schema (safe for production - only adds missing tables/columns)
|
||||
if [ "$RUN_DB_PUSH" = "true" ]; then
|
||||
echo "Pushing database schema..."
|
||||
npx drizzle-kit push --force || echo "Warning: db:push failed, continuing anyway..."
|
||||
fi
|
||||
|
||||
echo "Starting application..."
|
||||
exec "$@"
|
||||
|
|
|
|||
32
apps/todo/apps/landing/.gitignore
vendored
Normal file
32
apps/todo/apps/landing/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# build output
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
.env.local
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Wrangler
|
||||
.wrangler/
|
||||
15
apps/todo/apps/landing/.prettierrc
Normal file
15
apps/todo/apps/landing/.prettierrc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
19
apps/todo/apps/landing/astro.config.mjs
Normal file
19
apps/todo/apps/landing/astro.config.mjs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind()],
|
||||
output: 'static',
|
||||
build: {
|
||||
inlineStylesheets: 'auto',
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@components': '/src/components',
|
||||
'@layouts': '/src/layouts',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
34
apps/todo/apps/landing/package.json
Normal file
34
apps/todo/apps/landing/package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@todo/landing",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev --port 4323",
|
||||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"type-check": "astro check",
|
||||
"format": "prettier --write .",
|
||||
"clean": "rm -rf dist .astro node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.0",
|
||||
"@manacore/shared-landing-ui": "workspace:*",
|
||||
"astro": "^5.16.0",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"@types/node": "^20.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-astro": "^1.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^3.4.0"
|
||||
}
|
||||
}
|
||||
3
apps/todo/apps/landing/public/favicon.svg
Normal file
3
apps/todo/apps/landing/public/favicon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="#10b981" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 317 B |
60
apps/todo/apps/landing/src/components/CTA.astro
Normal file
60
apps/todo/apps/landing/src/components/CTA.astro
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
// Call to Action section
|
||||
---
|
||||
|
||||
<section class="relative overflow-hidden bg-dark-bg">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-primary-950/30 via-dark-bg to-primary-950/30">
|
||||
</div>
|
||||
|
||||
<div class="container relative">
|
||||
<div class="mx-auto max-w-3xl text-center">
|
||||
<h2 class="mb-6 text-3xl font-bold md:text-4xl lg:text-5xl">
|
||||
Bereit, produktiver zu werden?
|
||||
</h2>
|
||||
<p class="mb-10 text-lg text-gray-400">
|
||||
Starte kostenlos und erlebe, wie einfach Task-Management sein kann. Keine Kreditkarte
|
||||
erforderlich.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<a href="#" class="btn btn-primary text-lg">
|
||||
Jetzt kostenlos starten
|
||||
<svg class="ml-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#features" class="btn btn-secondary"> Mehr erfahren </a>
|
||||
</div>
|
||||
|
||||
<!-- Benefits list -->
|
||||
<div class="mt-12 flex flex-wrap items-center justify-center gap-6 text-sm text-gray-500">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Kostenlos starten</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Keine Kreditkarte</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Jederzeit kündbar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
84
apps/todo/apps/landing/src/components/Features.astro
Normal file
84
apps/todo/apps/landing/src/components/Features.astro
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
// Features section for Todo landing page
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>`,
|
||||
title: 'Projekte & Ordner',
|
||||
description:
|
||||
'Organisiere deine Aufgaben in farbcodierten Projekten. Behalte den Überblick über Arbeit, Privates und mehr.',
|
||||
},
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"></path>
|
||||
</svg>`,
|
||||
title: 'Unteraufgaben',
|
||||
description:
|
||||
'Teile grosse Aufgaben in handhabbare Schritte auf. Verfolge den Fortschritt mit Checklisten.',
|
||||
},
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"></path>
|
||||
</svg>`,
|
||||
title: 'Kanban-Boards',
|
||||
description:
|
||||
'Visualisiere deine Workflows mit Drag-and-Drop Kanban-Boards. Perfekt für agile Arbeitsweisen.',
|
||||
},
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>`,
|
||||
title: 'Wiederkehrende Tasks',
|
||||
description:
|
||||
'Erstelle Tasks, die sich automatisch wiederholen - täglich, wöchentlich, monatlich oder nach deinen Regeln.',
|
||||
},
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
|
||||
</svg>`,
|
||||
title: 'Smarte Erinnerungen',
|
||||
description:
|
||||
'Verpasse nie wieder eine Deadline. Push-Benachrichtigungen und E-Mail-Erinnerungen zur rechten Zeit.',
|
||||
},
|
||||
{
|
||||
icon: `<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>`,
|
||||
title: 'Labels & Tags',
|
||||
description:
|
||||
'Kategorisiere deine Aufgaben mit farbigen Labels. Filtere und finde Tasks blitzschnell.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<section id="features" class="bg-dark-surface">
|
||||
<div class="container">
|
||||
<!-- Section header -->
|
||||
<div class="mx-auto mb-16 max-w-3xl text-center">
|
||||
<span class="mb-4 inline-block text-sm font-medium uppercase tracking-wider text-primary-400">
|
||||
Funktionen
|
||||
</span>
|
||||
<h2 class="mb-6 text-3xl font-bold md:text-4xl lg:text-5xl">Alles was du brauchst</h2>
|
||||
<p class="text-lg text-gray-400">
|
||||
Todo bietet alle Funktionen, die du für effektives Task-Management benötigst.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Features grid -->
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{
|
||||
features.map((feature) => (
|
||||
<div class="group rounded-xl border border-dark-border bg-dark-card p-6 transition-all duration-300 hover:border-primary-500/50 hover:bg-dark-card/80">
|
||||
<div class="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-primary-500/10 text-primary-400 transition-colors group-hover:bg-primary-500/20">
|
||||
<Fragment set:html={feature.icon} />
|
||||
</div>
|
||||
<h3 class="mb-3 text-xl font-semibold">{feature.title}</h3>
|
||||
<p class="text-gray-400">{feature.description}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
109
apps/todo/apps/landing/src/components/Footer.astro
Normal file
109
apps/todo/apps/landing/src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
---
|
||||
// Footer component
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const links = {
|
||||
product: [
|
||||
{ name: 'Funktionen', href: '#features' },
|
||||
{ name: 'Preise', href: '#pricing' },
|
||||
{ name: 'Changelog', href: '/changelog' },
|
||||
{ name: 'Roadmap', href: '/roadmap' },
|
||||
],
|
||||
legal: [
|
||||
{ name: 'Impressum', href: '/impressum' },
|
||||
{ name: 'Datenschutz', href: '/datenschutz' },
|
||||
{ name: 'AGB', href: '/agb' },
|
||||
],
|
||||
support: [
|
||||
{ name: 'FAQ', href: '/faq' },
|
||||
{ name: 'Kontakt', href: '/kontakt' },
|
||||
{ name: 'Status', href: '/status' },
|
||||
],
|
||||
};
|
||||
---
|
||||
|
||||
<footer class="border-t border-dark-border bg-dark-bg py-12">
|
||||
<div class="container">
|
||||
<div class="grid gap-8 md:grid-cols-4">
|
||||
<!-- Brand -->
|
||||
<div class="md:col-span-1">
|
||||
<div class="mb-4 flex items-center gap-2">
|
||||
<svg
|
||||
class="h-8 w-8 text-primary-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="text-xl font-bold">Todo</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Smart Task Management für bessere Produktivität.</p>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div>
|
||||
<h4 class="mb-4 font-semibold">Produkt</h4>
|
||||
<ul class="space-y-2 text-sm text-gray-400">
|
||||
{
|
||||
links.product.map((link) => (
|
||||
<li>
|
||||
<a href={link.href} class="transition-colors hover:text-white">
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="mb-4 font-semibold">Rechtliches</h4>
|
||||
<ul class="space-y-2 text-sm text-gray-400">
|
||||
{
|
||||
links.legal.map((link) => (
|
||||
<li>
|
||||
<a href={link.href} class="transition-colors hover:text-white">
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="mb-4 font-semibold">Support</h4>
|
||||
<ul class="space-y-2 text-sm text-gray-400">
|
||||
{
|
||||
links.support.map((link) => (
|
||||
<li>
|
||||
<a href={link.href} class="transition-colors hover:text-white">
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom bar -->
|
||||
<div
|
||||
class="mt-12 flex flex-col items-center justify-between gap-4 border-t border-dark-border pt-8 md:flex-row"
|
||||
>
|
||||
<p class="text-sm text-gray-500">
|
||||
© {currentYear} Todo. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Ein <a href="https://manacore.app" class="text-primary-400 hover:underline">Manacore</a> Produkt
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
146
apps/todo/apps/landing/src/components/Hero.astro
Normal file
146
apps/todo/apps/landing/src/components/Hero.astro
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
// Hero section for Todo landing page
|
||||
---
|
||||
|
||||
<section class="relative overflow-hidden py-20 md:py-32">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-primary-950/30 via-dark-bg to-dark-bg"></div>
|
||||
|
||||
<!-- Grid pattern -->
|
||||
<div class="absolute inset-0 bg-[url('/grid.svg')] bg-center opacity-10"></div>
|
||||
|
||||
<div class="container relative">
|
||||
<div class="mx-auto max-w-4xl text-center">
|
||||
<!-- Badge -->
|
||||
<div
|
||||
class="mb-8 inline-flex items-center gap-2 rounded-full border border-primary-500/30 bg-primary-500/10 px-4 py-2 text-sm text-primary-400"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Smart Task-Management</span>
|
||||
</div>
|
||||
|
||||
<!-- Headline -->
|
||||
<h1 class="mb-6 text-4xl font-bold leading-tight md:text-6xl lg:text-7xl">
|
||||
Erledige mehr mit
|
||||
<span class="gradient-text">weniger Stress</span>
|
||||
</h1>
|
||||
|
||||
<!-- Subheadline -->
|
||||
<p class="mx-auto mb-10 max-w-2xl text-lg text-gray-400 md:text-xl">
|
||||
Projekte, Unteraufgaben, Kanban-Boards, wiederkehrende Tasks und smarte Erinnerungen - alles
|
||||
an einem Ort. Behalte den Überblick über alles, was wichtig ist.
|
||||
</p>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<a href="#" class="btn btn-primary group text-lg">
|
||||
Kostenlos starten
|
||||
<svg
|
||||
class="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#features" class="btn btn-secondary"> Funktionen entdecken </a>
|
||||
</div>
|
||||
|
||||
<!-- Social proof -->
|
||||
<div class="mt-16 flex flex-col items-center gap-4">
|
||||
<div class="flex -space-x-2">
|
||||
{
|
||||
[1, 2, 3, 4, 5].map((i) => (
|
||||
<div class="h-10 w-10 rounded-full border-2 border-dark-bg bg-gradient-to-br from-primary-400 to-primary-600" />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">
|
||||
<span class="font-semibold text-white">500+</span> Nutzer vertrauen Todo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview mockup -->
|
||||
<div class="relative mx-auto mt-16 max-w-5xl">
|
||||
<div
|
||||
class="absolute -inset-4 rounded-2xl bg-gradient-to-r from-primary-500/20 via-transparent to-primary-500/20 blur-3xl"
|
||||
>
|
||||
</div>
|
||||
<div class="relative rounded-xl border border-dark-border bg-dark-card p-2 shadow-2xl">
|
||||
<div class="flex gap-2 px-4 py-3">
|
||||
<div class="h-3 w-3 rounded-full bg-red-500"></div>
|
||||
<div class="h-3 w-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="h-3 w-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<div class="aspect-[16/9] overflow-hidden rounded-lg bg-dark-surface">
|
||||
<!-- Task list preview -->
|
||||
<div class="p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="text-xl font-semibold">Heute</h3>
|
||||
<div class="flex gap-2">
|
||||
<button class="rounded-lg bg-primary-500 px-3 py-1 text-sm">+ Aufgabe</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
{
|
||||
[
|
||||
{ title: 'Meeting mit Team vorbereiten', priority: 'high', done: true },
|
||||
{ title: 'E-Mails beantworten', priority: 'medium', done: false },
|
||||
{ title: 'Projekt-Dokumentation aktualisieren', priority: 'low', done: false },
|
||||
{ title: 'Code-Review durchführen', priority: 'high', done: false },
|
||||
{ title: 'Sprint Planning vorbereiten', priority: 'medium', done: false },
|
||||
].map((task) => (
|
||||
<div class="flex items-center gap-3 rounded-lg bg-dark-card p-3">
|
||||
<div
|
||||
class={`h-5 w-5 rounded-full border-2 flex items-center justify-center ${
|
||||
task.done
|
||||
? 'border-primary-500 bg-primary-500'
|
||||
: task.priority === 'high'
|
||||
? 'border-red-500'
|
||||
: task.priority === 'medium'
|
||||
? 'border-yellow-500'
|
||||
: 'border-gray-500'
|
||||
}`}
|
||||
>
|
||||
{task.done && (
|
||||
<svg
|
||||
class="h-3 w-3 text-white"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<span class={task.done ? 'text-gray-500 line-through' : 'text-white'}>
|
||||
{task.title}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
48
apps/todo/apps/landing/src/layouts/Layout.astro
Normal file
48
apps/todo/apps/landing/src/layouts/Layout.astro
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title = 'Todo - Smart Task Management',
|
||||
description = 'Organisiere deine Aufgaben intelligent. Projekte, Unteraufgaben, wiederkehrende Tasks, Kanban-Boards und mehr. Kostenlos starten.',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="de" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
|
||||
<!-- Preconnect to Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Umami Analytics -->
|
||||
<script defer src="https://stats.mana.how/script.js" data-website-id="todo-landing"></script>
|
||||
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body class="antialiased">
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
253
apps/todo/apps/landing/src/pages/index.astro
Normal file
253
apps/todo/apps/landing/src/pages/index.astro
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
---
|
||||
import Layout from '@layouts/Layout.astro';
|
||||
import Hero from '@components/Hero.astro';
|
||||
import Features from '@components/Features.astro';
|
||||
import CTA from '@components/CTA.astro';
|
||||
import Footer from '@components/Footer.astro';
|
||||
|
||||
// Try to import shared components if available
|
||||
let StepsSection: any = null;
|
||||
let PricingSection: any = null;
|
||||
try {
|
||||
const shared = await import('@manacore/shared-landing-ui/sections/StepsSection.astro');
|
||||
StepsSection = shared.default;
|
||||
} catch {
|
||||
// Shared component not available
|
||||
}
|
||||
try {
|
||||
const shared = await import('@manacore/shared-landing-ui/sections/PricingSection.astro');
|
||||
PricingSection = shared.default;
|
||||
} catch {
|
||||
// Shared component not available
|
||||
}
|
||||
|
||||
// Steps data
|
||||
const steps = [
|
||||
{
|
||||
number: '1',
|
||||
title: 'Aufgabe erfassen',
|
||||
description:
|
||||
'Erstelle Aufgaben mit natürlicher Sprache. Füge Fälligkeiten, Prioritäten und Tags hinzu - alles in einem Schritt.',
|
||||
image: '/screenshots/quick-add.png',
|
||||
},
|
||||
{
|
||||
number: '2',
|
||||
title: 'Organisieren',
|
||||
description:
|
||||
'Ordne Aufgaben in Projekte ein, füge Unteraufgaben hinzu und nutze das Kanban-Board für visuelle Workflows.',
|
||||
image: '/screenshots/organize.png',
|
||||
},
|
||||
{
|
||||
number: '3',
|
||||
title: 'Erledigen',
|
||||
description:
|
||||
'Arbeite deine Tasks ab, verfolge deinen Fortschritt in Statistiken und feiere deine Erfolge.',
|
||||
image: '/screenshots/complete.png',
|
||||
},
|
||||
];
|
||||
|
||||
// Pricing data
|
||||
const pricingPlans = [
|
||||
{
|
||||
name: 'Free',
|
||||
price: '0',
|
||||
period: '/Monat',
|
||||
description: 'Perfekt für Einzelpersonen',
|
||||
features: [
|
||||
{ text: '3 Projekte', included: true },
|
||||
{ text: 'Unbegrenzte Aufgaben', included: true },
|
||||
{ text: 'Labels & Tags', included: true },
|
||||
{ text: 'Web-App Zugang', included: true },
|
||||
{ text: 'Kanban-Boards', included: false },
|
||||
{ text: 'Wiederkehrende Tasks', included: false },
|
||||
],
|
||||
cta: {
|
||||
text: 'Kostenlos starten',
|
||||
href: '#',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
price: '4,99',
|
||||
period: '/Monat',
|
||||
description: 'Für Power-User',
|
||||
features: [
|
||||
{ text: 'Unbegrenzte Projekte', included: true },
|
||||
{ text: 'Unbegrenzte Aufgaben', included: true },
|
||||
{ text: 'Kanban-Boards', included: true },
|
||||
{ text: 'Wiederkehrende Tasks', included: true },
|
||||
{ text: 'Statistiken & Heatmaps', included: true },
|
||||
{ text: 'Priority Support', included: true },
|
||||
],
|
||||
cta: {
|
||||
text: 'Pro starten',
|
||||
href: '#',
|
||||
},
|
||||
highlighted: true,
|
||||
badge: 'Beliebt',
|
||||
},
|
||||
{
|
||||
name: 'Team',
|
||||
price: '9,99',
|
||||
period: '/Monat',
|
||||
description: 'Für Teams & Unternehmen',
|
||||
features: [
|
||||
{ text: 'Alles aus Pro', included: true },
|
||||
{ text: 'Team-Verwaltung', included: true },
|
||||
{ text: 'Geteilte Projekte', included: true },
|
||||
{ text: 'API-Zugang', included: true },
|
||||
{ text: 'Admin-Dashboard', included: true },
|
||||
{ text: 'SLA Garantie', included: true },
|
||||
],
|
||||
cta: {
|
||||
text: 'Team erstellen',
|
||||
href: '#',
|
||||
},
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Todo - Smart Task Management"
|
||||
description="Organisiere deine Aufgaben intelligent. Projekte, Unteraufgaben, Kanban-Boards, wiederkehrende Tasks und smarte Erinnerungen. Kostenlos starten."
|
||||
>
|
||||
<Hero />
|
||||
<Features />
|
||||
|
||||
{
|
||||
StepsSection && (
|
||||
<StepsSection
|
||||
id="how-it-works"
|
||||
title="So einfach geht's"
|
||||
subtitle="In drei Schritten zur Produktivität"
|
||||
steps={steps}
|
||||
showImages={false}
|
||||
alternateLayout={true}
|
||||
class="bg-dark-surface"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!StepsSection && (
|
||||
<section id="how-it-works" class="bg-dark-surface">
|
||||
<div class="container">
|
||||
<div class="mx-auto mb-16 max-w-3xl text-center">
|
||||
<span class="mb-4 inline-block text-sm font-medium uppercase tracking-wider text-primary-400">
|
||||
So funktioniert's
|
||||
</span>
|
||||
<h2 class="mb-6 text-3xl font-bold md:text-4xl">So einfach geht's</h2>
|
||||
<p class="text-lg text-gray-400">In drei Schritten zur Produktivität</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-8 md:grid-cols-3">
|
||||
{steps.map((step) => (
|
||||
<div class="text-center">
|
||||
<div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary-500/20 text-2xl font-bold text-primary-400">
|
||||
{step.number}
|
||||
</div>
|
||||
<h3 class="mb-3 text-xl font-semibold">{step.title}</h3>
|
||||
<p class="text-gray-400">{step.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
PricingSection && (
|
||||
<PricingSection
|
||||
id="pricing"
|
||||
title="Einfache, transparente Preise"
|
||||
subtitle="Starte kostenlos, upgrade wenn du mehr brauchst"
|
||||
plans={pricingPlans}
|
||||
class="bg-dark-bg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!PricingSection && (
|
||||
<section id="pricing" class="bg-dark-bg">
|
||||
<div class="container">
|
||||
<div class="mx-auto mb-16 max-w-3xl text-center">
|
||||
<span class="mb-4 inline-block text-sm font-medium uppercase tracking-wider text-primary-400">
|
||||
Preise
|
||||
</span>
|
||||
<h2 class="mb-6 text-3xl font-bold md:text-4xl">Einfache, transparente Preise</h2>
|
||||
<p class="text-lg text-gray-400">Starte kostenlos, upgrade wenn du mehr brauchst</p>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto grid max-w-5xl gap-8 md:grid-cols-3">
|
||||
{pricingPlans.map((plan) => (
|
||||
<div
|
||||
class={`relative rounded-xl border p-6 ${plan.highlighted ? 'border-primary-500 bg-primary-500/10' : 'border-dark-border bg-dark-card'}`}
|
||||
>
|
||||
{plan.badge && (
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2 rounded-full bg-primary-500 px-3 py-1 text-xs font-medium text-white">
|
||||
{plan.badge}
|
||||
</div>
|
||||
)}
|
||||
<h3 class="mb-2 text-xl font-semibold">{plan.name}</h3>
|
||||
<p class="mb-4 text-sm text-gray-400">{plan.description}</p>
|
||||
<div class="mb-6">
|
||||
<span class="text-4xl font-bold">{plan.price}€</span>
|
||||
<span class="text-gray-500">{plan.period}</span>
|
||||
</div>
|
||||
<ul class="mb-8 space-y-3">
|
||||
{plan.features.map((feature) => (
|
||||
<li
|
||||
class={`flex items-center gap-2 text-sm ${feature.included ? 'text-white' : 'text-gray-600'}`}
|
||||
>
|
||||
{feature.included ? (
|
||||
<svg
|
||||
class="h-5 w-5 text-green-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{feature.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a
|
||||
href={plan.cta.href}
|
||||
class={`btn w-full ${plan.highlighted ? 'btn-primary' : 'btn-secondary'}`}
|
||||
>
|
||||
{plan.cta.text}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
<CTA />
|
||||
<Footer />
|
||||
</Layout>
|
||||
78
apps/todo/apps/landing/src/styles/global.css
Normal file
78
apps/todo/apps/landing/src/styles/global.css
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-background-page: #0a0a0a;
|
||||
--color-background-card: #1a1a1a;
|
||||
--color-background-card-hover: #242424;
|
||||
--color-text-primary: #ffffff;
|
||||
--color-text-secondary: #d1d5db;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-border: #262626;
|
||||
--color-border-hover: #3f3f3f;
|
||||
--color-primary: #10b981;
|
||||
--color-primary-hover: #059669;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-dark-bg text-white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-dark-bg;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-dark-border rounded-full;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-gray-600;
|
||||
}
|
||||
|
||||
/* Section padding */
|
||||
section {
|
||||
@apply py-16 md:py-24;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
@apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
/* Gradient text */
|
||||
.gradient-text {
|
||||
@apply bg-gradient-to-r from-primary-400 to-primary-600 bg-clip-text text-transparent;
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center rounded-lg px-6 py-3 font-medium transition-all duration-200;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-primary-500 text-white hover:bg-primary-600;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply border border-dark-border bg-dark-card text-white hover:bg-dark-surface;
|
||||
}
|
||||
53
apps/todo/apps/landing/tailwind.config.mjs
Normal file
53
apps/todo/apps/landing/tailwind.config.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
|
||||
'../../packages/shared-landing-ui/src/**/*.{astro,html,js,jsx,ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Todo app theme - green/teal
|
||||
primary: {
|
||||
DEFAULT: '#10b981',
|
||||
50: '#ecfdf5',
|
||||
100: '#d1fae5',
|
||||
200: '#a7f3d0',
|
||||
300: '#6ee7b7',
|
||||
400: '#34d399',
|
||||
500: '#10b981',
|
||||
600: '#059669',
|
||||
700: '#047857',
|
||||
800: '#065f46',
|
||||
900: '#064e3b',
|
||||
950: '#022c22',
|
||||
},
|
||||
dark: {
|
||||
bg: '#0a0a0a',
|
||||
surface: '#111111',
|
||||
card: '#1a1a1a',
|
||||
border: '#262626',
|
||||
},
|
||||
// CSS variable mappings for shared-landing-ui compatibility
|
||||
background: {
|
||||
page: 'var(--color-background-page, #0a0a0a)',
|
||||
card: 'var(--color-background-card, #1a1a1a)',
|
||||
'card-hover': 'var(--color-background-card-hover, #242424)',
|
||||
},
|
||||
text: {
|
||||
primary: 'var(--color-text-primary, #ffffff)',
|
||||
secondary: 'var(--color-text-secondary, #d1d5db)',
|
||||
muted: 'var(--color-text-muted, #9ca3af)',
|
||||
},
|
||||
border: {
|
||||
DEFAULT: 'var(--color-border, #262626)',
|
||||
hover: 'var(--color-border-hover, #3f3f3f)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
10
apps/todo/apps/landing/tsconfig.json
Normal file
10
apps/todo/apps/landing/tsconfig.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@components/*": ["src/components/*"],
|
||||
"@layouts/*": ["src/layouts/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
apps/todo/apps/landing/wrangler.toml
Normal file
3
apps/todo/apps/landing/wrangler.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name = "todo-landing"
|
||||
compatibility_date = "2024-12-01"
|
||||
pages_build_output_dir = "dist"
|
||||
19
apps/todo/apps/web/.env.production.example
Normal file
19
apps/todo/apps/web/.env.production.example
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Todo Web App - Production Environment Variables
|
||||
# Copy this file to .env.production and fill in the values
|
||||
|
||||
# =============================================================================
|
||||
# REQUIRED
|
||||
# =============================================================================
|
||||
|
||||
# Backend API URL
|
||||
PUBLIC_BACKEND_URL=https://todo-api.mana.how
|
||||
|
||||
# Mana Core Auth URL for authentication
|
||||
PUBLIC_MANA_CORE_AUTH_URL=https://auth.mana.how
|
||||
|
||||
# =============================================================================
|
||||
# OPTIONAL
|
||||
# =============================================================================
|
||||
|
||||
# Analytics (if using Umami or similar)
|
||||
# PUBLIC_ANALYTICS_ID=your-analytics-id
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"type-check": "echo 'Skipping type-check for now'"
|
||||
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { RequestHandler } from './$types';
|
|||
export const GET: RequestHandler = async () => {
|
||||
return json({
|
||||
status: 'ok',
|
||||
service: 'todo-web',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'todo-web',
|
||||
});
|
||||
};
|
||||
|
|
|
|||
66
apps/todo/docker-compose.prod.yml
Normal file
66
apps/todo/docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Todo Backend API
|
||||
todo-backend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: apps/todo/apps/backend/Dockerfile
|
||||
container_name: todo-backend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3018:3018"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3018
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- MANA_CORE_AUTH_URL=${MANA_CORE_AUTH_URL}
|
||||
- CORS_ORIGINS=${CORS_ORIGINS}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3018/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- todo-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.todo-api.rule=Host(`todo-api.mana.how`)"
|
||||
- "traefik.http.routers.todo-api.tls=true"
|
||||
- "traefik.http.routers.todo-api.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.todo-api.loadbalancer.server.port=3018"
|
||||
|
||||
# Todo Web App (SvelteKit with Node adapter)
|
||||
todo-web:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: apps/todo/apps/web/Dockerfile
|
||||
container_name: todo-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5188:5188"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=5188
|
||||
- PUBLIC_BACKEND_URL=${PUBLIC_BACKEND_URL}
|
||||
- PUBLIC_MANA_CORE_AUTH_URL=${PUBLIC_MANA_CORE_AUTH_URL}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5188"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- todo-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.todo-web.rule=Host(`todo.mana.how`)"
|
||||
- "traefik.http.routers.todo-web.tls=true"
|
||||
- "traefik.http.routers.todo-web.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.todo-web.loadbalancer.server.port=5188"
|
||||
|
||||
networks:
|
||||
todo-network:
|
||||
external: true
|
||||
name: manacore-network
|
||||
93
apps/todo/scripts/deploy.sh
Executable file
93
apps/todo/scripts/deploy.sh
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/bin/bash
|
||||
# Todo Deployment Script
|
||||
# Usage: ./scripts/deploy.sh [--build]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
MONOREPO_ROOT="$(dirname "$(dirname "$PROJECT_DIR")")"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} Todo App Deployment${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
|
||||
# Check for .env file
|
||||
if [ ! -f "$PROJECT_DIR/.env" ]; then
|
||||
echo -e "${RED}Error: .env file not found!${NC}"
|
||||
echo -e "${YELLOW}Copy .env.example to .env and configure it:${NC}"
|
||||
echo " cp $PROJECT_DIR/.env.example $PROJECT_DIR/.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Change to project directory
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Build if requested
|
||||
if [ "$1" == "--build" ]; then
|
||||
echo -e "\n${YELLOW}Building Docker images...${NC}"
|
||||
docker compose -f docker-compose.prod.yml build
|
||||
fi
|
||||
|
||||
# Pull latest images (if not building)
|
||||
if [ "$1" != "--build" ]; then
|
||||
echo -e "\n${YELLOW}Pulling latest images...${NC}"
|
||||
docker compose -f docker-compose.prod.yml pull 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Stop existing containers
|
||||
echo -e "\n${YELLOW}Stopping existing containers...${NC}"
|
||||
docker compose -f docker-compose.prod.yml down --remove-orphans
|
||||
|
||||
# Start containers
|
||||
echo -e "\n${YELLOW}Starting containers...${NC}"
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for health checks
|
||||
echo -e "\n${YELLOW}Waiting for services to be healthy...${NC}"
|
||||
sleep 10
|
||||
|
||||
# Check health
|
||||
echo -e "\n${YELLOW}Checking service health...${NC}"
|
||||
|
||||
check_health() {
|
||||
local service=$1
|
||||
local url=$2
|
||||
local max_attempts=30
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if curl -sf "$url" > /dev/null 2>&1; then
|
||||
echo -e " ${GREEN}$service: healthy${NC}"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
echo -e " ${RED}$service: unhealthy${NC}"
|
||||
return 1
|
||||
}
|
||||
|
||||
check_health "Backend" "http://localhost:3018/health"
|
||||
check_health "Web" "http://localhost:5188"
|
||||
|
||||
# Show container status
|
||||
echo -e "\n${YELLOW}Container status:${NC}"
|
||||
docker compose -f docker-compose.prod.yml ps
|
||||
|
||||
echo -e "\n${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} Deployment complete!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo " Backend API: http://localhost:3018"
|
||||
echo " Web App: http://localhost:5188"
|
||||
echo ""
|
||||
echo "View logs with:"
|
||||
echo " docker compose -f docker-compose.prod.yml logs -f"
|
||||
Loading…
Add table
Add a link
Reference in a new issue