feat(app): wire dashboard administration flows
This commit is contained in:
@@ -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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user