new-site/performancewest_erpnext/performancewest_erpnext/www/set-password.html
justin 5c1341e6a1 portal: fix dead set-password link (rename controller to underscore)
Root cause of the 'Link invalid' onboarding link: Frappe's TemplatePage
resolves a www page's Python controller by converting hyphens to underscores
(see frappe/website/page_renderers/template_page.py set_pymodule: it looks for
'set_password.py' next to 'set-password.html'). Our controller was named
'set-password.py' (hyphen), so os.path.exists() missed it, pymodule_name stayed
None, get_context never ran over HTTP, and the template rendered with no
context -> raw {{ email }}, title 'Link invalid', token never verified. (It
worked under bench/in-process only because we called get_context directly.)

Fix: rename www/set-password.py -> www/set_password.py (route stays
/set-password, driven by the .html filename) and update the whitelisted submit
endpoint path in set-password.html to ...www.set_password.submit.

NOTE: the sibling legacy CRTC/CDR admin pages (admin-filings.py,
admin-resellers.py, cdr-*.py) have the same latent hyphen bug; left as-is since
they're outside the compliance portal, but they are silently controller-less.
2026-06-02 23:10:36 -05:00

124 lines
4.3 KiB
HTML

{% extends "templates/web.html" %}
{% block page_content %}
<style>
:root { --pw-navy: #1e3a5f; --pw-blue: #2d4e78; --pw-green: #059669; }
.pw-sp-card {
max-width: 440px;
margin: 3rem auto;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 2rem 2rem 2.25rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
}
.pw-sp-card h1 { margin: 0 0 .5rem; color: var(--pw-navy); font-size: 1.4rem; }
.pw-sp-card p.pw-intro { margin: 0 0 1.25rem; color: #475569; font-size: .95rem; }
.pw-sp-card label { display: block; font-weight: 600; margin: 1rem 0 .4rem; color: #1f2937; }
.pw-sp-card input[type="password"] {
width: 100%; padding: .6rem .75rem; border: 1px solid #cbd5e1;
border-radius: 6px; font-size: 1rem;
}
.pw-sp-card button {
margin-top: 1.5rem; width: 100%;
background: var(--pw-green); color: #fff; border: 0;
padding: .75rem 1rem; border-radius: 6px;
font-size: 1rem; font-weight: 600; cursor: pointer;
}
.pw-sp-card button:disabled { background: #9ca3af; cursor: not-allowed; }
.pw-sp-err { color: #b91c1c; margin-top: 1rem; font-size: .9rem; }
.pw-sp-ok { color: #065f46; margin-top: 1rem; font-size: .9rem; }
.pw-sp-help { color: #64748b; font-size: .8rem; margin-top: .4rem; }
</style>
<div class="pw-sp-card">
{% if error %}
<h1>Link invalid</h1>
<p class="pw-intro">{{ error }}</p>
{% else %}
<h1>Set your password</h1>
<p class="pw-intro">
Hi — set a password for <strong>{{ email }}</strong> to view your compliance
deliverables and place follow-up orders.
{% if order_number %}
<br><span class="pw-order-id">Order: {{ order_number }}</span>
{% endif %}
</p>
<form id="pw-set-password-form" autocomplete="off">
<input type="hidden" name="token" value="{{ token }}">
<label for="pw-pass">New password</label>
<input id="pw-pass" name="password" type="password" minlength="8" required>
<div class="pw-sp-help">At least 8 characters.</div>
<label for="pw-pass2">Confirm password</label>
<input id="pw-pass2" name="password2" type="password" minlength="8" required>
<button type="submit" id="pw-sp-submit">Set password &amp; continue</button>
<div id="pw-sp-msg"></div>
</form>
{% endif %}
</div>
<script>
(function () {
const form = document.getElementById("pw-set-password-form");
if (!form) return;
const msg = document.getElementById("pw-sp-msg");
const btn = document.getElementById("pw-sp-submit");
form.addEventListener("submit", async function (e) {
e.preventDefault();
msg.textContent = "";
msg.className = "";
const pass = form.password.value;
const pass2 = form.password2.value;
if (pass !== pass2) {
msg.className = "pw-sp-err";
msg.textContent = "Passwords do not match.";
return;
}
btn.disabled = true;
btn.textContent = "Setting password…";
try {
const resp = await fetch(
"/api/method/performancewest_erpnext.www.set_password.submit",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
token: form.token.value,
password: pass,
}).toString(),
credentials: "same-origin",
}
);
const data = await resp.json();
if (resp.ok && data.message && data.message.success) {
msg.className = "pw-sp-ok";
msg.textContent = "Password set — redirecting…";
window.location.href = data.message.redirect || "/orders";
} else {
const err =
(data && (data._server_messages || data.exc || data.message && data.message.error)) ||
"Could not set password.";
msg.className = "pw-sp-err";
msg.textContent = typeof err === "string" ? err : "Could not set password.";
btn.disabled = false;
btn.textContent = "Set password & continue";
}
} catch (err) {
msg.className = "pw-sp-err";
msg.textContent = "Network error. Please try again.";
btn.disabled = false;
btn.textContent = "Set password & continue";
}
});
})();
</script>
{% endblock %}