From 6e5376b1d8664e281aaa17c4c30f84ac588147e7 Mon Sep 17 00:00:00 2001 From: levig Date: Sun, 5 Jul 2026 00:57:35 -0300 Subject: [PATCH 1/4] feat(apps): render one badge per platform Show an individual badge for each entry in the app's platforms array on the dashboard app cards, the Manage Apps grid, and the app detail page, instead of a single combined label. Centralize the platform display labels in constants/platforms and drop the old platformLabel switch in AppCard. --- pr-us-43.md | 22 +++++++++++++++++++ src/components/common/PlatformMultiSelect.tsx | 7 +----- src/components/pages/AppDetailPage.tsx | 5 ++++- src/components/pages/AppsPage.tsx | 7 ++++-- src/components/pages/dashboard/AppCard.tsx | 20 ++++------------- src/constants/platforms.ts | 13 +++++++++++ 6 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 pr-us-43.md create mode 100644 src/constants/platforms.ts diff --git a/pr-us-43.md b/pr-us-43.md new file mode 100644 index 0000000..3884eae --- /dev/null +++ b/pr-us-43.md @@ -0,0 +1,22 @@ +## Summary + +US-4.3 / OBT-206 — Let admins select any combination of platforms (Web, Android, iOS) independently when creating or editing an app, replacing the single Web/Mobile/Both dropdown. + +## Changes + +1. New `src/components/common/PlatformMultiSelect.tsx` — a reusable toggle-button group (Web, Android, iOS) styled with Shema tokens; active options use the telha accent. +2. `AppsPage.tsx`: form state is now `platforms: string[]`; the create/edit dialog uses `PlatformMultiSelect`; requires at least one platform (toast on empty); sends the array to the API. Removed the transitional legacy bridge. +3. `AppDetailPage.tsx`: same treatment for the edit dialog; editing pre-selects the app's current platforms. + +## Type of Change + +- [x] Feature + +## Testing + +- `npm run typecheck` — pass +- `npm run lint` — 0 errors (pre-existing ProjectsPage warning only) +- `npm run build` — pass + +> **Stacked on US-4.2 / OBT-205 (#15).** Merge #15 first, then this PR. +> Depends on backend US-4.1 / OBT-204 (PR #94) for runtime validation. diff --git a/src/components/common/PlatformMultiSelect.tsx b/src/components/common/PlatformMultiSelect.tsx index 377ac41..b60ccf4 100644 --- a/src/components/common/PlatformMultiSelect.tsx +++ b/src/components/common/PlatformMultiSelect.tsx @@ -1,10 +1,5 @@ import { cn } from "@/utils/cn" - -const PLATFORM_OPTIONS = [ - { value: "web", label: "Web" }, - { value: "android", label: "Android" }, - { value: "ios", label: "iOS" }, -] +import { PLATFORM_OPTIONS } from "@/constants/platforms" interface PlatformMultiSelectProps { value: string[] diff --git a/src/components/pages/AppDetailPage.tsx b/src/components/pages/AppDetailPage.tsx index b1d9330..3be7f81 100644 --- a/src/components/pages/AppDetailPage.tsx +++ b/src/components/pages/AppDetailPage.tsx @@ -33,6 +33,7 @@ import { InfoTooltip } from "@/components/common/InfoTooltip" import { ImageUpload } from "@/components/common/ImageUpload" import { PlatformMultiSelect } from "@/components/common/PlatformMultiSelect" import { ConfirmDialog } from "@/components/common/ConfirmDialog" +import { platformLabel } from "@/constants/platforms" import { Switch } from "@/components/ui/switch" import { formatDate } from "@/utils/format" @@ -74,7 +75,9 @@ function AppInfoCard({ )}
- {app.platforms[0] ?? "web"} + {app.platforms.map((platform) => ( + {platformLabel(platform)} + ))} {app.is_active ? "Active" : "Inactive"} diff --git a/src/components/pages/AppsPage.tsx b/src/components/pages/AppsPage.tsx index d1bc433..8bb1a52 100644 --- a/src/components/pages/AppsPage.tsx +++ b/src/components/pages/AppsPage.tsx @@ -26,6 +26,7 @@ import { ConfirmDialog } from "@/components/common/ConfirmDialog" import { ImageUpload } from "@/components/common/ImageUpload" import { PlatformMultiSelect } from "@/components/common/PlatformMultiSelect" import { Switch } from "@/components/ui/switch" +import { platformLabel } from "@/constants/platforms" interface AppFormState { app_key: string @@ -232,8 +233,10 @@ export default function AppsPage() { {app.description && (

{app.description}

)} -
- {app.platforms[0] ?? "web"} +
+ {app.platforms.map((platform) => ( + {platformLabel(platform)} + ))} {app.is_active ? "Active" : "Inactive"} diff --git a/src/components/pages/dashboard/AppCard.tsx b/src/components/pages/dashboard/AppCard.tsx index 05414d9..d2e028f 100644 --- a/src/components/pages/dashboard/AppCard.tsx +++ b/src/components/pages/dashboard/AppCard.tsx @@ -3,21 +3,7 @@ import type { UserAppResponse } from "@/types" import { cn } from "@/utils/cn" import { Badge } from "@/components/ui/badge" import { buttonVariants } from "@/components/ui/button" - -function platformLabel(platform: string | null): string { - switch (platform) { - case "ios": - return "iOS" - case "android": - return "Android" - case "mobile": - return "Mobile" - case "both": - return "Web + Mobile" - default: - return "Web" - } -} +import { platformLabel } from "@/constants/platforms" export function AppCard({ app }: { app: UserAppResponse }) { const hasLinks = app.app_url || app.ios_url || app.android_url @@ -84,7 +70,9 @@ export function AppCard({ app }: { app: UserAppResponse }) { {!app.is_platform_admin && app.roles.length === 0 && ( No role )} - {platformLabel(app.platforms[0] ?? null)} + {app.platforms.map((platform) => ( + {platformLabel(platform)} + ))}
diff --git a/src/constants/platforms.ts b/src/constants/platforms.ts new file mode 100644 index 0000000..70d8373 --- /dev/null +++ b/src/constants/platforms.ts @@ -0,0 +1,13 @@ +export const PLATFORM_OPTIONS = [ + { value: "web", label: "Web" }, + { value: "android", label: "Android" }, + { value: "ios", label: "iOS" }, +] + +const PLATFORM_LABELS: Record = Object.fromEntries( + PLATFORM_OPTIONS.map((o) => [o.value, o.label]), +) + +export function platformLabel(platform: string): string { + return PLATFORM_LABELS[platform] ?? platform +} From 2253b34fee45a29feb18cb044ff7bdb1b56ba23b Mon Sep 17 00:00:00 2001 From: Levi Gomes Date: Thu, 9 Jul 2026 22:08:27 -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 ----------------------- pr-us-43.md | 22 ---------------------- 6 files changed, 137 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 delete mode 100644 pr-us-43.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 `