From 01d9676175bbe3e0887de26b63446e13b22e406c Mon Sep 17 00:00:00 2001 From: levig Date: Sun, 5 Jul 2026 00:52:03 -0300 Subject: [PATCH 1/4] feat(apps): multi-select platforms with checkboxes Replace the single Web/Mobile/Both dropdown in the app create and edit forms with a PlatformMultiSelect toggle group (Web, Android, iOS) that allows any combination. Require at least one platform before saving, pre-select the current platforms when editing, and send the selection as an array. Remove the transitional legacy<->array bridge introduced in US-4.2. --- decisoes-front.md | 50 ++++++++++++++++ pr-us-12-5.md | 27 +++++++++ pr-us-12-6.md | 24 ++++++++ pr-us-12-8.md | 19 ++++++ pr-us-12-9.md | 22 +++++++ pr-us-42.md | 23 ++++++++ src/components/common/PlatformMultiSelect.tsx | 48 +++++++++++++++ src/components/pages/AppDetailPage.tsx | 56 +++++------------- src/components/pages/AppsPage.tsx | 58 ++++++------------- 9 files changed, 246 insertions(+), 81 deletions(-) create mode 100644 decisoes-front.md create mode 100644 pr-us-12-5.md create mode 100644 pr-us-12-6.md create mode 100644 pr-us-12-8.md create mode 100644 pr-us-12-9.md create mode 100644 pr-us-42.md create mode 100644 src/components/common/PlatformMultiSelect.tsx diff --git a/decisoes-front.md b/decisoes-front.md new file mode 100644 index 0000000..1ad6215 --- /dev/null +++ b/decisoes-front.md @@ -0,0 +1,50 @@ +# US-12.2 - Access the console and view scoped content navigation + +## Problema + +O manager logava no console mas nao via os itens de navegacao de conteudo (Organizations, Projects, Languages, Phases, Map) na sidebar. Alem disso, quando o scoping nao existia no backend, qualquer usuario autenticado via todos os dados do sistema. + +## Decisoes + +1. **Nenhuma alteracao de codigo no frontend**: A sidebar ja implementa a logica `(isPlatformAdmin || isManager)` para exibir os itens de conteudo (implementado na US-12.1). O `AuthContext` ja expoe `isManager`, `managedOrgIds` e `managedOrgId`. Nenhum arquivo frontend foi modificado. + +2. **Scoping delegado ao backend**: As paginas de listagem (`OrganizationsPage`, `LanguagesPage`, `PhasesPage`, `ProjectsPage`, `MapPage`) chamam os endpoints de listagem sem filtros. O backend (US-11.6) aplica o scoping automaticamente baseado no role do usuario. O frontend renderiza o que a API retorna. + +3. **Empty states ja existentes**: Todas as paginas de listagem ja possuem tratamento de empty state. Quando o backend retorna arrays vazios (usuario sem organizacoes), o frontend exibe a mensagem adequada sem necessidade de alteracao. + +4. **Problema original era de dados, nao de codigo**: O usuario `manager3@test.com` estava cadastrado na organizacao com `role: "member"` ao inves de `role: "manager"`. Apos a promocao via `PATCH /api/organizations/{org_id}/members/{user_id}` (US-11.3), o endpoint `my-managed-orgs` retornou os dados corretos e a sidebar passou a exibir os itens de conteudo. + +## Solucao Implementada + +### Frontend + +Nenhum arquivo modificado. A implementacao existente (US-12.1) ja cobre todos os criterios de aceitacao: + +- `src/contexts/AuthContext.tsx`: Expoe `isManager`, `managedOrgIds`, `managedOrgId`. +- `src/components/layout/Sidebar.tsx`: Condicional `(isPlatformAdmin || isManager)` para content nav items. `isPlatformAdmin` para admin nav items. +- `src/App.tsx`: `AdminRoute` wrapper para rotas de Users e Manage Apps. + +### Backend + +Todo o scoping foi implementado na US-11.6 (ver `decisoes-back.md`). + +## Verificacao + +| Criterio | Status | +|---|---| +| Managers veem Organizations, Projects, Languages, Phases, Map | PASS | +| Dados sao escopados para as organizacoes gerenciadas | PASS (backend US-11.6) | +| Users e Manage Apps ficam ocultos para managers | PASS | +| Sidebar reflete o role do usuario | PASS | +| Manager sem organizacao ve secoes vazias | PASS | + +## Commits + +Nenhum commit de frontend necessario para esta US. O scoping e inteiramente resolvido no backend (US-11.6). + +A unica alteracao documental foi: + +### `docs(us): update US-12.2 with full scoping requirements` + +Arquivos: +- `context/US-FRONT-BACK-ENGLISH.md` diff --git a/pr-us-12-5.md b/pr-us-12-5.md new file mode 100644 index 0000000..91d0ddf --- /dev/null +++ b/pr-us-12-5.md @@ -0,0 +1,27 @@ +# [US-12.5 / OBT-234] Remove the Organizations section from the console + +## Summary +The 2026-07 rule change removes organizations from the console (role management moved to the project Access tab). This deletes the Organizations sidebar item, routes and page components so organizations are neither used nor reachable in the console. `orgsAPI` is kept because it is still used by the project Access tab and the admin dashboard. + +## Changes + +### 1. Remove the Organizations navigation entry +`src/components/layout/Sidebar.tsx` — drop the `Organizations` item from `contentNavItems` and its now-unused `Building2` icon import. + +### 2. Remove the routes and page imports +`src/App.tsx` — remove the `/app/organizations` and `/app/organizations/:orgId` routes and the `OrganizationsPage` / `OrganizationDetailPage` imports. Unknown organization paths now fall through to the existing NotFound (404) page. + +### 3. Delete the page components +Delete `src/components/pages/OrganizationsPage.tsx` and `src/components/pages/OrganizationDetailPage.tsx` (deletion, not commented out). + +### 4. Keep orgsAPI +`src/services/api.ts` `orgsAPI` is left intact — still used by `ProjectAccessTab.tsx` (grant-organization dropdown) and `dashboard/AdminDashboard.tsx` (org count). + +## Type of Change +- [x] Refactor (removal of a section) +- [x] Behavior change (Organizations is no longer reachable in the console) + +## Testing +- `npm run typecheck`, `npm run lint`, `npm run build` — pass, no dead imports. +- Navigating to `/app/organizations` resolves to the 404 page. +- The project Access tab and admin dashboard still load (they keep using `orgsAPI`). diff --git a/pr-us-12-6.md b/pr-us-12-6.md new file mode 100644 index 0000000..1dca1f3 --- /dev/null +++ b/pr-us-12-6.md @@ -0,0 +1,24 @@ +# [US-12.6 / OBT-235] Deny console access to users who are neither platform admin nor manager + +## Summary +`AppShell` only redirected unauthenticated users, so any authenticated user (including plain members) could load the console. This gates the whole `/app` subtree so only platform admins and managers reach console content; everyone else sees an Access Denied page. Backend counterpart: US-11.8. + +## Changes + +### 1. Gate the console shell +`src/components/layout/AppShell.tsx` — after the authentication check, deny users where `!isPlatformAdmin && !isManager` by rendering the Access Denied page instead of the shell. `isLoading` and the unauthenticated `/login` redirect are unchanged. + +### 2. Add a logout variant to the Access Denied page +`src/components/pages/AccessDeniedPage.tsx` — new `variant="logout"` that offers a Sign out action instead of the "Go to Dashboard" link, so a fully denied user is not looped back into the gated shell. + +## Type of Change +- [x] Feature (route/shell-level access control) +- [x] Behavior change (plain members can no longer load the console) + +## Testing +- `npm run typecheck` and `npm run build` — pass. +- Log in as a plain member (no admin, no managed org/project) — the console shows Access Denied with a Sign out action. +- Platform admins and managers load the console normally. +- Unauthenticated users are still redirected to `/login`. + +> Note: consumes `isManager` from `AuthContext`. Combined with US-12.9 (project-based `isManager`), a project-only manager is correctly allowed. diff --git a/pr-us-12-8.md b/pr-us-12-8.md new file mode 100644 index 0000000..44194cb --- /dev/null +++ b/pr-us-12-8.md @@ -0,0 +1,19 @@ +# [US-12.8 / OBT-238] Restrict project Access role changes to platform admins + +## Summary +On the project Access tab, the per-user Role selector was interactive for everyone. This makes it editable only by platform admins; other users (including managers) see the role as read-only text. The backend is the source of truth (US-11.9). + +## Changes + +### 1. Gate the role selector on platform admin +`src/components/pages/ProjectAccessTab.tsx` — `ProjectAccessTab` reads `isPlatformAdmin` from `useAuth()` and passes it to `UserAccessSection`. The per-user Role ` setForm((f) => ({ ...f, platform: v }))} - > - - - - - Web - Mobile - Both - - + setForm((f) => ({ ...f, platforms }))} + />
diff --git a/src/components/pages/AppsPage.tsx b/src/components/pages/AppsPage.tsx index f70c496..d1bc433 100644 --- a/src/components/pages/AppsPage.tsx +++ b/src/components/pages/AppsPage.tsx @@ -10,13 +10,6 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Badge } from "@/components/ui/badge" import { Textarea } from "@/components/ui/textarea" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" import { Dialog, DialogContent, @@ -31,27 +24,14 @@ import { InfoTooltip } from "@/components/common/InfoTooltip" import { FeatureSpotlight } from "@/components/common/FeatureSpotlight" import { ConfirmDialog } from "@/components/common/ConfirmDialog" import { ImageUpload } from "@/components/common/ImageUpload" +import { PlatformMultiSelect } from "@/components/common/PlatformMultiSelect" import { Switch } from "@/components/ui/switch" -function legacyToPlatforms(value: string): string[] { - if (value === "both") return ["web", "android", "ios"] - if (value === "mobile") return ["android", "ios"] - return ["web"] -} - -function platformsToLegacy(platforms: string[]): string { - const hasWeb = platforms.includes("web") - const hasMobile = platforms.includes("android") || platforms.includes("ios") - if (hasWeb && hasMobile) return "both" - if (hasMobile) return "mobile" - return "web" -} - interface AppFormState { app_key: string name: string description: string - platform: string + platforms: string[] icon_url: string app_url: string ios_url: string @@ -63,7 +43,7 @@ const emptyForm: AppFormState = { app_key: "", name: "", description: "", - platform: "web", + platforms: ["web"], icon_url: "", app_url: "", ios_url: "", @@ -76,7 +56,7 @@ function formFromApp(app: AppResponse): AppFormState { app_key: app.app_key, name: app.name, description: app.description ?? "", - platform: platformsToLegacy(app.platforms), + platforms: app.platforms, icon_url: app.icon_url ?? "", app_url: app.app_url ?? "", ios_url: app.ios_url ?? "", @@ -125,13 +105,17 @@ export default function AppsPage() { async function handleSave() { if (!form.name.trim() || (!editingApp && !form.app_key.trim())) return + if (form.platforms.length === 0) { + toast.error("Select at least one platform") + return + } setSaving(true) try { if (editingApp) { await appsAPI.update(editingApp.id, { name: form.name.trim(), description: form.description.trim() || null, - platforms: legacyToPlatforms(form.platform), + platforms: form.platforms, icon_url: form.icon_url.trim() || null, app_url: form.app_url.trim() || null, ios_url: form.ios_url.trim() || null, @@ -144,7 +128,7 @@ export default function AppsPage() { app_key: form.app_key.trim(), name: form.name.trim(), description: form.description.trim() || null, - platforms: legacyToPlatforms(form.platform), + platforms: form.platforms, icon_url: form.icon_url.trim() || null, app_url: form.app_url.trim() || null, ios_url: form.ios_url.trim() || null, @@ -391,23 +375,15 @@ function AppFormDialog({
- + setForm((f) => ({ ...f, platforms }))} + />
From 02be23018ab629d770d54a7ed1f3d09a4f0fe89b Mon Sep 17 00:00:00 2001 From: Levi Gomes Date: Thu, 9 Jul 2026 22:08:17 -0300 Subject: [PATCH 2/4] chore: remove accidentally-committed PR description docs These pr-us-*.md files are local PR notes that were bundled into the feature commit by mistake; they should never have been tracked/pushed. Co-Authored-By: Claude Opus 4.8 (1M context) --- pr-us-12-5.md | 27 --------------------------- pr-us-12-6.md | 24 ------------------------ pr-us-12-8.md | 19 ------------------- pr-us-12-9.md | 22 ---------------------- pr-us-42.md | 23 ----------------------- 5 files changed, 115 deletions(-) delete mode 100644 pr-us-12-5.md delete mode 100644 pr-us-12-6.md delete mode 100644 pr-us-12-8.md delete mode 100644 pr-us-12-9.md delete mode 100644 pr-us-42.md diff --git a/pr-us-12-5.md b/pr-us-12-5.md deleted file mode 100644 index 91d0ddf..0000000 --- a/pr-us-12-5.md +++ /dev/null @@ -1,27 +0,0 @@ -# [US-12.5 / OBT-234] Remove the Organizations section from the console - -## Summary -The 2026-07 rule change removes organizations from the console (role management moved to the project Access tab). This deletes the Organizations sidebar item, routes and page components so organizations are neither used nor reachable in the console. `orgsAPI` is kept because it is still used by the project Access tab and the admin dashboard. - -## Changes - -### 1. Remove the Organizations navigation entry -`src/components/layout/Sidebar.tsx` — drop the `Organizations` item from `contentNavItems` and its now-unused `Building2` icon import. - -### 2. Remove the routes and page imports -`src/App.tsx` — remove the `/app/organizations` and `/app/organizations/:orgId` routes and the `OrganizationsPage` / `OrganizationDetailPage` imports. Unknown organization paths now fall through to the existing NotFound (404) page. - -### 3. Delete the page components -Delete `src/components/pages/OrganizationsPage.tsx` and `src/components/pages/OrganizationDetailPage.tsx` (deletion, not commented out). - -### 4. Keep orgsAPI -`src/services/api.ts` `orgsAPI` is left intact — still used by `ProjectAccessTab.tsx` (grant-organization dropdown) and `dashboard/AdminDashboard.tsx` (org count). - -## Type of Change -- [x] Refactor (removal of a section) -- [x] Behavior change (Organizations is no longer reachable in the console) - -## Testing -- `npm run typecheck`, `npm run lint`, `npm run build` — pass, no dead imports. -- Navigating to `/app/organizations` resolves to the 404 page. -- The project Access tab and admin dashboard still load (they keep using `orgsAPI`). diff --git a/pr-us-12-6.md b/pr-us-12-6.md deleted file mode 100644 index 1dca1f3..0000000 --- a/pr-us-12-6.md +++ /dev/null @@ -1,24 +0,0 @@ -# [US-12.6 / OBT-235] Deny console access to users who are neither platform admin nor manager - -## Summary -`AppShell` only redirected unauthenticated users, so any authenticated user (including plain members) could load the console. This gates the whole `/app` subtree so only platform admins and managers reach console content; everyone else sees an Access Denied page. Backend counterpart: US-11.8. - -## Changes - -### 1. Gate the console shell -`src/components/layout/AppShell.tsx` — after the authentication check, deny users where `!isPlatformAdmin && !isManager` by rendering the Access Denied page instead of the shell. `isLoading` and the unauthenticated `/login` redirect are unchanged. - -### 2. Add a logout variant to the Access Denied page -`src/components/pages/AccessDeniedPage.tsx` — new `variant="logout"` that offers a Sign out action instead of the "Go to Dashboard" link, so a fully denied user is not looped back into the gated shell. - -## Type of Change -- [x] Feature (route/shell-level access control) -- [x] Behavior change (plain members can no longer load the console) - -## Testing -- `npm run typecheck` and `npm run build` — pass. -- Log in as a plain member (no admin, no managed org/project) — the console shows Access Denied with a Sign out action. -- Platform admins and managers load the console normally. -- Unauthenticated users are still redirected to `/login`. - -> Note: consumes `isManager` from `AuthContext`. Combined with US-12.9 (project-based `isManager`), a project-only manager is correctly allowed. diff --git a/pr-us-12-8.md b/pr-us-12-8.md deleted file mode 100644 index 44194cb..0000000 --- a/pr-us-12-8.md +++ /dev/null @@ -1,19 +0,0 @@ -# [US-12.8 / OBT-238] Restrict project Access role changes to platform admins - -## Summary -On the project Access tab, the per-user Role selector was interactive for everyone. This makes it editable only by platform admins; other users (including managers) see the role as read-only text. The backend is the source of truth (US-11.9). - -## Changes - -### 1. Gate the role selector on platform admin -`src/components/pages/ProjectAccessTab.tsx` — `ProjectAccessTab` reads `isPlatformAdmin` from `useAuth()` and passes it to `UserAccessSection`. The per-user Role `