Skip to content

Commit e3b2753

Browse files
committed
fix(custom-domains): UI polish + UTC datetime serialization
UI: - Drop left-border accent on banners/notes/inline errors; use bg tint only for cleaner look - Bump padding and gaps for breathing room (modal body, banners, DNS record cards) - Move Verify CTA from body block-button to footer primary slot - Drop redundant footer Close button (header X handles dismissal) - Single setFooter() helper drives mode-aware button visibility Timezone: - CustomDomainResponse stamps UTC on naive datetimes from PyMongo - Wire format now includes +00:00 so JS parses to correct epoch ms - Fixes "Added 5 hrs ago" right after creating a domain in non-UTC browser locales (IST drift)
1 parent fb74615 commit e3b2753

4 files changed

Lines changed: 83 additions & 58 deletions

File tree

schemas/dto/responses/custom_domain.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from __future__ import annotations
44

5-
from datetime import datetime
5+
from datetime import datetime, timezone
66
from typing import Literal
77

8-
from pydantic import Field
8+
from pydantic import Field, field_serializer
99

1010
from schemas.dto.base import ResponseBase
1111
from schemas.enums.domain_status import DomainStatus, VerificationMethod
@@ -44,6 +44,17 @@ class CustomDomainResponse(ResponseBase):
4444
last_verified_at: datetime | None = None
4545
last_verification_error: str | None = None
4646

47+
@field_serializer("created_at", "updated_at", "last_verified_at")
48+
def _ser_as_utc(self, dt: datetime | None) -> str | None:
49+
# PyMongo returns naive datetimes; without explicit tzinfo the JSON
50+
# form omits the offset and clients parse it as local time. Stamp
51+
# UTC so the wire format is unambiguous (`...+00:00`).
52+
if dt is None:
53+
return None
54+
if dt.tzinfo is None:
55+
dt = dt.replace(tzinfo=timezone.utc)
56+
return dt.isoformat()
57+
4758
@classmethod
4859
def from_doc(cls, doc: CustomDomainDoc) -> CustomDomainResponse:
4960
return cls(

static/css/dashboard/domains.css

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,17 @@
205205
}
206206

207207
.modal.modal-large .modal-body {
208-
padding: 20px 24px;
208+
padding: 24px 28px 28px;
209209
}
210210

211211
.modal.modal-large .modal-footer {
212212
border-top: 1px solid var(--border-color);
213213
display: flex;
214+
align-items: center;
214215
justify-content: flex-end;
215-
gap: 8px;
216-
padding: 16px 24px;
216+
gap: 10px;
217+
padding: 16px 28px;
218+
min-height: 64px;
217219
}
218220

