import * as plugins from '../../../plugins.js'; import { customElement, DeesElement, property, html, cssManager, css, state, } from '@design.estate/dees-element'; import * as sharedStyles from '../sharedstyles.js'; import * as accountState from '../../../states/accountstate.js'; import { IdpState } from '../../../states/idp.state.js'; declare global { interface HTMLElementTagNameMap { 'lele-accountview-apps': AppsView; } } interface IAppDisplay { id: string; name: string; description: string; logoUrl: string; appUrl: string; category: string; isConnected: boolean; } @customElement('lele-accountview-apps') export class AppsView extends DeesElement { @state() accessor globalApps: IAppDisplay[] = []; @state() accessor loading: boolean = true; @state() accessor activeTab: 'global' | 'store' | 'custom' = 'global'; @state() accessor organizationId: string = ''; public static styles = [ cssManager.defaultStyles, sharedStyles.accountDesignTokens, sharedStyles.viewBaseStyles, sharedStyles.cardStyles, sharedStyles.typographyStyles, css` :host { padding: 48px; max-width: 1000px; margin: 0 auto; } .tabs { display: flex; gap: 4px; margin-bottom: 32px; border-bottom: 1px solid var(--border); padding-bottom: 8px; } .tab { padding: 10px 20px; border-radius: 8px 8px 0 0; font-size: 14px; font-weight: 500; color: var(--muted-foreground); cursor: pointer; transition: all 0.15s ease; border: none; background: transparent; } .tab:hover { color: var(--foreground); background: var(--muted); } .tab.active { color: var(--foreground); background: var(--muted); } .app-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; } .app-card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 24px; transition: all 0.15s ease; } .app-card:hover { border-color: var(--muted-foreground); } .app-header { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 16px; } .app-logo { width: 48px; height: 48px; border-radius: 12px; background: var(--muted); display: flex; align-items: center; justify-content: center; flex-shrink: 0; overflow: hidden; } .app-logo img { width: 100%; height: 100%; object-fit: cover; } .app-logo dees-icon { font-size: 24px; opacity: 0.7; } .app-info { flex: 1; min-width: 0; } .app-name { font-size: 16px; font-weight: 600; color: var(--foreground); margin: 0 0 4px 0; } .app-category { font-size: 12px; color: var(--muted-foreground); text-transform: uppercase; letter-spacing: 0.05em; } .app-description { font-size: 14px; color: var(--muted-foreground); line-height: 1.5; margin: 0 0 16px 0; } .app-actions { display: flex; align-items: center; justify-content: space-between; } .app-link { font-size: 13px; color: var(--muted-foreground); text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.15s ease; } .app-link:hover { color: var(--foreground); } .app-link dees-icon { font-size: 14px; } .toggle-container { display: flex; align-items: center; gap: 8px; } .toggle-label { font-size: 13px; color: var(--muted-foreground); } .empty-state { text-align: center; padding: 48px; color: var(--muted-foreground); } .empty-state dees-icon { font-size: 48px; opacity: 0.5; margin-bottom: 16px; } .loading { display: flex; align-items: center; justify-content: center; padding: 48px; color: var(--muted-foreground); } .coming-soon { text-align: center; padding: 48px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; } .coming-soon dees-icon { font-size: 48px; opacity: 0.5; margin-bottom: 16px; } `, ]; public render() { return html`

Apps

Manage apps connected to your organization. Connect global apps, browse the AppStore, or create custom OAuth clients.

${this.renderTabContent()} `; } private renderTabContent() { switch (this.activeTab) { case 'global': return this.renderGlobalApps(); case 'store': return this.renderAppStore(); case 'custom': return this.renderCustomOidc(); } } private renderGlobalApps() { if (this.loading) { return html`
Loading apps...
`; } if (this.globalApps.length === 0) { return html`

No Global Apps Available

There are no global apps configured yet.

`; } return html`
${this.globalApps.map(app => html`

${app.name}

${app.category}

${app.description}

Visit App
${app.isConnected ? 'Connected' : 'Disconnected'} this.toggleAppConnection(app.id, e.detail)} >
`)}
`; } private renderAppStore() { return html`

App Store

Browse and install partner apps from other organizations.

Coming soon in Phase 3

`; } private renderCustomOidc() { return html`

Custom OIDC Apps

Create and manage your own OAuth/OIDC client applications.

Coming soon in Phase 2

`; } public async firstUpdated() { await this.loadApps(); } private async loadApps() { this.loading = true; try { // Get the organization ID from the URL const pathParts = window.location.pathname.split('/'); const orgSlug = pathParts[3]; const currentState = accountState.accountState.getState(); const selectedOrg = currentState.organizations.find(org => org.data.slug === orgSlug); if (!selectedOrg) { console.error('Organization not found'); this.loading = false; return; } this.organizationId = selectedOrg.id; // Get JWT from IdpState const idpState = await IdpState.getSingletonInstance(); const jwt = await idpState.idpClient.getJwt(); // Fetch global apps const typedRequest = idpState.idpClient.typedsocket.createTypedRequest( 'getGlobalApps' ); const appsResponse = await typedRequest.fire({ jwt, }); // Fetch connections for this organization const connectionsRequest = idpState.idpClient.typedsocket.createTypedRequest( 'getAppConnections' ); const connectionsResponse = await connectionsRequest.fire({ jwt, organizationId: this.organizationId, }); // Map apps with connection status const connectionMap = new Map( connectionsResponse.connections .filter(c => c.data.status === 'active') .map(c => [c.data.appId, true]) ); this.globalApps = appsResponse.apps.map(app => ({ id: app.id, name: app.data.name, description: app.data.description, logoUrl: app.data.logoUrl, appUrl: app.data.appUrl, category: app.data.category, isConnected: connectionMap.has(app.id), })); } catch (error) { console.error('Error loading apps:', error); } finally { this.loading = false; } } private async toggleAppConnection(appId: string, isConnected: boolean) { try { // Get JWT from IdpState const idpState = await IdpState.getSingletonInstance(); const jwt = await idpState.idpClient.getJwt(); const typedRequest = idpState.idpClient.typedsocket.createTypedRequest( 'toggleAppConnection' ); await typedRequest.fire({ jwt, organizationId: this.organizationId, appId: appId, action: isConnected ? 'connect' : 'disconnect', }); // Update local state this.globalApps = this.globalApps.map(app => app.id === appId ? { ...app, isConnected } : app ); } catch (error) { console.error('Error toggling app connection:', error); // Revert the checkbox on error await this.loadApps(); } } }