portal: improve onboarding login UX

This commit is contained in:
Brad Stein 2026-01-04 08:44:25 -03:00
parent 59830e19c8
commit 72dae3e7a2
3 changed files with 60 additions and 9 deletions

View File

@ -92,10 +92,17 @@ export async function initAuth() {
return initPromise; return initPromise;
} }
export async function login(redirectPath = window.location.pathname + window.location.search + window.location.hash) { export async function login(
redirectPath = window.location.pathname + window.location.search + window.location.hash,
loginHint = "",
) {
if (!keycloak) return; if (!keycloak) return;
const redirectUri = new URL(redirectPath, window.location.origin).toString(); const redirectUri = new URL(redirectPath, window.location.origin).toString();
await keycloak.login({ redirectUri }); const options = { redirectUri };
if (typeof loginHint === "string" && loginHint.trim()) {
options.loginHint = loginHint.trim();
}
await keycloak.login(options);
} }
export async function logout() { export async function logout() {

View File

@ -26,7 +26,10 @@
<div v-if="requestUsername" class="status-meta"> <div v-if="requestUsername" class="status-meta">
<div class="meta-row"> <div class="meta-row">
<span class="label mono">Username</span> <span class="label mono">Username</span>
<span class="value mono">{{ requestUsername }}</span> <button class="copy mono" type="button" @click="copyUsername">
{{ requestUsername }}
<span v-if="usernameCopied" class="copied">copied</span>
</button>
</div> </div>
</div> </div>
@ -87,7 +90,8 @@
<div v-if="initialPassword" class="initial-password"> <div v-if="initialPassword" class="initial-password">
<h3>Temporary password</h3> <h3>Temporary password</h3>
<p class="muted"> <p class="muted">
Use this password to log in for the first time. Keycloak will prompt you to change it. Use this password to log in for the first time. Keycloak will prompt you to change it. This password is shown
once copy it now.
</p> </p>
<div class="request-code-row"> <div class="request-code-row">
<span class="label mono">Password</span> <span class="label mono">Password</span>
@ -212,6 +216,7 @@ const error = ref("");
const onboarding = ref({ required_steps: [], completed_steps: [] }); const onboarding = ref({ required_steps: [], completed_steps: [] });
const initialPassword = ref(""); const initialPassword = ref("");
const copied = ref(false); const copied = ref(false);
const usernameCopied = ref(false);
const tasks = ref([]); const tasks = ref([]);
const blocked = ref(false); const blocked = ref(false);
@ -304,8 +309,35 @@ async function copyInitialPassword() {
} }
} }
async function copyUsername() {
if (!requestUsername.value) return;
try {
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(requestUsername.value);
} else {
const textarea = document.createElement("textarea");
textarea.value = requestUsername.value;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.top = "-9999px";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
document.execCommand("copy");
document.body.removeChild(textarea);
}
usernameCopied.value = true;
setTimeout(() => (usernameCopied.value = false), 1500);
} catch (err) {
error.value = err?.message || "Failed to copy username";
}
}
async function loginToContinue() { async function loginToContinue() {
await login(`/onboarding?code=${encodeURIComponent(requestCode.value.trim())}`); const trimmedCode = requestCode.value.trim();
const hint = requestUsername.value.trim() || trimmedCode.split("~", 1)[0] || "";
await login(`/onboarding?code=${encodeURIComponent(trimmedCode)}`, hint);
} }
async function toggleStep(step, event) { async function toggleStep(step, event) {

View File

@ -112,10 +112,6 @@
Verifying email Verifying email
</div> </div>
<div v-if="onboardingUrl" class="actions" style="margin-top: 12px;">
<a class="primary" :href="onboardingUrl">Continue onboarding</a>
</div>
<div v-if="tasks.length" class="task-box"> <div v-if="tasks.length" class="task-box">
<div class="module-head" style="margin-bottom: 10px;"> <div class="module-head" style="margin-bottom: 10px;">
<h2>Automation</h2> <h2>Automation</h2>
@ -134,6 +130,13 @@
One or more automation steps failed. Fix the error above, then check again. One or more automation steps failed. Fix the error above, then check again.
</p> </p>
</div> </div>
<div
v-if="onboardingUrl && (status === 'awaiting_onboarding' || status === 'ready')"
class="actions onboarding-actions"
>
<a class="primary onboarding-cta" :href="onboardingUrl">Continue onboarding</a>
</div>
</div> </div>
<div v-if="error" class="error-box"> <div v-if="error" class="error-box">
@ -429,6 +432,15 @@ h1 {
margin-top: 6px; margin-top: 6px;
} }
.onboarding-actions {
margin-top: 14px;
}
.onboarding-cta {
flex: 1;
text-align: center;
}
.status-form { .status-form {
display: flex; display: flex;
gap: 10px; gap: 10px;