219221
.modal-footer .btn-danger-ghost {
@@ -225,6 +227,7 @@
225227

226228
.modal-footer .btn-danger-ghost:hover {
227229
background: rgba(239, 68, 68, 0.1);
230+
color: var(--color-danger);
228231
}
229232

230233
/* Mobile: full-screen overlay */
@@ -240,10 +243,11 @@
240243
/* ── Banner (status indicator inside modal) ────────────────────────── */
241244

242245
.banner {
243-
padding: 12px 14px;
244-
border-radius: var(--radius-sm);
245-
margin-bottom: 18px;
246+
padding: 14px 16px;
247+
border-radius: var(--radius-md);
248+
margin-bottom: 24px;
246249
font-size: 14px;
250+
line-height: 1.5;
247251
display: flex;
248252
align-items: center;
249253
gap: 10px;
@@ -252,19 +256,16 @@
252256
.banner-pending {
253257
background: rgba(245, 158, 11, 0.08);
254258
color: var(--text-primary);
255-
border-left: 3px solid var(--color-warning);
256259
}
257260

258261
.banner-suspended {
259262
background: rgba(239, 68, 68, 0.08);
260263
color: var(--text-primary);
261-
border-left: 3px solid var(--color-danger);
262264
}
263265

264266
.banner-active {
265267
background: rgba(34, 197, 94, 0.08);
266268
color: var(--text-primary);
267-
border-left: 3px solid var(--color-success);
268269
}
269270

270271
.banner-active i {
@@ -275,36 +276,44 @@
275276
.banner-revoked {
276277
background: rgba(148, 163, 184, 0.05);
277278
color: var(--text-secondary);
278-
border-left: 3px solid rgba(148, 163, 184, 0.4);
279279
}
280280

281281
/* ── Section + DNS records ─────────────────────────────────────────── */
282282

283+
.section-head {
284+
display: flex;
285+
align-items: center;
286+
justify-content: space-between;
287+
gap: 16px;
288+
margin: 0 0 12px;
289+
min-height: 32px;
290+
}
291+
283292
.section-title {
284293
font-size: 12px;
285294
font-weight: 600;
286295
text-transform: uppercase;
287296
letter-spacing: 0.5px;
288297
color: var(--text-secondary);
289-
margin: 0 0 10px;
298+
margin: 0;
290299
}
291300

292301
.dns-records {
293302
display: flex;
294303
flex-direction: column;
295-
gap: 8px;
296-
margin-bottom: 14px;
304+
gap: 10px;
305+
margin-bottom: 20px;
297306
}
298307

299308
.dns-record {
300309
display: grid;
301310
grid-template-columns: auto 1fr auto;
302-
gap: 14px;
311+
gap: 16px;
303312
align-items: center;
304-
padding: 12px 14px;
313+
padding: 16px 18px;
305314
background: rgba(255, 255, 255, 0.03);
306315
border: 1px solid var(--border-color);
307-
border-radius: var(--radius-sm);
316+
border-radius: var(--radius-md);
308317
}
309318

310319
.rec-type {
@@ -318,7 +327,7 @@
318327
.rec-fields {
319328
display: flex;
320329
flex-direction: column;
321-
gap: 6px;
330+
gap: 8px;
322331
min-width: 0;
323332
}
324333

@@ -374,38 +383,38 @@
374383
/* ── Setup notes (CF orange-cloud, etc.) ───────────────────────────── */
375384

376385
.setup-notes {
377-
margin: 0 0 14px;
378-
padding: 10px 12px;
379-
background: rgba(96, 165, 250, 0.08);
380-
border-left: 3px solid #60a5fa;
381-
border-radius: var(--radius-sm);
386+
margin: 0 0 18px;
387+
padding: 12px 14px;
388+
background: rgba(96, 165, 250, 0.07);
389+
border-radius: var(--radius-md);
382390
color: var(--text-primary);
383391
font-size: 13px;
392+
line-height: 1.5;
384393
}
385394

386395
.setup-notes p { margin: 0; }
387-
.setup-notes p + p { margin-top: 4px; }
396+
.setup-notes p + p { margin-top: 6px; }
388397

389398
/* ── Inline feedback messages ─────────────────────────────────────── */
390399

391400
.inline-error {
392-
margin-top: 10px;
393-
padding: 10px 12px;
401+
margin-top: 14px;
402+
padding: 12px 14px;
394403
background: rgba(239, 68, 68, 0.08);
395-
border-left: 3px solid var(--color-danger);
396-
border-radius: var(--radius-sm);
404+
border-radius: var(--radius-md);
397405
color: var(--color-danger);
398406
font-size: 13px;
407+
line-height: 1.5;
399408
}
400409

401410
.inline-info {
402-
margin-top: 10px;
403-
padding: 10px 12px;
404-
background: rgba(96, 165, 250, 0.08);
405-
border-left: 3px solid #60a5fa;
406-
border-radius: var(--radius-sm);
411+
margin-top: 14px;
412+
padding: 12px 14px;
413+
background: rgba(96, 165, 250, 0.07);
414+
border-radius: var(--radius-md);
407415
color: var(--text-secondary);
408416
font-size: 13px;
417+
line-height: 1.5;
409418
}
410419

411420
.field-hint {
@@ -458,7 +467,7 @@
458467
/* ── Active mode extras ───────────────────────────────────────────── */
459468

460469
.active-meta {
461-
margin: 14px 0;
470+
margin: 20px 0;
462471
color: var(--text-secondary);
463472
font-size: 13px;
464473
text-align: center;

static/js/dashboard/domains.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ function showSlot(mode) {
9999
currentMode = mode;
100100
}
101101

102+
// Footer button visibility per mode. `submit` accepts a label string (shows
103+
// the button with that label) or false (hides).
104+
function setFooter({ cancel = false, submit = false, revoke = false, verify = false }) {
105+
el.modalCancel.style.display = cancel ? '' : 'none';
106+
if (submit) {
107+
el.modalSubmit.style.display = '';
108+
el.modalSubmit.textContent = submit;
109+
} else {
110+
el.modalSubmit.style.display = 'none';
111+
}
112+
el.modalRevoke.style.display = revoke ? '' : 'none';
113+
el.setupVerify.style.display = verify ? '' : 'none';
114+
}
115+
102116
// ── List fetch + render ───────────────────────────────────────────────────
103117

104118
async function fetchDomains() {
@@ -169,10 +183,7 @@ function openAddModal({ push = true } = {}) {
169183
el.modalTitle.textContent = 'Add a domain';
170184
el.modalBadge.style.display = 'none';
171185

172-
el.modalCancel.style.display = '';
173-
el.modalSubmit.style.display = '';
174-
el.modalSubmit.textContent = 'Add';
175-
el.modalRevoke.style.display = 'none';
186+
setFooter({ cancel: true, submit: 'Add', revoke: false, verify: false });
176187

177188
showSlot('add');
178189
showModal();
@@ -186,9 +197,7 @@ async function openDomainModal(id, { push = true } = {}) {
186197

187198
el.modalTitle.textContent = 'Loading…';
188199
el.modalBadge.style.display = 'none';
189-
el.modalCancel.style.display = '';
190-
el.modalSubmit.style.display = 'none';
191-
el.modalRevoke.style.display = 'none';
200+
setFooter({ cancel: false, submit: false, revoke: false, verify: false });
192201

193202
showModal();
194203
if (push) syncUrl({ domain: id });
@@ -242,9 +251,6 @@ function renderForStatus(doc) {
242251
el.modalBadge.className = `status-badge modal-status-badge ${statusClass(status)}`;
243252
el.modalBadge.style.display = '';
244253

245-
el.modalSubmit.style.display = 'none';
246-
el.modalCancel.textContent = 'Close';
247-
248254
if (status === 'PENDING' || status === 'SUSPENDED') {
249255
renderSetup(doc, status);
250256
} else if (status === 'ACTIVE') {
@@ -281,7 +287,7 @@ function renderSetup(doc, status) {
281287
el.setupPoll.style.display = 'none';
282288
resetVerifyButton();
283289

284-
el.modalRevoke.style.display = '';
290+
setFooter({ revoke: true, verify: true });
285291

286292
showSlot('setup');
287293
}
@@ -297,13 +303,13 @@ function renderActive(doc) {
297303

298304
renderDnsRecords(el.activeDnsRecords, doc.dns_records || []);
299305

300-
el.modalRevoke.style.display = '';
306+
setFooter({ revoke: true });
301307

302308
showSlot('active');
303309
}
304310

305311
function renderRevoked(doc) {
306-
el.modalRevoke.style.display = 'none';
312+
setFooter({});
307313
showSlot('revoked');
308314
}
309315

templates/dashboard/domains.html

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,13 @@ <h2 id="domainModalTitle">Add a domain</h2>
8787
<!-- Setup mode (PENDING / SUSPENDED) -->
8888
<div class="modal-slot" data-mode="setup" style="display:none">
8989
<div class="banner banner-pending" id="setup-banner">
90-
Add the DNS record below at your DNS provider, then click Verify.
90+
Add the DNS record(s) below at your DNS provider, then click Verify.
9191
</div>
9292

9393
<h3 class="section-title">DNS record</h3>
9494
<div class="dns-records" id="dns-records"></div>
9595
<div class="setup-notes" id="setup-notes" style="display:none"></div>
9696

97-
<button class="btn btn-primary btn-block" id="btn-verify">
98-
<span class="btn-label">Verify domain</span>
99-
<span class="btn-spinner" style="display:none">
100-
<i class="ti ti-loader-2"></i>
101-
</span>
102-
</button>
10397
<div id="setup-error" class="inline-error" style="display:none"></div>
10498
<div id="setup-poll-status" class="inline-info" style="display:none">
10599
Checking DNS… we'll keep watching for a minute.
@@ -135,10 +129,15 @@ <h3 class="section-title">DNS record</h3>
135129
</div>
136130

137131
<div class="modal-footer" id="domain-modal-footer">
138-
<!-- Footer button set per mode (rendered by JS) -->
139-
<button type="button" class="btn btn-ghost" id="btn-domain-modal-cancel">Cancel</button>
140132
<button type="button" class="btn btn-danger-ghost" id="btn-domain-revoke" style="display:none">Revoke</button>
141-
<button type="button" class="btn btn-primary" id="btn-domain-modal-submit">Add</button>
133+
<button type="button" class="btn btn-ghost" id="btn-domain-modal-cancel" style="display:none">Cancel</button>
134+
<button type="button" class="btn btn-primary" id="btn-domain-modal-submit" style="display:none">Add</button>
135+
<button type="button" class="btn btn-primary" id="btn-verify" style="display:none">
136+
<span class="btn-label">Verify</span>
137+
<span class="btn-spinner" style="display:none">
138+
<i class="ti ti-loader-2"></i>
139+
</span>
140+
</button>
142141
</div>
143142
</div>
144143
</div>

0 commit comments

Comments
 (0)