diff --git a/backend/src/server.ts b/backend/src/server.ts index 905cbe6..a5f9e52 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -59,7 +59,25 @@ const publicDir = config.publicDir.startsWith('/') console.log(`Serving frontend from: ${publicDir}`); -app.use(express.static(publicDir)); +// Avoid stale frontend JS while we are actively developing the MVP. +app.use((req, res, next) => { + if ( + req.path.endsWith('.js') || + req.path.endsWith('.css') || + req.path.endsWith('.html') + ) { + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + } + + next(); +}); + +app.use(express.static(publicDir, { + etag: false, + lastModified: false, +})); app.get('*', (_req, res) => { res.sendFile(resolve(publicDir, 'index.html')); diff --git a/frontend/app.js b/frontend/app.js index 00fc37c..7732ab0 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -169,11 +169,106 @@ function modal(html) { function renderCreateMailboxModal() { const domain = state.selectedDomain || ''; - const d = modal(`

New mailbox

`); - const input = d.querySelector('input[name="email"]'); - input.focus(); - input.setSelectionRange(0, 0); - d.querySelector('#createForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); await api('/api/mailboxes', { method:'POST', body: JSON.stringify({ email:f.get('email'), password:f.get('password') }) }); d.remove(); await loadDomains(false); render(); }); + + const d = modal(` +

New mailbox

+ +
+ + + + +
+ +
+
+ `); + + const emailInput = d.querySelector('#createEmail'); + const passwordInput = d.querySelector('#createPassword'); + const submitButton = d.querySelector('#createMailboxSubmit'); + + emailInput.focus(); + + // Cursor before the @domain, so you can directly type "test". + try { + emailInput.setSelectionRange(0, 0); + } catch { + // Some browsers do not allow selection on email inputs. + } + + const createMailbox = guard(async () => { + const email = String(emailInput.value || '').trim().toLowerCase(); + const password = String(passwordInput.value || ''); + + if (!email || !email.includes('@')) { + throw new Error('Please enter a valid email address.'); + } + + if (!email.endsWith(`@${domain}`)) { + throw new Error(`Mailbox must belong to ${domain}.`); + } + + if (password.length < 8) { + throw new Error('Password must have at least 8 characters.'); + } + + submitButton.disabled = true; + submitButton.textContent = 'Creating...'; + + await api('/api/mailboxes', { + method: 'POST', + body: JSON.stringify({ + email, + password, + }), + }); + + d.remove(); + + state.message = `Mailbox created: ${email}`; + + await loadDomains(false); + await loadMailboxes(true); + + render(); + }); + + submitButton.onclick = createMailbox; + + passwordInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + createMailbox(); + } + }); + + emailInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + passwordInput.focus(); + } + }); } function renderDeleteModal(email) {