Last updated

/iblai-vibe-account

Mirrored from iblai/vibe Β· skills/organizations/iblai-vibe-account/SKILL.md. This page is generated β€” edit it in the repository, not here.

First time here? If iblai.env has no ARCHITECTURE=, run /iblai-vibe-start first (four questions; two minutes) β€” it decides single-org / multi-org / headless and who signs in, and every skill reads the answer.

Add an account/organization settings page whose sidebar groups its tabs under four headings: Workspace (Organization, Management, Integrations), Finance (Monetization, Billing), AI & data (Benchmarks, Memory) and System (Advanced). Every group whose tabs are all gated off disappears, so a small organization sees two or three headings, not four.

Account Page
Account Page

Template: the page this skill creates is bundled as assets/account-page.tsx.j2. See /iblai-vibe-scaffold for the {{ }} contract.

Navbar: If the user wants a navbar with links to the account page, guide them to /iblai-vibe-navbar first. That skill creates the full navbar with logo, page links, notification bell, and profile dropdown.

Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.

Step 0: Start from vibe-starter? (new projects)

Before running this skill, ask the user:

Are you starting a new project from scratch? vibe-starter (https://github.com/iblai/vibe/tree/main/skills/start/iblai-vibe-ops-init/assets/vibe-starter) already ships the /account page wired up, alongside auth, navbar, and profile/notifications. Want to use that instead?

If yes, copy the bundled starter template from the installed iblai-vibe-ops-init skill's assets/vibe-starter/ directory (it sits alongside this skill in your skills directory; in the vibe repo it lives under skills/start/), or fetch it from the vibe repo if those assets are not installed -- tell the user which path you took -- then skip this skill:

cp -a <skills-dir>/iblai-vibe-ops-init/assets/vibe-starter/. .
# or, without local assets:
git clone --depth 1 https://github.com/iblai/vibe.git vibe-tmp && cp -a vibe-tmp/skills/start/iblai-vibe-ops-init/assets/vibe-starter/. . && rm -rf vibe-tmp

pnpm install --ignore-scripts

Run with --ignore-scripts to skip package lifecycle (postinstall) scripts.

If they prefer to add the account page to an existing app, continue below.

Prerequisites

  • Auth must be set up first (/iblai-vibe-auth)
  • MCP server + skills configured (@iblai/mcp in .mcp.json)

Step 1: Check Environment

Before proceeding, check for a iblai.env in the project root. Look for PLATFORM, DOMAIN, and TOKEN variables. If the file does not exist or is missing these variables, tell the user: "You need an iblai.env with your platform configuration. Download the template and fill in your values: curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env"

Step 2: Create the Page

Render assets/account-page.tsx.j2 into app/(app)/account/page.tsx (substitute {{ }} placeholders), or just use the reference implementation in Step 3.

Files created

FilePurpose
app/(app)/account/page.tsxAccount/organization settings page with tabs

The page reads userData, tenant/current_tenant, and tenants from localStorage. Admin status is derived from the tenants array.

Note: The Account component uses next/image internally -- it is imported from @iblai/iblai-js/web-containers/next.

Step 3: Wrap in a White Container

The SDK Account component has no outer background. Wrap it in a white container so it renders as a card against the gray page background (--sidebar-bg: #fafbfc).

Reference implementation

// app/(app)/account/page.tsx
"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Account } from "@iblai/iblai-js/web-containers/next";
import config from "@/lib/iblai/config";
import { resolveAppTenant } from "@/lib/iblai/tenant";

export default function AccountPage() {
  const router = useRouter();
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [tenantKey, setTenantKey] = useState("");
  const [tenants, setTenants] = useState<any[]>([]);
  const [isAdmin, setIsAdmin] = useState(false);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    try {
      const raw = localStorage.getItem("userData");
      if (raw) {
        const parsed = JSON.parse(raw);
        setUsername(parsed.user_nicename ?? parsed.username ?? "");
        setEmail(parsed.user_email ?? parsed.email ?? "");
      }
    } catch {}

    const resolved = resolveAppTenant();
    setTenantKey(resolved);

    try {
      const tenantsRaw = localStorage.getItem("tenants");
      if (tenantsRaw) {
        const parsed = JSON.parse(tenantsRaw);
        setTenants(parsed);
        const match = parsed.find((t: any) => t.key === resolved);
        if (match) setIsAdmin(!!match.is_admin);
      }
    } catch {}

    setReady(true);
  }, []);

  if (!ready || !tenantKey) {
    return (
      <div className="flex flex-1 items-center justify-center">
        <p className="text-sm text-gray-400">Loading account settings...</p>
      </div>
    );
  }

  return (
    <div className="mx-auto w-full flex-1 overflow-auto px-4 py-8 md:w-[75vw] md:px-0">
      <div className="rounded-lg border border-[var(--border-color)] bg-white overflow-hidden">
        <Account
          tenant={tenantKey}
          tenants={tenants}
          username={username}
          email={email}
          mainPlatformKey={config.mainTenantKey()}
          isAdmin={isAdmin}
          authURL={config.authUrl()}
          currentPlatformBaseDomain={config.platformBaseDomain()}
          currentSPA="agent"
          onInviteClick={() => {}}
          onClose={() => router.push("/")}
          targetTab="organization"
          showPlatformName={true}
          useGravatarPicFallback={true}
        />
      </div>
    </div>
  );
}

