feat(account): Refactor account UI: migrate modals to promise-based show() API and improve navigation URL tracking
This commit is contained in:
@@ -14,6 +14,8 @@ import * as plugins from '../../plugins.js';
|
||||
import * as states from '../../states/accountstate.js';
|
||||
import { IdpState } from '../../states/idp.state.js';
|
||||
import { accountDesignTokens } from './sharedstyles.js';
|
||||
import { CreateOrgModal } from './create-org-modal.js';
|
||||
import { OrgSelectModal } from './org-select-modal.js';
|
||||
|
||||
import { commitinfo } from '../../../dist_ts/00_commitinfo_data.js';
|
||||
|
||||
@@ -28,10 +30,39 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
@state()
|
||||
accessor isGlobalAdmin: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor currentPath: string = window.location.pathname;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private async navigateTo(path: string) {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl(path);
|
||||
// Update state after navigation to trigger re-render
|
||||
this.currentPath = window.location.pathname;
|
||||
}
|
||||
|
||||
private async navigateToOrgPage(page: string) {
|
||||
const currentState = states.accountState.getState();
|
||||
if (currentState.selectedOrg) {
|
||||
const path = page ? `/org/${currentState.selectedOrg.data.slug}/${page}` : `/org/${currentState.selectedOrg.data.slug}`;
|
||||
await this.navigateTo(path);
|
||||
} else {
|
||||
const targetPath = page ? `/org/:orgName/${page}` : '/org/:orgName';
|
||||
const description = page ? `Choose an organization to view its ${page}.` : 'Choose an organization to view its overview.';
|
||||
const result = await OrgSelectModal.show({
|
||||
targetPath,
|
||||
title: 'Select Organization',
|
||||
description,
|
||||
});
|
||||
if (result) {
|
||||
await this.navigateTo(result.path.replace('/account', ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
accountDesignTokens,
|
||||
@@ -136,6 +167,15 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.navigationOption.active {
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.navigationOption.active dees-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
@@ -165,11 +205,8 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
<div class="navContent">
|
||||
<div class="navigationGroupLabel">Account</div>
|
||||
<div
|
||||
class="navigationOption"
|
||||
@click=${async () => {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl('');
|
||||
}}
|
||||
class="navigationOption ${this.isActive('') ? 'active' : ''}"
|
||||
@click=${() => this.navigateTo('')}
|
||||
>
|
||||
<dees-icon .icon=${'lucide:home'}></dees-icon>
|
||||
Overview
|
||||
@@ -202,10 +239,10 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
@selectedOption=${async (eventArg: CustomEvent) => {
|
||||
// Handle "Create new..." option
|
||||
if (eventArg.detail.key === '__create_new__') {
|
||||
this.dispatchEvent(new CustomEvent('open-create-org-modal', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
const org = await CreateOrgModal.show();
|
||||
if (org) {
|
||||
await this.navigateTo(`/org/${org.data.slug}/billing`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const currentState = states.accountState.getState();
|
||||
@@ -214,62 +251,29 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
|
||||
// Auto-navigate to new org's current page type (reactivity)
|
||||
const currentPath = window.location.pathname;
|
||||
const subrouter = await this.getAccountRouter();
|
||||
if (currentPath.includes('/org/') && newOrg) {
|
||||
// Extract the page type (apps, billing, etc.) and navigate to new org
|
||||
const pathParts = currentPath.split('/');
|
||||
const pageType = pathParts[5]; // /account/org/:orgName/:pageType
|
||||
if (pageType) {
|
||||
subrouter.pushUrl(`/org/${newOrg.data.slug}/${pageType}`);
|
||||
await this.navigateTo(`/org/${newOrg.data.slug}/${pageType}`);
|
||||
} else {
|
||||
subrouter.pushUrl(`/org/${newOrg.data.slug}`);
|
||||
await this.navigateTo(`/org/${newOrg.data.slug}`);
|
||||
}
|
||||
}
|
||||
}}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<div
|
||||
class="navigationOption"
|
||||
@click=${async () => {
|
||||
const currentState = states.accountState.getState();
|
||||
if (currentState.selectedOrg) {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl(`/org/${currentState.selectedOrg.data.slug}`);
|
||||
} else {
|
||||
this.dispatchEvent(new CustomEvent('open-org-select-modal', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
targetPath: '/org/:orgName',
|
||||
title: 'Select Organization',
|
||||
description: 'Choose an organization to view its overview.',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}}
|
||||
class="navigationOption ${this.isActive('org-overview') ? 'active' : ''}"
|
||||
@click=${() => this.navigateToOrgPage('')}
|
||||
>
|
||||
<dees-icon .icon=${'lucide:home'}></dees-icon>
|
||||
Overview
|
||||
</div>
|
||||
<div
|
||||
class="navigationOption"
|
||||
@click=${async () => {
|
||||
const currentState = states.accountState.getState();
|
||||
if (currentState.selectedOrg) {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl(`/org/${currentState.selectedOrg.data.slug}/apps`);
|
||||
} else {
|
||||
this.dispatchEvent(new CustomEvent('open-org-select-modal', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
targetPath: '/org/:orgName/apps',
|
||||
title: 'Select Organization',
|
||||
description: 'Choose an organization to view its apps.',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}}
|
||||
class="navigationOption ${this.isActive('apps') ? 'active' : ''}"
|
||||
@click=${() => this.navigateToOrgPage('apps')}
|
||||
>
|
||||
<dees-icon .icon=${'lucide:box'}></dees-icon>
|
||||
Apps
|
||||
@@ -289,24 +293,8 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
Activity
|
||||
</div>
|
||||
<div
|
||||
class="navigationOption"
|
||||
@click=${async () => {
|
||||
const currentState = states.accountState.getState();
|
||||
if (currentState.selectedOrg) {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl(`/org/${currentState.selectedOrg.data.slug}/billing`);
|
||||
} else {
|
||||
this.dispatchEvent(new CustomEvent('open-org-select-modal', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
targetPath: '/org/:orgName/billing',
|
||||
title: 'Select Organization',
|
||||
description: 'Choose an organization to view its billing.',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}}
|
||||
class="navigationOption ${this.isActive('billing') ? 'active' : ''}"
|
||||
@click=${() => this.navigateToOrgPage('billing')}
|
||||
>
|
||||
<dees-icon .icon=${'lucide:wallet'}></dees-icon>
|
||||
Billing
|
||||
@@ -327,11 +315,8 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
<div class="divider"></div>
|
||||
<div class="navigationGroupLabel">Platform</div>
|
||||
<div
|
||||
class="navigationOption"
|
||||
@click=${async () => {
|
||||
const subrouter = await this.getAccountRouter();
|
||||
subrouter.pushUrl('/admin');
|
||||
}}
|
||||
class="navigationOption ${this.isActive('admin') ? 'active' : ''}"
|
||||
@click=${() => this.navigateTo('/admin')}
|
||||
>
|
||||
<dees-icon .icon=${'lucide:shield'}></dees-icon>
|
||||
Global Admin
|
||||
@@ -339,7 +324,37 @@ export class LeleAccountNavigation extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public firstUpdated() {
|
||||
private isActive(page: string): boolean {
|
||||
const path = this.currentPath;
|
||||
if (page === '') {
|
||||
// Account overview - exact match
|
||||
return path === '/account' || path === '/account/';
|
||||
}
|
||||
if (page === 'org-overview') {
|
||||
// Org overview - /account/org/:slug without trailing page type
|
||||
return /^\/account\/org\/[^\/]+\/?$/.test(path);
|
||||
}
|
||||
// For other pages, check if the path contains the page segment
|
||||
return path.includes(`/${page}`);
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
// Listen for popstate (browser back/forward)
|
||||
window.addEventListener('popstate', () => {
|
||||
this.currentPath = window.location.pathname;
|
||||
});
|
||||
|
||||
// Watch for URL changes from external navigation (e.g., modals)
|
||||
let lastPath = this.currentPath;
|
||||
const checkPath = () => {
|
||||
if (window.location.pathname !== lastPath) {
|
||||
lastPath = window.location.pathname;
|
||||
this.currentPath = lastPath;
|
||||
}
|
||||
requestAnimationFrame(checkPath);
|
||||
};
|
||||
requestAnimationFrame(checkPath);
|
||||
|
||||
const deesInputDropdown = this.shadowRoot.querySelector('dees-input-dropdown');
|
||||
const orgToMenuEntry = (orgArg?: plugins.idpInterfaces.data.IOrganization) => {
|
||||
if (!orgArg) {
|
||||
|
||||
Reference in New Issue
Block a user