feat: Customize hub + roles editor, terminal logo, invite links

IA refactor and the custom-roles editor pass 11 left as data-only.

Customize hub:
- New /customize (admin/board) with a left section rail: Roles,
  Membership tiers, Areas, Invite codes, Onboarding.
- One focused module per panel under app/(app)/customize/panels/
  plus a shared card and types file. The client shell is ~75 lines;
  each panel is self-contained (smart separation of concerns).
- Settings slimmed to Space, Integrations, Webhooks. Removed the
  Roles/Tiers/Invites/Onboarding tabs and the Areas block from
  settings-client.tsx (~700 lines) and the matching fetches from
  settings/page.tsx. Dead props/state/types removed.
- Sidebar: added a Customize NavLink (admin/board).

Roles editor:
- Rename/recolor the five built-in roles (upsertRoleLabel) and full
  CRUD on custom org roles. Adds the read path the audit flagged as
  missing: lib/role-labels.ts (getRoleLabelMap/roleDisplayName) is
  consumed in (app)/layout.tsx so the sidebar shows the renamed role.

Logo:
- Reverted the brand mark to the terminal >_ glyph
  (components/brand-mark.tsx) on the landing nav/footer, onboarding
  header, and favicon (public/logo.svg). Removed the dead local
  IcoTerminal. AtlasLogo stays only on the resources project page.

Invite links:
- InvitesPanel has Copy code and Copy link (/signup?invite=CODE).
  signup reads ?invite= on mount, prefills the code, preselects join.

Deferred and tracked in HANDOFF: types/database.ts sync, renamed-role
display on the members table, splitting the still-large
settings-client.tsx, custom-role assignment UI.

266/266 tests pass. Production build clean; /customize, /onboarding,
slimmed /settings all in the route table. No migration this pass.
This commit is contained in:
Moheeb Zara
2026-05-15 16:03:52 -07:00
parent 30bba045f8
commit 71d5e243cf
21 changed files with 791 additions and 856 deletions
+5 -3
View File
@@ -13,7 +13,7 @@ import {
Menu, X, LayoutDashboard, ListChecks, FolderKanban, Settings2,
MessageSquare, Users, CreditCard, BookUser, Download, LogOut,
Vote, ShieldAlert, ScrollText, LineChart, UserCircle, UserSearch,
MessagesSquare,
MessagesSquare, SlidersHorizontal,
} from 'lucide-react'
interface NavLinkProps {
@@ -52,12 +52,13 @@ function NavLink({ href, label, icon: Icon, badge, active, onClick }: NavLinkPro
interface AppSidebarProps {
member: SpaceMember & { spaces: Space | null }
roleName?: string
taskBadge?: number
commsBadge?: number
paymentBadge?: number
}
export function AppSidebar({ member, taskBadge = 0, commsBadge = 0, paymentBadge = 0 }: AppSidebarProps) {
export function AppSidebar({ member, roleName, taskBadge = 0, commsBadge = 0, paymentBadge = 0 }: AppSidebarProps) {
const pathname = usePathname()
const space = member.spaces
const initials = (member.display_name || 'U').split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
@@ -120,6 +121,7 @@ export function AppSidebar({ member, taskBadge = 0, commsBadge = 0, paymentBadge
<div className="px-3 mt-4 mb-1">
<p className="text-[10px] uppercase tracking-widest text-[var(--sidebar-foreground)]/30 px-2 py-1 font-mono">Admin</p>
</div>
<NavLink href="/customize" label="Customize" icon={SlidersHorizontal} active={isActive('/customize')} onClick={onNav} />
<NavLink href="/import" label="Import / Sync" icon={Download} active={isActive('/import')} onClick={onNav} />
<NavLink href="/settings" label="Settings" icon={Settings2} active={isActive('/settings')} onClick={onNav} />
</>
@@ -137,7 +139,7 @@ export function AppSidebar({ member, taskBadge = 0, commsBadge = 0, paymentBadge
</div>
<div className="flex-1 text-left min-w-0">
<p className="text-[var(--sidebar-foreground)] text-xs font-medium truncate">{member.display_name}</p>
<p className="text-[var(--sidebar-foreground)]/40 text-[10px] font-mono">{member.role} · {member.tier}</p>
<p className="text-[var(--sidebar-foreground)]/40 text-[10px] font-mono">{roleName || member.role} · {member.tier}</p>
</div>
<LogOut className="w-3.5 h-3.5 text-[var(--sidebar-foreground)]/30" />
</button>
+21
View File
@@ -0,0 +1,21 @@
// The hackerspace.sh brand mark: a minimal terminal prompt glyph (a chevron
// and an underscore). Inherits the surrounding text color via currentColor,
// so it picks up the lime accent wherever it sits on the dark sidebar.
export function BrandMark({ className }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
strokeLinejoin="round"
role="img"
aria-label="hackerspace.sh"
>
<path d="M5 7l5 5-5 5" />
<path d="M13 17h6" />
</svg>
)
}