509 lines
13 KiB
TypeScript
509 lines
13 KiB
TypeScript
import * as plugins from '../../../plugins.js';
|
|
import {
|
|
customElement,
|
|
DeesElement,
|
|
property,
|
|
html,
|
|
cssManager,
|
|
css,
|
|
state,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
|
|
import * as sharedStyles from '../sharedstyles.js';
|
|
import * as accountStateModule from '../../../states/accountstate.js';
|
|
import { IdpState } from '../../../states/idp.state.js';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'lele-accountview-orgview': OrgView;
|
|
}
|
|
}
|
|
|
|
interface IOrgStats {
|
|
memberCount: number;
|
|
appCount: number;
|
|
}
|
|
|
|
@customElement('lele-accountview-orgview')
|
|
export class OrgView extends DeesElement {
|
|
@state()
|
|
accessor loading: boolean = true;
|
|
|
|
@state()
|
|
accessor organization: plugins.idpInterfaces.data.IOrganization | null = null;
|
|
|
|
@state()
|
|
accessor userRole: plugins.idpInterfaces.data.IRole | null = null;
|
|
|
|
@state()
|
|
accessor stats: IOrgStats = { memberCount: 0, appCount: 0 };
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
sharedStyles.accountDesignTokens,
|
|
sharedStyles.viewBaseStyles,
|
|
css`
|
|
|
|
.container {
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
padding: 32px 24px;
|
|
}
|
|
|
|
.header {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 32px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
letter-spacing: -0.02em;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
h1 dees-icon {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.subtitle {
|
|
color: #71717a;
|
|
margin-top: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.stat-card {
|
|
background: #18181b;
|
|
border: 1px solid #27272a;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 32px;
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 13px;
|
|
color: #71717a;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.dashboard-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.card {
|
|
background: #18181b;
|
|
border: 1px solid #27272a;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card.full-width {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid #27272a;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.card-title dees-icon {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 16px 20px;
|
|
}
|
|
|
|
.card-body.no-padding {
|
|
padding: 0;
|
|
}
|
|
|
|
/* Info rows */
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #27272a;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 13px;
|
|
color: #71717a;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.info-value.slug {
|
|
font-family: 'Geist Mono', monospace;
|
|
background: #27272a;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* Role badge */
|
|
.role-badge {
|
|
padding: 4px 12px;
|
|
border-radius: 9999px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
background: rgba(59, 130, 246, 0.1);
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.role-badge.admin {
|
|
background: rgba(245, 158, 11, 0.1);
|
|
color: #f59e0b;
|
|
}
|
|
|
|
.role-badge.owner {
|
|
background: rgba(139, 92, 246, 0.1);
|
|
color: #8b5cf6;
|
|
}
|
|
|
|
/* Quick actions */
|
|
.action-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.action-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px 20px;
|
|
border-bottom: 1px solid #27272a;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
}
|
|
|
|
.action-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.action-item:hover {
|
|
background: #27272a;
|
|
}
|
|
|
|
.action-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
background: #27272a;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.action-item:hover .action-icon {
|
|
background: #3f3f46;
|
|
}
|
|
|
|
.action-icon dees-icon {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.action-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.action-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.action-description {
|
|
font-size: 12px;
|
|
color: #71717a;
|
|
}
|
|
|
|
.action-arrow {
|
|
color: #71717a;
|
|
}
|
|
|
|
/* Billing status */
|
|
.billing-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.billing-indicator {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #71717a;
|
|
}
|
|
|
|
.billing-indicator.active {
|
|
background: #22c55e;
|
|
}
|
|
|
|
.billing-indicator.none {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
/* Loading state */
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 48px;
|
|
color: #71717a;
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
if (this.loading) {
|
|
return html`
|
|
<div class="container">
|
|
<div class="loading">Loading organization...</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (!this.organization) {
|
|
return html`
|
|
<div class="container">
|
|
<div class="loading">Organization not found</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
const roleName = this.userRole?.data.roles?.[0] || 'member';
|
|
const roleClass = roleName === 'owner' ? 'owner' : roleName === 'admin' ? 'admin' : '';
|
|
const roleDisplay = roleName.charAt(0).toUpperCase() + roleName.slice(1);
|
|
|
|
return html`
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>
|
|
<dees-icon .icon=${'lucide:building2'}></dees-icon>
|
|
${this.organization.data.name}
|
|
</h1>
|
|
<p class="subtitle">Organization dashboard and settings</p>
|
|
</div>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value">${this.stats.memberCount}</div>
|
|
<div class="stat-label">Members</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">${this.stats.appCount}</div>
|
|
<div class="stat-label">Connected Apps</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">
|
|
<span class="role-badge ${roleClass}">${roleDisplay}</span>
|
|
</div>
|
|
<div class="stat-label">Your Role</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<!-- Organization Info -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<span class="card-title">
|
|
<dees-icon .icon=${'lucide:info'}></dees-icon>
|
|
Organization Info
|
|
</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="info-row">
|
|
<span class="info-label">Name</span>
|
|
<span class="info-value">${this.organization.data.name}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Slug</span>
|
|
<span class="info-value slug">${this.organization.data.slug}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Billing</span>
|
|
<span class="info-value">
|
|
<div class="billing-status">
|
|
<span class="billing-indicator ${this.organization.data.billingPlanId ? 'active' : 'none'}"></span>
|
|
${this.organization.data.billingPlanId ? 'Active' : 'Not configured'}
|
|
</div>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<span class="card-title">
|
|
<dees-icon .icon=${'lucide:zap'}></dees-icon>
|
|
Quick Actions
|
|
</span>
|
|
</div>
|
|
<div class="card-body no-padding">
|
|
<div class="action-list">
|
|
<div class="action-item" @click=${this.navigateToApps}>
|
|
<div class="action-icon">
|
|
<dees-icon .icon=${'lucide:box'}></dees-icon>
|
|
</div>
|
|
<div class="action-info">
|
|
<div class="action-name">Manage Apps</div>
|
|
<div class="action-description">Connect and configure applications</div>
|
|
</div>
|
|
<dees-icon class="action-arrow" .icon=${'lucide:chevron-right'}></dees-icon>
|
|
</div>
|
|
<div class="action-item" @click=${this.navigateToBilling}>
|
|
<div class="action-icon">
|
|
<dees-icon .icon=${'lucide:wallet'}></dees-icon>
|
|
</div>
|
|
<div class="action-info">
|
|
<div class="action-name">View Billing</div>
|
|
<div class="action-description">Manage subscription and invoices</div>
|
|
</div>
|
|
<dees-icon class="action-arrow" .icon=${'lucide:chevron-right'}></dees-icon>
|
|
</div>
|
|
<div class="action-item" @click=${this.handleInviteUser}>
|
|
<div class="action-icon">
|
|
<dees-icon .icon=${'lucide:user-plus'}></dees-icon>
|
|
</div>
|
|
<div class="action-info">
|
|
<div class="action-name">Invite Member</div>
|
|
<div class="action-description">Add team members to your organization</div>
|
|
</div>
|
|
<dees-icon class="action-arrow" .icon=${'lucide:chevron-right'}></dees-icon>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
public async firstUpdated() {
|
|
await this.loadOrgData();
|
|
}
|
|
|
|
private async loadOrgData() {
|
|
this.loading = true;
|
|
|
|
try {
|
|
// Get the organization slug from the URL
|
|
const pathParts = window.location.pathname.split('/');
|
|
const orgSlug = pathParts[3];
|
|
|
|
const currentState = accountStateModule.accountState.getState();
|
|
const selectedOrg = currentState.organizations.find(org => org.data.slug === orgSlug);
|
|
|
|
if (!selectedOrg) {
|
|
console.error('Organization not found');
|
|
this.loading = false;
|
|
return;
|
|
}
|
|
|
|
this.organization = selectedOrg;
|
|
|
|
// Find user's role in this org
|
|
this.userRole = currentState.roles.find(r => r.data.organizationId === selectedOrg.id) || null;
|
|
|
|
// Calculate stats
|
|
const memberCount = selectedOrg.data.roleIds?.length || 1;
|
|
|
|
// Get app connections count
|
|
let appCount = 0;
|
|
try {
|
|
const idpState = await IdpState.getSingletonInstance();
|
|
const jwt = await idpState.idpClient.getJwt();
|
|
|
|
const connectionsRequest = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetAppConnections>(
|
|
'getAppConnections'
|
|
);
|
|
|
|
const connectionsResponse = await connectionsRequest.fire({
|
|
jwt,
|
|
organizationId: selectedOrg.id,
|
|
});
|
|
|
|
appCount = connectionsResponse.connections?.filter(c => c.data.status === 'active').length || 0;
|
|
} catch (error) {
|
|
console.error('Error loading app connections:', error);
|
|
}
|
|
|
|
this.stats = { memberCount, appCount };
|
|
} catch (error) {
|
|
console.error('Error loading org data:', error);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
private async navigateToApps() {
|
|
if (!this.organization) return;
|
|
const parentElement = (this.getRootNode() as any).host;
|
|
parentElement.subrouter.pushUrl(`/org/${this.organization.data.slug}/apps`);
|
|
}
|
|
|
|
private async navigateToBilling() {
|
|
if (!this.organization) return;
|
|
const parentElement = (this.getRootNode() as any).host;
|
|
parentElement.subrouter.pushUrl(`/org/${this.organization.data.slug}/billing`);
|
|
}
|
|
|
|
private handleInviteUser() {
|
|
// TODO: Implement invite user modal
|
|
alert('Invite member functionality coming soon');
|
|
}
|
|
}
|