feat: autumn migration (#902)

This commit is contained in:
MaheshtheDev 2026-05-08 22:42:55 +00:00
parent 35c606028c
commit debac33eba
13 changed files with 139 additions and 220 deletions

View file

@ -169,7 +169,7 @@ function AuthConnectContent() {
setIsUpgrading(true)
const safeSuccessUrl = `${window.location.origin}${window.location.pathname}?callback=${encodeURIComponent(callback ?? "")}&client=${encodeURIComponent(validClient ?? "")}`
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: safeSuccessUrl,
})
} catch (err) {

View file

@ -226,7 +226,7 @@ interface ConnectContentProps {
export function ConnectContent({ selectedProject }: ConnectContentProps) {
const queryClient = useQueryClient()
const autumn = useCustomer()
const isProUser = hasActivePlan(autumn.customer?.products, "api_pro")
const isProUser = hasActivePlan(autumn.data?.subscriptions, "api_pro")
const [connectingProvider, setConnectingProvider] =
useState<ConnectorProvider | null>(null)
const [gdriveSyncScope, setGdriveSyncScope] =
@ -244,7 +244,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
setIsUpgrading(true)
try {
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: window.location.href,
})
} catch (error) {
@ -254,9 +254,9 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
}
}
const connectionsFeature = autumn.customer?.features?.connections
const connectionsUsed = connectionsFeature?.usage ?? 0
const connectionsLimit = connectionsFeature?.included_usage ?? 10
const connectionsBalance = autumn.data?.balances?.connections
const connectionsUsed = connectionsBalance?.usage ?? 0
const connectionsLimit = connectionsBalance?.granted ?? 10
const canAddConnection = connectionsUsed < connectionsLimit
// Fetch connections

View file

