Move Granola connector to Pro plan (#1132)

## Summary
- move Granola from Max-gated to Pro-gated in Nova integrations and add-connection flows
- add Granola to Pro plan card connector copy in billing
- update Granola connector docs to say Pro Plan or higher

## Testing
- bunx biome check apps/web/components/settings/billing.tsx apps/web/components/integrations-view.tsx apps/web/components/add-document/connections.tsx apps/docs/connectors/granola.mdx
- git diff --check

Note: onboarding-brain was intentionally left unchanged.
This commit is contained in:
sreedharsreeram 2026-06-17 17:48:04 +00:00
parent def847af5f
commit 00d481e8c0
4 changed files with 35 additions and 33 deletions

View file

@ -7,7 +7,7 @@ icon: "/images/granola.svg"
Connect Granola to sync AI meeting notes and transcripts into your Supermemory knowledge base. The connector uses a Granola API key, so there is no OAuth redirect flow.
<Note>
The Granola connector requires a **Max Plan** or higher in Supermemory and a Granola plan that can create API keys. In Granola, create one from **Settings > Connectors > API keys**.
The Granola connector requires a **Pro Plan** or higher in Supermemory and a Granola plan that can create API keys. In Granola, create one from **Settings > Connectors > API keys**.
</Note>
## Quick Setup

View file

@ -310,9 +310,6 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
const queryClient = useQueryClient()
const autumn = useCustomer()
const isProUser = hasActivePlan(autumn.data?.subscriptions, "api_pro")
// Granola is a Max-tier (and above) connector, like the Gmail connector.
// Lower plans see it but can't connect — the API-key modal stays gated.
const isMaxUser = hasActivePlan(autumn.data?.subscriptions, "api_max")
const [connectingProvider, setConnectingProvider] =
useState<ConnectorProvider | null>(null)
const [granolaModalOpen, setGranolaModalOpen] = useState(false)
@ -608,15 +605,15 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
</div>
) : provider === "granola" ? (
<>
{!isMaxUser && (
{!isProUser && (
<span className="bg-[#0054AD] text-[#FAFAFA] text-[10px] font-bold px-1.5 py-[2px] rounded-[3px] uppercase tracking-wide">
Max
Pro
</span>
)}
<Button
onClick={() => {
if (!isMaxUser) {
handleUpgrade("api_max")
if (!isProUser) {
handleUpgrade("api_pro")
return
}
setGranolaModalOpen(true)
@ -624,7 +621,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
disabled={isUpgrading || autumn.isLoading}
className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 text-[14px] font-medium px-3 py-1.5 h-8"
>
{!isMaxUser
{!isProUser
? isUpgrading || autumn.isLoading
? "Upgrading..."
: "Upgrade"
@ -795,8 +792,8 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
<DropdownMenuItem
disabled={isUpgrading || autumn.isLoading}
onClick={() => {
if (!isMaxUser) {
handleUpgrade("api_max")
if (!isProUser) {
handleUpgrade("api_pro")
return
}
setGranolaModalOpen(true)
@ -807,16 +804,16 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
<div className="flex flex-col gap-0.5 min-w-0">
<span className="flex items-center gap-1.5 text-[14px] font-medium text-[#FAFAFA] leading-tight">
Granola
{!isMaxUser && (
{!isProUser && (
<span className="bg-[#0054AD] text-[#FAFAFA] text-[9px] font-bold px-1 py-px rounded-[3px] uppercase tracking-wide">
Max
Pro
</span>
)}
</span>
<span className="text-[11px] text-[#737373] leading-tight">
{isMaxUser
{isProUser
? "Meeting notes & transcripts"
: "Upgrade to Max"}
: "Upgrade to Pro"}
</span>
</div>
</DropdownMenuItem>
@ -881,7 +878,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
<>
<button
type="button"
onClick={handleUpgrade}
onClick={() => handleUpgrade()}
className="underline text-[#737373] hover:text-white transition-colors cursor-pointer"
>
Upgrade to Pro
@ -948,8 +945,8 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
/>
<GranolaConnectModal
open={isMaxUser && granolaModalOpen}
onOpenChange={(open) => setGranolaModalOpen(open && isMaxUser)}
open={isProUser && granolaModalOpen}
onOpenChange={(open) => setGranolaModalOpen(open && isProUser)}
containerTags={[selectedProject]}
/>
</div>

View file

@ -457,7 +457,8 @@ const SECTIONS: Array<{
tagline: "Sync AI meeting notes into your memory",
simpleTitle: "Your meeting notes, ready to recall",
icon: <Granola className="size-6" />,
max: true,
pro: true,
docsUrl: "https://supermemory.ai/docs/connectors/granola",
},
],
},
@ -1077,6 +1078,11 @@ const CONNECTOR_META: Record<
icon: <OneDrive className="size-6" />,
documentLabel: "documents",
},
granola: {
name: "Granola",
icon: <Granola className="size-6" />,
documentLabel: "notes",
},
}
interface PluginEntry {
@ -1527,6 +1533,7 @@ const CONNECTOR_SMALL_ICON: Record<ConnectorProvider, ReactNode> = {
"google-drive": <GoogleDrive className="size-3.5" />,
notion: <Notion className="size-3.5" />,
onedrive: <OneDrive className="size-3.5" />,
granola: <Granola className="size-3.5" />,
}
function pluginIconNode(iconSrc: string | null): ReactNode {
@ -1589,6 +1596,9 @@ function resolveDocSource(
if (type.includes("onedrive") || type.includes("microsoft")) {
return { label: "OneDrive", icon: <OneDrive className="size-3.5" /> }
}
if (type.includes("granola")) {
return { label: "Granola", icon: <Granola className="size-3.5" /> }
}
const host = hostnameOf(doc.url)
if (host) {
return { label: host, icon: <Globe className="size-3.5 text-[#737373]" /> }
@ -2386,8 +2396,6 @@ export function IntegrationsView({
const autumn = useCustomer({ queryOptions: { enabled: !publicMode } })
const hasProProduct =
!publicMode && hasActivePlan(autumn.data?.subscriptions, "api_pro")
const hasMaxProduct =
!publicMode && hasActivePlan(autumn.data?.subscriptions, "api_max")
const isAutumnLoading = !publicMode && autumn.isLoading
const [connectingPlugin, setConnectingPlugin] = useState<string | null>(null)
@ -3066,9 +3074,7 @@ export function IntegrationsView({
case "connector": {
const count = connectionsByProvider[item.provider].length
const isGranola = item.provider === "granola"
const needsPlanUpgrade = isGranola
? !hasMaxProduct
: !isAutumnLoading && !hasProProduct
const needsPlanUpgrade = !isAutumnLoading && !hasProProduct
if (count > 0) {
return (
<div className="flex w-full items-center justify-between gap-2">
@ -3080,8 +3086,8 @@ export function IntegrationsView({
onClick={() => {
trackCard(item)
if (isGranola) {
if (!hasMaxProduct) {
handleUpgrade("api_max")
if (!hasProProduct) {
handleUpgrade("api_pro")
return
}
setGranolaModalOpen(true)
@ -3101,9 +3107,7 @@ export function IntegrationsView({
}
if (needsPlanUpgrade) {
return (
<PillButton
onClick={() => handleUpgrade(isGranola ? "api_max" : "api_pro")}
>
<PillButton onClick={() => handleUpgrade("api_pro")}>
<Zap className="size-3.5 text-[#4BA0FA]" /> Upgrade
</PillButton>
)
@ -3114,8 +3118,8 @@ export function IntegrationsView({
onClick={() => {
trackCard(item)
if (isGranola) {
if (!hasMaxProduct) {
handleUpgrade("api_max")
if (!hasProProduct) {
handleUpgrade("api_pro")
return
}
setGranolaModalOpen(true)
@ -3895,8 +3899,8 @@ export function IntegrationsView({
</Dialog>
<GranolaConnectModal
open={hasMaxProduct && granolaModalOpen}
onOpenChange={(open) => setGranolaModalOpen(open && hasMaxProduct)}
open={hasProProduct && granolaModalOpen}
onOpenChange={(open) => setGranolaModalOpen(open && hasProProduct)}
/>
</div>
)

View file

@ -148,6 +148,7 @@ const PLAN_CARDS: PlanCardDefinition[] = [
description: "For people building with AI memory",
features: [
"Auto top-up when balance runs low",
"Google Drive, Notion, OneDrive & Granola connectors",
"All plugins (Claude Code, Cursor, Hermes...)",
"Priority support",
],