feat(app): wire dashboard administration flows

This commit is contained in:
2026-05-07 15:35:37 +00:00
parent e9eb9b4172
commit 91f06ccae1
91 changed files with 4087 additions and 5863 deletions
+36 -41
View File
@@ -97,14 +97,12 @@ export class BaseView extends DeesElement {
}
}
.card {
background: #18181b;
border: 1px solid #27272a;
border-radius: 12px;
idp-card.card::part(card) {
padding: 0;
overflow: hidden;
}
.card.full-width {
idp-card.card.full-width {
grid-column: 1 / -1;
}
@@ -124,7 +122,7 @@ export class BaseView extends DeesElement {
gap: 8px;
}
.card-title dees-icon {
.card-title idp-icon {
opacity: 0.7;
}
@@ -209,7 +207,7 @@ export class BaseView extends DeesElement {
flex-shrink: 0;
}
.org-icon dees-icon {
.org-icon idp-icon {
opacity: 0.7;
}
@@ -290,7 +288,7 @@ export class BaseView extends DeesElement {
flex-shrink: 0;
}
.session-icon dees-icon {
.session-icon idp-icon {
opacity: 0.7;
}
@@ -298,7 +296,7 @@ export class BaseView extends DeesElement {
background: rgba(34, 197, 94, 0.1);
}
.session-icon.current dees-icon {
.session-icon.current idp-icon {
color: #22c55e;
opacity: 1;
}
@@ -382,8 +380,7 @@ export class BaseView extends DeesElement {
flex-shrink: 0;
}
.activity-icon dees-icon {
font-size: 14px;
.activity-icon idp-icon {
opacity: 0.7;
}
@@ -391,7 +388,7 @@ export class BaseView extends DeesElement {
background: rgba(34, 197, 94, 0.1);
}
.activity-icon.login dees-icon {
.activity-icon.login idp-icon {
color: #22c55e;
opacity: 1;
}
@@ -400,7 +397,7 @@ export class BaseView extends DeesElement {
background: rgba(239, 68, 68, 0.1);
}
.activity-icon.logout dees-icon {
.activity-icon.logout idp-icon {
color: #ef4444;
opacity: 1;
}
@@ -427,8 +424,7 @@ export class BaseView extends DeesElement {
color: #71717a;
}
.empty-state dees-icon {
font-size: 32px;
.empty-state idp-icon {
opacity: 0.5;
margin-bottom: 12px;
}
@@ -467,7 +463,7 @@ export class BaseView extends DeesElement {
background: #27272a;
}
.create-org-btn dees-icon {
.create-org-btn idp-icon {
font-size: 14px;
}
`,
@@ -494,10 +490,10 @@ export class BaseView extends DeesElement {
<div class="dashboard-grid">
<!-- Profile Card -->
<div class="card">
<idp-card class="card">
<div class="card-header">
<span class="card-title">
<dees-icon .icon=${'lucide:user'}></dees-icon>
<idp-icon name="user" size="16"></idp-icon>
Profile
</span>
</div>
@@ -510,50 +506,49 @@ export class BaseView extends DeesElement {
</div>
</div>
</div>
</div>
</idp-card>
<!-- Organizations Card -->
<div class="card">
<idp-card class="card">
<div class="card-header">
<span class="card-title">
<dees-icon .icon=${'lucide:building2'}></dees-icon>
<idp-icon name="building2" size="16"></idp-icon>
Organizations
</span>
<button class="create-org-btn" @click=${this.handleCreateOrg}>
<dees-icon .icon=${'lucide:plus'}></dees-icon>
<idp-button variant="outline" size="sm" icon="plus" @click=${this.handleCreateOrg}>
New
</button>
</idp-button>
</div>
<div class="card-body no-padding">
${this.renderOrganizations()}
</div>
</div>
</idp-card>
<!-- Sessions Card -->
<div class="card">
<idp-card class="card">
<div class="card-header">
<span class="card-title">
<dees-icon .icon=${'lucide:monitor-smartphone'}></dees-icon>
<idp-icon name="monitor-smartphone" size="16"></idp-icon>
Active Sessions
</span>
</div>
<div class="card-body no-padding">
${this.renderSessions()}
</div>
</div>
</idp-card>
<!-- Activity Card -->
<div class="card">
<idp-card class="card">
<div class="card-header">
<span class="card-title">
<dees-icon .icon=${'lucide:activity'}></dees-icon>
<idp-icon name="activity" size="16"></idp-icon>
Recent Activity
</span>
</div>
<div class="card-body no-padding">
${this.renderActivity()}
</div>
</div>
</idp-card>
</div>
</div>
`;
@@ -563,7 +558,7 @@ export class BaseView extends DeesElement {
if (this.organizations.length === 0) {
return html`
<div class="empty-state">
<dees-icon .icon=${'lucide:building2'}></dees-icon>
<idp-icon name="building2" size="32"></idp-icon>
<p>You're not a member of any organizations yet.</p>
</div>
`;
@@ -580,13 +575,13 @@ export class BaseView extends DeesElement {
return html`
<div class="org-item" @click=${() => this.handleSelectOrg(org)}>
<div class="org-icon">
<dees-icon .icon=${'lucide:building2'}></dees-icon>
<idp-icon name="building2" size="16"></idp-icon>
</div>
<div class="org-info">
<div class="org-name">${org.data.name}</div>
<div class="org-role">${org.data.slug}</div>
</div>
<span class="role-badge ${roleClass}">${roleDisplay}</span>
<idp-badge variant=${roleClass === 'owner' ? 'accent' : roleClass === 'admin' ? 'warn' : 'outline'}>${roleDisplay}</idp-badge>
</div>
`;
})}
@@ -598,7 +593,7 @@ export class BaseView extends DeesElement {
if (this.sessions.length === 0) {
return html`
<div class="empty-state">
<dees-icon .icon=${'lucide:monitor'}></dees-icon>
<idp-icon name="monitor" size="32"></idp-icon>
<p>No active sessions found.</p>
</div>
`;
@@ -609,12 +604,12 @@ export class BaseView extends DeesElement {
${this.sessions.map((session) => html`
<div class="session-item" data-session-id=${session.id}>
<div class="session-icon ${session.isCurrent ? 'current' : ''}">
<dees-icon .icon=${this.getDeviceIcon(session.os)}></dees-icon>
<idp-icon name=${this.getDeviceIcon(session.os)} size="16"></idp-icon>
</div>
<div class="session-info">
<div class="session-device">
${session.deviceName || 'Unknown Device'}
${session.isCurrent ? html`<span class="current-badge">Current</span>` : ''}
${session.isCurrent ? html`<idp-badge variant="ok">Current</idp-badge>` : ''}
</div>
<div class="session-details">
${session.browser} · ${session.os} · Last active ${this.formatTimeAgo(session.lastActive)}
@@ -622,9 +617,9 @@ export class BaseView extends DeesElement {
</div>
${!session.isCurrent ? html`
<div class="session-actions">
<button class="revoke-btn" @click=${() => this.handleRevokeSession(session.id)}>
<idp-button variant="destructive" size="sm" @click=${() => this.handleRevokeSession(session.id)}>
Revoke
</button>
</idp-button>
</div>
` : ''}
</div>
@@ -637,7 +632,7 @@ export class BaseView extends DeesElement {
if (this.activities.length === 0) {
return html`
<div class="empty-state">
<dees-icon .icon=${'lucide:activity'}></dees-icon>
<idp-icon name="activity" size="32"></idp-icon>
<p>No recent activity.</p>
</div>
`;
@@ -648,7 +643,7 @@ export class BaseView extends DeesElement {
${this.activities.slice(0, 5).map((activity) => html`
<div class="activity-item">
<div class="activity-icon ${this.getActivityIconClass(activity.data.action)}">
<dees-icon .icon=${this.getActivityIcon(activity.data.action)}></dees-icon>
<idp-icon name=${this.getActivityIcon(activity.data.action)} size="14"></idp-icon>
</div>
<div class="activity-info">
<div class="activity-description">${activity.data.metadata.description}</div>
@@ -100,7 +100,7 @@ export class SubscriptionView extends DeesElement {
<h3>Paddle</h3>
<dees-button @click=${async () => {
// Extract org slug from current URL: /account/org/{orgSlug}/billing
// Extract org slug from current URL: /dash/org/{orgSlug}/settings
const pathParts = window.location.pathname.split('/');
const orgSlug = pathParts[3];
// Use parent's subrouter for proper navigation within account section
@@ -152,4 +152,4 @@ export class SubscriptionView extends DeesElement {
</div>
`;
}
}
}
+16 -5
View File
@@ -56,6 +56,9 @@ export class UsersView extends DeesElement {
@state()
accessor organizationName: string = '';
@state()
accessor organizationSlug: string = '';
@state()
accessor inviteEmail: string = '';
@@ -631,6 +634,7 @@ export class UsersView extends DeesElement {
this.organizationId = selectedOrg.id;
this.organizationName = selectedOrg.data.name;
this.organizationSlug = selectedOrg.data.slug;
this.currentUserId = currentState.user?.id || '';
// Check if current user is admin/owner
@@ -855,8 +859,8 @@ export class UsersView extends DeesElement {
}
private async handleTransferOwnership(newOwnerId: string, name: string) {
const confirmed = await this.showTransferConfirmation(name);
if (!confirmed) return;
const confirmationText = await this.showTransferConfirmation(name);
if (!confirmationText) return;
this.submitting = true;
this.actionMessage = null;
@@ -873,6 +877,7 @@ export class UsersView extends DeesElement {
jwt,
organizationId: this.organizationId,
newOwnerId,
confirmationText,
});
if (response.success) {
@@ -889,8 +894,10 @@ export class UsersView extends DeesElement {
}
}
private async showTransferConfirmation(name: string): Promise<boolean> {
private async showTransferConfirmation(name: string): Promise<string | null> {
return new Promise((resolve) => {
const expectedText = `transfer ${this.organizationSlug}`;
let confirmationText = '';
plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Transfer Ownership',
content: html`
@@ -899,11 +906,15 @@ export class UsersView extends DeesElement {
<p style="margin: 0; color: var(--muted-foreground);">
You will be demoted to admin role and will no longer be the owner of this organization.
</p>
<p style="margin: 12px 0 8px 0; color: var(--muted-foreground);">
Type <code>${expectedText}</code> to confirm.
</p>
<input style="box-sizing:border-box;width:100%;padding:8px;border:1px solid var(--border);border-radius:8px;" @input=${(eventArg: Event) => { confirmationText = (eventArg.target as HTMLInputElement).value; }} />
</div>
`,
menuOptions: [
{ name: 'Cancel', action: async (modal) => { modal.destroy(); resolve(false); } },
{ name: 'Transfer Ownership', action: async (modal) => { modal.destroy(); resolve(true); } },
{ name: 'Cancel', action: async (modal) => { modal.destroy(); resolve(null); } },
{ name: 'Transfer Ownership', action: async (modal) => { modal.destroy(); resolve(confirmationText.trim() === expectedText ? confirmationText.trim() : null); } },
],
width: 420,
});