@ -16,7 +16,7 @@ import { toast } from "sonner"
import { useDocumentMutations } from "../../hooks/use-document-mutations"
import { useCustomer } from "autumn-js/react"
import { useTokenUsage } from "@/hooks/use-token-usage"
import { tokensToCredits, formatUsageNumber } from "@/lib/billing-utils"
import { formatUsageNumber } from "@/lib/billing-utils"
import { SpaceSelector } from "../space-selector"
import { useIsMobile } from "@hooks/use-mobile"
import { addDocumentParam } from "@/lib/search-params"
@ -127,11 +127,8 @@ export function AddDocument({
const autumn = useCustomer()
const {
tokensUsed,
tokensLimit,
tokensPercent,
searchesUsed,
searchesLimit,
searchesPercent,
planUsagePct,
hasPaidPlan,
isLoading: isLoadingUsage,
} = useTokenUsage(autumn)
@ -298,72 +295,46 @@ export function AddDocument({
dmSansClassName(),
)}
>
Credits
Plan usage
</span>
<span
className={cn(
"text-sm font-medium",
"text-sm font-medium tabular-nums",
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
dmSansClassName(),
)}
>
{isLoadingUsage
? "…"
: `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`}
: `${planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)}% used`}
</span>
</div>
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${tokensPercent}%`,
width: `${planUsagePct}%`,
background:
tokensPercent > 80
planUsagePct > 80
? "#ef4444"
: hasPaidPlan
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
/>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<span
{!isLoadingUsage && (
<p
className={cn(
"text-[#FAFAFA] text-sm font-medium",
"text-xs text-[#737373] tabular-nums",
dmSansClassName(),
)}
>
Search Queries
</span>
<span
className={cn(
"text-sm font-medium",
hasPaidPlan ? "text-[#4BA0FA]" : "text-[#737373]",
dmSansClassName(),
)}
>
{isLoadingUsage
? "…"
: `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`}
</span>
</div>
<div className="h-2 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${searchesPercent}%`,
background:
searchesPercent > 80
? "#ef4444"
: hasPaidPlan
? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)"
: "#0054AD",
}}
/>
</div>
{formatUsageNumber(tokensUsed)} tokens ·{" "}
{formatUsageNumber(searchesUsed)} queries
</p>
)}
</div>
{!hasPaidPlan && (
@ -373,7 +344,7 @@ export function AddDocument({
setIsUpgrading(true)
try {
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: "https://app.supermemory.ai/settings#account",
})
window.location.reload()

View file

@ -170,7 +170,7 @@ export function IntegrationsView() {
const [, setAddDoc] = useQueryState("add", addDocumentParam)
const { org } = useAuth()
const autumn = useCustomer()
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
const { data: connections = [] } = useQuery({
queryKey: ["connections"],

View file

@ -386,7 +386,7 @@ export function PluginsDetail() {
})
const [keyCopied, setKeyCopied] = useState(false)
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
const { data: pluginsData } = useQuery({
queryFn: async () => {
@ -496,7 +496,7 @@ export function PluginsDetail() {
const handleUpgrade = async () => {
try {
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: "https://app.supermemory.ai/?view=integrations",
})
window.location.reload()

View file

@ -9,7 +9,7 @@ import {
} from "@/hooks/use-account-settings"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useTokenUsage } from "@/hooks/use-token-usage"
import { formatUsageNumber, tokensToCredits } from "@/lib/billing-utils"
import { formatUsageNumber } from "@/lib/billing-utils"
import {
Dialog,
DialogContent,
@ -153,11 +153,8 @@ export default function Account() {
const {
tokensUsed,
tokensLimit,
tokensPercent,
searchesUsed,
searchesLimit,
searchesPercent,
planUsagePct,
currentPlan,
hasPaidPlan,
isLoading: isCheckingStatus,
@ -176,7 +173,7 @@ export default function Account() {
setIsUpgrading(true)
try {
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: "https://app.supermemory.ai/settings#account",
})
window.location.reload()
@ -388,7 +385,7 @@ export default function Account() {
</p>
</div>
{/* Credits Usage Progress */}
{/* Plan usage (unified) */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<p
@ -397,84 +394,56 @@ export default function Account() {
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
)}
>
Credits Used
Plan usage
</p>
<span
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA]",
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA] tabular-nums",
)}
>
{tokensToCredits(tokensUsed)} /{" "}
{tokensToCredits(tokensLimit)}
{planUsagePct < 1 && planUsagePct > 0
? "< 1"
: Math.round(planUsagePct)}
% used
</span>
</div>
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${tokensPercent}%`,
width: `${planUsagePct}%`,
background:
tokensPercent > 80
planUsagePct > 80
? "#ef4444"
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
}}
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
/>
</div>
</div>
{/* Search Queries Progress */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<p
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
)}
>
Search Queries
</p>
<span
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#4BA0FA]",
)}
>
{formatUsageNumber(searchesUsed)} /{" "}
{formatUsageNumber(searchesLimit)}
</span>
</div>
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px]"
style={{
width: `${searchesPercent}%`,
background:
searchesPercent > 80
? "#ef4444"
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
}}
/>
</div>
</div>
{/* Days remaining indicator */}
{daysRemaining !== null && (
<p
className={cn(
dmSans125ClassName(),
"text-sm text-[#737373]",
"text-sm tracking-[-0.14px] text-[#737373] tabular-nums",
)}
>
Resets in {daysRemaining} day
{daysRemaining !== 1 ? "s" : ""}
{formatUsageNumber(tokensUsed)} tokens ·{" "}
{formatUsageNumber(searchesUsed)} queries
{daysRemaining !== null && (
<>
{" · resets in "}
{daysRemaining} day{daysRemaining !== 1 ? "s" : ""}
</>
)}
</p>
)}
</div>
<button
type="button"
onClick={() => {
autumn.openBillingPortal?.()
autumn.openCustomerPortal?.({
returnUrl: "https://app.supermemory.ai/settings#account",
})
}}
className={cn(
"relative w-full h-11 rounded-full flex items-center justify-center gap-2",
@ -501,10 +470,7 @@ export default function Account() {
Free plan
</p>
<div className="flex flex-col gap-2">
<PlanFeatureRow
icon="check"
text="10 credits / 1M tokens"
/>
<PlanFeatureRow icon="check" text="1M tokens / month" />
<PlanFeatureRow icon="check" text="10K search queries" />
<PlanFeatureRow icon="x" text="No connections" />
<PlanFeatureRow icon="check" text="Basic support" />
@ -536,7 +502,7 @@ export default function Account() {
<div className="flex flex-col gap-2">
<PlanFeatureRow
icon="check"
text="30 credits / 3M tokens"
text="3M tokens / month"
variant="highlight"
/>
<PlanFeatureRow
@ -580,7 +546,7 @@ export default function Account() {
</p>
</div>
{/* Credits Usage Progress */}
{/* Plan usage (unified) */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<p
@ -589,74 +555,46 @@ export default function Account() {
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
)}
>
Credits Used
Plan usage
</p>
<p
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#737373]",
"font-medium text-[16px] tracking-[-0.16px] text-[#737373] tabular-nums",
)}
>
{tokensToCredits(tokensUsed)} /{" "}
{tokensToCredits(tokensLimit)}
{planUsagePct < 1 && planUsagePct > 0
? "< 1"
: Math.round(planUsagePct)}
% used
</p>
</div>
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px] transition-all"
style={{
width: `${tokensPercent}%`,
background: tokensPercent > 80 ? "#ef4444" : "#0054AD",
width: `${planUsagePct}%`,
background: planUsagePct > 80 ? "#ef4444" : "#0054AD",
}}
title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`}
/>
</div>
</div>
{/* Search Queries Progress */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<p
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#FAFAFA]",
)}
>
Search Queries
</p>
<p
className={cn(
dmSans125ClassName(),
"font-medium text-[16px] tracking-[-0.16px] text-[#737373]",
)}
>
{formatUsageNumber(searchesUsed)} /{" "}
{formatUsageNumber(searchesLimit)}
</p>
</div>
<div className="h-3 w-full rounded-[40px] bg-[#2E353D] p-px overflow-hidden">
<div
className="h-full rounded-[40px] transition-all"
style={{
width: `${searchesPercent}%`,
background:
searchesPercent > 80 ? "#ef4444" : "#0054AD",
}}
/>
</div>
</div>
{/* Days remaining indicator */}
{daysRemaining !== null && (
<p
className={cn(
dmSans125ClassName(),
"text-sm text-[#737373]",
"text-sm tracking-[-0.14px] text-[#737373] tabular-nums",
)}
>
Resets in {daysRemaining} day
{daysRemaining !== 1 ? "s" : ""}
{formatUsageNumber(tokensUsed)} tokens ·{" "}
{formatUsageNumber(searchesUsed)} queries
{daysRemaining !== null && (
<>
{" · resets in "}
{daysRemaining} day{daysRemaining !== 1 ? "s" : ""}
</>
)}
</p>
)}
</div>
<button
type="button"
@ -701,10 +639,7 @@ export default function Account() {
Free plan
</p>
<div className="flex flex-col gap-2">
<PlanFeatureRow
icon="check"
text="10 credits / 1M tokens"
/>
<PlanFeatureRow icon="check" text="1M tokens / month" />
<PlanFeatureRow icon="check" text="10K search queries" />
<PlanFeatureRow icon="x" text="No connections" />
<PlanFeatureRow icon="check" text="Basic support" />
@ -737,7 +672,7 @@ export default function Account() {
<div className="flex flex-col gap-2">
<PlanFeatureRow
icon="check"
text="30 credits / 3M tokens"
text="3M tokens / month"
variant="highlight"
/>
<PlanFeatureRow

View file

@ -346,12 +346,11 @@ export default function ConnectionsMCP() {
const projects = (queryClient.getQueryData<Project[]>(["projects"]) ||
[]) as Project[]
const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro")
const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro")
// Get connections data directly from autumn customer
const connectionsFeature = autumn.customer?.features?.connections
const connectionsUsed = connectionsFeature?.usage ?? 0
const connectionsLimit = connectionsFeature?.included_usage ?? 10
const connectionsBalance = autumn.data?.balances?.connections
const connectionsUsed = connectionsBalance?.usage ?? 0
const connectionsLimit = connectionsBalance?.granted ?? 10
const canAddConnection = connectionsUsed < connectionsLimit
@ -425,7 +424,7 @@ export default function ConnectionsMCP() {
const handleUpgrade = async () => {
try {
await autumn.attach({
productId: "api_pro",
planId: "api_pro",
successUrl: "https://app.supermemory.ai/settings#connections",
})
window.location.reload()

View file

@ -4,8 +4,15 @@ import { calculateUsagePercent, getDaysRemaining } from "@/lib/billing-utils"
export type PlanType = "free" | "pro" | "scale" | "enterprise"
const TOKEN_METER_IDS = [
"sm_tokens_text",
"sm_tokens_rich",
"sm_superrag_text",
"sm_superrag_rich",
] as const
export function useTokenUsage(autumn: ReturnType<typeof useCustomer>) {
const status = getSubscriptionStatus(autumn.customer?.products)
const status = getSubscriptionStatus(autumn.data?.subscriptions)
let currentPlan: PlanType = "free"
if (isAllowedFrom(status, "api_enterprise")) {
@ -18,30 +25,36 @@ export function useTokenUsage(autumn: ReturnType<typeof useCustomer>) {
const hasPaidPlan = currentPlan !== "free"
// Get token usage from autumn customer features
const tokensFeature = autumn.customer?.features?.api_tokens
const tokensUsed = tokensFeature?.usage ?? 0
const tokensLimit = tokensFeature?.included_usage ?? 0
const tokensResetAt = tokensFeature?.next_reset_at
const balances = autumn.data?.balances ?? {}
// Get search queries usage from autumn customer features
const searchesFeature = autumn.customer?.features?.api_search_queries
const searchesUsed = searchesFeature?.usage ?? 0
const searchesLimit = searchesFeature?.included_usage ?? 0
const tokensUsed = TOKEN_METER_IDS.reduce((sum, id) => {
const balance = balances[id]
return sum + (balance?.usage ?? 0)
}, 0)
const searchesBalance = balances.sm_search_queries
const searchesUsed = searchesBalance?.usage ?? 0
const usdBalance = balances.usd_credits
const usdIncluded = usdBalance?.granted ?? 0
const usdSpent = usdBalance?.usage ?? 0
const planUsagePct =
usdIncluded > 0 ? calculateUsagePercent(usdSpent, usdIncluded) : 0
const resetAt =
usdBalance?.nextResetAt ??
balances.sm_tokens_text?.nextResetAt ??
searchesBalance?.nextResetAt ??
undefined
const daysRemaining = getDaysRemaining(resetAt)
const isLoading = autumn.isLoading
const tokensPercent = calculateUsagePercent(tokensUsed, tokensLimit)
const searchesPercent = calculateUsagePercent(searchesUsed, searchesLimit)
const daysRemaining = getDaysRemaining(tokensResetAt)
return {
tokensUsed,
tokensLimit,
tokensPercent,
searchesUsed,
searchesLimit,
searchesPercent,
planUsagePct,
currentPlan,
hasPaidPlan,
isLoading,

View file

@ -1,12 +1,3 @@
/**
* Convert tokens to credits
* 100,000 tokens = 1 credit
* Uses floor rounding (2.9 credits shows as 2, only shows 3 when it hits 3.0)
*/
export function tokensToCredits(tokens: number): number {
return Math.floor(tokens / 100_000)
}
/**
* Format a number with K/M suffix for display
* @example formatUsageNumber(1500000) => "1.5M"

View file

@ -72,7 +72,7 @@
"@types/dompurify": "^3.2.0",
"agents": "^0.4.0",
"ai": "^6.0.168",
"autumn-js": "0.0.116",
"autumn-js": "1.2.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"d3-force": "^3.0.0",

View file

@ -184,7 +184,7 @@
"@types/dompurify": "^3.2.0",
"agents": "^0.4.0",
"ai": "^6.0.168",
"autumn-js": "0.0.116",
"autumn-js": "1.2.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"d3-force": "^3.0.0",
@ -315,7 +315,7 @@
},
"packages/tools": {
"name": "@supermemory/tools",
"version": "1.4.7",
"version": "2.0.0",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.25",
"@ai-sdk/openai": "^2.0.23",
@ -2345,7 +2345,7 @@
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
"autumn-js": ["autumn-js@0.0.116", "", { "dependencies": { "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^3.24.1" }, "peerDependencies": { "better-auth": "^1.2.12", "better-call": "^1.0.12" }, "optionalPeers": ["better-auth", "better-call"] }, "sha512-h/kZKDTuUk7MOAnRSjyWuf++2BVH5HF/yIeTOFEL3YwLXtWHBjqlYSsHlQK7BH7R1TC1fPsXGKMRGRjgcJD/tA=="],
"autumn-js": ["autumn-js@1.2.12", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.6.0", "better-call": "^1.0.12", "express": "^5.2.1", "hono": "^4.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["better-auth", "better-call", "express", "hono", "next", "react"] }, "sha512-UALRS5oD2pkPQqMfgM5op7EoAZazbLJl8+sAyFeNzGi+sGUdEUen/SopnQtqiXxtWpTDlXf8UbJqL2LkeG+ltg=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
@ -2713,6 +2713,8 @@
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
"decode-uri-component": ["decode-uri-component@0.4.1", "", {}, "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
@ -3025,6 +3027,8 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"filter-obj": ["filter-obj@5.1.0", "", {}, "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
"find-process": ["find-process@2.1.0", "", { "dependencies": { "chalk": "~4.1.2", "commander": "^12.1.0", "loglevel": "^1.9.2" }, "bin": { "find-process": "dist/cjs/bin/find-process.js" } }, "sha512-uf1F6mFDAltKaVHf4/zlXstW/k01qPwv+OB6bMRnuIycO6dTmQo+sUW1Uj/xJzGmmVarYW4ZM7TBBbBy6f/y0g=="],
@ -4205,6 +4209,8 @@
"query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="],
"query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
@ -4519,6 +4525,8 @@
"split": ["split@1.0.1", "", { "dependencies": { "through": "2" } }, "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg=="],
"split-on-first": ["split-on-first@3.0.0", "", {}, "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
@ -5535,6 +5543,8 @@
"atmn/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],

View file

@ -54,10 +54,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const setActiveOrg = useCallback(async (slug: string) => {
if (!slug) return
const activeOrg = await authClient.organization.setActive({
const res = await authClient.organization.setActive({
organizationSlug: slug,
})
setOrg(activeOrg)
setOrg(res?.data ?? null)
localStorage.setItem(STORAGE_KEY, slug)
}, [])
@ -112,7 +112,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (!one) return
if (activeOrgId === one.id) {
const full = await authClient.organization.getFullOrganization()
if (!cancelled) setOrg(full)
if (!cancelled) setOrg(full?.data ?? null)
} else {
await setActiveOrg(one.slug)
}
@ -125,7 +125,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (match) {
if (activeOrgId === match.id) {
const full = await authClient.organization.getFullOrganization()
if (!cancelled) setOrg(full)
if (!cancelled) setOrg(full?.data ?? null)
} else {
await setActiveOrg(savedSlug)
}
@ -138,13 +138,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const fromList = orgs.find((o) => o.id === activeOrgId)
if (fromList) {
const full = await authClient.organization.getFullOrganization()
if (!cancelled) setOrg(full)
if (!cancelled) setOrg(full?.data ?? null)
return
}
}
const full = await authClient.organization.getFullOrganization()
if (!cancelled) setOrg(full)
if (!cancelled) setOrg(full?.data ?? null)
} catch (error) {
console.error("Failed to restore organization:", error)
} finally {

View file

@ -33,28 +33,28 @@ export function isAllowedFrom(
}
export function getSubscriptionStatus(
products: Array<{ id: string; status: string }> | undefined,
subscriptions: Array<{ planId: string; status: string }> | undefined,
): SubscriptionStatusMap {
const statusMap: SubscriptionStatusMap = { ...DEFAULT_SUBSCRIPTION_STATUS }
if (!products) return statusMap
if (!subscriptions) return statusMap
const productMap = new Map(products.map((p) => [p.id, p]))
const subMap = new Map(subscriptions.map((s) => [s.planId, s]))
for (const tier of PLAN_TIERS) {
const product = productMap.get(tier)
const sub = subMap.get(tier)
statusMap[tier] = {
allowed: product?.status === "active",
status: product?.status ?? null,
allowed: sub?.status === "active",
status: sub?.status ?? null,
}
}
return statusMap
}
export function hasActivePlan(
products: Array<{ id: string; status: string }> | undefined,
subscriptions: Array<{ planId: string; status: string }> | undefined,
minimumTier: PlanTier,
): boolean {
return isAllowedFrom(getSubscriptionStatus(products), minimumTier)
return isAllowedFrom(getSubscriptionStatus(subscriptions), minimumTier)
}
export const useDeleteDocument = (selectedProject: string) => {