mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-21 14:34:03 +00:00
- Header pill with trial days left (Autumn-first, org metadata fallback) - Brain home: Your Company Brain timeline card (trial strip, milestones from /brain/overview) promoted to top-right - Trial copy in CB onboarding Slack step and docked header - Brain home now reads the new /brain/overview endpoint (drops the dead /brain/connections fetch) Fixes ENG-1142
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
"use client"
|
|
|
|
import { useAuth } from "@lib/auth-context"
|
|
import { useCustomer } from "autumn-js/react"
|
|
import { useEffect, useMemo, useState } from "react"
|
|
import { useTokenUsage } from "@/hooks/use-token-usage"
|
|
import { getBrainTrialInfo } from "@/lib/billing-utils"
|
|
|
|
export type BrainTrialState = {
|
|
state: "trialing" | "ended" | "none"
|
|
endsAtMs: number | null
|
|
startedAtMs: number | null
|
|
daysRemaining: number | null
|
|
}
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000
|
|
const NONE: BrainTrialState = {
|
|
state: "none",
|
|
endsAtMs: null,
|
|
startedAtMs: null,
|
|
daysRemaining: null,
|
|
}
|
|
|
|
// Mirrors settings billing: open metadata trial OR Autumn trialing counts as trialing.
|
|
export function useBrainTrial(): BrainTrialState {
|
|
const { org } = useAuth()
|
|
const autumn = useCustomer()
|
|
const { isTrialing, trialEndsAtMs, hasPaidPlan, isLoading } =
|
|
useTokenUsage(autumn)
|
|
const [tick, setTick] = useState(0)
|
|
|
|
useEffect(() => {
|
|
const t = window.setInterval(() => setTick((v) => v + 1), 60_000)
|
|
return () => window.clearInterval(t)
|
|
}, [])
|
|
|
|
// biome-ignore lint/correctness/useExhaustiveDependencies: tick keeps daysRemaining current across midnight
|
|
return useMemo(() => {
|
|
const meta = getBrainTrialInfo(
|
|
org?.metadata as Record<string, unknown> | string | null | undefined,
|
|
)
|
|
const now = Date.now()
|
|
|
|
const metaOpen =
|
|
meta.status === "active" && (meta.endsAtMs == null || meta.endsAtMs > now)
|
|
const autumnOpen =
|
|
isTrialing && (trialEndsAtMs == null || trialEndsAtMs > now)
|
|
|
|
if (metaOpen || autumnOpen) {
|
|
const endsAtMs = meta.endsAtMs ?? trialEndsAtMs
|
|
return {
|
|
state: "trialing" as const,
|
|
endsAtMs,
|
|
startedAtMs:
|
|
meta.startedAtMs ??
|
|
(endsAtMs != null ? endsAtMs - 14 * DAY_MS : null),
|
|
daysRemaining:
|
|
endsAtMs != null
|
|
? Math.max(0, Math.ceil((endsAtMs - now) / DAY_MS))
|
|
: null,
|
|
}
|
|
}
|
|
|
|
const metaEnded =
|
|
meta.status === "expired" ||
|
|
meta.status === "exhausted" ||
|
|
(meta.status === "active" &&
|
|
meta.endsAtMs != null &&
|
|
meta.endsAtMs <= now)
|
|
const autumnEnded =
|
|
isTrialing && trialEndsAtMs != null && trialEndsAtMs <= now
|
|
// Wait for Autumn so a paid/converted org never flashes trial chrome.
|
|
if ((metaEnded || autumnEnded) && !isLoading && !hasPaidPlan) {
|
|
return {
|
|
state: "ended" as const,
|
|
endsAtMs: meta.endsAtMs ?? trialEndsAtMs,
|
|
startedAtMs: meta.startedAtMs,
|
|
daysRemaining: 0,
|
|
}
|
|
}
|
|
return NONE
|
|
}, [org?.metadata, isTrialing, trialEndsAtMs, hasPaidPlan, isLoading, tick])
|
|
}
|