import { DeesElement, customElement, html, css, cssManager, property, state, type TemplateResult, } from '@design.estate/dees-element'; declare global { interface HTMLElementTagNameMap { 'sz-app-store-view': SzAppStoreView; } } export interface IAppTemplate { id: string; name: string; description: string; category: string; iconUrl?: string; iconName?: string; image: string; port: number; envVars?: Array<{ key: string; value: string; description: string; required?: boolean }>; volumes?: string[]; enableMongoDB?: boolean; enableS3?: boolean; enableClickHouse?: boolean; } @customElement('sz-app-store-view') export class SzAppStoreView extends DeesElement { public static demo = () => html`
`; public static demoGroups = ['Services']; @property({ type: Array }) public accessor apps: IAppTemplate[] = []; @state() private accessor selectedCategory: string = 'All'; @state() private accessor searchQuery: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .header { margin-bottom: 24px; } .header-title { font-size: 20px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .header-subtitle { font-size: 14px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; margin-top: 4px; } .filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 24px; } .category-pills { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-width: 0; } .category-pill { padding: 6px 14px; border-radius: 9999px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 200ms ease; border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; white-space: nowrap; } .category-pill:hover { border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .category-pill.active { background: ${cssManager.bdTheme('#18181b', '#fafafa')}; color: ${cssManager.bdTheme('#fafafa', '#18181b')}; border-color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .search-input { width: 240px; padding: 8px 12px 8px 36px; background: ${cssManager.bdTheme('#ffffff', '#18181b')}; border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; border-radius: 6px; font-size: 14px; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; outline: none; transition: border-color 200ms ease; box-sizing: border-box; } .search-input:focus { border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; } .search-input::placeholder { color: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; } .search-wrapper { position: relative; flex-shrink: 0; } .search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; pointer-events: none; } .app-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } @media (max-width: 1024px) { .app-grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 640px) { .app-grid { grid-template-columns: 1fr; } .filter-bar { flex-direction: column; align-items: stretch; } .search-input { width: 100%; } } .app-card { background: ${cssManager.bdTheme('#ffffff', '#09090b')}; border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; border-radius: 8px; padding: 20px; display: flex; flex-direction: column; gap: 12px; transition: all 200ms ease; cursor: default; } .app-card:hover { border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(0,0,0,0.2)')}; transform: translateY(-1px); } .card-top { display: flex; align-items: flex-start; gap: 14px; } .app-icon { width: 44px; height: 44px; border-radius: 10px; background: ${cssManager.bdTheme('#f4f4f5', '#18181b')}; display: flex; align-items: center; justify-content: center; flex-shrink: 0; overflow: hidden; } .app-icon svg { width: 22px; height: 22px; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .app-icon img { width: 100%; height: 100%; object-fit: cover; } .app-icon .letter-fallback { font-size: 20px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; user-select: none; } .card-info { flex: 1; min-width: 0; } .app-name { font-size: 15px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; line-height: 1.3; } .category-badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 9999px; font-size: 11px; font-weight: 500; margin-top: 4px; background: ${cssManager.bdTheme('#eff6ff', 'rgba(59, 130, 246, 0.15)')}; color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; } .app-description { font-size: 13px; line-height: 1.5; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; flex: 1; } .card-footer { display: flex; align-items: center; justify-content: space-between; padding-top: 12px; border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')}; margin-top: auto; } .image-tag { font-family: monospace; font-size: 12px; color: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; } .deploy-button { display: inline-flex; align-items: center; gap: 6px; padding: 7px 16px; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 200ms ease; border: none; background: ${cssManager.bdTheme('#18181b', '#fafafa')}; color: ${cssManager.bdTheme('#fafafa', '#18181b')}; white-space: nowrap; flex-shrink: 0; } .deploy-button:hover { opacity: 0.85; } .deploy-button svg { width: 14px; height: 14px; } .details-button { display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 200ms ease; border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; background: transparent; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; white-space: nowrap; flex-shrink: 0; } .details-button:hover { border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .card-actions { display: flex; align-items: center; gap: 8px; } .empty-state { grid-column: 1 / -1; padding: 64px 24px; text-align: center; } .empty-icon { width: 48px; height: 48px; color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; margin: 0 auto 16px; } .empty-title { font-size: 16px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; margin-bottom: 4px; } .empty-description { font-size: 14px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; } `, ]; private get categories(): string[] { const cats = new Set(this.apps.map((app) => app.category)); return ['All', ...Array.from(cats).sort()]; } private get filteredApps(): IAppTemplate[] { let result = this.apps; if (this.selectedCategory !== 'All') { result = result.filter((app) => app.category === this.selectedCategory); } if (this.searchQuery.trim()) { const query = this.searchQuery.trim().toLowerCase(); result = result.filter( (app) => app.name.toLowerCase().includes(query) || app.description.toLowerCase().includes(query) || app.category.toLowerCase().includes(query) ); } return result; } public render(): TemplateResult { const filtered = this.filteredApps; return html`
App Store
Deploy popular applications with one click
${this.categories.map( (cat) => html` ` )}
{ this.searchQuery = (e.target as HTMLInputElement).value; }} >
${filtered.length > 0 ? filtered.map((app) => this.renderAppCard(app)) : html`
No apps found
Try adjusting your search or filter to find what you're looking for.
`}
`; } private renderAppCard(app: IAppTemplate): TemplateResult { return html`
${app.iconUrl ? html`${app.name}` : app.iconName ? this.renderIconByName(app.iconName) : html`${app.name.charAt(0).toUpperCase()}`}
${app.name}
${app.category}
${app.description}
`; } private renderIconByName(name: string): TemplateResult { const icons: Record = { 'file-text': html``, 'git-branch': html``, 'book-open': html``, 'globe': html``, 'database': html``, 'server': html``, 'package': html``, 'mail': html``, 'shield': html``, 'monitor': html``, }; return icons[name] || html`?`; } private handleViewDetails(app: IAppTemplate) { this.dispatchEvent( new CustomEvent('view-app', { detail: { app }, bubbles: true, composed: true, }) ); } private handleDeploy(app: IAppTemplate) { this.dispatchEvent( new CustomEvent('deploy-app', { detail: { app }, bubbles: true, composed: true, }) ); } }