summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/NavStrip.tsx
blob: 6fe4ba90f199d6e30d7f074ce0d2988d5ef36699 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                  
                                                                                
                                                           




                                            
                         
                     





                                                                           



                                       
                                                                   
                                                           











                             
                                                             
                                                             


                            
                                                                         
                                                        

                                                              
                                                                                    





                                     


                                                                    







                                                                                                                                                                                  
                                                                

                                                               




















                                                                                                                                                                
            


















                                                                                                                                                                       


          
import { useAuth } from "../contexts/AuthContext";
import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext";
import { useUserSettings } from "../hooks/useUserSettings";
import { RewriteLink } from "./RewriteLink";

interface NavLink {
  label: string;
  href: string;
  requiresAuth?: boolean;
  external?: boolean;
  /**
   * When true the link is hidden once the user has flipped on the
   * document-mode UI — those areas (Exec, Contracts) are subsumed by the
   * directive-document interface and surfacing them just creates noise.
   */
  hideInDocumentMode?: boolean;
}

const NAV_LINKS: NavLink[] = [
  { label: "Listen", href: "/listen" },
  { label: "Directives", href: "/directives", requiresAuth: true },
  { label: "Orders", href: "/orders", requiresAuth: true },
  {
    label: "Contracts",
    href: "/contracts",
    requiresAuth: true,
    hideInDocumentMode: true,
  },
  {
    label: "Exec",
    href: "/exec",
    requiresAuth: true,
    hideInDocumentMode: true,
  },
  { label: "Daemons", href: "/daemons", requiresAuth: true },
  { label: "History", href: "/history", requiresAuth: true },
];

export function NavStrip() {
  const { isAuthenticated, isAuthConfigured, signOut, user } = useAuth();
  const { pendingQuestions } = useSupervisorQuestions();
  const { settings } = useUserSettings();
  const documentMode = settings?.documentModeEnabled ?? false;
  const directiveQuestionCount = pendingQuestions.filter(q => q.directiveId).length;

  const handleSignOut = async () => {
    await signOut();
    window.location.href = "/login";
  };

  // Check if user has access (authenticated or auth not configured)
  const hasAccess = isAuthenticated || !isAuthConfigured;

  return (
    <nav
      className="flex items-center gap-2.5 px-3 py-2.5 border-t border-b border-dashed border-[rgba(117,170,252,0.35)] bg-[#0c1729] font-mono uppercase tracking-wide text-[11px]"
      aria-label="Main navigation"
    >
      <span className="text-[#9bc3ff] pr-2.5 border-r border-[rgba(117,170,252,0.35)]">
        NAV//
      </span>
      <div className="flex flex-wrap gap-2 items-center flex-1">
        {NAV_LINKS.filter(
          (link) => !(documentMode && link.hideInDocumentMode),
        ).map((link) => {
          // In document mode, the directive concept is surfaced to the user
          // as "Contracts" — keep the same href (/directives) so existing
          // links and bookmarks continue to work.
          const displayLabel =
            documentMode && link.label === "Directives" ? "Contracts" : link.label;
          return (
            <span key={link.label} className="relative inline-flex items-center">
              <RewriteLink
                to={link.href}
                disabled={link.requiresAuth && !hasAccess}
                external={link.external}
              >
                {displayLabel}
              </RewriteLink>
              {link.label === "Directives" && directiveQuestionCount > 0 && (
                <span className="ml-0.5 inline-block w-2 h-2 rounded-full bg-amber-400 animate-pulse" title={`${directiveQuestionCount} pending question(s)`} />
              )}
            </span>
          );
        })}
      </div>
      <div className="flex items-center gap-2 pl-2.5 border-l border-[rgba(117,170,252,0.35)]">
        {isAuthenticated && isAuthConfigured ? (
          <>
            {user?.email && (
              <span className="text-[#9bc3ff] opacity-60">{user.email}</span>
            )}
            <RewriteLink to="/settings">Settings</RewriteLink>
            <button
              type="button"
              onClick={handleSignOut}
              className="bg-transparent border-none text-[#9bc3ff] hover:text-white transition-colors cursor-pointer uppercase text-[11px] font-mono tracking-wide p-0"
            >
              Logout
            </button>
          </>
        ) : (
          <RewriteLink to="/login">Login</RewriteLink>
        )}
      </div>
    </nav>
  );
}