Developer Docs

Organization & Malet Management

Configure Malets, connect domains, monitor activity, and manage billing โ€” all from the management dashboard.

๐ŸŽจ Malet Designer

Route: /{{malet}}/manage/designer
Backend: updateThemeConfig and updateLayoutConfig mutations
Data: verticalConfig(slug) query for vertical-aware presets

The Malet Designer provides a high-density, professional CAD-style environment allowing Malet Owners to customize their visual brand identity and page layout. The fixed-viewport interface uses a non-scrolling sidebar + live iframe preview architecture, with an accordion-based settings panel for managing cognitive load across many configuration options.

The sidebar features two navigation tabs: Theme and Layout. Each tab uses collapsible accordion sections with inline value summaries in the header (color dots, font names, pixel values), allowing Malet Owners to see current settings at a glance without expanding every section.

Theme Editor

The Theme tab organizes settings into six collapsible accordion sections:

1. Theme Presets

One-click preset cards that apply a complete theme configuration (colors, fonts, mode, animations, radius). Presets are grouped into two tiers:

  • Vertical Presets โ€” Dynamically generated from the malet's vertical config branding defaults via verticalConfig(slug). Only shown if the malet has a verticalType set. Includes a Light and Dark variant using the vertical's primaryColor and accentColor.
  • Universal Presets โ€” Four curated presets available to all malets:
Preset Colors Font Mode
Mallnline Default Blue / Amber Inter AUTO
Midnight Pro Indigo / Violet DM Sans DARK
Clean Slate Gray / Neutral Source Sans Pro LIGHT
Warm Editorial Amber / Warm Tones Playfair / Lato LIGHT

2. Brand Colors

Interactive ColorWheelPicker with pill selectors for Primary, Secondary, Accent, and Background. The accordion header shows four inline color dot swatches reflecting current values.

3. Typography

Custom font picker dropdown that renders each option in its actual typeface (not a native <select>). Includes a specimen preview box showing heading and body text with the current selection. Supports 11 curated fonts: Inter, Roboto, Outfit, Playfair Display, Source Sans Pro, Poppins, Montserrat, Lato, Raleway, Open Sans, and DM Sans.

4. Display

Theme Mode (LIGHT / DARK / AUTO) and Animation Level (MINIMAL / STANDARD / RICH) configured via segmented radio buttons. The accordion summary shows the current values inline (e.g., "AUTO ยท STANDARD").

5. Border Radius

Range slider (0โ€“64px) with live preview shape and pixel readout.

6. Advanced (Custom CSS)

Monospace textarea for custom CSS overrides with a character counter (2000 limit). Includes a warning badge alerting Malet Owners that overrides may break their Malet layout. The value is persisted via the customCSS field on ThemeConfig.

Preview Toolbar

The preview panel includes a responsive toolbar above the iframe:

Control Description
Desktop Full-width iframe (default)
Tablet Constrained to 768px width with centered shadow frame
Mobile Constrained to 375px width with centered shadow frame
Refresh Reloads the preview iframe
External Link Opens the live Malet in a new tab

Changes are reflected in real-time via cross-origin postMessage with the SYNC_PREVIEW_CONFIG payload. The preview sandbox intercepts navigation clicks and shows an "Action disabled in Preview Builder" toast.

Layout Editor

Configure which Malet sections appear and in what order. The system supports 31 slot types organized into two categories:

Core Sections (10)

Available to all malets regardless of vertical:

Slot Type Description
๐Ÿ–ผ๏ธ Hero Banner Full-width header image
โญ Featured Products Highlighted product showcase
๐Ÿ›๏ธ Product Grid Main catalog grid
๐Ÿ“ About Section Malet description & story
๐Ÿ’ฌ Reviews Visitor reviews/testimonials
๐Ÿ“ฐ Blog Feed Latest blog posts
๐Ÿ“ง Contact Form Visitor inquiry form
๐Ÿ“ฌ Newsletter Email subscription
๐Ÿ”— Social Links Social media links
๐Ÿ”ง Custom HTML Freeform HTML block

Vertical Widgets (21)

