Files
Emailsorter/public/index.html
ANDJ abf761db07 Email Sorter Beta
Ich habe soweit automatisiert the Emails sortieren aber ich muss noch schauen was es fur bugs es gibt wenn die app online  ist deswegen wurde ich mit diesen Commit die website veroffentlichen obwohjl es sein konnte  das es noch nicht fertig ist und verkaufs bereit
2026-01-22 19:32:12 +01:00

702 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EmailSorter API</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-dark: #0a0a0f;
--bg-card: #12121a;
--bg-hover: #1a1a25;
--border: #2a2a3a;
--text: #e4e4e7;
--text-muted: #71717a;
--accent: #6366f1;
--accent-glow: rgba(99, 102, 241, 0.3);
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--get: #22c55e;
--post: #3b82f6;
--delete: #ef4444;
--patch: #f59e0b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Space Grotesk', sans-serif;
background: var(--bg-dark);
color: var(--text);
min-height: 100vh;
line-height: 1.6;
}
/* Animated background */
.bg-pattern {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 20%, var(--accent-glow) 0%, transparent 40%),
radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.15) 0%, transparent 40%);
pointer-events: none;
z-index: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
position: relative;
z-index: 1;
}
/* Header */
header {
text-align: center;
padding: 4rem 0 3rem;
}
.logo {
display: inline-flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.logo-icon {
width: 64px;
height: 64px;
background: linear-gradient(135deg, var(--accent), #8b5cf6);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
box-shadow: 0 0 40px var(--accent-glow);
}
h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, var(--text), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
color: var(--text-muted);
font-size: 1.1rem;
margin-top: 0.5rem;
}
/* Status Cards */
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 3rem;
}
.status-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s ease;
}
.status-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.status-card h3 {
font-size: 0.875rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.status-value {
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status-dot.healthy { background: var(--success); }
.status-dot.warning { background: var(--warning); }
.status-dot.error { background: var(--error); }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* API Endpoints */
.section-title {
font-size: 1.5rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.section-title::before {
content: '';
width: 4px;
height: 24px;
background: var(--accent);
border-radius: 2px;
}
.endpoint-group {
margin-bottom: 2rem;
}
.endpoint-group-title {
font-size: 1rem;
color: var(--text-muted);
margin-bottom: 1rem;
padding-left: 1rem;
border-left: 2px solid var(--border);
}
.endpoint {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
margin-bottom: 0.75rem;
overflow: hidden;
transition: all 0.2s ease;
}
.endpoint:hover {
border-color: var(--accent);
}
.endpoint-header {
padding: 1rem 1.25rem;
display: flex;
align-items: center;
gap: 1rem;
cursor: pointer;
}
.method {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
font-weight: 600;
padding: 0.25rem 0.75rem;
border-radius: 6px;
min-width: 70px;
text-align: center;
}
.method.get { background: rgba(34, 197, 94, 0.2); color: var(--get); }
.method.post { background: rgba(59, 130, 246, 0.2); color: var(--post); }
.method.delete { background: rgba(239, 68, 68, 0.2); color: var(--delete); }
.method.patch { background: rgba(245, 158, 11, 0.2); color: var(--patch); }
.endpoint-path {
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
flex: 1;
}
.endpoint-desc {
color: var(--text-muted);
font-size: 0.875rem;
}
.endpoint-details {
display: none;
padding: 1rem 1.25rem;
background: var(--bg-hover);
border-top: 1px solid var(--border);
}
.endpoint.open .endpoint-details {
display: block;
}
.endpoint-details pre {
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
background: var(--bg-dark);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
}
.endpoint-details h4 {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
margin-bottom: 0.5rem;
margin-top: 1rem;
}
.endpoint-details h4:first-child {
margin-top: 0;
}
/* Features */
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 3rem;
}
.feature-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
}
.feature-card h3 {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.feature-card p {
color: var(--text-muted);
font-size: 0.9rem;
}
/* Footer */
footer {
text-align: center;
padding: 3rem 0;
color: var(--text-muted);
font-size: 0.875rem;
}
footer a {
color: var(--accent);
text-decoration: none;
}
/* Loading animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Try button */
.try-btn {
background: var(--accent);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.try-btn:hover {
background: #4f46e5;
transform: scale(1.05);
}
</style>
</head>
<body>
<div class="bg-pattern"></div>
<div class="container">
<header>
<div class="logo">
<div class="logo-icon">📧</div>
<div>
<h1>EmailSorter API</h1>
<p class="subtitle">KI-gestützte E-Mail-Sortierung</p>
</div>
</div>
</header>
<!-- Status Cards -->
<div class="status-grid">
<div class="status-card">
<h3>Status</h3>
<div class="status-value">
<span class="status-dot healthy" id="status-dot"></span>
<span id="status-text">Wird geladen...</span>
</div>
</div>
<div class="status-card">
<h3>Version</h3>
<div class="status-value" id="version">-</div>
</div>
<div class="status-card">
<h3>Uptime</h3>
<div class="status-value" id="uptime">-</div>
</div>
<div class="status-card">
<h3>Environment</h3>
<div class="status-value" id="environment">-</div>
</div>
</div>
<!-- API Endpoints -->
<h2 class="section-title">API Endpoints</h2>
<div class="endpoint-group">
<div class="endpoint-group-title">🔧 System</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/health</span>
<span class="endpoint-desc">Server Status prüfen</span>
<button class="try-btn" onclick="event.stopPropagation(); tryEndpoint('/api/health')">Testen</button>
</div>
<div class="endpoint-details">
<h4>Response</h4>
<pre id="health-response">{ "success": true, "data": { "status": "healthy", ... } }</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/config</span>
<span class="endpoint-desc">Öffentliche Konfiguration</span>
<button class="try-btn" onclick="event.stopPropagation(); tryEndpoint('/api/config')">Testen</button>
</div>
<div class="endpoint-details">
<h4>Response</h4>
<pre id="config-response">Klicke auf "Testen"</pre>
</div>
</div>
</div>
<div class="endpoint-group">
<div class="endpoint-group-title">🔐 OAuth</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/oauth/status</span>
<span class="endpoint-desc">OAuth Provider Status</span>
<button class="try-btn" onclick="event.stopPropagation(); tryEndpoint('/api/oauth/status')">Testen</button>
</div>
<div class="endpoint-details">
<h4>Response</h4>
<pre id="oauth-response">Klicke auf "Testen"</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/oauth/gmail/connect</span>
<span class="endpoint-desc">Gmail OAuth starten</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{ "success": true, "data": { "url": "https://accounts.google.com/..." } }</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/oauth/outlook/connect</span>
<span class="endpoint-desc">Outlook OAuth starten</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{ "success": true, "data": { "url": "https://login.microsoftonline.com/..." } }</pre>
</div>
</div>
</div>
<div class="endpoint-group">
<div class="endpoint-group-title">📬 E-Mail</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/email/accounts</span>
<span class="endpoint-desc">Verbundene E-Mail-Konten</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{
"success": true,
"data": [
{ "id": "...", "email": "user@gmail.com", "provider": "gmail", "connected": true }
]
}</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method post">POST</span>
<span class="endpoint-path">/api/email/sort</span>
<span class="endpoint-desc">E-Mails sortieren (KI)</span>
</div>
<div class="endpoint-details">
<h4>Request Body</h4>
<pre>{
"userId": "string (required)",
"accountId": "string (required)",
"maxEmails": 50
}</pre>
<h4>Response</h4>
<pre>{
"success": true,
"data": {
"sorted": 42,
"categories": { "vip": 5, "newsletters": 20, ... },
"timeSaved": { "minutes": 10, "formatted": "10 Minuten" }
}
}</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/email/stats</span>
<span class="endpoint-desc">Sortier-Statistiken</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{
"success": true,
"data": {
"totalSorted": 1250,
"todaySorted": 45,
"weekSorted": 312,
"timeSaved": 312
}
}</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method delete">DELETE</span>
<span class="endpoint-path">/api/email/accounts/:accountId</span>
<span class="endpoint-desc">E-Mail-Konto trennen</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{ "success": true, "message": "Konto erfolgreich getrennt" }</pre>
</div>
</div>
</div>
<div class="endpoint-group">
<div class="endpoint-group-title">💳 Subscription</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method post">POST</span>
<span class="endpoint-path">/api/subscription/checkout</span>
<span class="endpoint-desc">Checkout Session erstellen</span>
</div>
<div class="endpoint-details">
<h4>Request Body</h4>
<pre>{
"userId": "string (required)",
"plan": "basic | pro | business (required)",
"email": "string (optional)"
}</pre>
<h4>Response</h4>
<pre>{ "success": true, "data": { "url": "https://checkout.stripe.com/...", "sessionId": "..." } }</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method get">GET</span>
<span class="endpoint-path">/api/subscription/status</span>
<span class="endpoint-desc">Subscription Status</span>
</div>
<div class="endpoint-details">
<h4>Query Parameter</h4>
<pre>userId: string (required)</pre>
<h4>Response</h4>
<pre>{
"success": true,
"data": {
"status": "active",
"plan": "pro",
"features": { "emailAccounts": 3, ... },
"currentPeriodEnd": "2026-02-15T00:00:00Z"
}
}</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method post">POST</span>
<span class="endpoint-path">/api/subscription/portal</span>
<span class="endpoint-desc">Stripe Kundenportal</span>
</div>
<div class="endpoint-details">
<h4>Request Body</h4>
<pre>{ "userId": "string (required)" }</pre>
<h4>Response</h4>
<pre>{ "success": true, "data": { "url": "https://billing.stripe.com/..." } }</pre>
</div>
</div>
<div class="endpoint">
<div class="endpoint-header" onclick="toggleEndpoint(this)">
<span class="method post">POST</span>
<span class="endpoint-path">/api/subscription/cancel</span>
<span class="endpoint-desc">Subscription kündigen</span>
</div>
<div class="endpoint-details">
<h4>Request Body</h4>
<pre>{ "userId": "string (required)" }</pre>
</div>
</div>
</div>
<!-- Features -->
<h2 class="section-title">Features</h2>
<div class="features">
<div class="feature-card">
<h3>🤖 Mistral AI</h3>
<p>Intelligente E-Mail-Kategorisierung mit modernster KI-Technologie.</p>
</div>
<div class="feature-card">
<h3>📧 Multi-Provider</h3>
<p>Unterstützt Gmail und Outlook mit OAuth 2.0 Authentifizierung.</p>
</div>
<div class="feature-card">
<h3>💳 Stripe Billing</h3>
<p>Sichere Zahlungsabwicklung mit Subscriptions und Kundenportal.</p>
</div>
<div class="feature-card">
<h3>🔒 Rate Limiting</h3>
<p>Schutz vor Missbrauch mit intelligenten Request-Limits.</p>
</div>
<div class="feature-card">
<h3>📊 Statistiken</h3>
<p>Detaillierte Einblicke in sortierte E-Mails und gesparte Zeit.</p>
</div>
<div class="feature-card">
<h3>🌐 Webhooks</h3>
<p>Real-time Updates für Gmail und Outlook Benachrichtigungen.</p>
</div>
</div>
<footer>
<p>EmailSorter API v2.0.0 &bull; <a href="/api/health">Health Check</a> &bull; Powered by Node.js + Express</p>
</footer>
</div>
<script>
// Load server status
async function loadStatus() {
try {
const res = await fetch('/api/health');
const data = await res.json();
if (data.success) {
document.getElementById('status-text').textContent = 'Online';
document.getElementById('status-dot').className = 'status-dot healthy';
document.getElementById('version').textContent = data.data.version;
document.getElementById('uptime').textContent = formatUptime(data.data.uptime);
document.getElementById('environment').textContent = data.data.environment;
document.getElementById('health-response').textContent = JSON.stringify(data, null, 2);
}
} catch (e) {
document.getElementById('status-text').textContent = 'Offline';
document.getElementById('status-dot').className = 'status-dot error';
}
}
function formatUptime(seconds) {
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`;
}
function toggleEndpoint(header) {
header.parentElement.classList.toggle('open');
}
async function tryEndpoint(path) {
const responseId = path.replace('/api/', '').replace('/', '-') + '-response';
const pre = document.getElementById(responseId) || document.querySelector('.endpoint.open pre');
if (pre) {
pre.textContent = 'Wird geladen...';
}
try {
const res = await fetch(path);
const data = await res.json();
if (pre) {
pre.textContent = JSON.stringify(data, null, 2);
}
} catch (e) {
if (pre) {
pre.textContent = `Error: ${e.message}`;
}
}
}
// Initial load
loadStatus();
// Refresh every 30 seconds
setInterval(loadStatus, 30000);
</script>
</body>
</html>