Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 95 additions & 10 deletions apps/web/components/new/settings/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,18 @@ import {
DialogTrigger,
DialogClose,
} from "@ui/components/dialog"
import { authClient } from "@lib/auth"
import { Popover, PopoverContent, PopoverTrigger } from "@ui/components/popover"
import { useCustomer } from "autumn-js/react"
import { Check, X, Trash2, LoaderIcon, Settings } from "lucide-react"
import {
Check,
X,
Trash2,
LoaderIcon,
Settings,
ChevronDown,
Building2,
} from "lucide-react"
import { useState } from "react"

function SectionTitle({ children }: { children: React.ReactNode }) {
Expand Down Expand Up @@ -76,11 +86,25 @@ function PlanFeatureRow({
}

export default function Account() {
const { user, org } = useAuth()
const { user, org, setActiveOrg } = useAuth()
const autumn = useCustomer()
const [isUpgrading, setIsUpgrading] = useState(false)
const [deleteConfirmText, setDeleteConfirmText] = useState("")
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [switchingOrgId, setSwitchingOrgId] = useState<string | null>(null)
const { data: allOrgs } = authClient.useListOrganizations()

const handleOrgSwitch = async (orgSlug: string, orgId: string) => {
if (orgId === org?.id) return
setSwitchingOrgId(orgId)
try {
await setActiveOrg(orgSlug)
window.location.reload()
} catch (error) {
console.error("Failed to switch organization:", error)
setSwitchingOrgId(null)
}
}

const {
memoriesUsed,
Expand Down Expand Up @@ -163,7 +187,6 @@ export default function Account() {
</div>
</div>

{/* Organization + Member since */}
<div className="flex gap-4">
<div className="flex-1 flex flex-col gap-2">
<p
Expand All @@ -174,14 +197,76 @@ export default function Account() {
>
Organization
</p>
<p
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
<Popover>
<PopoverTrigger
className={cn(
"flex items-center gap-2 cursor-pointer transition-opacity hover:opacity-90",
dmSans125ClassName(),
)}
>
<span
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
)}
>
{org?.name ?? "Personal"}
</span>
<ChevronDown className="size-4 text-[#737373]" />
</PopoverTrigger>
{allOrgs && allOrgs.length > 1 && (
<PopoverContent
align="start"
className="w-72 bg-[#1B1F24] rounded-[12px] border-white/10 p-1.5 shadow-[0px_4px_16px_rgba(0,0,0,0.4)]"
>
{allOrgs.map((organization) => {
const isCurrent = organization.id === org?.id
const isSwitching = switchingOrgId === organization.id
const isConsumer =
organization.metadata?.isConsumer === true
return (
<button
key={organization.id}
type="button"
disabled={isCurrent || isSwitching}
onClick={() =>
handleOrgSwitch(
organization.slug,
organization.id,
)
}
className={cn(
"w-full flex items-center gap-3 px-3 py-2.5 rounded-[8px] text-left transition-colors",
isCurrent
? "bg-white/5"
: "hover:bg-white/5 cursor-pointer",
"disabled:opacity-60 disabled:cursor-default",
dmSans125ClassName(),
)}
>
<Building2 className="size-4 text-[#737373] shrink-0" />
<div className="flex-1 min-w-0 flex items-center gap-2">
<p className="text-[14px] tracking-[-0.14px] text-[#FAFAFA] truncate">
{organization.name}
</p>
{isCurrent && (
<Check className="size-4 text-[#4BA0FA] shrink-0" />
)}
{isSwitching && (
<LoaderIcon className="size-4 text-[#4BA0FA] shrink-0 animate-spin" />
)}
</div>
{!isConsumer && (
<span className="text-[11px] font-medium tracking-[0.3px] px-1.5 py-0.5 rounded-[4px] shrink-0 bg-[#737373]/15 text-[#737373]">
API
</span>
)}
</button>
)
})}
</PopoverContent>
)}
>
{org?.name ?? "Personal"}
</p>
</Popover>
</div>
<div className="flex-1 flex flex-col gap-2">
<p
Expand Down
32 changes: 11 additions & 21 deletions packages/lib/auth-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
organizationSlug: slug,
})
setOrg(activeOrg)
localStorage.setItem("supermemory-consumer-last-org-slug", slug)
}

const updateOrgMetadata = useCallback((partial: Record<string, unknown>) => {
Expand All @@ -52,28 +53,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {

// biome-ignore lint/correctness/useExhaustiveDependencies: ignoring the setActiveOrg dependency
useEffect(() => {
if (session?.session.activeOrganizationId) {
authClient.organization
.getFullOrganization()
.then((org) => {
if (org.metadata?.isConsumer === true) {
console.log("Consumer organization:", org)
setOrg(org)
} else {
console.log("ALl orgs:", orgs)
const consumerOrg = orgs?.find(
(o) => o.metadata?.isConsumer === true,
)
if (consumerOrg) {
setActiveOrg(consumerOrg.slug)
}
}
})
.catch((error) => {
// Silently handle organization fetch failures to prevent unhandled rejections
console.error("Failed to fetch organization:", error)
})
if (!session?.session.activeOrganizationId || !orgs) return

const savedSlug = localStorage.getItem("supermemory-consumer-last-org-slug")

if (savedSlug && orgs.find((o) => o.slug === savedSlug)) {
setActiveOrg(savedSlug)
return
}

if (savedSlug) localStorage.removeItem("supermemory-consumer-last-org-slug")
authClient.organization.getFullOrganization().then(setOrg)
}, [session?.session.activeOrganizationId, orgs])

// When a session exists and there is a pending login method recorded,
Expand Down
Loading