Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 4 | marketplace-portal-navigation | draft | false | none | 2026-06-22 |
Phase 4 — UI Design Contract
Visual and interaction contract for Marketplace & Portal Navigation. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Design System
| Property | Value |
|---|---|
| Tool | none (hand-rolled components with shadcn-compatible CSS variable naming) |
| Preset | not applicable |
| Component library | none (custom components, Radix-compatible token structure) |
| Icon library | Inline SVG (project convention from Phase 1-3, no external icon library) |
| Font | Inter, system-ui, -apple-system, sans-serif (declared as --font-sans in globals.css) |
Note: The project uses shadcn-compatible CSS custom property naming (--primary, --card, --muted, etc.) and Tailwind v4 @theme inline mapping, but does not have components.json. All UI components are hand-rolled. This phase continues that pattern.
Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|---|---|---|
| xs | 4px | Icon gaps, inline badge padding, chip internal spacing |
| sm | 8px | Compact element spacing, card internal gaps, filter chip gaps |
| md | 16px | Default element spacing, card padding, search bar padding |
| lg | 24px | Section padding, space between marketplace header and grid |
| xl | 32px | Layout gaps, space between major page sections |
| 2xl | 48px | Empty state vertical padding, page top/bottom padding |
| 3xl | 64px | Not used in this phase |
Exceptions: Sidebar search input height at 36px (not a spacing token — component-specific dimension for comfortable text input within narrow 240px sidebar). Marketplace card minimum height at 180px for visual consistency in the grid.
Typography
| Role | Size | Weight | Line Height | Usage in Phase 4 |
|---|---|---|---|---|
| Body | 14px | 400 (regular) | 1.5 | Card descriptions, sidebar module names, filter labels |
| Label | 12px | 600 (semibold) | 1.4 | Category badges, status badges, sidebar section headers, version text |
| Heading | 20px | 700 (bold) | 1.2 | Page titles ("Marktplatz"), section headings |
| Display | 28px | 700 (bold) | 1.2 | Not used in this phase (reserved for Dashboard phase) |
Font weights used: 400 (regular) and 600 (semibold). 700 (bold) only for page headings per existing codebase convention (text-2xl font-bold in admin pages).
Color
All values in OKLCH (project convention from Phase 1, defined in globals.css).
| Role | Light Mode | Dark Mode | Usage |
|---|---|---|---|
| Dominant (60%) | oklch(0.99 0 0) — near-white |
oklch(0.23 0.01 260) — dark gray-blue |
Page background, marketplace grid background |
| Secondary (30%) | oklch(1 0 0) / oklch(0.96 0 0) — white/light gray |
oklch(0.27 0.01 260) / oklch(0.30 0.01 260) — dark card/muted |
Marketplace cards (bg-card), sidebar (bg-sidebar), filter bar background, tenant selector dropdown |
| Accent (10%) | oklch(0.91 0.19 102) — Tessera yellow |
oklch(0.91 0.19 102) — same yellow |
See reserved-for list below |
| Destructive | oklch(0.55 0.2 27) — red |
oklch(0.55 0.2 27) — same red |
Deactivation confirmation dialog only |
| Success | oklch(0.55 0.16 145) — green |
oklch(0.55 0.16 145) — green |
"Aktiviert" status badge, activation success toast |
Accent reserved for:
- "Aktivieren" / "Modul aktivieren" primary CTA button background
- Active module card border highlight on hover (
hover:border-primary/30— existing pattern) - Active sidebar item background accent (
bg-sidebar-accent) - Marketplace search input focus ring (
ring-ringmaps to primary) - Tenant context selector active state indicator
- Toggle switch active state (existing pattern from admin/modules)
Accent explicitly NOT used for:
- Category badges (use
bg-mutedwithtext-muted-foreground) - Status text (use semantic green/red)
- Card backgrounds (use
bg-card)
Component Inventory
New Components (Phase 4)
| Component | Location | Purpose |
|---|---|---|
MarketplaceCard |
apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx |
Module card for marketplace grid — extends ModuleCard pattern with activation toggle and status badge |
MarketplaceSearch |
apps/web/src/app/(portal)/marketplace/components/MarketplaceSearch.tsx |
Search input with live filter, debounced 300ms |
CategoryFilter |
apps/web/src/app/(portal)/marketplace/components/CategoryFilter.tsx |
Horizontal chip/pill row for category filtering |
StatusFilter |
apps/web/src/app/(portal)/marketplace/components/StatusFilter.tsx |
Tab bar: "Alle" / "Aktiviert" / "Verfuegbar" for filtering by activation status |
TenantContextSelector |
apps/web/src/app/(portal)/marketplace/components/TenantContextSelector.tsx |
Dropdown for Super-Admin to switch tenant context in marketplace |
ModuleDetailView |
apps/web/src/app/(portal)/marketplace/[slug]/page.tsx |
Compact detail page for a single module |
ActivationDialog |
apps/web/src/app/(portal)/marketplace/components/ActivationDialog.tsx |
Confirmation dialog for deactivation only (activation is immediate with undo toast) |
SidebarSearch |
apps/web/src/components/layout/sidebar-search.tsx |
Inline search/filter field within sidebar categories section |
Reused Components (from Phase 1-3)
| Component | Source | Reuse |
|---|---|---|
ModuleCard |
apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx |
Pattern reference for MarketplaceCard — same card structure, extended with activation controls |
Sidebar |
apps/web/src/components/layout/sidebar.tsx |
Extended with SidebarSearch and individual module links per category |
Header |
apps/web/src/components/layout/header.tsx |
Unchanged — breadcrumb updates for marketplace route |
AppShell |
apps/web/src/components/layout/app-shell.tsx |
Unchanged — marketplace pages render within existing shell |
Layout Specifications
Marketplace Page (/marketplace)
+----------------------------------------------------------+
| Header (sticky, 60px) |
+--------+-------------------------------------------------+
| Sidebar | Marketplace Page |
| 240px | |
| | [Tenant Selector - Super-Admin only] |
| | |
| | Marktplatz (h1, 20px bold) |
| | Entdecken Sie verfuegbare Module (14px muted) |
| | |
| | [Search input, full width] |
| | [StatusTabs: Alle | Aktiviert | Verfuegbar] |
| | [CategoryChips: Alle | Domain-Tools | Utils...] |
| | |
| | +--------+ +--------+ +--------+ |
| | | Card 1 | | Card 2 | | Card 3 | |
| | +--------+ +--------+ +--------+ |
| | +--------+ +--------+ +--------+ |
| | | Card 4 | | Card 5 | | Card 6 | |
| | +--------+ +--------+ +--------+ |
+--------+-------------------------------------------------+
Grid responsive breakpoints:
- Mobile (<768px): 1 column, sidebar hidden
- Tablet (768px-1023px): 2 columns, sidebar collapsed or hidden
- Desktop (1024px-1279px): 3 columns, sidebar expanded
- Wide (1280px+): 4 columns, sidebar expanded
Grid gap: 16px (gap-4)
MarketplaceCard Anatomy
+------------------------------------------+
| [Icon 24x24] Module Name |
| Category Badge Status Badge|
| |
| Localized description text, max 2 lines |
| with line-clamp-2 truncation. |
| |
| v1.0.0 [Aktivieren] (button) |
+------------------------------------------+
- Card padding: 20px (
p-5) - Card border:
border border-border(1px) - Card background:
bg-card - Card border-radius: 8px (
rounded-lg) - Card hover:
hover:shadow-md hover:border-primary/30(existing pattern) - Icon container:
rounded-md bg-muted p-2.5(40px total, existing pattern) - Status badge (activated):
bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400rounded-full px-2 py-0.5 text-xs - Status badge (available):
bg-muted text-muted-foregroundrounded-full px-2 py-0.5 text-xs - Category badge:
bg-muted text-muted-foregroundrounded-full px-2 py-0.5 text-xs - Activation button (activate):
bg-primary text-primary-foregroundrounded-md px-3 py-1.5 text-sm font-medium - Activation button (deactivate):
border border-border text-foreground hover:bg-mutedrounded-md px-3 py-1.5 text-sm font-medium
Module Detail Page (/marketplace/[slug])
Compact detail view (appropriate for v1 module count). Single-column layout within main content area.
+------------------------------------------+
| < Zurueck zum Marktplatz |
| |
| [Icon 48x48] |
| Module Name (h1, 20px bold) |
| Category Badge v1.0.0 |
| |
| Full localized description text. |
| No line clamping. Renders full text. |
| |
| Status: Aktiviert / Nicht aktiviert |
| [Aktivieren / Deaktivieren] (button) |
+------------------------------------------+
- Back link:
text-sm text-muted-foreground hover:text-foregroundwith left chevron icon - Icon container:
rounded-lg bg-muted p-4(56px total) — larger than card icon - Max content width:
max-w-2xl(672px)
Sidebar Enhancement
Sidebar (240px expanded):
+----------------------------------+
| [Dashboard icon] Dashboard |
| [Cart icon] Marktplatz |
+----------------------------------+
| [Search input _______________] | <-- NEW: SidebarSearch
+----------------------------------+
| KATEGORIEN [chevron] |
| [Folder] Domain Tools (1) |
| - Domaincheck |
| [Folder] Utilities (2) |
| - Tool A |
| - Tool B |
+----------------------------------+
| VERWALTUNG |
| [Users icon] Benutzer |
| [Home icon] Mandanten | (Super-Admin only)
| [Box icon] Module |
| [Layers icon] LDAP |
+----------------------------------+
| [Collapse] |
| [User Info] |
+----------------------------------+
Sidebar (64px collapsed):
+--------+
| [Dash] |
| [Cart] |
+--------+
| [Usr] |
| [Home] | (Super-Admin only)
| [Box] |
| [Lyrs] |
+--------+
| [<] |
| [Avtr] |
+--------+
Sidebar Search (SidebarSearch):
- Position: Between main nav (Dashboard/Marketplace) and Categories section
- Height: 36px
- Padding: 8px horizontal within sidebar's 12px padding (
px-3withinp-3nav) - Placeholder text: "Module suchen..." (14px, muted-foreground)
- Border:
border border-border rounded-md - Focus:
focus:ring-2 focus:ring-ring focus:border-transparent - Behavior: Filters visible module names and category names in real-time. Categories with no matching modules are hidden.
- Collapsed sidebar: Search field hidden (not enough space at 64px)
Sidebar collapsed mode decision: Show main navigation icons (Dashboard, Marketplace) and admin icons. Do NOT show individual module icons -- category/module list is hidden when collapsed. This keeps the collapsed sidebar clean and avoids icon overload.
Tenant Context Selector
+------------------------------------------+
| Mandanten-Kontext: [Dropdown v] |
| +--------------------------------------+ |
| | Firma A GmbH [x] | | <-- currently selected
| | Firma B AG | |
| | Test-Mandant | |
| +--------------------------------------+ |
+------------------------------------------+
- Position: Top of marketplace page, above title, only visible for
SUPER_ADMINrole - Container:
bg-muted/50 rounded-lg p-3 mb-4 border border-border - Label: "Mandanten-Kontext:" 12px semibold text-muted-foreground
- Dropdown: Native
<select>styled withbg-card border border-border rounded-md px-3 py-1.5 text-sm - Width: auto (fits content), min-width 200px
- Behavior: Changing tenant re-fetches all module activation states. Shows loading spinner during fetch.
Interaction Contracts
Marketplace Filtering
Status Tabs (Claude's Discretion decision: Tab bar + badge combination):
- Three tabs: "Alle", "Aktiviert", "Verfuegbar"
- Tab style: underline indicator,
text-sm font-medium - Active tab:
text-foreground border-b-2 border-primary - Inactive tab:
text-muted-foreground hover:text-foreground - Tab counts shown as inline badge:
(3)in muted text after label - Reason for tabs over pure badges: Clear top-level filtering for the most important dimension (activated vs. not). Badges on cards provide per-card status, tabs provide batch filtering.
Category Chips:
- Horizontal scrollable row of pill buttons below status tabs
- First chip: "Alle" (always present, default selected)
- Remaining chips: One per unique category from module list
- Selected:
bg-primary text-primary-foreground - Unselected:
bg-muted text-foreground hover:bg-muted/80 - Chip size:
px-3 py-1 text-xs font-medium rounded-full - Gap between chips: 8px
- Overflow: horizontal scroll with
overflow-x-autoand hidden scrollbar
Search:
- Full-width input above tabs
- Debounce: 300ms
- Filters module name and description (client-side, all modules loaded)
- Clear button (X icon) appears when text is entered
- Empty search shows all modules (respecting active tab + category filter)
Module Activation Flow
Activation (Claude's Discretion decision: Immediate action with success toast):
- User clicks "Aktivieren" button on card or detail page
- Button shows loading spinner (replace text with spinner, button disabled)
- POST
/modules/{id}/activatefires - On success: Button changes to "Deaktivieren" (outline style), status badge updates to green "Aktiviert", success toast appears: "Modul erfolgreich aktiviert"
- On error: Error toast: "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut."
- Sidebar refreshes active modules list (re-fetch
/modules/active)
Deactivation (Claude's Discretion decision: Confirmation dialog for deactivation only):
- User clicks "Deaktivieren" button
- Confirmation dialog appears (modal overlay):
- Title: "Modul deaktivieren"
- Body: "Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren? Das Modul wird aus der Seitenleiste entfernt."
- Cancel button: "Abbrechen" (outline style)
- Confirm button: "Deaktivieren" (destructive style:
bg-destructive text-destructive-foreground)
- On confirm: POST
/modules/{id}/deactivate, button/badge update, sidebar refresh - On cancel: Dialog closes, no action
Reason for asymmetric UX: Activation is low-risk and easily reversible. Deactivation could disrupt users who depend on the module, so a confirmation prevents accidental removal.
Sidebar Module Navigation
Clicking a module in sidebar:
- Navigates to
/modules/{category}/{slug}(existing route from Phase 3) - Module opens in main content area within AppShell
- Sidebar highlights the active module with
bg-sidebar-accent text-sidebar-accent-foreground
Sidebar active state: Current page URL determines which sidebar item gets the active class. Compare pathname against link href.
Toast Notifications
- Position: Bottom-right of viewport
- Duration: 4000ms auto-dismiss
- Style:
bg-card border border-border shadow-lg rounded-lg p-4 - Success icon: Green checkmark
- Error icon: Red X circle
- Max visible: 3 stacked, newest on top
- Animation: Slide in from right, fade out
Copywriting Contract
All copy in both DE and EN via next-intl. Below shows DE as primary, EN in parentheses.
| Element | DE Copy | EN Copy |
|---|---|---|
| Page title | "Marktplatz" | "Marketplace" |
| Page subtitle | "Entdecken und aktivieren Sie Module fuer Ihren Mandanten" | "Discover and activate modules for your tenant" |
| Primary CTA | "Modul aktivieren" | "Activate Module" |
| Deactivation CTA | "Deaktivieren" | "Deactivate" |
| Search placeholder | "Module suchen..." | "Search modules..." |
| Sidebar search placeholder | "Module suchen..." | "Search modules..." |
| Status tab: All | "Alle" | "All" |
| Status tab: Active | "Aktiviert" | "Activated" |
| Status tab: Available | "Verfuegbar" | "Available" |
| Category chip: All | "Alle" | "All" |
| Empty state heading | "Noch keine Module verfuegbar" | "No modules available yet" |
| Empty state body | "Module werden vom Systemadministrator bereitgestellt. Schauen Sie spaeter noch einmal vorbei." | "Modules are provided by the system administrator. Check back later." |
| Empty state (filtered) heading | "Keine Ergebnisse" | "No results" |
| Empty state (filtered) body | "Versuchen Sie einen anderen Suchbegriff oder aendern Sie die Filter." | "Try a different search term or adjust your filters." |
| Error state | "Module konnten nicht geladen werden. Bitte laden Sie die Seite neu." | "Could not load modules. Please reload the page." |
| Activation success toast | "Modul erfolgreich aktiviert" | "Module activated successfully" |
| Deactivation success toast | "Modul erfolgreich deaktiviert" | "Module deactivated successfully" |
| Activation error toast | "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut." | "Activation failed. Please try again." |
| Deactivation dialog title | "Modul deaktivieren" | "Deactivate Module" |
| Deactivation dialog body | "Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren? Das Modul wird aus der Seitenleiste entfernt." | "Do you want to deactivate {moduleName} for this tenant? The module will be removed from the sidebar." |
| Deactivation dialog confirm | "Deaktivieren" | "Deactivate" |
| Deactivation dialog cancel | "Abbrechen" | "Cancel" |
| Tenant selector label | "Mandanten-Kontext" | "Tenant Context" |
| Back to marketplace | "Zurueck zum Marktplatz" | "Back to Marketplace" |
| Detail: version | "Version {version}" | "Version {version}" |
| Detail: status active | "Aktiviert fuer diesen Mandanten" | "Activated for this tenant" |
| Detail: status inactive | "Nicht aktiviert" | "Not activated" |
| Sidebar no results | "Keine Module gefunden" | "No modules found" |
States Matrix
| View | Loading | Empty | Populated | Error | Filtered Empty |
|---|---|---|---|---|---|
| Marketplace grid | Spinner centered in grid area (existing pattern: h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent) |
Illustration + heading + body from copywriting contract | Card grid with modules | Error banner (border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive) + retry hint |
"Keine Ergebnisse" + filter suggestion |
| Module detail | Spinner centered | N/A (404 redirect) | Full detail layout | Error banner | N/A |
| Sidebar modules | No loading state (instant from cache/store) | "Keine Module" (existing) | Category accordion with modules | Silent fail (existing pattern) | "Keine Module gefunden" when search yields nothing |
| Tenant selector | "Laden..." in dropdown | N/A (Super-Admin always has at least 1 tenant) | Tenant list dropdown | Toast error | N/A |
Accessibility
| Requirement | Implementation |
|---|---|
| Keyboard navigation | All interactive elements reachable via Tab. Cards focusable via group-focus-visible:ring-2. Activation buttons in natural tab order. |
| Screen reader labels | aria-label on icon-only buttons. Status badges use role="status". Toggle buttons use role="switch" with aria-checked (existing pattern). |
| Focus management | Dialog traps focus when open (ActivationDialog). Return focus to trigger button on close. |
| Color contrast | All text meets WCAG 2.1 AA (4.5:1 for body, 3:1 for large text). Status badges use text + background, not color alone. |
| Reduced motion | Loading spinner respects prefers-reduced-motion with motion-safe:animate-spin. Toast slide animation gated on motion preference. |
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none (project does not use components.json) | not applicable |
| Third-party | none | not applicable |
Note: Project uses hand-rolled components following shadcn CSS variable naming conventions but without the shadcn CLI or components.json. No registry dependencies exist. All components for this phase are custom-built.
Discretion Decisions Summary
Decisions made by this design contract for areas marked as "Claude's Discretion" in 04-CONTEXT.md:
| Area | Decision | Rationale |
|---|---|---|
| Filter/Status UI | Tab bar ("Alle" / "Aktiviert" / "Verfuegbar") + category chips | Tabs provide clear top-level filtering for the primary dimension. Category chips offer secondary filtering. Badges on cards still show per-item status. |
| Activation confirmation | Immediate activation with success toast. Deactivation gets confirmation dialog. | Asymmetric risk: activation is easily reversed, deactivation affects active users. |
| Visual feedback | Toast notification + inline badge/button state update | Consistent with existing admin/modules toggle pattern. Toast adds explicit confirmation. |
| Sidebar categories | Keep existing accordion pattern | Already established in Phase 1, works well, categories expand to show individual modules. |
| Sidebar collapsed mode | Show only main nav icons (Dashboard, Marketplace) and admin icons. Hide module list. | Module list needs text labels to be useful. Icons alone at 64px width would be confusing. |
| Sidebar search | Inline search field within sidebar, between main nav and categories section | PRTAL-05 requires sidebar search. Inline placement keeps it contextual. Header search reserved for future global search. |
| Module detail depth | Compact single-column detail view | Appropriate for v1 module count. Full description, status, activation button. No screenshots/reviews (v2). |
| Navigation pattern | Separate pages: /marketplace for browsing, /marketplace/[slug] for detail, /modules/{cat}/{slug} for module usage |
Clean URL separation. Marketplace is for discovery/activation. Module routes are for usage. App Router route groups keep them distinct. |
Checker Sign-Off
- Dimension 1 Copywriting: PASS
- Dimension 2 Visuals: PASS
- Dimension 3 Color: PASS
- Dimension 4 Typography: PASS
- Dimension 5 Spacing: PASS
- Dimension 6 Registry Safety: PASS
Approval: pending