feat(frontend): improve multi-role guidance and dashboard role switch UX
This commit is contained in:
parent
c435053810
commit
f3ef5f0aad
4 changed files with 58 additions and 12 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, Show, createEffect, For } from 'solid-js';
|
import { Component, Show, createEffect, For, createSignal } from 'solid-js';
|
||||||
import { useNavigate, A } from '@solidjs/router';
|
import { useNavigate, A } from '@solidjs/router';
|
||||||
import { authState, fetchRuntimeConfig, logout, hasModule } from '~/lib/auth';
|
import { authState, logout, switchRole } from '~/lib/auth';
|
||||||
|
|
||||||
// ── Icons (inline SVGs for zero deps) ─────────────────────────────────────────
|
// ── Icons (inline SVGs for zero deps) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ const MODULE_NAV_MAP: Record<string, { label: string; href: string; icon: Compon
|
||||||
|
|
||||||
export default function DashboardLayout(props: { children: any }) {
|
export default function DashboardLayout(props: { children: any }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const state = authState();
|
const [switchingRole, setSwitchingRole] = createSignal(false);
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const s = authState();
|
const s = authState();
|
||||||
|
|
@ -101,6 +101,8 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const rc = () => authState().runtime_config;
|
const rc = () => authState().runtime_config;
|
||||||
|
const roleOptions = () => rc()?.user?.roles ?? [];
|
||||||
|
const activeRole = () => rc()?.user?.active_role ?? rc()?.role ?? '';
|
||||||
|
|
||||||
const navItems = () => {
|
const navItems = () => {
|
||||||
if (rc()?.role === 'USER') {
|
if (rc()?.role === 'USER') {
|
||||||
|
|
@ -127,6 +129,18 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
navigate('/auth/login', { replace: true });
|
navigate('/auth/login', { replace: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRoleSwitch(nextRole: string) {
|
||||||
|
const normalized = String(nextRole || '').trim().toUpperCase();
|
||||||
|
if (!normalized || normalized === activeRole()) return;
|
||||||
|
setSwitchingRole(true);
|
||||||
|
try {
|
||||||
|
await switchRole(normalized);
|
||||||
|
navigate('/dashboard', { replace: true });
|
||||||
|
} finally {
|
||||||
|
setSwitchingRole(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="dashboard-shell">
|
<div class="dashboard-shell">
|
||||||
{/* ── Sidebar ── */}
|
{/* ── Sidebar ── */}
|
||||||
|
|
@ -166,6 +180,23 @@ export default function DashboardLayout(props: { children: any }) {
|
||||||
<header class="dashboard-topbar">
|
<header class="dashboard-topbar">
|
||||||
<div class="topbar-title"> </div>
|
<div class="topbar-title"> </div>
|
||||||
<div class="topbar-right">
|
<div class="topbar-right">
|
||||||
|
<Show when={roleOptions().length > 1}>
|
||||||
|
<select
|
||||||
|
class="input"
|
||||||
|
style={{ width: '210px', 'font-size': '13px', padding: '8px 10px' }}
|
||||||
|
value={activeRole()}
|
||||||
|
disabled={switchingRole()}
|
||||||
|
onChange={(e) => handleRoleSwitch(e.currentTarget.value)}
|
||||||
|
title="Switch your dashboard view"
|
||||||
|
>
|
||||||
|
<For each={roleOptions()}>
|
||||||
|
{(role) => <option value={role}>{role.replaceAll('_', ' ')}</option>}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
</Show>
|
||||||
|
<A href="/choose-role" class="btn btn-sm" style={{ 'text-decoration': 'none' }}>
|
||||||
|
Choose What You Need
|
||||||
|
</A>
|
||||||
<A href="/dashboard/notifications" class="topbar-icon-btn" title="Notifications">
|
<A href="/dashboard/notifications" class="topbar-icon-btn" title="Notifications">
|
||||||
<IconBell />
|
<IconBell />
|
||||||
</A>
|
</A>
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export async function switchRole(roleKey: string): Promise<void> {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ requested_role: roleKey }),
|
body: JSON.stringify({ role_key: roleKey }),
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createSignal, Show, For } from 'solid-js';
|
import { createMemo, createSignal, Show, For } from 'solid-js';
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
import { authState } from '~/lib/auth';
|
import { authState, switchRole } from '~/lib/auth';
|
||||||
|
|
||||||
const ALL_ROLES = [
|
const ALL_ROLES = [
|
||||||
{ key: 'COMPANY', label: 'Company', icon: '🏢', desc: 'Post jobs and hire talent' },
|
{ key: 'COMPANY', label: 'Company', icon: '🏢', desc: 'Post jobs and hire talent' },
|
||||||
|
|
@ -21,11 +21,18 @@ export default function ChooseRole() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
const [error, setError] = createSignal('');
|
const [error, setError] = createSignal('');
|
||||||
|
const registeredRoles = createMemo(() => new Set(authState().runtime_config?.user?.roles ?? []));
|
||||||
|
|
||||||
async function selectRole(roleKey: string) {
|
async function selectRole(roleKey: string, alreadyRegistered: boolean) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
|
if (alreadyRegistered) {
|
||||||
|
await switchRole(roleKey);
|
||||||
|
navigate('/dashboard', { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Register for the role then redirect to onboarding
|
// Register for the role then redirect to onboarding
|
||||||
const token = authState().access_token;
|
const token = authState().access_token;
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL ?? 'http://localhost:8000'}/api/me/roles/register`, {
|
const res = await fetch(`${import.meta.env.VITE_API_URL ?? 'http://localhost:8000'}/api/me/roles/register`, {
|
||||||
|
|
@ -56,7 +63,7 @@ export default function ChooseRole() {
|
||||||
<div class="choose-role-header">
|
<div class="choose-role-header">
|
||||||
<div class="brand-logo-text">NXTGAUGE</div>
|
<div class="brand-logo-text">NXTGAUGE</div>
|
||||||
<h1>What brings you here?</h1>
|
<h1>What brings you here?</h1>
|
||||||
<p>Choose your role to get started. You can add more roles later.</p>
|
<p>Choose a role to continue onboarding. You can register multiple roles and switch between them anytime.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={error()}>
|
<Show when={error()}>
|
||||||
|
|
@ -66,15 +73,23 @@ export default function ChooseRole() {
|
||||||
<div class="role-grid">
|
<div class="role-grid">
|
||||||
<For each={ALL_ROLES}>
|
<For each={ALL_ROLES}>
|
||||||
{(role) => (
|
{(role) => (
|
||||||
|
(() => {
|
||||||
|
const alreadyRegistered = registeredRoles().has(role.key);
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
class="role-card"
|
class="role-card"
|
||||||
disabled={loading()}
|
disabled={loading()}
|
||||||
onClick={() => selectRole(role.key)}
|
onClick={() => selectRole(role.key, alreadyRegistered)}
|
||||||
>
|
>
|
||||||
<span class="role-icon">{role.icon}</span>
|
<span class="role-icon">{role.icon}</span>
|
||||||
<span class="role-label">{role.label}</span>
|
<span class="role-label">{role.label}</span>
|
||||||
<span class="role-desc">{role.desc}</span>
|
<span class="role-desc">{role.desc}</span>
|
||||||
|
<Show when={alreadyRegistered}>
|
||||||
|
<span class="role-desc" style={{ color: '#16a34a', 'font-weight': '700' }}>Already registered</span>
|
||||||
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
|
);
|
||||||
|
})()
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import { A } from '@solidjs/router';
|
||||||
export default function DashboardExplorePage() {
|
export default function DashboardExplorePage() {
|
||||||
return (
|
return (
|
||||||
<section class="dashboard-card">
|
<section class="dashboard-card">
|
||||||
<h1>Explore Nxtgauge</h1>
|
<h1>Choose What You Want to Do</h1>
|
||||||
<p class="dashboard-muted">
|
<p class="dashboard-muted">
|
||||||
Add an additional role to unlock more modules in your dashboard.
|
Tell us how you want to use Nxtgauge next. Each path has a quick onboarding and review step.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Go to <A href="/choose-role">Choose Role</A> to register another role.
|
Go to <A href="/choose-role">Choose Your Path</A> to continue. If you unlock more than one path, you can switch from the dashboard top bar.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue