);
}
-
-function AccountMenu({ mobileOnly = false }: { mobileOnly?: boolean }) {
- const { data: session } = useSession();
- const isHostedMode = isHostedClientAuthMode();
- const email = session?.user?.email;
-
- const handleSignOut = () => signOutAndRedirect();
-
- const menu = (
-
- );
-
- if (mobileOnly) {
- return menu;
- }
-
- return (
- <>
-
- {menu}
- >
- );
-}
diff --git a/src/client/layout/AppShellParts.tsx b/src/client/layout/AppShellParts.tsx
index 4605e1a..c451762 100644
--- a/src/client/layout/AppShellParts.tsx
+++ b/src/client/layout/AppShellParts.tsx
@@ -58,45 +58,29 @@ function SeoApiStatusBanners({
);
}
-function AppContent({
- drawerOpen,
+function MobileSidebarDrawer({
+ open,
projectId,
- onCloseDrawer,
- children,
+ onClose,
}: {
- drawerOpen: boolean;
+ open: boolean;
projectId: string | null;
- onCloseDrawer: () => void;
- children: React.ReactNode;
+ onClose: () => void;
}) {
+ if (!open) return null;
+
return (
- <>
-
-
{children}
-
- {drawerOpen && projectId ? (
-
- ) : null}
+
+
+
+
-
-
- {children}
-
- >
+
);
}
@@ -160,4 +144,4 @@ const MissingSeoSetupModal = React.forwardRef<
MissingSeoSetupModal.displayName = "MissingSeoSetupModal";
-export { AppContent, MissingSeoSetupModal, SeoApiStatusBanners };
+export { MissingSeoSetupModal, MobileSidebarDrawer, SeoApiStatusBanners };
diff --git a/src/client/navigation/items.ts b/src/client/navigation/items.ts
index 139af7f..b2ef682 100644
--- a/src/client/navigation/items.ts
+++ b/src/client/navigation/items.ts
@@ -1,5 +1,4 @@
import {
- BarChart3,
Bookmark,
Bot,
ClipboardCheck,
@@ -11,69 +10,60 @@ import {
TrendingUp,
} from "lucide-react";
import { linkOptions } from "@tanstack/react-router";
+import { GoogleGlyphMuted } from "@/client/features/gsc/GoogleGlyph";
const projectNavItems = [
{
to: "/p/$projectId/keywords" as const,
label: "Keyword Research",
icon: Search,
- matchSegment: "/keywords",
},
{
to: "/p/$projectId/saved" as const,
label: "Saved Keywords",
icon: Bookmark,
- matchSegment: "/saved",
},
{
to: "/p/$projectId/rank-tracking" as const,
label: "Rank Tracking",
icon: TrendingUp,
- matchSegment: "/rank-tracking",
},
{
to: "/p/$projectId/search-performance" as const,
- label: "Search Performance",
- icon: BarChart3,
- matchSegment: "/search-performance",
+ label: "GSC Insights",
+ icon: GoogleGlyphMuted,
},
{
to: "/p/$projectId/domain" as const,
label: "Domain Overview",
icon: Globe,
- matchSegment: "/domain",
},
{
to: "/p/$projectId/backlinks" as const,
label: "Backlinks",
icon: Link2,
- matchSegment: "/backlinks",
},
{
to: "/p/$projectId/audit" as const,
label: "Site Audit",
icon: ClipboardCheck,
- matchSegment: "/audit",
},
{
to: "/p/$projectId/brand-lookup" as const,
label: "Brand Lookup",
icon: Sparkles,
- matchSegment: "/brand-lookup",
},
{
to: "/p/$projectId/prompt-explorer" as const,
label: "Prompt Explorer",
icon: MessageSquare,
- matchSegment: "/prompt-explorer",
},
] as const;
-const aiNavItem = linkOptions({
+export const aiNavItem = linkOptions({
to: "/ai" as const,
label: "AI & MCP",
icon: Bot,
- matchSegment: "/ai",
});
function getProjectNavItems(projectId: string) {
@@ -86,48 +76,33 @@ function getProjectNavItems(projectId: string) {
);
}
+// Grouped by scope: "My Site" is the project's own domain (tracked data),
+// "Research" is point-at-anything lookup tools.
export function getProjectNavGroups(projectId: string) {
const all = getProjectNavItems(projectId);
- const bySegment = (seg: string) => all.find((i) => i.matchSegment === seg)!;
+ const byPath = (path: (typeof projectNavItems)[number]["to"]) =>
+ all.find((i) => i.to === path)!;
return [
{
- type: "group" as const,
- label: "Keywords",
- icon: Search,
- matchSegments: ["/keywords", "/saved", "/rank-tracking"],
+ label: "Research",
items: [
- bySegment("/keywords"),
- bySegment("/saved"),
- bySegment("/rank-tracking"),
+ byPath("/p/$projectId/keywords"),
+ byPath("/p/$projectId/domain"),
+ byPath("/p/$projectId/backlinks"),
+ byPath("/p/$projectId/brand-lookup"),
+ byPath("/p/$projectId/prompt-explorer"),
],
},
{
- type: "standalone" as const,
- item: bySegment("/search-performance"),
- },
- {
- type: "group" as const,
- label: "Domain",
- icon: Globe,
- matchSegments: ["/domain", "/backlinks", "/audit"],
+ label: "My Site",
items: [
- bySegment("/domain"),
- bySegment("/backlinks"),
- bySegment("/audit"),
+ byPath("/p/$projectId/search-performance"),
+ byPath("/p/$projectId/rank-tracking"),
+ byPath("/p/$projectId/saved"),
+ byPath("/p/$projectId/audit"),
],
},
- {
- type: "group" as const,
- label: "AI Visibility",
- icon: Sparkles,
- matchSegments: ["/brand-lookup", "/prompt-explorer"],
- items: [bySegment("/brand-lookup"), bySegment("/prompt-explorer")],
- },
- {
- type: "standalone" as const,
- item: aiNavItem,
- },
];
}
diff --git a/src/client/styles/app.css b/src/client/styles/app.css
index 98e65f4..f8564f1 100644
--- a/src/client/styles/app.css
+++ b/src/client/styles/app.css
@@ -29,11 +29,11 @@
--color-base-200: oklch(97% 0 0);
--color-base-300: oklch(92% 0 0);
--color-base-content: oklch(20% 0 0);
- --color-primary: oklch(55% 0.18 260);
+ --color-primary: oklch(50% 0.12 262);
--color-primary-content: oklch(100% 0 0);
--color-secondary: oklch(55% 0.15 145);
--color-secondary-content: oklch(100% 0 0);
- --color-accent: oklch(55% 0.18 260);
+ --color-accent: oklch(50% 0.12 262);
--color-accent-content: oklch(100% 0 0);
--color-neutral: oklch(25% 0 0);
--color-neutral-content: oklch(100% 0 0);
@@ -61,14 +61,14 @@
prefersdark: true;
color-scheme: dark;
--color-base-100: oklch(18% 0 0);
- --color-base-200: oklch(14% 0 0);
- --color-base-300: oklch(22% 0 0);
+ --color-base-200: oklch(12% 0 0);
+ --color-base-300: oklch(27% 0 0);
--color-base-content: oklch(92% 0 0);
- --color-primary: oklch(60% 0.18 260);
+ --color-primary: oklch(66% 0.12 262);
--color-primary-content: oklch(100% 0 0);
--color-secondary: oklch(60% 0.15 145);
--color-secondary-content: oklch(100% 0 0);
- --color-accent: oklch(60% 0.18 260);
+ --color-accent: oklch(66% 0.12 262);
--color-accent-content: oklch(100% 0 0);
--color-neutral: oklch(85% 0 0);
--color-neutral-content: oklch(20% 0 0);
@@ -148,6 +148,45 @@ select {
}
}
+/* Dark mode: neutral interactive surfaces should lift above the panel, not
+ sink below it. DaisyUI derives default buttons and the tabs-box well from
+ base-200, which is darker than the base-100 content panel in dark mode,
+ so they read as sunken/disabled. Colored variants (primary, error, ...)
+ set their own --btn-color and are excluded. */
+html[data-theme="openseo-dark"]
+ .btn:not(
+ .btn-primary,
+ .btn-error,
+ .btn-neutral,
+ .btn-ghost,
+ .btn-outline,
+ .btn-soft
+ ) {
+ --btn-color: oklch(25% 0 0);
+}
+
+/* Light mode: the base-200 default button is nearly invisible on the
+ base-100 content panel; use a white surface with a visible border,
+ matching inputs/selects. */
+html[data-theme="openseo"]
+ .btn:not(
+ .btn-primary,
+ .btn-error,
+ .btn-neutral,
+ .btn-ghost,
+ .btn-outline,
+ .btn-soft
+ ) {
+ --btn-color: var(--color-base-100);
+ --btn-border: var(--color-base-300);
+}
+
+/* Underline tabs: DaisyUI's tabs-border underlines the active tab with
+ currentColor; use the primary accent instead (PostHog-style). */
+.tabs-border > .tab.tab-active::before {
+ --tab-border-color: var(--color-primary);
+}
+
/* Custom alert styling with darker borders and transparent background */
.alert {
@apply flex gap-3 rounded-lg border p-4 text-base-content/70;