Specialized sections unlocked per vertical. Only the vertical matching the malet's verticalType is shown in the "Add Section" picker:

Vertical Widget Slots
Fashion Lookbook Gallery, Collection Browser
Entertainment Event Calendar, Credit Store, Leaderboard
Professional Client Portal, Document Exchange, Consultation Calendar
Wellness Wellness Profile, Membership Plans, Practitioner Booking
Culture Exhibition Gallery, Artist Showcase, Season Calendar
Tech Spec Comparison, Warranty Tracker, Trade-In Portal
Arcade Digital Vault, Game Profile, Pre-Order Queue
Tour Itinerary Timeline, Tour Overview, Departure Booking
Restaurant Kitchen Board, Menu Order Cards, Daily Stats
Photographer Gallery Showcase, Proofing Board, License Tiers
Author Subscription Plans, Gated Content, Member Portal

Vertical widget slots display a purple badge in the active sections list and are visually distinguished with a purple accent in the "Add Section" picker.

Layout Controls

  • Drag to reorder: Grab the โ ฟ handle to rearrange sections
  • Toggle visibility: Click the ๐Ÿ‘๏ธ/โœ• icon to show/hide
  • Custom headings: Type in the heading input to override section titles
  • Remove: Hover to reveal the ๐Ÿ—‘๏ธ remove button
  • Add Section: Open the "Add Section" accordion to browse available core and vertical-specific widgets

NOTE

Vertical-specific widgets show a purple badge indicator. The "Add Section" picker only displays widgets relevant to the malet's vertical, keeping the UI clean and contextual.


๐ŸŒ Custom Domains

Route: /{{malet}}/manage/settings (scroll to Custom Domains section)
Backend: addCustomDomain, verifyCustomDomain, removeCustomDomain mutations

Connect your own domain to your Malet. After adding a domain, the system provides DNS TXT record instructions for verification.

How It Works

  1. Add your domain: Enter your domain name (e.g. shop.example.com) in the input field and click "Add Domain".
  2. Add DNS TXT Record: Copy the provided verification details:
    • Host: _ngwenya-verify.your-domain.com
    • Value: The unique verification token shown Add this as a TXT record in your DNS provider (GoDaddy, Cloudflare, Namecheap, etc.)
  3. Click Verify: Once the DNS propagates (usually 1-24 hours), click the "๐Ÿ” Verify" button. The system checks for the TXT record.
  4. SSL Provisioning: After verification, an SSL certificate is automatically provisioned. Watch the cert status badge update to ACTIVE.

Domain Status Reference

Status Badge Meaning
PENDING โณ PENDING Awaiting DNS verification
VERIFIED โœ… VERIFIED Domain ownership confirmed
FAILED โŒ FAILED DNS check failed โ€” recheck your records

๐Ÿ“‹ Team Activity Feed

Route: /orgs/{{slug}}/manage โ†’ "Activity" tab
Backend: organization.activityFeeds connection (paginated)

The Activity Feed displays a chronological timeline of all team actions within your organization. Every membership change, role update, and setting modification is logged.

Tracked Event Types

Event Icon Example Trigger
MEMBER_INVITED ๐Ÿ“จ Admin invites a new team member
MEMBER_JOINED ๐Ÿค Invited member accepts and joins
MEMBER_REMOVED ๐Ÿ‘‹ Member is removed or leaves voluntarily
ROLE_CHANGED ๐Ÿ”„ Member promoted/demoted (e.g. MEMBER โ†’ ADMIN)
VERTICAL_ROLE_CHANGED ๐Ÿท๏ธ Per-vertical role assignment changed
ORG_CREATED ๐Ÿ—๏ธ Organization first created
ORG_UPDATED โœ๏ธ Org settings or profile updated
ORG_DELETED ๐Ÿ—‘๏ธ Organization deleted

Each event displays the actor (who performed the action), the target (who was affected, if applicable), a relative timestamp ("3h ago"), and any relevant metadata (old/new role, org name, etc.).

TIP

Pagination: The feed loads 15 events at a time with a "Load More" button at the bottom. Uses cursor-based pagination via the activityFeeds GQL connection.


๐Ÿ“Š Organization Analytics

