This commit is contained in:
2026-04-26 16:28:38 -05:00
parent a97f372a76
commit 32a00b3706
2 changed files with 119 additions and 6 deletions

View File

@@ -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'));

View File

@@ -169,11 +169,106 @@ function modal(html) {
function renderCreateMailboxModal() {
const domain = state.selectedDomain || '';
const d = modal(`<h2>New mailbox</h2><form id="createForm" class="form-grid"><label>Email<input name="email" type="email" value="@${esc(domain)}" placeholder="@${esc(domain)}" required></label><label>Password<input name="password" type="password" value="" minlength="8" autocomplete="new-password" required></label><div><button>Create</button></div></form>`);
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(`
<h2>New mailbox</h2>
<div class="form-grid">
<label>
Email
<input
id="createEmail"
type="email"
value="@${esc(domain)}"
placeholder="@${esc(domain)}"
autocomplete="off"
required
>
</label>
<label>
Password
<input
id="createPassword"
type="password"
value=""
minlength="8"
autocomplete="new-password"
required
>
</label>
<div>
<button type="button" id="createMailboxSubmit">Create</button>
</div>
</div>
`);
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) {