update ui

This commit is contained in:
2025-11-28 10:24:10 +00:00
parent 3dfc882f85
commit 84b974f095
14 changed files with 653 additions and 246 deletions

View File

@@ -11,64 +11,67 @@ import { AuthService } from '../../core/services/auth.service';
<div class="p-6 max-w-7xl mx-auto">
<!-- Header -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Dashboard</h1>
<p class="text-gray-500 dark:text-gray-400 mt-1">Welcome back, {{ userName() }}</p>
<div class="section-header mb-2">
<div class="section-indicator"></div>
<span class="section-label">Dashboard</span>
</div>
<h1 class="font-mono text-2xl font-bold text-foreground">Welcome back, {{ userName() }}</h1>
</div>
<!-- Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="card card-content">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
<svg class="w-6 h-6 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="p-3 bg-primary/10">
<svg class="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Organizations</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ organizations().length }}</p>
<p class="label">Organizations</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ organizations().length }}</p>
</div>
</div>
</div>
<div class="card card-content">
<div class="flex items-center gap-4">
<div class="p-3 bg-green-100 dark:bg-green-900/30 rounded-lg">
<svg class="w-6 h-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="p-3 bg-accent/10">
<svg class="w-6 h-6 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Packages</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ packages().length }}</p>
<p class="label">Packages</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ packages().length }}</p>
</div>
</div>
</div>
<div class="card card-content">
<div class="flex items-center gap-4">
<div class="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<svg class="w-6 h-6 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="p-3 bg-accent/10">
<svg class="w-6 h-6 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Total Downloads</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ totalDownloads() }}</p>
<p class="label">Total Downloads</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ totalDownloads() }}</p>
</div>
</div>
</div>
<div class="card card-content">
<div class="flex items-center gap-4">
<div class="p-3 bg-orange-100 dark:bg-orange-900/30 rounded-lg">
<svg class="w-6 h-6 text-orange-600 dark:text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="p-3 bg-primary/10">
<svg class="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Last Activity</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">Today</p>
<p class="label">Last Activity</p>
<p class="font-mono text-2xl font-bold text-foreground">Today</p>
</div>
</div>
</div>
@@ -79,22 +82,25 @@ import { AuthService } from '../../core/services/auth.service';
<!-- Recent Packages -->
<div class="card">
<div class="card-header flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Recent Packages</h2>
<a routerLink="/packages" class="text-sm text-primary-600 dark:text-primary-400 hover:underline">View all</a>
<div class="section-header">
<div class="section-indicator bg-accent"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Recent Packages</span>
</div>
<a routerLink="/packages" class="font-mono text-xs text-primary uppercase tracking-wider hover:underline">View all</a>
</div>
<div class="card-content p-0">
@if (packages().length === 0) {
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<div class="p-6 text-center text-muted-foreground font-mono text-sm">
No packages yet
</div>
} @else {
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
<ul class="divide-y divide-border">
@for (pkg of packages().slice(0, 5); track pkg.id) {
<li class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50">
<li class="px-6 py-4 hover:bg-muted/30 transition-colors">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-gray-900 dark:text-gray-100">{{ pkg.name }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ pkg.protocol }} · {{ pkg.latestVersion || 'No versions' }}</p>
<p class="font-mono font-medium text-foreground">{{ pkg.name }}</p>
<p class="font-mono text-xs text-muted-foreground uppercase">{{ pkg.protocol }} · {{ pkg.latestVersion || 'No versions' }}</p>
</div>
<span class="badge-default">{{ pkg.downloadCount }} downloads</span>
</div>
@@ -108,27 +114,30 @@ import { AuthService } from '../../core/services/auth.service';
<!-- Organizations -->
<div class="card">
<div class="card-header flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Your Organizations</h2>
<a routerLink="/organizations" class="text-sm text-primary-600 dark:text-primary-400 hover:underline">View all</a>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Your Organizations</span>
</div>
<a routerLink="/organizations" class="font-mono text-xs text-primary uppercase tracking-wider hover:underline">View all</a>
</div>
<div class="card-content p-0">
@if (organizations().length === 0) {
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<div class="p-6 text-center text-muted-foreground font-mono text-sm">
No organizations yet
</div>
} @else {
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
<ul class="divide-y divide-border">
@for (org of organizations().slice(0, 5); track org.id) {
<li class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50">
<li class="px-6 py-4 hover:bg-muted/30 transition-colors">
<a [routerLink]="['/organizations', org.id]" class="flex items-center gap-4">
<div class="w-10 h-10 bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
<div class="w-10 h-10 bg-muted flex items-center justify-center">
<span class="font-mono text-sm font-medium text-muted-foreground">
{{ org.name.charAt(0).toUpperCase() }}
</span>
</div>
<div>
<p class="font-medium text-gray-900 dark:text-gray-100">{{ org.displayName }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ org.memberCount }} members</p>
<p class="font-mono font-medium text-foreground">{{ org.displayName }}</p>
<p class="font-mono text-xs text-muted-foreground">{{ org.memberCount }} members</p>
</div>
</a>
</li>
@@ -141,43 +150,46 @@ import { AuthService } from '../../core/services/auth.service';
<!-- Quick Actions -->
<div class="mt-8">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Quick Actions</h2>
<div class="section-header mb-4">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Quick Actions</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<a routerLink="/organizations" class="card card-content flex items-center gap-3 hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<div class="p-2 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
<svg class="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a routerLink="/organizations" class="card card-content flex items-center gap-3 hover:border-primary/50 transition-colors">
<div class="p-2 bg-primary/10">
<svg class="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Create Organization</span>
<span class="font-mono text-sm font-medium text-foreground uppercase">Create Organization</span>
</a>
<a routerLink="/tokens" class="card card-content flex items-center gap-3 hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<div class="p-2 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
<svg class="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a routerLink="/tokens" class="card card-content flex items-center gap-3 hover:border-primary/50 transition-colors">
<div class="p-2 bg-primary/10">
<svg class="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Generate API Token</span>
<span class="font-mono text-sm font-medium text-foreground uppercase">Generate API Token</span>
</a>
<a routerLink="/packages" class="card card-content flex items-center gap-3 hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<div class="p-2 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
<svg class="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a routerLink="/packages" class="card card-content flex items-center gap-3 hover:border-primary/50 transition-colors">
<div class="p-2 bg-primary/10">
<svg class="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Search Packages</span>
<span class="font-mono text-sm font-medium text-foreground uppercase">Search Packages</span>
</a>
<a routerLink="/settings" class="card card-content flex items-center gap-3 hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<div class="p-2 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
<svg class="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a routerLink="/settings" class="card card-content flex items-center gap-3 hover:border-primary/50 transition-colors">
<div class="p-2 bg-primary/10">
<svg class="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Account Settings</span>
<span class="font-mono text-sm font-medium text-foreground uppercase">Account Settings</span>
</a>
</div>
</div>

View File

@@ -9,21 +9,29 @@ import { ToastService } from '../../core/services/toast.service';
standalone: true,
imports: [FormsModule],
template: `
<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
<div class="min-h-screen flex items-center justify-center bg-background px-4">
<div class="max-w-md w-full">
<!-- Logo -->
<div class="text-center mb-8">
<div class="w-16 h-16 bg-primary-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="w-16 h-16 bg-primary flex items-center justify-center mx-auto mb-4">
<svg class="w-10 h-10 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Stack.Gallery Registry</h1>
<p class="text-gray-500 dark:text-gray-400 mt-2">Sign in to your account</p>
<h1 class="font-mono text-2xl font-bold text-foreground uppercase tracking-wider">Stack.Gallery</h1>
<p class="font-mono text-sm text-muted-foreground mt-2 uppercase tracking-wider">Registry</p>
</div>
<!-- Login form -->
<form (ngSubmit)="login()" class="card p-6 space-y-6">
<!-- Terminal header -->
<div class="code-header -mx-6 -mt-6 mb-6">
<div class="terminal-dot dot-red"></div>
<div class="terminal-dot dot-orange"></div>
<div class="terminal-dot dot-green"></div>
<span class="ml-2 font-mono text-xs text-muted-foreground uppercase">Sign In</span>
</div>
<div class="space-y-4">
<div>
<label for="email" class="label block mb-1.5">Email</label>
@@ -55,8 +63,8 @@ import { ToastService } from '../../core/services/toast.service';
</div>
@if (error()) {
<div class="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
<p class="text-sm text-red-600 dark:text-red-400">{{ error() }}</p>
<div class="p-3 bg-destructive/10 border border-destructive/30">
<p class="font-mono text-sm text-destructive">{{ error() }}</p>
</div>
}
@@ -77,7 +85,7 @@ import { ToastService } from '../../core/services/toast.service';
</button>
</form>
<p class="text-center text-sm text-gray-500 dark:text-gray-400 mt-6">
<p class="text-center font-mono text-xs text-muted-foreground mt-6 uppercase tracking-wider">
Enterprise Package Registry
</p>
</div>

View File

@@ -12,8 +12,11 @@ import { ToastService } from '../../core/services/toast.service';
<div class="p-6 max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Organizations</h1>
<p class="text-gray-500 dark:text-gray-400 mt-1">Manage your organizations and repositories</p>
<div class="section-header mb-2">
<div class="section-indicator"></div>
<span class="section-label">Organizations</span>
</div>
<h1 class="font-mono text-2xl font-bold text-foreground">Manage your organizations</h1>
</div>
<button (click)="showCreateModal.set(true)" class="btn-primary btn-md">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -25,18 +28,18 @@ import { ToastService } from '../../core/services/toast.service';
@if (loading()) {
<div class="flex items-center justify-center py-12">
<svg class="animate-spin h-8 w-8 text-primary-600" fill="none" viewBox="0 0 24 24">
<svg class="animate-spin h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
} @else if (organizations().length === 0) {
<div class="card card-content text-center py-12">
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 text-muted-foreground mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No organizations yet</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4">Create your first organization to start managing packages</p>
<h3 class="font-mono text-lg font-medium text-foreground mb-2">No organizations yet</h3>
<p class="font-mono text-sm text-muted-foreground mb-4">Create your first organization to start managing packages</p>
<button (click)="showCreateModal.set(true)" class="btn-primary btn-md">
Create Organization
</button>
@@ -44,28 +47,28 @@ import { ToastService } from '../../core/services/toast.service';
} @else {
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@for (org of organizations(); track org.id) {
<a [routerLink]="['/organizations', org.id]" class="card hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<a [routerLink]="['/organizations', org.id]" class="card hover:border-primary/50 transition-colors">
<div class="card-content">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center flex-shrink-0">
<span class="text-lg font-medium text-gray-600 dark:text-gray-300">
<div class="w-12 h-12 bg-muted flex items-center justify-center flex-shrink-0">
<span class="font-mono text-lg font-medium text-muted-foreground">
{{ org.name.charAt(0).toUpperCase() }}
</span>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 truncate">{{ org.displayName }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">&#64;{{ org.name }}</p>
<h3 class="font-mono font-semibold text-foreground truncate">{{ org.displayName }}</h3>
<p class="font-mono text-sm text-muted-foreground">&#64;{{ org.name }}</p>
</div>
@if (org.isPublic) {
<span class="badge-default">Public</span>
<span class="badge-accent">Public</span>
} @else {
<span class="badge-warning">Private</span>
<span class="badge-primary">Private</span>
}
</div>
@if (org.description) {
<p class="mt-3 text-sm text-gray-600 dark:text-gray-400 line-clamp-2">{{ org.description }}</p>
<p class="mt-3 text-sm text-muted-foreground line-clamp-2">{{ org.description }}</p>
}
<div class="mt-4 flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<div class="mt-4 flex items-center gap-4 font-mono text-xs text-muted-foreground">
<span class="flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
@@ -81,10 +84,13 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Create Modal -->
@if (showCreateModal()) {
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div class="card w-full max-w-md mx-4">
<div class="card-header flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Create Organization</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Create Organization</span>
</div>
<button (click)="showCreateModal.set(false)" class="btn-ghost btn-sm p-1">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -103,7 +109,7 @@ import { ToastService } from '../../core/services/toast.service';
required
pattern="^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
/>
<p class="text-xs text-gray-500 mt-1">Lowercase letters, numbers, and hyphens only</p>
<p class="font-mono text-xs text-muted-foreground mt-1">Lowercase letters, numbers, and hyphens only</p>
</div>
<div>
<label class="label block mb-1.5">Display Name</label>
@@ -130,9 +136,9 @@ import { ToastService } from '../../core/services/toast.service';
[(ngModel)]="newOrg.isPublic"
name="isPublic"
id="isPublic"
class="w-4 h-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
class="w-4 h-4 border-border text-primary focus:ring-primary"
/>
<label for="isPublic" class="text-sm text-gray-700 dark:text-gray-300">Make this organization public</label>
<label for="isPublic" class="font-mono text-sm text-foreground">Make this organization public</label>
</div>
</form>
<div class="card-footer flex justify-end gap-3">

View File

@@ -11,8 +11,11 @@ import { ToastService } from '../../core/services/toast.service';
template: `
<div class="p-6 max-w-7xl mx-auto">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Packages</h1>
<p class="text-gray-500 dark:text-gray-400 mt-1">Browse and search all available packages</p>
<div class="section-header mb-2">
<div class="section-indicator bg-accent"></div>
<span class="section-label">Packages</span>
</div>
<h1 class="font-mono text-2xl font-bold text-foreground">Browse and search packages</h1>
</div>
<!-- Search and Filters -->
@@ -46,18 +49,18 @@ import { ToastService } from '../../core/services/toast.service';
@if (loading()) {
<div class="flex items-center justify-center py-12">
<svg class="animate-spin h-8 w-8 text-primary-600" fill="none" viewBox="0 0 24 24">
<svg class="animate-spin h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
} @else if (packages().length === 0) {
<div class="card card-content text-center py-12">
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 text-muted-foreground mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No packages found</h3>
<p class="text-gray-500 dark:text-gray-400">
<h3 class="font-mono text-lg font-medium text-foreground mb-2">No packages found</h3>
<p class="font-mono text-sm text-muted-foreground">
@if (searchQuery || selectedProtocol) {
Try adjusting your search or filters
} @else {
@@ -67,29 +70,29 @@ import { ToastService } from '../../core/services/toast.service';
</div>
} @else {
<div class="card">
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
<ul class="divide-y divide-border">
@for (pkg of packages(); track pkg.id) {
<li>
<a [routerLink]="['/packages', pkg.id]" class="block px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50">
<a [routerLink]="['/packages', pkg.id]" class="block px-6 py-4 hover:bg-muted/30 transition-colors">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<h3 class="font-medium text-gray-900 dark:text-gray-100 truncate">{{ pkg.name }}</h3>
<span class="badge-primary">{{ pkg.protocol }}</span>
<h3 class="font-mono font-medium text-foreground truncate">{{ pkg.name }}</h3>
<span class="badge-accent">{{ pkg.protocol }}</span>
@if (pkg.isPrivate) {
<span class="badge-warning">Private</span>
<span class="badge-primary">Private</span>
}
</div>
@if (pkg.description) {
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">{{ pkg.description }}</p>
<p class="text-sm text-muted-foreground mt-1 line-clamp-2">{{ pkg.description }}</p>
}
<div class="flex items-center gap-4 mt-2 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-4 mt-2 font-mono text-xs text-muted-foreground">
<span>{{ pkg.latestVersion || 'No versions' }}</span>
<span>{{ pkg.downloadCount }} downloads</span>
<span>Updated {{ formatDate(pkg.updatedAt) }}</span>
</div>
</div>
<svg class="w-5 h-5 text-gray-400 ml-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5 text-muted-foreground ml-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</div>

View File

@@ -9,23 +9,32 @@ import { ToastService } from '../../core/services/toast.service';
imports: [FormsModule],
template: `
<div class="p-6 max-w-2xl mx-auto">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">Account Settings</h1>
<div class="mb-6">
<div class="section-header mb-2">
<div class="section-indicator"></div>
<span class="section-label">Settings</span>
</div>
<h1 class="font-mono text-2xl font-bold text-foreground">Account Settings</h1>
</div>
<!-- Profile Section -->
<div class="card mb-6">
<div class="card-header">
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Profile</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Profile</span>
</div>
</div>
<div class="card-content space-y-4">
<div class="flex items-center gap-4 pb-4 border-b border-gray-200 dark:border-gray-700">
<div class="w-16 h-16 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center">
<span class="text-2xl font-medium text-gray-600 dark:text-gray-300">
<div class="flex items-center gap-4 pb-4 border-b border-border">
<div class="w-16 h-16 bg-muted flex items-center justify-center">
<span class="font-mono text-2xl font-medium text-muted-foreground">
{{ userInitial() }}
</span>
</div>
<div>
<p class="font-medium text-gray-900 dark:text-gray-100">{{ user()?.displayName }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ user()?.email }}</p>
<p class="font-mono font-medium text-foreground">{{ user()?.displayName }}</p>
<p class="font-mono text-sm text-muted-foreground">{{ user()?.email }}</p>
</div>
</div>
@@ -43,10 +52,10 @@ import { ToastService } from '../../core/services/toast.service';
<input
type="text"
[value]="user()?.username"
class="input bg-gray-50 dark:bg-gray-900"
class="input bg-muted"
disabled
/>
<p class="text-xs text-gray-500 mt-1">Username cannot be changed</p>
<p class="font-mono text-xs text-muted-foreground mt-1">Username cannot be changed</p>
</div>
<div>
@@ -54,10 +63,10 @@ import { ToastService } from '../../core/services/toast.service';
<input
type="email"
[value]="user()?.email"
class="input bg-gray-50 dark:bg-gray-900"
class="input bg-muted"
disabled
/>
<p class="text-xs text-gray-500 mt-1">Contact support to change your email</p>
<p class="font-mono text-xs text-muted-foreground mt-1">Contact support to change your email</p>
</div>
</div>
<div class="card-footer flex justify-end">
@@ -74,7 +83,10 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Security Section -->
<div class="card mb-6">
<div class="card-header">
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Security</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Security</span>
</div>
</div>
<div class="card-content space-y-4">
<div>
@@ -119,10 +131,13 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Sessions Section -->
<div class="card">
<div class="card-header flex items-center justify-between">
<h2 class="font-semibold text-gray-900 dark:text-gray-100">Sessions</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Sessions</span>
</div>
</div>
<div class="card-content">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
<p class="font-mono text-sm text-muted-foreground mb-4">
Sign out of all other browser sessions. This will not affect your current session.
</p>
<button (click)="logoutAllSessions()" class="btn-secondary btn-md">
@@ -132,15 +147,18 @@ import { ToastService } from '../../core/services/toast.service';
</div>
<!-- Danger Zone -->
<div class="card mt-6 border-red-200 dark:border-red-800">
<div class="card-header bg-red-50 dark:bg-red-900/20">
<h2 class="font-semibold text-red-700 dark:text-red-400">Danger Zone</h2>
<div class="card mt-6 border-destructive/50">
<div class="card-header bg-destructive/10">
<div class="section-header">
<div class="section-indicator bg-destructive"></div>
<span class="font-mono text-sm font-semibold text-destructive uppercase">Danger Zone</span>
</div>
</div>
<div class="card-content">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
<p class="font-mono text-sm text-muted-foreground mb-4">
Once you delete your account, there is no going back. Please be certain.
</p>
<button class="btn-md bg-red-600 text-white hover:bg-red-700">
<button class="btn-md bg-destructive text-destructive-foreground hover:bg-destructive/90">
Delete Account
</button>
</div>

View File

@@ -12,8 +12,11 @@ import { ToastService } from '../../core/services/toast.service';
<div class="p-6 max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">API Tokens</h1>
<p class="text-gray-500 dark:text-gray-400 mt-1">Manage your API tokens for registry access</p>
<div class="section-header mb-2">
<div class="section-indicator"></div>
<span class="section-label">API Tokens</span>
</div>
<h1 class="font-mono text-2xl font-bold text-foreground">Manage your API tokens for registry access</h1>
</div>
<button (click)="showCreateModal.set(true)" class="btn-primary btn-md">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -25,44 +28,44 @@ import { ToastService } from '../../core/services/toast.service';
@if (loading()) {
<div class="flex items-center justify-center py-12">
<svg class="animate-spin h-8 w-8 text-primary-600" fill="none" viewBox="0 0 24 24">
<svg class="animate-spin h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
} @else if (tokens().length === 0) {
<div class="card card-content text-center py-12">
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 text-muted-foreground mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No API tokens</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4">Create a token to authenticate with the registry</p>
<h3 class="font-mono text-lg font-medium text-foreground mb-2">No API tokens</h3>
<p class="font-mono text-sm text-muted-foreground mb-4">Create a token to authenticate with the registry</p>
<button (click)="showCreateModal.set(true)" class="btn-primary btn-md">Create Token</button>
</div>
} @else {
<div class="card">
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
<ul class="divide-y divide-border">
@for (token of tokens(); track token.id) {
<li class="px-6 py-4">
<li class="px-6 py-4 hover:bg-muted/30 transition-colors">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-3">
<h3 class="font-medium text-gray-900 dark:text-gray-100">{{ token.name }}</h3>
<h3 class="font-mono font-medium text-foreground">{{ token.name }}</h3>
@for (protocol of token.protocols.slice(0, 3); track protocol) {
<span class="badge-primary text-xs">{{ protocol }}</span>
<span class="badge-accent">{{ protocol }}</span>
}
@if (token.protocols.length > 3) {
<span class="badge-default text-xs">+{{ token.protocols.length - 3 }}</span>
<span class="badge-default">+{{ token.protocols.length - 3 }}</span>
}
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
<code class="font-mono">{{ token.tokenPrefix }}...</code>
<p class="font-mono text-sm text-muted-foreground mt-1">
<code>{{ token.tokenPrefix }}...</code>
@if (token.expiresAt) {
<span class="mx-2">·</span>
<span>Expires {{ formatDate(token.expiresAt) }}</span>
}
</p>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
<p class="font-mono text-xs text-muted-foreground/70 mt-1">
Created {{ formatDate(token.createdAt) }}
@if (token.lastUsedAt) {
· Last used {{ formatDate(token.lastUsedAt) }}
@@ -70,7 +73,7 @@ import { ToastService } from '../../core/services/toast.service';
· {{ token.usageCount }} uses
</p>
</div>
<button (click)="revokeToken(token)" class="btn-ghost btn-sm text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-900/20">
<button (click)="revokeToken(token)" class="btn-ghost btn-sm text-destructive hover:text-destructive hover:bg-destructive/10">
Revoke
</button>
</div>
@@ -82,10 +85,13 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Create Modal -->
@if (showCreateModal()) {
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div class="card w-full max-w-lg mx-4">
<div class="card-header flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Create API Token</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Create API Token</span>
</div>
<button (click)="closeCreateModal()" class="btn-ghost btn-sm p-1">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -107,10 +113,10 @@ import { ToastService } from '../../core/services/toast.service';
<div class="flex flex-wrap gap-2">
@for (protocol of availableProtocols; track protocol) {
<label
class="flex items-center gap-2 px-3 py-1.5 rounded-md border cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
class="flex items-center gap-2 px-3 py-1.5 border cursor-pointer hover:bg-muted/30 transition-colors font-mono text-sm"
[ngClass]="{
'bg-primary-50 border-primary-300 dark:bg-primary-900/20': newToken.protocols.includes(protocol),
'border-gray-300 dark:border-gray-600': !newToken.protocols.includes(protocol)
'bg-primary/10 border-primary text-primary': newToken.protocols.includes(protocol),
'border-border text-foreground': !newToken.protocols.includes(protocol)
}">
<input
type="checkbox"
@@ -118,7 +124,7 @@ import { ToastService } from '../../core/services/toast.service';
(change)="toggleProtocol(protocol)"
class="sr-only"
/>
<span class="text-sm">{{ protocol }}</span>
<span>{{ protocol }}</span>
</label>
}
</div>
@@ -150,21 +156,24 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Token Created Modal -->
@if (createdToken()) {
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div class="card w-full max-w-lg mx-4">
<div class="card-header">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Token Created</h2>
<div class="section-header">
<div class="section-indicator bg-accent"></div>
<span class="font-mono text-sm font-semibold text-foreground uppercase">Token Created</span>
</div>
</div>
<div class="card-content space-y-4">
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<p class="text-sm text-yellow-800 dark:text-yellow-200 font-medium mb-2">
<div class="p-4 bg-primary/10 border border-primary/30">
<p class="font-mono text-sm text-primary font-medium">
Make sure to copy your token now. You won't be able to see it again!
</p>
</div>
<div>
<label class="label block mb-1.5">Your new token:</label>
<div class="flex gap-2">
<code class="flex-1 px-3 py-2 bg-gray-100 dark:bg-gray-900 rounded-md text-sm font-mono overflow-x-auto">
<code class="flex-1 px-3 py-2 bg-muted text-sm font-mono text-foreground overflow-x-auto">
{{ createdToken() }}
</code>
<button (click)="copyToken()" class="btn-secondary btn-md">

View File

@@ -10,55 +10,55 @@ import { ToastService } from '../../../core/services/toast.service';
template: `
<div class="min-h-screen flex">
<!-- Sidebar -->
<aside class="w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col">
<aside class="w-64 bg-background border-r border-border flex flex-col">
<!-- Logo -->
<div class="h-16 flex items-center px-6 border-b border-gray-200 dark:border-gray-700">
<a routerLink="/" class="flex items-center gap-2">
<div class="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="h-16 flex items-center px-6 border-b border-border">
<a routerLink="/" class="flex items-center gap-3">
<div class="w-8 h-8 bg-primary flex items-center justify-center">
<svg class="w-5 h-5 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<span class="font-semibold text-lg">Stack.Gallery</span>
<span class="font-mono font-bold text-lg uppercase tracking-wider text-foreground">Stack.Gallery</span>
</a>
</div>
<!-- Navigation -->
<nav class="flex-1 p-4 space-y-1">
<a routerLink="/dashboard" routerLinkActive="bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400"
class="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
<a routerLink="/dashboard" routerLinkActive="bg-primary/10 text-primary"
class="nav-link">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Dashboard
</a>
<a routerLink="/organizations" routerLinkActive="bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400"
class="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
<a routerLink="/organizations" routerLinkActive="bg-primary/10 text-primary"
class="nav-link">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
Organizations
</a>
<a routerLink="/packages" routerLinkActive="bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400"
class="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
<a routerLink="/packages" routerLinkActive="bg-primary/10 text-primary"
class="nav-link">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
Packages
</a>
<a routerLink="/tokens" routerLinkActive="bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400"
class="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
<a routerLink="/tokens" routerLinkActive="bg-primary/10 text-primary"
class="nav-link">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
API Tokens
</a>
<a routerLink="/settings" routerLinkActive="bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400"
class="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
<a routerLink="/settings" routerLinkActive="bg-primary/10 text-primary"
class="nav-link">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
@@ -68,22 +68,22 @@ import { ToastService } from '../../../core/services/toast.service';
</nav>
<!-- User section -->
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
<div class="p-4 border-t border-border">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
<div class="w-8 h-8 bg-muted flex items-center justify-center">
<span class="font-mono text-sm font-medium text-muted-foreground">
{{ userInitial() }}
</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
<p class="font-mono text-sm font-medium text-foreground truncate">
{{ userName() }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400 truncate">
<p class="font-mono text-xs text-muted-foreground truncate">
{{ userEmail() }}
</p>
</div>
<button (click)="logout()" class="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">
<button (click)="logout()" class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted/30 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
@@ -93,7 +93,7 @@ import { ToastService } from '../../../core/services/toast.service';
</aside>
<!-- Main content -->
<main class="flex-1 bg-gray-50 dark:bg-gray-900 overflow-auto">
<main class="flex-1 bg-background overflow-auto">
<router-outlet />
</main>
</div>