From d126fe84dca575250d10ac28f9de56efeb4c57d9 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Mon, 23 Feb 2026 19:06:46 +0100 Subject: [PATCH] Restyle navbar/topbar with light/dark themes and restore invite functionality - NavigationBar: add light/dark theme support with adaptive text colors - TopBar: match navbar background color in both themes - main.css: make navbar-background/darker CSS variables theme-aware - tablo-details: restore working invite button with full share dialog Co-Authored-By: Claude Sonnet 4.6 --- apps/main/src/components/NavigationBar.tsx | 42 +++--- apps/main/src/components/TopBar.tsx | 2 +- apps/main/src/main.css | 8 +- apps/main/src/pages/tablo-details.tsx | 159 +++++++++++++++++++-- 4 files changed, 175 insertions(+), 36 deletions(-) diff --git a/apps/main/src/components/NavigationBar.tsx b/apps/main/src/components/NavigationBar.tsx index b633b23..ea1992f 100644 --- a/apps/main/src/components/NavigationBar.tsx +++ b/apps/main/src/components/NavigationBar.tsx @@ -69,8 +69,8 @@ function NavLink({ isActive, children }: NavLinkProps) { "*:data-[ui=notification-badge]:text-xs/6", "*:data-[ui=notification-badge]:font-semibold", isActive - ? "bg-navbar-darker font-semibold text-white *:data-[ui=notification-badge]:bg-transparent" - : ["font-medium", "text-gray-300/90 [&:not(:hover)>[data-ui=icon]]:bg-navbar-darker"] + ? "bg-navbar-darker font-semibold text-gray-900 dark:text-white *:data-[ui=notification-badge]:bg-transparent" + : ["font-medium", "text-gray-500 dark:text-gray-300/90 [&:not(:hover)>[data-ui=icon]]:bg-navbar-darker"] )} > {children} @@ -84,13 +84,13 @@ export function UserMenuPopover({ isCollapsed }: { isCollapsed: boolean }) { const { t } = useTranslation("navigation"); const MenuSeparator = () => { - return ; + return ; }; const itemVariants = cva("", { variants: { variant: { - default: "text-gray-200/90 focus:bg-gray-500/80 focus:text-white", + default: "text-gray-600 dark:text-gray-200/90 focus:bg-gray-200/80 dark:focus:bg-gray-500/80 focus:text-gray-900 dark:focus:text-white", destructive: "text-red-500/80 focus:bg-red-500/80 focus:text-white", }, }, @@ -136,10 +136,10 @@ export function UserMenuPopover({ isCollapsed }: { isCollapsed: boolean }) { {!isCollapsed && (
- + {user.first_name} {user.last_name} - + {user.email}
@@ -147,7 +147,7 @@ export function UserMenuPopover({ isCollapsed }: { isCollapsed: boolean }) { - + {user.name?.charAt(0).toUpperCase()} @@ -163,10 +163,10 @@ export function UserMenuPopover({ isCollapsed }: { isCollapsed: boolean }) {
- + {user.name} - + {user.email}
@@ -248,7 +248,7 @@ export const SideNavigation = ({ isMobileMenuOpen }: { isMobileMenuOpen: boolean />

@@ -265,7 +265,7 @@ export const SideNavigation = ({ isMobileMenuOpen }: { isMobileMenuOpen: boolean className={twMerge( isCollapsed ? "relative" : "absolute top-2 right-2", "size-5 p-1", - "text-gray-300 hover:text-white", + "text-gray-500 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white", "transition-all duration-300", "bg-navbar-background", "rounded-full shadow-md", @@ -300,9 +300,9 @@ function RecentProjectsSection() { return (
- +
- + {t("projects", "Projects")}
@@ -316,12 +316,12 @@ function RecentProjectsSection() { className={twMerge( "flex items-center gap-2.5 px-2 py-1.5 rounded-lg text-sm transition-colors", isActive - ? "bg-navbar-darker text-white font-semibold" - : "text-gray-300/90 hover:bg-navbar-darker hover:text-white" + ? "bg-navbar-darker text-gray-900 dark:text-white font-semibold" + : "text-gray-500 dark:text-gray-300/90 hover:bg-navbar-darker hover:text-gray-900 dark:hover:text-white" )} > {tablo.name.charAt(0).toUpperCase()} @@ -437,7 +437,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) { if ("isHorizontalBar" in item) { return (
  • - +
  • ); } @@ -458,7 +458,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) { @@ -621,7 +621,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) { @@ -632,7 +632,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
  • - +
  • diff --git a/apps/main/src/components/TopBar.tsx b/apps/main/src/components/TopBar.tsx index e6b9107..f224607 100644 --- a/apps/main/src/components/TopBar.tsx +++ b/apps/main/src/components/TopBar.tsx @@ -341,7 +341,7 @@ export function TopBar() { }; return ( -
    +
    { const { data: tablos, isLoading } = useTablosList(); const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); + const [isShareDialogOpen, setIsShareDialogOpen] = useState(false); + const [inviteEmail, setInviteEmail] = useState(""); + + const currentUser = useUser(); + const { data: members } = useTabloMembers(tabloId ?? ""); + const { data: pendingInvites } = usePendingTabloInvitesByTablo(tabloId ?? ""); + const { mutate: inviteUser, isPending: isInvitingUser } = useInviteUser(); + + const isEmailValid = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const handleSendInvite = () => { + if (inviteEmail.trim() && tabloId) { + inviteUser({ email: inviteEmail, tablo_id: tabloId }); + setInviteEmail(""); + } + }; + + const filteredMembers = members?.filter( + (member) => !pendingInvites?.some((invite) => invite.invited_email === member.email), + ); const sectionParam = searchParams.get("section") as TabSection | null; const activeSection: TabSection = @@ -196,17 +232,16 @@ export const TabloDetailsPage = () => { Discussion - + {isAdmin && ( + + )}
    @@ -479,6 +514,106 @@ export const TabloDetailsPage = () => { initialStatus="todo" /> )} + + {/* Share / Invite Dialog */} + + + + Partager le projet + Invitez des personnes à collaborer sur ce projet + + +
    + {/* Invite Input */} +
    + setInviteEmail(e.target.value)} + placeholder="Email de l'utilisateur" + className="flex-1" + /> + {isInvitingUser ? ( +
    +
    +
    + ) : ( + + )} +
    + + {/* Pending Invites */} + {pendingInvites && pendingInvites.length > 0 && ( +
    +

    + Invitations en attente ({pendingInvites.length}) +

    +
    + {pendingInvites.map((invite) => ( +
    +
    + + + +
    +
    + + {invite.invited_email} + +
    +
    + ))} +
    +
    + )} + + {/* Members List */} + {filteredMembers && filteredMembers.length > 0 && ( +
    +

    + Membres ({filteredMembers.length}) +

    +
    + {filteredMembers.map((member) => { + const isCurrentUser = member.id === currentUser.id; + const avatarUrl = isCurrentUser ? currentUser.avatar_url : null; + return ( +
    + + {avatarUrl && } + + {member.name.charAt(0).toUpperCase()} + + +
    + + {member.name} + + + {member.is_admin ? "Admin" : "Invité"} + +
    +
    + ); + })} +
    +
    + )} +
    + +
    ); };