Key patterns

  • White container wrapper: Wrap the Account component in a bg-white rounded-lg border border-[var(--border-color)] overflow-hidden div so it renders as a card against the gray page background.
  • Responsive width: w-full px-4 on mobile, md:w-[75vw] md:px-0 on desktop.

Step 4: Use MCP Tools for Customization

get_component_info("Account")

<Account> Props

Required

PropTypeDescription
tenantstringPlatform key
tenantsTenant[]Full list of user platforms from localStorage
usernamestringUsername
emailstringUser email (from userData in localStorage)
mainPlatformKeystringMain org key β€” config.mainTenantKey()
onInviteClick() => voidCalled when "Invite user" is clicked
onClose() => voidCancel/close callback
authURLstringAuth service URL
isAdminbooleanControls tab visibility -- most tabs require true

Optional

PropTypeDescription
targetTabstringInitial tab: organization, management, integrations, monetization, billing, datasets, memory, advanced β€” a tab this viewer may not open falls back to their first visible one
currentPlatformBaseDomainstringBase domain for custom domain settings
currentSPAstringCurrent app identifier (e.g., "agent")
showBenchmarksbooleanShow the Benchmarks tab (default false; each app opts in)
showGradebookTabbooleanShow the Gradebook tab in the profile preview that Management β†’ Users opens for a selected user
onTabChange(tabId: string) => voidFires with "billing" / "monetization" when either opens and with "" when you leave them β€” mirror it into your URL so those two tabs are linkable
userActiveAppUserApp | nullThe app whose plan Billing should show
currentPlanstringCurrent plan name, for the Billing header
defaultSupportPhonestringInitial support phone on the Organization tab when metadata has none
enableSupportPhonebooleanShow the support-phone field at all
onboardingBasePathstringBase URL for the copyable per-agent onboarding link (Management β†’ Onboarding); defaults to the browser's origin
enableRbacbooleanTurn on RBAC permission checks for Management. Only ever pass this together with rbacPermissions β€” see the warning below
rbacPermissionsobjectThe caller's effective permissions, from POST /dm/api/core/rbac/permissions/check/. Defaults to {}
onLoadGroupPermissions(permissions: object) => voidOptional; the Teams tab calls it with group-scoped permissions so you can merge them back in
showPlatformNamebooleanShow platform name badge in sidebar
useGravatarPicFallbackbooleanUse Gravatar when no org logo

enableRbac without rbacPermissions hides Management entirely. checkRbacPermission(perms, resource, enableRbac) returns true when RBAC is off, but denies everything when it is on and the permissions object is empty β€” so passing the flag alone is strictly worse than omitting it, and the Management tab disappears even for an org admin. Either pass both (load them with POST rbac/permissions/check/, see /iblai-vibe-rbac) or pass neither. Single-org apps whose admin routes are already admin-gated should pass neither: vibe-starter and the ibl.ai OS's own admin dialog both do.

Layout and navigation

Account and Profile share one shell β€” a 280px sidebar (organization logo, name, key and role chip over grouped navigation) beside a content column with a fixed header carrying the active tab's title and description. Below lg the sidebar becomes a compact identity row and a scrolling strip of pills, the active one solid blue. See /iblai-vibe-profile for the same description from the profile side.

