From 5cc7f0178bbe610c36f11c220ad51196e40d0716 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Fri, 15 May 2026 09:21:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20redesign=20More=20popover=20?= =?UTF-8?q?=E2=80=94=20user=20card=20+=20lean=20nav?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add user identity card at top of GlobalNavMenu, mirroring web sidebar dropdown (packages/views/layout/app-sidebar.tsx:496). Tap pushes into the existing settings page where account / workspaces / sign-out already live. - Trim NAV_ITEMS to Projects only. Inbox / My Issues / Chat are bottom tabs; Settings is reached via the user card. - Delete six orphaned stub routes (favorites, initiatives, views, teams, notifications, pins) — no remaining external references. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../app/(app)/[workspace]/more/favorites.tsx | 16 --- .../(app)/[workspace]/more/initiatives.tsx | 16 --- .../(app)/[workspace]/more/notifications.tsx | 12 --- .../app/(app)/[workspace]/more/pins.tsx | 12 --- .../app/(app)/[workspace]/more/teams.tsx | 16 --- .../app/(app)/[workspace]/more/views.tsx | 16 --- .../mobile/components/nav/global-nav-menu.tsx | 98 +++++++++++++++---- 7 files changed, 81 insertions(+), 105 deletions(-) delete mode 100644 apps/mobile/app/(app)/[workspace]/more/favorites.tsx delete mode 100644 apps/mobile/app/(app)/[workspace]/more/initiatives.tsx delete mode 100644 apps/mobile/app/(app)/[workspace]/more/notifications.tsx delete mode 100644 apps/mobile/app/(app)/[workspace]/more/pins.tsx delete mode 100644 apps/mobile/app/(app)/[workspace]/more/teams.tsx delete mode 100644 apps/mobile/app/(app)/[workspace]/more/views.tsx diff --git a/apps/mobile/app/(app)/[workspace]/more/favorites.tsx b/apps/mobile/app/(app)/[workspace]/more/favorites.tsx deleted file mode 100644 index 5596466a1..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/favorites.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -/** - * Favorites placeholder. Real implementation deferred — list of pinned - * issues / projects / views, mirroring the web Favorites surface. - */ -export default function FavoritesPage() { - return ( - - - Favorites coming soon. - - - ); -} diff --git a/apps/mobile/app/(app)/[workspace]/more/initiatives.tsx b/apps/mobile/app/(app)/[workspace]/more/initiatives.tsx deleted file mode 100644 index 5fd2c50aa..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/initiatives.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -/** - * Initiatives placeholder. Read-only list of workspace initiatives, filled - * in a later phase to mirror the web Initiatives surface. - */ -export default function InitiativesPage() { - return ( - - - Initiatives coming soon. - - - ); -} diff --git a/apps/mobile/app/(app)/[workspace]/more/notifications.tsx b/apps/mobile/app/(app)/[workspace]/more/notifications.tsx deleted file mode 100644 index ed9f3eb40..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/notifications.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -export default function NotificationsPage() { - return ( - - - Notification preferences coming soon. - - - ); -} diff --git a/apps/mobile/app/(app)/[workspace]/more/pins.tsx b/apps/mobile/app/(app)/[workspace]/more/pins.tsx deleted file mode 100644 index ae528ca08..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/pins.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -export default function PinsPage() { - return ( - - - Pins coming soon. - - - ); -} diff --git a/apps/mobile/app/(app)/[workspace]/more/teams.tsx b/apps/mobile/app/(app)/[workspace]/more/teams.tsx deleted file mode 100644 index 86a321037..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/teams.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -/** - * Teams placeholder. Workspace team list, filled in a later phase to mirror - * the web Teams surface. - */ -export default function TeamsPage() { - return ( - - - Teams coming soon. - - - ); -} diff --git a/apps/mobile/app/(app)/[workspace]/more/views.tsx b/apps/mobile/app/(app)/[workspace]/more/views.tsx deleted file mode 100644 index 79d205c6b..000000000 --- a/apps/mobile/app/(app)/[workspace]/more/views.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View } from "react-native"; -import { Text } from "@/components/ui/text"; - -/** - * Views placeholder. Saved-filter views surface, filled in a later phase to - * mirror the web Views surface. - */ -export default function ViewsPage() { - return ( - - - Views coming soon. - - - ); -} diff --git a/apps/mobile/components/nav/global-nav-menu.tsx b/apps/mobile/components/nav/global-nav-menu.tsx index add450b6f..bb6b4108d 100644 --- a/apps/mobile/components/nav/global-nav-menu.tsx +++ b/apps/mobile/components/nav/global-nav-menu.tsx @@ -1,7 +1,6 @@ /** - * GlobalNavMenu — top-right `…` popover that lets the user jump to any - * top-level destination (Inbox, My Issues, Favorites, Projects, Initiatives, - * Views, Teams, Settings, Search) and switch workspace. + * GlobalNavMenu — bottom-right popover anchored above the More tab. Three + * sections: user identity card → workspace switcher → real feature entries. * * Why a popover and not a tab: the iOS HIG treats tab-bar items as * destinations, not action triggers, so "More" was an anti-pattern. Linear / @@ -11,16 +10,30 @@ * Reanimated v3 and the mobile app is on Reanimated v4. Same Modal+Pressable * pattern as status-picker-sheet.tsx etc. — keeps the dependency surface * untouched. + * + * Composition mirrors web's sidebar dropdown (packages/views/layout/ + * app-sidebar.tsx:496-511): user info row (avatar + name + email) sits above + * the workspace list. On mobile the row is a tappable card that pushes into + * the existing settings page, since there isn't enough screen real estate to + * inline account / workspaces / sign-out the way web does. */ import { useMemo, useState } from "react"; -import { ActivityIndicator, Modal, Pressable, ScrollView, View } from "react-native"; +import { + ActivityIndicator, + Image, + Modal, + Pressable, + ScrollView, + View, +} from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Ionicons } from "@expo/vector-icons"; import { router, usePathname } from "expo-router"; import { useQuery } from "@tanstack/react-query"; -import type { Workspace } from "@multica/core/types"; +import type { User, Workspace } from "@multica/core/types"; import { Text } from "@/components/ui/text"; import { workspaceListOptions } from "@/data/queries/workspaces"; +import { useAuthStore } from "@/data/auth-store"; import { useWorkspaceStore } from "@/data/workspace-store"; import { cn } from "@/lib/utils"; @@ -31,16 +44,11 @@ interface NavItem { path: string; } +// Inbox / My Issues / Chat live on the bottom tab bar; Settings is reached +// via the user card at the top of this popover. Only entries that are NOT +// covered by either of those surfaces belong here. const NAV_ITEMS: NavItem[] = [ - { label: "Inbox", icon: "mail-outline", path: "/inbox" }, - { label: "My Issues", icon: "list-outline", path: "/my-issues" }, - { label: "Favorites", icon: "star-outline", path: "/more/favorites" }, { label: "Projects", icon: "cube-outline", path: "/more/projects" }, - { label: "Initiatives", icon: "navigate-outline", path: "/more/initiatives" }, - { label: "Views", icon: "layers-outline", path: "/more/views" }, - { label: "Teams", icon: "people-outline", path: "/more/teams" }, - { label: "Settings", icon: "settings-outline", path: "/more/settings" }, - { label: "Search", icon: "search-outline", path: "/search" }, ]; const ICON_COLOR = "#3f3f46"; @@ -54,6 +62,7 @@ interface Props { export function GlobalNavMenu({ visible, onClose }: Props) { const insets = useSafeAreaInsets(); const slug = useWorkspaceStore((s) => s.currentWorkspaceSlug); + const user = useAuthStore((s) => s.user); const pathname = usePathname(); const [showWorkspaces, setShowWorkspaces] = useState(false); @@ -75,6 +84,13 @@ export function GlobalNavMenu({ visible, onClose }: Props) { router.push(`/${slug}${path}`); }; + const onOpenSettings = () => { + if (!slug) return; + onClose(); + setShowWorkspaces(false); + router.push(`/${slug}/more/settings`); + }; + return ( {}}> + {/* User identity card — tap pushes into settings, where + account info, workspace list, and sign out already live. */} + + {/* Workspace switcher header */} setShowWorkspaces((v) => !v)} @@ -240,3 +261,46 @@ function useCurrentWorkspace(slug: string | null): Workspace | undefined { [data, slug], ); } + +function UserCard({ + user, + onPress, +}: { + user: User | null; + onPress: () => void; +}) { + const initial = (user?.name ?? user?.email ?? "U").charAt(0).toUpperCase(); + return ( + + {user?.avatar_url ? ( + + ) : ( + + + {initial} + + + )} + + + {user?.name ?? "—"} + + {user?.email ? ( + + {user.email} + + ) : null} + + + + ); +}