Route: /orgs/{{slug}}/manage โ†’ "Analytics" tab
Component: src/lib/components/admin/OrgAnalyticsPanel.svelte
Backend: Client-side aggregation from organizationAuditEvents + organizationTeams + membership data

The Organization Analytics tab provides Org Owners and Admins with visual insights into team activity and composition. Since the organizations subgraph has no dedicated analytics resolver, all aggregation is done client-side from existing queries.

KPI Cards

Card Source Description
Total Members members.length Current org membership count
Total Activity Audit event count Total actions logged
Active Contributors Unique actorId count Members who have taken actions
Teams organizationTeams count Number of teams in the org

Charts & Visualizations

  1. Activity Over Time โ€” Bar chart showing daily event counts for the last 30 days
  2. Role Distribution โ€” SVG donut showing OWNER/ADMIN/MEMBER/VIEWER proportions
  3. Event Type Breakdown โ€” Horizontal colored bars showing counts per event type
  4. Top Contributors โ€” Leaderboard table with ๐Ÿฅ‡๐Ÿฅˆ๐Ÿฅ‰ medals, action count, last active
  5. Teams Table โ€” Team name, member count, scoped Malets, creation date

Data Fetching

The panel fetches data via Promise.allSettled for resilient parallel loading:

  • organizationAuditEvents(orgId, limit: 200) โ€” raw event stream
  • organizationTeams(orgId) โ€” team listing
  • members โ€” passed as prop from the parent page (already loaded)

Component Props

Prop Type Default Description
orgId string โ€” Organization ID (required)
members { userId, role, joinedAt }[] [] Membership data from parent
getMemberDisplayName (userId: string) => string โ€” Optional name resolver function

โš™๏ธ Organization Settings

Route: /orgs/{{slug}}/manage โ†’ "General" tab
Backend: updateOrganization mutation with settings input

The Organization Settings page has three panels: Organization Profile, Billing Preferences, and Notification Controls.

๐Ÿ’ณ Billing Preferences

Field Type Description
Currency Dropdown 9 currencies: USD, EUR, GBP, ZAR, KES, NGN, JPY, AUD, CAD
Tax ID Text Organization's tax identification number
Billing Email Email Email address for invoices and billing notices

๐Ÿ”” Notification Controls

Toggle Default Description
Low Stock Alerts โœ… On Notified when any product inventory drops below threshold
New Murchase Notifications โœ… On Alert on every new Visitor Murchase
Payout Notifications โœ… On Updates about payments, withdrawals, and settlements

๐Ÿ’ณ Organization Billing & Subscriptions

Route: /orgs/{{slug}}/billing
Backend: Future implementation in the payments and organizations subgraphs (Pending P0 Epic).

The Subscription Management page allows organization admins to upgrade their tier (Starter/Pro/Enterprise), manage payment methods, and view invoice history. As the backend infrastructure is currently in development, this frontend page operates using robust, strongly-typed mocks that mirror the intended GraphQL schema.

Feature Capabilities

  • Tiers Preview: Real-time toggle between monthly/annual pricing (20% discount standard).
  • Payment Methods: List verified payment methods via Stripe Setup Intents structure.
  • Invoices: Downloadable PDF invoice stubs.
  • Design Tokens: Extensive use of $mall tokens (var(--mall-accent), var(--mall-light-bg)) to ensure consistency with the system theme.

Mock Data Architecture

The data structures used here (Subscription, PaymentMethod, Invoice) are defined in src/routes/orgs/[slug]/billing/mockData.ts. These interface models must be directly copied when authoring the backend GraphQL schema in the Phase 2 launch.


๐Ÿ“ก GraphQL Reference

All management features are powered by these GraphQL operations:

Mutations

updateThemeConfig(id: ID!, input: ThemeConfigInput!): Malet
updateLayoutConfig(id: ID!, input: LayoutConfigInput!): Malet
addCustomDomain(id: ID!, input: CustomDomainInput!): Malet
verifyCustomDomain(id: ID!, domain: String!): Malet
removeCustomDomain(id: ID!, domain: String!): Malet
updateOrganization(input: UpdateOrganizationInput!): Organization
# input.settings contains:
#   billing: { currency, taxId, billingEmail }
#   notifications: { lowStockAlerts, newOrderNotifications, payoutNotifications }

