Files
denya-onecare/app/templates/login.html
T
root a6eb799efa feat(whatsapp,branding): WhatsApp demo-number wiring + Denya logo assets
WhatsApp demo path (relay #748):
- WHATSAPP_DEMO_TO config under the WhatsApp section (env-based, .env-only;
  .env.example keeps an empty placeholder; real numbers never enter source).
- build_demo_webhook_payload() in app/routers/whatsapp.py builds the Meta
  demo payload from it (fails closed when unset), so the webhook round trip
  logs from_number = demo number (surfaces in GET /api/whatsapp/mock-log) and
  the auto-reply targets the same number.
- tests/test_whatsapp_demo_number.py: default empty + never committed in
  tracked files, payload builder from/to, 200/403/401 gates unchanged.

Branding (logo-assets-v1, sha256-verified, same-origin app/static/branding):
- Login header uses h96 full lockup; logged-in topbar (base.html) uses h48 on
  a light chip (logo ink is ~2:1 vs the dark nav); favicons 32x32 + 16x16 in
  <head>. img-src 'self' data: blob: already allows /static/branding/*.
- tests/test_branding_assets.py: page placement + same-origin serving + CSP.
- AGENTS.md synced.
2026-09-09 19:32:21 +00:00

73 lines
3.6 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="min-h-[80vh] flex items-center justify-center">
<div class="w-full max-w-md" x-data="loginForm()">
<div class="bg-white rounded-2xl shadow-lg p-8">
<div class="text-center mb-8">
<!-- Denya Developers full lockup (same-origin app/static/branding) -->
<img src="/static/branding/denya-logo-h96.png" alt="Denya Developers"
class="mx-auto mb-4 h-24 w-auto">
<h1 class="text-2xl font-bold text-gray-900">Denya OneCare</h1>
<p class="text-gray-500 mt-1">Sign in to your dashboard</p>
</div>
<form @submit.prevent="submitLogin" class="space-y-5">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" x-model="email" required
class="w-full px-4 py-2.5 rounded-lg border border-gray-300 focus:ring-2 focus:ring-denya-500 focus:border-transparent outline-none transition"
placeholder="you@example.com">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" x-model="password" required
class="w-full px-4 py-2.5 rounded-lg border border-gray-300 focus:ring-2 focus:ring-denya-500 focus:border-transparent outline-none transition"
placeholder="••••••••">
</div>
<div x-show="error" class="text-red-600 text-sm bg-red-50 p-3 rounded-lg" x-text="error"></div>
<button type="submit" :disabled="submitting"
class="w-full py-2.5 px-4 bg-denya-600 hover:bg-denya-700 text-white font-medium rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2">
<svg x-show="submitting" class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
<span x-text="submitting ? 'Signing in...' : 'Sign In'"></span>
</button>
</form>
<div class="mt-6 pt-6 border-t border-gray-200">
<p class="text-xs text-gray-400 text-center">
<strong>Demo Accounts:</strong><br>
bella@denya.com / denya123 — CS Rep<br>
nicholas@denya.com / denya123 — FM Dispatcher<br>
scott@denya.com / denya123 — CEO<br>
jerome@denya.com / denya123 — Admin
</p>
</div>
</div>
</div>
</div>
<script>
function loginForm() {
return {
email: '',
password: '',
error: '',
submitting: false,
async submitLogin() {
this.error = '';
this.submitting = true;
try {
await app().login(this.email, this.password);
} catch (e) {
this.error = e.message || 'Invalid credentials. Please try again.';
} finally {
this.submitting = false;
}
}
}
}
</script>
{% endblock %}