mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
feat(web): org brain switcher in header + plan badges (#1006)
- Replace the top-left logo menu with an org switcher (Switch brain): lists organizations with avatar monograms, brand-blue active row, and the active org name (sans "Organization") shown above the supermemory wordmark. - Switching an org persists the active org and reloads so all data re-scopes. - Show each org's plan badge in the switcher, reusing the account settings badge (extracted OrgPlanBadge + resolveOrgPlan to a shared component). - Match the profile dropdown to the same design (rows, hover/focus, border). - Remove the 'Be part of the next supermemory app' research CTA card. - Add hideCount prop to SpaceSelector; hide the space count in chat input.
This commit is contained in:
parent
fdecae74fc
commit
aedcbeb321
9 changed files with 155 additions and 78 deletions
|
|
@ -1,14 +1,12 @@
|
|||
"use client"
|
||||
|
||||
import { EnsureWorkspace } from "@/components/ensure-workspace"
|
||||
import { NextAppResearchCta } from "@/components/next-app-research-cta"
|
||||
import { PWAInstallPrompt } from "@/components/pwa-install-prompt"
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<EnsureWorkspace>{children}</EnsureWorkspace>
|
||||
<NextAppResearchCta />
|
||||
<PWAInstallPrompt />
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export function HomeChatComposer({
|
|||
onValueChange={setChatSpaceProjects}
|
||||
variant="insideOut"
|
||||
includeAuto
|
||||
hideCount
|
||||
triggerClassName="h-auto min-h-0 max-w-[min(160px,35vw)] rounded-full border border-[#161F2C] bg-[#000000] px-3 py-1.5 shadow-none hover:bg-[#05080D]"
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -980,6 +980,7 @@ export function ChatSidebar({
|
|||
onValueChange={setChatSpaceProjects}
|
||||
variant="insideOut"
|
||||
includeAuto
|
||||
hideCount
|
||||
triggerClassName="h-10 min-h-10 max-w-[min(192px,42vw)] border border-[#73737333] bg-[#0D121A] shadow-[1.5px_1.5px_4.5px_0_rgba(0,0,0,0.70)_inset]"
|
||||
/>
|
||||
</>
|
||||
|
|
@ -1176,6 +1177,7 @@ export function ChatSidebar({
|
|||
onValueChange={setChatSpaceProjects}
|
||||
variant="insideOut"
|
||||
includeAuto
|
||||
hideCount
|
||||
triggerClassName="h-auto min-h-0 max-w-[min(160px,35vw)] rounded-full border border-[#161F2C] bg-[#000000] px-3 py-1.5 shadow-none hover:bg-[#05080D]"
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -39,14 +39,39 @@ import { FeedbackModal } from "./feedback-modal"
|
|||
import { useViewMode } from "@/lib/view-mode-context"
|
||||
import { useQueryState } from "nuqs"
|
||||
import { feedbackParam } from "@/lib/search-params"
|
||||
import { useCustomer } from "autumn-js/react"
|
||||
import { useTokenUsage } from "@/hooks/use-token-usage"
|
||||
import { useOrgSummaries } from "@/hooks/use-org-summaries"
|
||||
import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge"
|
||||
|
||||
interface HeaderProps {
|
||||
onAddMemory?: () => void
|
||||
onOpenSearch?: () => void
|
||||
}
|
||||
|
||||
const brainItemClass = (active: boolean) =>
|
||||
cn(
|
||||
"gap-2.5 rounded-lg px-2 py-1.5 text-sm font-medium cursor-pointer transition-colors",
|
||||
"hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white",
|
||||
active ? "bg-white/[0.06] text-white" : "text-white/85",
|
||||
)
|
||||
|
||||
const brainTileClass = (active: boolean) =>
|
||||
cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-lg border text-[11px] font-semibold",
|
||||
active
|
||||
? "border-[#2261CA66] bg-[#0B2A57] text-[#7EB0FF]"
|
||||
: "border-white/[0.08] bg-white/[0.04] text-white/70",
|
||||
)
|
||||
|
||||
export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
||||
const { user, isRestoring } = useAuth()
|
||||
const { user, isRestoring, org, organizations, setActiveOrg } = useAuth()
|
||||
const autumn = useCustomer()
|
||||
const { currentPlan } = useTokenUsage(autumn)
|
||||
const { data: orgSummaries } = useOrgSummaries()
|
||||
const planByOrgId = new Map(
|
||||
(orgSummaries ?? []).map((s) => [s.orgId, s.plan] as const),
|
||||
)
|
||||
const { selectedProjects, setSelectedProjects } = useProject()
|
||||
const router = useRouter()
|
||||
const isMobile = useIsMobile()
|
||||
|
|
@ -66,6 +91,14 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
user?.email?.split("@")[0] ||
|
||||
""
|
||||
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "My"
|
||||
const orgLabel = org?.name.replace(/\s*organizations?\s*$/i, "").trim()
|
||||
const topLabel = orgLabel || userName
|
||||
const hasOrgs = (organizations?.length ?? 0) > 0
|
||||
|
||||
const selectOrg = (slug: string, isActive: boolean) => {
|
||||
if (isActive) return
|
||||
void setActiveOrg(slug).then(() => window.location.reload())
|
||||
}
|
||||
return (
|
||||
<div className="relative z-10 flex shrink-0 items-center justify-between gap-1.5 p-2.5 md:gap-2 md:p-3">
|
||||
<div className="z-10! flex min-w-0 shrink items-center justify-center gap-1.5 md:gap-3">
|
||||
|
|
@ -73,34 +106,65 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex shrink-0 cursor-pointer items-center rounded-lg px-1.5 py-1 transition-colors hover:bg-white/5 focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none md:-ml-2"
|
||||
className="relative flex shrink-0 cursor-pointer items-center rounded-lg px-1.5 py-1 transition-colors hover:bg-white/5 outline-none focus-visible:outline-none md:-ml-2 before:absolute before:-inset-x-2 before:-inset-y-2.5 before:content-['']"
|
||||
>
|
||||
<Logo className="h-6 md:h-7" />
|
||||
{userName && (
|
||||
<div className="ml-1.5 flex flex-col items-start justify-center sm:ml-2">
|
||||
<p className="text-[10px] leading-tight text-[#6B6B6B] sm:text-[11px]">
|
||||
{userName}
|
||||
</p>
|
||||
<p className="-mt-0.5 text-sm leading-none font-medium text-white/90 sm:text-lg">
|
||||
supermemory
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<Logo className="h-6" />
|
||||
<div className="ml-1.5 flex flex-col items-start justify-center sm:ml-2">
|
||||
<p className="max-w-[16ch] truncate text-[10px] leading-tight text-[#6B6B6B] sm:text-[11px]">
|
||||
{topLabel}
|
||||
</p>
|
||||
<p className="-mt-0.5 text-sm leading-none font-medium text-white/90 sm:text-lg">
|
||||
supermemory
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
alignOffset={12}
|
||||
className={cn(
|
||||
"min-w-[200px] p-1.5 rounded-xl border border-[#2E3033] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
|
||||
"min-w-[244px] p-1.5 rounded-xl border border-white/[0.08] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
|
||||
}}
|
||||
>
|
||||
{hasOrgs && (
|
||||
<>
|
||||
<p className="px-2 pt-1 pb-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-[#5B6675]">
|
||||
Switch brain
|
||||
</p>
|
||||
<div className="flex max-h-[40vh] flex-col gap-0.5 overflow-y-auto overscroll-contain">
|
||||
{organizations?.map((o) => {
|
||||
const active = org?.id === o.id
|
||||
const plan = resolveOrgPlan(
|
||||
o.id,
|
||||
active,
|
||||
currentPlan,
|
||||
planByOrgId,
|
||||
)
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={o.id}
|
||||
onClick={() => selectOrg(o.slug, active)}
|
||||
className={cn(brainItemClass(active))}
|
||||
>
|
||||
<span className={cn(brainTileClass(active))}>
|
||||
{o.name?.trim().charAt(0).toUpperCase() || "?"}
|
||||
</span>
|
||||
<span className="flex-1 truncate">{o.name}</span>
|
||||
<OrgPlanBadge plan={plan} />
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<DropdownMenuSeparator className="mx-1 my-1.5 bg-white/[0.06]" />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Link href="/">
|
||||
<Home className="size-4 text-[#737373]" />
|
||||
|
|
@ -109,7 +173,7 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<a
|
||||
href="https://console.supermemory.ai"
|
||||
|
|
@ -122,7 +186,7 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<a href="https://supermemory.ai" target="_blank" rel="noreferrer">
|
||||
<ExternalLink className="size-4 text-[#737373]" />
|
||||
|
|
@ -290,49 +354,49 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
>
|
||||
<DropdownMenuItem
|
||||
onClick={onAddMemory}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Plus className="size-4 text-[#737373]" />
|
||||
Add memory
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => void setViewMode("dashboard")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Home className="size-4 text-[#737373]" />
|
||||
Home
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setViewMode("integrations")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Sun className="size-4 text-[#737373]" />
|
||||
Integrations
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => void setViewMode("graph")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<GraphIcon className="size-4 text-[#737373]" />
|
||||
Graph
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onOpenSearch?.()}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<SearchIcon className="size-4 text-[#737373]" />
|
||||
Search
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => void setViewMode("list")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<LayoutGrid className="size-4 text-[#737373]" />
|
||||
Memories
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => void setViewMode("chat")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<MessageCircleIcon className="size-4 text-[#737373]" />
|
||||
Chat with Nova
|
||||
|
|
@ -340,14 +404,14 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) {
|
|||
<DropdownMenuSeparator className="bg-[#2E3033]" />
|
||||
<DropdownMenuItem
|
||||
onClick={handleFeedback}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<LifeBuoy className="size-4 text-[#737373]" />
|
||||
Feedback
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => router.push("/settings")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Settings className="size-4 text-[#737373]" />
|
||||
Settings
|
||||
|
|
|
|||
35
apps/web/components/org-plan-badge.tsx
Normal file
35
apps/web/components/org-plan-badge.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { cn } from "@lib/utils"
|
||||
import { dmSans125ClassName } from "@/lib/fonts"
|
||||
import { PLAN_DISPLAY_NAMES, type PlanType } from "@/hooks/use-token-usage"
|
||||
|
||||
const orgPlanBadgeBase = cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-[18px] min-w-[42px] shrink-0 items-center justify-center rounded-[3px] px-1.5 text-[10px] uppercase",
|
||||
)
|
||||
|
||||
const ORG_PLAN_BADGE_STYLES: Record<PlanType, string> = {
|
||||
free: "bg-[#2E353D] font-mono font-medium tracking-[0.12em] text-[#A3A3A3]",
|
||||
pro: "bg-[#4BA0FA] font-bold tracking-[0.36px] text-[#00171A]",
|
||||
scale: "bg-[#0054AD] font-bold tracking-[0.36px] text-[#FAFAFA]",
|
||||
enterprise: "bg-[#FAFAFA] font-bold tracking-[0.36px] text-[#0D121A]",
|
||||
}
|
||||
|
||||
export function OrgPlanBadge({ plan }: { plan: PlanType }) {
|
||||
return (
|
||||
<span className={cn(orgPlanBadgeBase, ORG_PLAN_BADGE_STYLES[plan])}>
|
||||
{PLAN_DISPLAY_NAMES[plan]}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function resolveOrgPlan(
|
||||
orgId: string,
|
||||
isCurrent: boolean,
|
||||
currentPlan: PlanType,
|
||||
planByOrgId: Map<string, PlanType>,
|
||||
): PlanType {
|
||||
const fromSummary = planByOrgId.get(orgId)
|
||||
if (fromSummary) return fromSummary
|
||||
if (isCurrent) return currentPlan
|
||||
return "free"
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ import { cn } from "@lib/utils"
|
|||
import { useAuth } from "@lib/auth-context"
|
||||
import { authClient } from "@lib/auth"
|
||||
import { useOrgSummaries } from "@/hooks/use-org-summaries"
|
||||
import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
|
||||
import {
|
||||
PLAN_DISPLAY_NAMES,
|
||||
PLAN_RANK,
|
||||
useTokenUsage,
|
||||
type PlanType,
|
||||
|
|
@ -76,25 +76,6 @@ function SettingsCard({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
/** Matches ACTIVE / RECOMMENDED pills in billing settings. */
|
||||
const orgPlanBadgeBase = cn(
|
||||
dmSans125ClassName(),
|
||||
"inline-flex h-[18px] min-w-[42px] shrink-0 items-center justify-center rounded-[3px] px-1.5 text-[10px] uppercase",
|
||||
)
|
||||
|
||||
const ORG_PLAN_BADGE_STYLES: Record<PlanType, string> = {
|
||||
free: "bg-[#2E353D] font-mono font-medium tracking-[0.12em] text-[#A3A3A3]",
|
||||
pro: "bg-[#4BA0FA] font-bold tracking-[0.36px] text-[#00171A]",
|
||||
scale: "bg-[#0054AD] font-bold tracking-[0.36px] text-[#FAFAFA]",
|
||||
enterprise: "bg-[#FAFAFA] font-bold tracking-[0.36px] text-[#0D121A]",
|
||||
}
|
||||
|
||||
function OrgPlanBadge({ plan }: { plan: PlanType }) {
|
||||
return (
|
||||
<span className={cn(orgPlanBadgeBase, ORG_PLAN_BADGE_STYLES[plan])}>
|
||||
{PLAN_DISPLAY_NAMES[plan]}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
owner: "Owner",
|
||||
|
|
@ -152,18 +133,6 @@ function isPendingInvitation(invitation: {
|
|||
return new Date(invitation.expiresAt).getTime() > Date.now()
|
||||
}
|
||||
|
||||
function resolveOrgPlan(
|
||||
orgId: string,
|
||||
isCurrent: boolean,
|
||||
currentPlan: PlanType,
|
||||
planByOrgId: Map<string, PlanType>,
|
||||
): PlanType {
|
||||
const fromSummary = planByOrgId.get(orgId)
|
||||
if (fromSummary) return fromSummary
|
||||
if (isCurrent) return currentPlan
|
||||
return "free"
|
||||
}
|
||||
|
||||
export default function Account() {
|
||||
const {
|
||||
user,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export interface SpaceSelectorProps {
|
|||
enableDelete?: boolean
|
||||
compact?: boolean
|
||||
includeAuto?: boolean
|
||||
hideCount?: boolean
|
||||
}
|
||||
|
||||
const triggerVariants = {
|
||||
|
|
@ -112,6 +113,7 @@ export function SpaceSelector({
|
|||
enableDelete = false,
|
||||
compact = false,
|
||||
includeAuto = false,
|
||||
hideCount = false,
|
||||
}: SpaceSelectorProps) {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [showSelectSpacesModal, setShowSelectSpacesModal] = useState(false)
|
||||
|
|
@ -430,11 +432,14 @@ export function SpaceSelector({
|
|||
>
|
||||
{isLoading ? "…" : displayInfo.name}
|
||||
</span>
|
||||
{!compact && spaceCountData !== undefined && spaceCountData > 0 && (
|
||||
<span className="shrink-0 text-[11px] text-[#737373] tabular-nums">
|
||||
· {formatCount(spaceCountData)}
|
||||
</span>
|
||||
)}
|
||||
{!compact &&
|
||||
!hideCount &&
|
||||
spaceCountData !== undefined &&
|
||||
spaceCountData > 0 && (
|
||||
<span className="shrink-0 text-[11px] text-[#737373] tabular-nums">
|
||||
· {formatCount(spaceCountData)}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
className="size-3.5 shrink-0 text-[#737373]"
|
||||
aria-hidden
|
||||
|
|
|
|||
|
|
@ -125,28 +125,28 @@ export function UserProfileMenu({
|
|||
<DropdownMenuContent
|
||||
align="end"
|
||||
className={cn(
|
||||
"min-w-[220px] p-1.5 rounded-xl border border-[#2E3033] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
|
||||
"min-w-[240px] p-1.5 rounded-xl border border-white/[0.08] shadow-[0px_1.5px_20px_0px_rgba(0,0,0,0.65)]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
|
||||
}}
|
||||
>
|
||||
<div id="user-info" className="px-3 py-2.5">
|
||||
<div id="user-info" className="px-2.5 py-2">
|
||||
<p className="text-white text-sm font-medium truncate">{user.name}</p>
|
||||
<p className="text-[#737373] text-xs truncate">{user.email}</p>
|
||||
</div>
|
||||
<DropdownMenuSeparator className="bg-[#2E3033]" />
|
||||
<DropdownMenuSeparator className="mx-1 my-1.5 bg-white/[0.06]" />
|
||||
<DropdownMenuItem
|
||||
onClick={() => router.push("/settings")}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<Settings className="size-4 text-[#737373]" />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={handleTryOnboarding}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<RotateCcw className="size-4 text-[#737373]" />
|
||||
Try onboarding
|
||||
|
|
@ -154,16 +154,16 @@ export function UserProfileMenu({
|
|||
{onOpenFeedback ? (
|
||||
<DropdownMenuItem
|
||||
onClick={onOpenFeedback}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<LifeBuoy className="size-4 text-[#737373]" />
|
||||
Feedback
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
<DropdownMenuSeparator className="bg-[#2E3033]" />
|
||||
<DropdownMenuSeparator className="mx-1 my-1.5 bg-white/[0.06]" />
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<a href="mailto:support@supermemory.com">
|
||||
<HelpCircle className="size-4 text-[#737373]" />
|
||||
|
|
@ -172,7 +172,7 @@ export function UserProfileMenu({
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<a
|
||||
href="https://supermemory.link/discord"
|
||||
|
|
@ -190,10 +190,10 @@ export function UserProfileMenu({
|
|||
Discord
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="bg-[#2E3033]" />
|
||||
<DropdownMenuSeparator className="mx-1 my-1.5 bg-white/[0.06]" />
|
||||
<DropdownMenuItem
|
||||
onClick={handleSignOut}
|
||||
className="px-3 py-2.5 rounded-md hover:bg-[#293952]/40 cursor-pointer text-white text-sm font-medium gap-2"
|
||||
className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer"
|
||||
>
|
||||
<LogOut className="size-4 text-[#737373]" />
|
||||
Logout
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ interface AuthContextType {
|
|||
isRestoring: boolean
|
||||
isSessionPending: boolean
|
||||
setActiveOrg: (orgSlug: string) => Promise<void>
|
||||
clearActiveOrg: () => void
|
||||
clearActiveOrg: () => Promise<void>
|
||||
updateOrgMetadata: (partial: Record<string, unknown>) => void
|
||||
refetchActiveOrg: () => Promise<Organization | null>
|
||||
refetchOrganizations: () => Promise<unknown>
|
||||
|
|
@ -62,7 +62,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||
localStorage.setItem(STORAGE_KEY, slug)
|
||||
}, [])
|
||||
|
||||
const clearActiveOrg = useCallback(() => {
|
||||
const clearActiveOrg = useCallback(async () => {
|
||||
try {
|
||||
await authClient.organization.setActive({ organizationId: null })
|
||||
} catch {}
|
||||
setOrg(null)
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue