techprotrade commited on
Commit
66309d3
·
verified ·
1 Parent(s): 14e3c0d

Login UI + Neon-ready DATABASE_URL (2026-07-30 06:41 UTC) (part 2)

Browse files
Files changed (4) hide show
  1. README.md +3 -1
  2. frontend/index.html +2 -1
  3. frontend/login.html +131 -0
  4. serve.py +3 -0
README.md CHANGED
@@ -14,6 +14,7 @@ Single Space process on port **7860**:
14
 
15
  | Path | Content |
16
  |------|---------|
 
17
  | `/` | AIMONEYFLOW client portal (Vite, fast) |
18
  | `/clients/` | Existing client HTML dossiers (Kliendibaas) |
19
  | `/dashboard.html` | Client dashboard |
@@ -33,4 +34,5 @@ Single Space process on port **7860**:
33
  - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, …
34
  - `ALLOWED_ORIGINS` — CORS list
35
 
36
- <!-- last_local_sync: 2026-07-30 05:41 UTC -->
 
 
14
 
15
  | Path | Content |
16
  |------|---------|
17
+ | `/login.html` | Login / register UI |
18
  | `/` | AIMONEYFLOW client portal (Vite, fast) |
19
  | `/clients/` | Existing client HTML dossiers (Kliendibaas) |
20
  | `/dashboard.html` | Client dashboard |
 
34
  - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, …
35
  - `ALLOWED_ORIGINS` — CORS list
36
 
37
+ <!-- last_local_sync: 2026-07-30 06:41 UTC login+neon-ready -->
38
+
frontend/index.html CHANGED
@@ -13,5 +13,6 @@
13
  </head>
14
  <body>
15
  <div id="root"></div>
16
- </body>
17
  </html>
 
 
13
  </head>
14
  <body>
15
  <div id="root"></div>
16
+ <a href="/login.html" style="position:fixed;top:12px;right:12px;z-index:9999;background:#059669;color:#fff;padding:10px 14px;border-radius:8px;text-decoration:none;font-family:system-ui;font-weight:600">Login</a></body>
17
  </html>
18
+
frontend/login.html ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="et">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>ATOM · Login</title>
7
+ <style>
8
+ :root { --green:#059669; --bg:#f8fafc; --card:#fff; --text:#0f172a; --muted:#64748b; --err:#dc3545; }
9
+ * { box-sizing:border-box; }
10
+ body { margin:0; min-height:100vh; font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
11
+ background:linear-gradient(145deg,#ecfdf5 0%,#f0f9ff 50%,#f8fafc 100%); color:var(--text);
12
+ display:flex; align-items:center; justify-content:center; padding:1rem; }
13
+ .card { width:100%; max-width:420px; background:var(--card); border-radius:16px; padding:2rem;
14
+ box-shadow:0 20px 50px rgba(15,23,42,.08); border:1px solid #e2e8f0; }
15
+ h1 { margin:0 0 .25rem; font-size:1.75rem; color:var(--green); }
16
+ p.sub { margin:0 0 1.5rem; color:var(--muted); }
17
+ label { display:block; font-size:.875rem; font-weight:600; margin:.75rem 0 .35rem; }
18
+ input { width:100%; padding:.7rem .85rem; border:1px solid #cbd5e1; border-radius:10px; font-size:1rem; }
19
+ input:focus { outline:2px solid #6ee7b7; border-color:var(--green); }
20
+ button { width:100%; margin-top:1.25rem; padding:.85rem; border:0; border-radius:10px;
21
+ background:var(--green); color:#fff; font-weight:700; font-size:1rem; cursor:pointer; min-height:44px; }
22
+ button:disabled { opacity:.6; cursor:not-allowed; }
23
+ button.secondary { background:transparent; color:var(--green); border:1px solid var(--green); margin-top:.75rem; }
24
+ .err { background:#fef2f2; color:var(--err); border:1px solid #fecaca; padding:.75rem; border-radius:10px;
25
+ margin-bottom:1rem; font-size:.9rem; display:none; }
26
+ .ok { background:#ecfdf5; color:#065f46; border:1px solid #a7f3d0; padding:.75rem; border-radius:10px;
27
+ margin-bottom:1rem; font-size:.9rem; display:none; }
28
+ .row { display:grid; grid-template-columns:1fr 1fr; gap:.75rem; }
29
+ .hint { margin-top:1rem; font-size:.8rem; color:var(--muted); text-align:center; }
30
+ a { color:var(--green); }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="card">
35
+ <h1>ATOM</h1>
36
+ <p class="sub" id="subtitle">Logi sisse oma kontoga</p>
37
+ <div class="err" id="err" role="alert"></div>
38
+ <div class="ok" id="ok" role="status"></div>
39
+ <form id="form" novalidate>
40
+ <div id="names" class="row" style="display:none">
41
+ <div>
42
+ <label for="first_name">Eesnimi</label>
43
+ <input id="first_name" name="first_name" autocomplete="given-name" />
44
+ </div>
45
+ <div>
46
+ <label for="last_name">Perekonnanimi</label>
47
+ <input id="last_name" name="last_name" autocomplete="family-name" />
48
+ </div>
49
+ </div>
50
+ <label for="email">Email</label>
51
+ <input id="email" name="email" type="email" required autocomplete="username" placeholder="sina@email.ee" />
52
+ <label for="password">Parool</label>
53
+ <input id="password" name="password" type="password" required autocomplete="current-password" minlength="6" />
54
+ <button type="submit" id="submit">Logi sisse</button>
55
+ <button type="button" class="secondary" id="toggle">Loo uus konto</button>
56
+ </form>
57
+ <p class="hint">API: <a href="/docs">/docs</a> · Health: <a href="/api/healthz">/api/healthz</a> · <a href="/">Avaleht</a></p>
58
+ </div>
59
+ <script>
60
+ const API = ''; // same origin on HF Space
61
+ let mode = 'login'; // or register
62
+ const err = document.getElementById('err');
63
+ const ok = document.getElementById('ok');
64
+ const names = document.getElementById('names');
65
+ const subtitle = document.getElementById('subtitle');
66
+ const submit = document.getElementById('submit');
67
+ const toggle = document.getElementById('toggle');
68
+
69
+ function showErr(msg) { err.style.display='block'; ok.style.display='none'; err.textContent=msg; }
70
+ function showOk(msg) { ok.style.display='block'; err.style.display='none'; ok.textContent=msg; }
71
+ function clearMsg() { err.style.display='none'; ok.style.display='none'; }
72
+
73
+ function formatDetail(data) {
74
+ if (!data) return 'Viga';
75
+ const d = data.detail ?? data.message ?? data.error;
76
+ if (typeof d === 'string') return d;
77
+ if (Array.isArray(d)) return d.map(x => x.msg || JSON.stringify(x)).join('; ');
78
+ return JSON.stringify(d);
79
+ }
80
+
81
+ toggle.onclick = () => {
82
+ mode = mode === 'login' ? 'register' : 'login';
83
+ clearMsg();
84
+ names.style.display = mode === 'register' ? 'grid' : 'none';
85
+ subtitle.textContent = mode === 'login' ? 'Logi sisse oma kontoga' : 'Loo uus ATOM konto';
86
+ submit.textContent = mode === 'login' ? 'Logi sisse' : 'Registreeri';
87
+ toggle.textContent = mode === 'login' ? 'Loo uus konto' : 'Tagasi sisselogimisele';
88
+ };
89
+
90
+ document.getElementById('form').onsubmit = async (e) => {
91
+ e.preventDefault();
92
+ clearMsg();
93
+ submit.disabled = true;
94
+ const email = document.getElementById('email').value.trim();
95
+ const password = document.getElementById('password').value;
96
+ try {
97
+ let res, body;
98
+ if (mode === 'login') {
99
+ res = await fetch(API + '/api/auth/login', {
100
+ method: 'POST',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({ username: email, password })
103
+ });
104
+ } else {
105
+ body = {
106
+ email, password,
107
+ first_name: document.getElementById('first_name').value.trim() || 'User',
108
+ last_name: document.getElementById('last_name').value.trim() || 'Atom'
109
+ };
110
+ res = await fetch(API + '/api/auth/register', {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify(body)
114
+ });
115
+ }
116
+ const data = await res.json().catch(() => ({}));
117
+ if (!res.ok) throw new Error(formatDetail(data) || ('HTTP ' + res.status));
118
+ if (!data.access_token) throw new Error('Token puudub vastuses');
119
+ localStorage.setItem('token', data.access_token);
120
+ localStorage.setItem('auth_token', data.access_token);
121
+ showOk('Sisselogimine õnnestus — suunan edasi…');
122
+ setTimeout(() => { location.href = '/'; }, 600);
123
+ } catch (ex) {
124
+ showErr(ex.message || String(ex));
125
+ } finally {
126
+ submit.disabled = false;
127
+ }
128
+ };
129
+ </script>
130
+ </body>
131
+ </html>
serve.py CHANGED
@@ -28,6 +28,8 @@ os.environ.setdefault(
28
  "ALLOWED_ORIGINS",
29
  "*,https://techprotrade-annator-atom.hf.space,http://localhost:7860",
30
  )
 
 
31
  os.environ.setdefault("DATABASE_URL", f"sqlite:///{(ROOT / 'data' / 'atom.db').as_posix()}")
32
  os.environ.setdefault("SKIP_USER_BOOTSTRAP", "true")
33
  os.environ.setdefault("ATOM_MOCK_DATABASE", "false")
@@ -249,3 +251,4 @@ if __name__ == "__main__":
249
 
250
  port = int(os.getenv("PORT", "7860"))
251
  uvicorn.run("serve:app", host="0.0.0.0", port=port, factory=False)
 
 
28
  "ALLOWED_ORIGINS",
29
  "*,https://techprotrade-annator-atom.hf.space,http://localhost:7860",
30
  )
31
+ if not os.environ.get('DATABASE_URL') and os.environ.get('NEON_DATABASE_URL'):
32
+ os.environ['DATABASE_URL'] = os.environ['NEON_DATABASE_URL']
33
  os.environ.setdefault("DATABASE_URL", f"sqlite:///{(ROOT / 'data' / 'atom.db').as_posix()}")
34
  os.environ.setdefault("SKIP_USER_BOOTSTRAP", "true")
35
  os.environ.setdefault("ATOM_MOCK_DATABASE", "false")
 
251
 
252
  port = int(os.getenv("PORT", "7860"))
253
  uvicorn.run("serve:app", host="0.0.0.0", port=port, factory=False)
254
+