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

188
design-system.md Normal file
View File

@@ -0,0 +1,188 @@
# Stack.Gallery Design System
Bloomberg terminal-inspired aesthetic with dark theme, sharp corners, and orange/green accent colors.
## Colors (HSL)
### Dark Theme (Default)
| Token | HSL | Hex | Usage |
|--------------------|---------------|---------|------------------------------------|
| background | 0 0% 0% | #000000 | Page background |
| foreground | 0 0% 100% | #FFFFFF | Primary text |
| primary | 33 100% 50% | #FF8000 | Bloomberg orange, CTAs, highlights |
| primary-foreground | 0 0% 0% | #000000 | Text on primary buttons |
| accent | 142 71% 45% | #22C55E | Terminal green, success states |
| accent-foreground | 0 0% 0% | #000000 | Text on accent |
| muted | 0 0% 8% | #141414 | Subtle backgrounds |
| muted-foreground | 0 0% 55% | #8C8C8C | Secondary text, labels |
| card | 0 0% 4% | #0A0A0A | Card backgrounds |
| border | 0 0% 15% | #262626 | All borders, dividers |
| destructive | 0 84% 60% | #EF4444 | Errors, terminal red dots |
### Light Theme
| Token | HSL | Hex |
|------------------|---------------|---------|
| background | 0 0% 100% | #FFFFFF |
| foreground | 0 0% 5% | #0D0D0D |
| primary | 33 100% 45% | #E67300 |
| accent | 142 71% 35% | #16A34A |
| muted | 0 0% 96% | #F5F5F5 |
| muted-foreground | 0 0% 40% | #666666 |
| card | 0 0% 98% | #FAFAFA |
| border | 0 0% 90% | #E5E5E5 |
---
## Typography
### Font Families
```css
--font-mono: 'JetBrains Mono', 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
```
### Usage
- **Monospace**: All headings, labels, navigation, buttons, data, code
- **Sans-serif**: Body text, descriptions only
### Font Sizes
| Element | Size | Weight | Letter Spacing |
|-----------------|------------------------|--------|-----------------|
| H1 (Hero) | 3rem / 4rem (md) | 700 | -0.02em (tight) |
| H2 (Section) | 1.5rem / 1.875rem (md) | 700 | -0.02em |
| H3 (Card title) | 0.875rem | 600 | normal |
| Labels | 0.75rem | 400 | 0.05em (wider) |
| Body | 0.875rem | 400 | normal |
| Small/Tags | 0.75rem | 400 | 0.05em |
### Text Transform
- Navigation, labels, buttons, tags: `uppercase`
- Body text, descriptions: `normal case`
---
## Spacing & Layout
### Border Radius
```css
--radius: 0px; /* All elements: sharp corners */
```
### Container
- Max width: 1280px
- Padding: 1rem (16px) horizontal
### Grid Gaps
- Section padding: 2rem / 4rem vertical
- Card padding: 1.5rem
- Grid gap: 2rem
### Borders
- Width: 1px
- Color: `var(--border)`
- Used extensively: section dividers, cards, tables, inputs
---
## Components
### Buttons
```css
/* Primary */
height: 40px;
padding: 0 24px;
background: var(--primary);
color: var(--primary-foreground);
font: 700 0.875rem/1 var(--font-mono);
text-transform: uppercase;
border: none;
/* Secondary/Outline */
height: 40px;
padding: 0 24px;
background: transparent;
color: var(--foreground);
border: 1px solid var(--border);
font: 400 0.875rem/1 var(--font-mono);
text-transform: uppercase;
```
### Tags/Badges
```css
padding: 4px 8px;
font: 400 0.75rem/1 var(--font-mono);
color: var(--accent);
background: hsl(var(--accent) / 0.05);
border: 1px solid hsl(var(--accent) / 0.3);
text-transform: uppercase;
```
### Code Blocks
```css
background: var(--card);
border: 1px solid var(--border);
font: 400 0.875rem/1.6 var(--font-mono);
/* Header bar */
background: hsl(var(--muted) / 0.5);
border-bottom: 1px solid var(--border);
padding: 8px 12px;
/* Terminal dots */
.dot-red: var(--destructive) / 0.6
.dot-orange: var(--primary) / 0.6
.dot-green: var(--accent) / 0.6
/* Each dot: 8px x 8px square */
```
### Data Tables
```css
border: 1px solid var(--border);
font: 400 0.75rem/1 var(--font-mono);
/* Header row */
background: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-foreground);
/* Data cells */
border-bottom: 1px solid var(--border);
padding: 12px 16px;
/* Status indicators */
.supported: var(--accent) /* green */
```
---
## Visual Patterns
1. **Section Headers**: Small colored square (8x8px) + uppercase label + count on right
2. **Grid Dividers**: Use `divide-x` and `divide-y` with border color
3. **Numbering**: Bold primary-colored numbers (01, 02, etc.)
4. **Hover States**: Subtle background change `hsl(var(--muted) / 0.3)`
5. **Status Indicators**: Colored squares or text (green = active/supported)
---
## Icon Library
Using Lucide Icons (stroke-based, 24px default):
- Github, Book, Terminal, Menu, X, Copy, Check
- Size in UI: 16px (`h-4 w-4`) or 20px (`h-5 w-5`)