Queries (Activity Feed)

query GetOrgActivityFeed($orgId: ID!, $first: Int, $after: String) {
	organization(id: $orgId) {
		activityFeeds(first: $first, after: $after) {
			edges {
				node {
					id
					eventType
					actorId
					targetUserId
					metadata
					createdAt
				}
			}
			pageInfo {
				hasNextPage
				endCursor
			}
		}
	}
}

๐Ÿ”„ Org Context Switcher

Component: src/lib/components/OrgSwitcher.svelte
Store: src/stores/orgs.ts
Utilities: src/lib/utils/orgContextUtils.ts

The Org Context Switcher allows authenticated users with organization memberships to toggle between personal and organization scoped contexts. All subsequent GraphQL requests include the x-org-id header so that downstream subgraphs resolve permissions, data, and activity within the correct org boundary.

Architecture

OrgSwitcher.svelte (UI)
  โ†“ selectOrg(id)
orgsStore.switchContext(id) (atomic state transition)
  โ†“ sets isSwitching=true โ† localStorage flag: ngwenya_org_switching
  โ†“ updates currentOrgId in localStorage + store
  โ†“ returns { success, orgName }
  โ†“ isSwitching=false (after 500ms drain window)
goto('/lobby') โ†’ invalidateAll()
  โ†“ SvelteKit re-runs load() functions
client.ts headers() โ†’ reads localStorage('currentOrgId')
  โ†“ sends x-org-id header
Gateway auth.context.ts โ†’ validateMembership(orgId, userId)
  โ†“ sets ctx.orgId if valid

401 Suppression During Switch

When switching contexts, stale in-flight requests from the previous page may hit org-scoped guards and receive 401 responses. The client.ts response middleware reads a ngwenya_org_switching localStorage flag (set by orgsStore.switchContext) to suppress session-expiry handling during the transition window (500ms).

Without this fix, the response middleware would interpret the 401 as a session expiry, logout the user, and redirect to /auth.

Utility Functions

Function Purpose
isOrgScopedError(error) Detects if a GraphQL error is org-scope authorization vs real session expiry
buildOrgHeaders(orgId) Builds { 'x-org-id': id } or {} โ€” spreadable into headers
validateOrgSwitch(memberships, targetOrgId) Pre-validates the user has membership before switching

Component Props

OrgSwitcher takes no props. It reads from derived stores:

Store Type Description
$currentOrg Organization | null Active org (null = personal)
$currentOrgId string | null Active org ID
$memberships Membership[] All user memberships
$isSwitching boolean True during context transition
$authStore AuthState Only shown when authenticated

Data Attributes (E2E)

Selector Element
[data-testid="org-switcher"] Root container
[data-testid="org-switcher-toggle"] Trigger button
[data-testid="org-switcher-dropdown"] Dropdown menu
[data-testid="org-switcher-personal"] Personal context option
[data-testid="org-switcher-org-{slug}"] Org option by slug

๐Ÿ“‚ File Reference

Feature File Purpose
Designer [malet]/manage/designer/+page.svelte Theme + Layout editor page
Color Picker lib/components/ColorWheelPicker.svelte Reusable color wheel component
Domains [malet]/manage/settings/+page.svelte Custom Domains section (add/verify/remove)
Org Manage orgs/[slug]/manage/+page.svelte General + Members + Analytics + Activity tabs
Org Analytics lib/components/admin/OrgAnalyticsPanel.svelte Organization analytics dashboard (client-side aggregation)
Org Analytics Tests tests/orgAnalytics.test.ts Unit tests for aggregation utilities
Org Data lib/queries/organizations.ts Activity feed query + org settings types
Malet Data lib/queries/malet.ts Theme/Layout/Domain mutations + types
Org Switcher lib/components/OrgSwitcher.svelte Context switch dropdown (Personal โ†” Org)
Org Store stores/orgs.ts Org memberships, switching state, derived
Org Context Utils lib/utils/orgContextUtils.ts Error detection, header building, validation