Two things are specific to Account:

  • The navigation is buttons, not ARIA tabs. Each item is a plain <button> carrying aria-current="page" when active, inside nav[aria-label="Organization settings"] (the small-screen strip is "Organization settings (mobile)") β€” that is the selector to write e2e tests against, since Profile uses role="tab" instead. Arrow keys, Home and End still move between them.
  • targetTab falls back. Asking for a tab this viewer may not open β€” a watcher sent to organization, say β€” lands on the first tab they can actually see, and it re-runs once tenants arrives and the paywall and permission checks settle.

Tabs

TabGrouptargetTab idRequires
OrganizationWorkspaceorganizationisAdmin === true
ManagementWorkspacemanagementenableRbac unset (any admin), or enableRbac plus a rbacPermissions object granting one of /platforms/<org>/#can_manage_users, /groups/#list, /roles/#list, /policies/#list, /usergroups/#list, /watchedgroups/#list
IntegrationsWorkspaceintegrationsisAdmin === true
MonetizationFinancemonetizationisAdmin === true, the organization has monetization enabled and the caller holds /platforms/<org>/#can_sell_items (with RBAC off that check passes automatically)
BillingFinancebillingThe organization's paywall is on (show_paywall) and the caller is an admin or holds can_sell_items
BenchmarksAI & datadatasetsshowBenchmarks and isAdmin === true β€” test-input suites for evaluating agents
MemoryAI & datamemoryisAdmin === true β€” organization-wide global and per-agent memories (see /iblai-vibe-memory)
AdvancedSystemadvancedisAdmin === true β€” organization-wide switches, among them Allow users to control chat privacy, the gate that turns Incognito on for the organization (PATCH …/chat-privacy-config/, admin-only; see /iblai-vibe-incognito)

Every tab but Management and Billing needs isAdmin; those two answer to RBAC instead (Billing also to an admin). Integrations is itself split into LLMs, Data Sources and APIs, and the Memory tab into Global and Agent memories.

Management sub-tabs

Management is itself tabbed β€” Users, Groups, Roles, Policies, Teams, Alerts, Applications and Onboarding β€” each shown when the matching permission is granted (can_manage_users, /groups/#list, /roles/#list, /policies/#list, /usergroups/#list or can_invite, /watchedgroups/#list). The Invite dialog reached from Users invites by user, by course or by program, and calls back through onInviteClick.

Picking a user in Users opens their profile in a read-only preview β€” the same Profile component as /iblai-vibe-profile, minus Security, with the Usage tab showing that user's AI spend. showGradebookTab is the one customization this path forwards.

Shot
WorkspaceOrganization Β· Advanced
ManagementUsers Β· Groups Β· Roles Β· Policies Β· Teams Β· Alerts Β· Applications Β· Onboarding
Invite dialogusers Β· courses Β· programs
IntegrationsLLMs Β· APIs Β· Datasources
MemoryGlobal Β· Agent

Step 5: Verify

Run /iblai-vibe-ops-test before telling the user the work is ready:

  1. pnpm build -- must pass with zero errors
  2. pnpm test -- vitest must pass
  3. Touch test /account in the browser as /iblai-vibe-ops-test says: open it, use every control this skill added, read the console, check the phone width.
  4. In the browser, check the sidebar headings against what the organization actually has β€” no Finance group unless it sells something, no Benchmarks unless you passed showBenchmarks β€” then open each tab the sidebar lists; none may 404 or come up blank.

Important Notes

  • Next.js required: Import from @iblai/iblai-js/web-containers/next (uses next/image)
  • Redux store: Must include mentorReducer and mentorMiddleware
  • initializeDataLayer(): 5 args (v1.2+)
  • @reduxjs/toolkit: Deduplicated via webpack aliases in next.config.ts
  • currentPlatformBaseDomain: Must be {config.platformBaseDomain()} β€” uses the config helper, not a raw env var. This is correct and intentional.
  • SDK hardcoded styles: The SDK Account component uses bg-white and bg-gray-50 internally. Do NOT override these. Instead, wrap the component in a white container so it renders correctly against the gray page background.
  • Give the wrapper a height: the settings shell fills its parent (h-full); the sidebar and the content panel only scroll independently when that parent is bounded β€” h-[80vh] or min-h-[640px] is enough.
  • Billing is not URL-driven: it shows up when the organization's paywall is on and the caller may see it, not because you passed a Stripe URL.
  • Brand guidelines: BRAND.md

Copyright Β© ibl.ai | support@iblai.zendesk.com