View File

@@ -26,5 +26,8 @@
"license": "MIT",
"devDependencies": {
"concurrently": "^9.1.2"
},
"dependencies": {
"@push.rocks/smartdata": "link:../../push.rocks/smartdata"
}
}

7
pnpm-lock.yaml generated
View File

@@ -4,9 +4,16 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
'@push.rocks/smartdata': link:../../push.rocks/smartdata
importers:
.:
dependencies:
'@push.rocks/smartdata':
specifier: link:../../push.rocks/smartdata
version: link:../../push.rocks/smartdata
devDependencies:
concurrently:
specifier: ^9.1.2

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
overrides:
'@push.rocks/smartdata': link:../../push.rocks/smartdata

19
tsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"lib": ["ES2022"],
"types": ["node"],
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
},
"include": ["ts/**/*", "mod.ts"],
"exclude": ["node_modules", "dist", "ui"]
}

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>

View File

@@ -3,80 +3,106 @@
@tailwind utilities;
@layer base {
/* Stack.Gallery Design System - Dark Theme (Default) */
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 199 89% 48%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 199 89% 48%;
--radius: 0.5rem;
--background: 0 0% 0%;
--foreground: 0 0% 100%;
--card: 0 0% 4%;
--card-foreground: 0 0% 100%;
--popover: 0 0% 4%;
--popover-foreground: 0 0% 100%;
--primary: 33 100% 50%;
--primary-foreground: 0 0% 0%;
--secondary: 0 0% 8%;
--secondary-foreground: 0 0% 100%;
--muted: 0 0% 8%;
--muted-foreground: 0 0% 55%;
--accent: 142 71% 45%;
--accent-foreground: 0 0% 0%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 15%;
--input: 0 0% 15%;
--ring: 33 100% 50%;
--radius: 0px;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 199 89% 48%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 199 89% 48%;
/* Light Theme */
.light {
--background: 0 0% 100%;
--foreground: 0 0% 5%;
--card: 0 0% 98%;
--card-foreground: 0 0% 5%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 5%;
--primary: 33 100% 45%;
--primary-foreground: 0 0% 100%;
--secondary: 0 0% 96%;
--secondary-foreground: 0 0% 5%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 40%;
--accent: 142 71% 35%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 90%;
--input: 0 0% 90%;
--ring: 33 100% 45%;
}
}
@layer base {
* {
@apply border-gray-200 dark:border-gray-800;
@apply border-border;
}
body {
@apply bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100;
@apply bg-background text-foreground font-sans;
font-feature-settings: "rlig" 1, "calt" 1;
}
/* Typography - Monospace for headings */
h1, h2, h3, h4, h5, h6 {
@apply font-mono tracking-tighter;
}
h1 {
@apply text-3xl md:text-4xl font-bold;
}
h2 {
@apply text-xl md:text-2xl font-bold;
}
h3 {
@apply text-sm font-semibold;
}
}
@layer components {
/* Buttons */
.btn {
@apply inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
@apply inline-flex items-center justify-center h-10 px-6
font-mono text-sm font-bold uppercase tracking-wider
transition-colors
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
disabled:pointer-events-none disabled:opacity-50;
}
.btn-primary {
@apply btn bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500;
@apply btn bg-primary text-primary-foreground hover:opacity-90;
}
.btn-secondary {
@apply btn bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700;
@apply btn bg-transparent text-foreground border border-border hover:bg-muted;
}
.btn-ghost {
@apply btn hover:bg-gray-100 dark:hover:bg-gray-800;
@apply btn hover:bg-muted/30;
}
.btn-destructive {
@apply btn bg-destructive text-destructive-foreground hover:opacity-90;
}
.btn-sm {
@@ -91,54 +117,146 @@
@apply h-12 px-6;
}
/* Inputs */
.input {
@apply flex h-10 w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800
px-3 py-2 text-sm placeholder:text-gray-400 dark:placeholder:text-gray-500
focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent
@apply flex h-10 w-full border border-border bg-background
px-3 py-2 font-mono text-sm
placeholder:text-muted-foreground
focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent
disabled:cursor-not-allowed disabled:opacity-50;
}
.label {
@apply text-sm font-medium text-gray-700 dark:text-gray-300;
@apply font-mono text-xs font-normal uppercase tracking-wider text-muted-foreground;
}
/* Cards */
.card {
@apply rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-800 shadow-sm;
@apply bg-card text-card-foreground border border-border;
}
.card-header {
@apply px-6 py-4 border-b border-gray-200 dark:border-gray-700;
@apply px-6 py-4 border-b border-border;
}
.card-content {
@apply px-6 py-4;
@apply p-6;
}
.card-footer {
@apply px-6 py-4 border-t border-gray-200 dark:border-gray-700;
@apply px-6 py-4 border-t border-border;
}
/* Badges/Tags */
.badge {
@apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium;
@apply inline-flex items-center px-2 py-1
font-mono text-xs uppercase tracking-wider
border;
}
.badge-default {
@apply badge bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200;
@apply badge bg-muted text-muted-foreground border-border;
}
.badge-primary {
@apply badge bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-200;
@apply badge text-primary bg-primary/5 border-primary/30;
}
.badge-accent {
@apply badge text-accent bg-accent/5 border-accent/30;
}
.badge-success {
@apply badge bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200;
@apply badge text-accent bg-accent/5 border-accent/30;
}
.badge-warning {
@apply badge bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200;
@apply badge text-primary bg-primary/5 border-primary/30;
}
.badge-destructive {
@apply badge bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200;
@apply badge text-destructive bg-destructive/5 border-destructive/30;
}
/* Code Blocks */
.code-block {
@apply bg-card border border-border font-mono text-sm;
}
.code-header {
@apply flex items-center gap-2 px-3 py-2 bg-muted/50 border-b border-border;
}
/* Terminal dots */
.terminal-dot {
@apply w-2 h-2;
}
.dot-red {
@apply bg-destructive/60;
}
.dot-orange {
@apply bg-primary/60;
}
.dot-green {
@apply bg-accent/60;
}
/* Tables */
.table {
@apply w-full border border-border font-mono text-xs;
}
.table-header {
@apply bg-muted uppercase tracking-wider text-muted-foreground;
}
.table-cell {
@apply border-b border-border px-4 py-3;
}
/* Section header pattern */
.section-header {
@apply flex items-center gap-3;
}
.section-indicator {
@apply w-2 h-2 bg-primary;
}
.section-label {
@apply font-mono text-xs uppercase tracking-wider text-muted-foreground;
}
/* Navigation */
.nav-link {
@apply flex items-center gap-3 px-3 py-2
font-mono text-sm uppercase tracking-wider
text-muted-foreground
hover:text-foreground hover:bg-muted/30
transition-colors;
}
.nav-link-active {
@apply text-primary bg-primary/10;
}
/* Status indicators */
.status-dot {
@apply w-2 h-2;
}
.status-active {
@apply bg-accent;
}
.status-inactive {
@apply bg-muted-foreground;
}
.status-error {
@apply bg-destructive;
}
}

View File

@@ -7,36 +7,50 @@ module.exports = {
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
accent: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
950: '#4a044e',
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'sans-serif'],
mono: ['JetBrains Mono', 'SF Mono', 'Consolas', 'Liberation Mono', 'monospace'],
},
borderRadius: {
DEFAULT: '0px',
none: '0px',
sm: '0px',
md: '0px',
lg: '0px',
xl: '0px',
'2xl': '0px',
'3xl': '0px',
full: '0px',
},
letterSpacing: {
tighter: '-0.02em',
wider: '0.05em',
},
},
},