--- import Admin from '../../../layouts/Admin.astro'; import { adminBase } from '../../../lib/auth.js'; const base = adminBase(); import { getSettings, setSetting, resolvePaymentProvider, FEATURE_KEYS, feature, abandonedCartStats, countPendingReviews, backupStatus } from '../../../lib/store.js'; import { mailerStatus } from '../../../lib/mailer.js'; const FEATURE_LABELS = { feature_newsletter: ['Newsletter', 'Newsletter-Popup & Anmeldung im Storefront'], feature_accounts: ['Kundenkonten', 'Registrierung, Login, Bestellhistorie & Adressbuch für Kund:innen; Adresse im Checkout vorbefüllt'], feature_reviews: ['Bewertungen', 'Sterne-Bewertungen auf Produktseiten mit Moderation (Freigabe im Admin)'], feature_wishlist: ['Merkliste', 'Herz-Button auf Produktkarten & Detailseite; Merkliste unter /merkliste (clientseitig)'], feature_abandoned_cart: ['Warenkorb-Erinnerung', 'Begonnene Checkouts speichern & per Mail erinnern (Cron /api/cron/abandoned)'], feature_search: ['Suche', 'Volltextsuche im Storefront-Header mit Ergebnisseite /suche'], }; let flash = ''; if (Astro.request.method === 'POST') { const f = await Astro.request.formData(); const action = String(f.get('_action') || 'general'); if (action === 'features') { for (const k of FEATURE_KEYS) setSetting(k, f.get(k) === 'on' ? '1' : '0'); flash = 'Module aktualisiert.'; } else if (action === 'payment') { setSetting('payment_provider', String(f.get('payment_provider') || '')); flash = 'Zahlungsanbieter gespeichert.'; } else { setSetting('shop_name', f.get('shop_name') || 'hd-commerce'); setSetting('shop_tagline', f.get('shop_tagline') || ''); setSetting('shop_email', f.get('shop_email') || ''); setSetting('brand_accent', f.get('brand_accent') || '#b8566a'); setSetting('brand_accent_dark', f.get('brand_accent_dark') || '#8d3f50'); setSetting('currency', f.get('currency') || 'EUR'); setSetting('free_shipping_cents', String(Math.round(parseFloat(String(f.get('free_shipping') || '49').replace(',', '.')) * 100) || 4900)); flash = 'Einstellungen gespeichert.'; } } const s = getSettings(); const freeShipStr = ((Number(s.free_shipping_cents) || 4900) / 100).toFixed(2).replace('.', ','); const currencies = ['EUR', 'CHF', 'USD', 'GBP']; const pp = resolvePaymentProvider(); const providerLabel = { mollie: 'Mollie', stripe: 'Stripe', demo: 'Demo-Fallback' }[pp.provider] || pp.provider; const mollieSet = /^(test|live)_\w{20,}/.test((process.env.MOLLIE_API_KEY || '').trim()); const stripeSet = /^sk_(test|live)_[A-Za-z0-9]{16,}/.test((process.env.STRIPE_SECRET_KEY || '').trim()); const providerSetting = s.payment_provider || ''; const mail = mailerStatus(); const acStats = abandonedCartStats(); const pendingReviews = countPendingReviews(); const cronToken = (process.env.CRON_TOKEN || '').trim(); const backup = backupStatus(); ---
{flash &&
✓ {flash}
}
Shop
Branding
Die Akzentfarbe wird im Storefront und im Admin als CSS-Variable injiziert.
Verkauf
Versandzonen & länderabhängige Preise unter „Versand".
Zahlung

Aktiv: {providerLabel} {pp.source === 'auto' ? 'Auto-Wahl' : pp.source === 'setting' ? 'manuell' : 'ENV'}

  • Mollie-Key (MOLLIE_API_KEY): {mollieSet ? 'gesetzt' : 'fehlt'}
  • Stripe-Key (STRIPE_SECRET_KEY): {stripeSet ? 'gesetzt' : 'fehlt'}
{pp.provider === 'demo' &&

Ohne gültigen Key läuft der Checkout im Demo-Fallback (Bestellung ohne echte Zahlung).

}
E-Mail-Versand

{mail.provider === 'log' ? 'Log-Fallback' : mail.provider} {mail.provider !== 'log' ? (mail.configured ? '· konfiguriert' : '· unvollständig') : ''}

Absender: {mail.from}

Bestellbestätigungen werden bei bezahlter Bestellung versendet. Ohne Provider-ENV landet die Mail im E-Mail-Log.

ENV: MAIL_PROVIDER (listmonk|smtp), MAIL_FROM, Listmonk-/SMTP-Variablen.

System

Datenbank: SQLite (DB_PATH). Admin-Zugang über Session-Login; Initial-Owner aus ADMIN_EMAIL / ADMIN_PASS. Admin-Pfad über ADMIN_PATH.

{feature('feature_abandoned_cart') && (
Warenkorb-Erinnerung

Gespeicherte Warenkörbe: {acStats.total} · offen {acStats.open} · erinnert {acStats.reminded} · wiederhergestellt {acStats.recovered}

Versand-Trigger: POST /api/cron/abandoned (Header Authorization: Bearer <CRON_TOKEN> oder ?token=). {cronToken ? 'CRON_TOKEN gesetzt.' : 'CRON_TOKEN noch nicht gesetzt — Endpoint bleibt gesperrt.'}

)} {feature('feature_reviews') && pendingReviews > 0 && (
Bewertungen

{pendingReviews} Bewertung(en) warten auf Freigabe — jetzt prüfen.

)}
Backup (Litestream)
{backup.configured ? ( <>

aktiv Streaming-Backup nach {backup.target}{backup.endpoint ? ` (Endpoint ${backup.endpoint})` : ''}.

{!backup.fullCredentials &&

Achtung: Zugangsdaten unvollständig — LITESTREAM_ACCESS_KEY_ID / LITESTREAM_SECRET_ACCESS_KEY prüfen.

}

DB: {backup.dbPath}. Restore: litestream restore -if-replica-exists {backup.dbPath}

) : (

inaktiv Kein Replica konfiguriert. Setze LITESTREAM_REPLICA_URL (S3/Backblaze B2) plus Zugangsdaten als ENV, um stündliche Streaming-Backups zu aktivieren (siehe README).

)}
Module (Feature-Flags)

Schalte einzelne Funktionen zentral an oder aus. Abgeschaltete Module verschwinden aus Storefront und Admin.

{FEATURE_KEYS.map((k) => ( ))}