feat(team): public Team page + HQ CMS panel

New "Team" section — a LinkedIn-style minimal profile page for the FLUX
team, fully editable from the HQ Command Center.

Data model:
- New TeamMember model (name, role, bio, photoUrl, optional social links:
  email/linkedin/x/website, order, isActive, translationsJson).
- Additive migration 20260602120000_add_team_member (IF NOT EXISTS guards).
- Name stays as written; role + bio are translatable via the AI engine.

HQ panel (/hq-command/dashboard/team):
- Drag-to-reorder (same HTML5 pattern as the Hero panel).
- Inline auto-save for name/role/visibility; expandable editor for photo
  upload, bio, social links, and AI auto-translate to IT/VEC/ES/DE.
- Photo upload reuses /api/assets with a new flat "team" scope -> /public/team/.
- Dashboard tile added.

Public page (/[locale]/team):
- Responsive card grid (framer-motion stagger), portrait + name + role +
  bio + social icons (only the links that exist render).
- Per-member Person JSON-LD + breadcrumb for SEO.
- Localized via getLocalizedData; new TeamPage namespace in all 5 locales.
- NavBar item "Team" inserted before "Spare Parts" (translated 5 locales).
- Added to sitemap.

Infra:
- "team" scope registered in /api/assets (SCOPE_ROOTS + FLAT_SCOPES +
  buildPublicUrl) and revalidate.ts (RevalidateScope + path).
- Nginx serves /team/ from disk; docker-compose mounts public/team in both
  app and nginx (rw + ro).

Verified: production build compiles, all 5 /[locale]/team routes + the HQ
panel render; TypeScript clean; 5 message files valid JSON.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 07:17:09 -05:00
parent 1ee8288c7e
commit 148aefc68f
19 changed files with 862 additions and 6 deletions
@@ -0,0 +1,25 @@
-- ─────────────────────────────────────────────────────────────────────────
-- ADDITIVE MIGRATION — adds the TeamMember table for the public team page.
-- Nothing here modifies or drops existing data. Idempotent via IF NOT EXISTS.
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS "TeamMember" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"role" TEXT NOT NULL,
"bio" TEXT,
"photoUrl" TEXT,
"email" TEXT,
"linkedinUrl" TEXT,
"xUrl" TEXT,
"websiteUrl" TEXT,
"order" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"translationsJson" TEXT DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TeamMember_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "TeamMember_isActive_order_idx" ON "TeamMember" ("isActive", "order");
+32
View File
@@ -380,4 +380,36 @@ model ClientUser {
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 14. THE TEAM (Equipo de FLUX — página pública + CMS)
// ------------------------------------------------------
// Minimal LinkedIn-style profiles. Editable in the HQ Command Center with
// drag-to-reorder (same pattern as HeroSlide). Name stays as written; role
// and bio are translatable through the AI translation engine. Social links
// are all optional — only the ones filled in render on the public card.
model TeamMember {
id String @id @default(cuid())
name String // Proper name — never translated
role String // Job title, e.g. "Founder & CEO" — translatable
bio String? // Short biography (Markdown allowed) — translatable
photoUrl String? // Portrait, served from /team/ bucket
// Optional social links — render only when present
email String?
linkedinUrl String?
xUrl String? // X / Twitter
websiteUrl String?
order Int @default(0) // Drag-to-reorder
isActive Boolean @default(true)
// 🌍 Translation engine — holds localized role + bio per locale
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isActive, order])
}