From b1bf5423958373ee9e9945cb1606ba28b3a7d2e1 Mon Sep 17 00:00:00 2001
From: Yujong Lee
Date: Thu, 26 Mar 2026 14:29:43 -0700
Subject: [PATCH 1/3] save
Signed-off-by: Yujong Lee
---
apps/api/src/main.rs | 4 +-
apps/api/src/rate_limit.rs | 2 +-
apps/desktop/src/ai/hooks/useLLMConnection.ts | 10 ++--
apps/desktop/src/auth/billing.tsx | 6 +--
.../components/oauth/provider-content.tsx | 8 ++--
.../src/calendar/components/sidebar.tsx | 6 +--
.../src/onboarding/account/after-login.tsx | 4 +-
apps/desktop/src/onboarding/account/trial.tsx | 8 ++--
apps/desktop/src/onboarding/calendar.tsx | 8 ++--
.../desktop/src/settings/ai/llm/configure.tsx | 8 ++--
apps/desktop/src/settings/ai/llm/select.tsx | 6 +--
.../src/settings/ai/shared/eligibility.ts | 4 +-
.../settings/ai/shared/hypr-cloud-button.tsx | 12 ++---
apps/desktop/src/settings/ai/shared/index.tsx | 4 +-
.../desktop/src/settings/ai/stt/configure.tsx | 8 ++--
apps/desktop/src/settings/ai/stt/select.tsx | 6 +--
apps/desktop/src/sidebar/index.tsx | 6 ++-
apps/desktop/src/stt/useSTTConnection.ts | 4 +-
apps/web/src/env.ts | 1 +
apps/web/src/functions/billing.ts | 18 +++++--
.../_view/app/-account-integrations.tsx | 13 +++--
.../routes/_view/app/-account-settings.tsx | 9 ++--
.../app/-integrations-upgrade-prompt.tsx | 8 +---
apps/web/src/routes/_view/app/checkout.tsx | 3 +-
apps/web/src/routes/_view/app/integration.tsx | 2 +-
apps/web/src/routes/_view/pricing.tsx | 38 ++++++++++++---
crates/api-auth/src/lib.rs | 40 +++++++++++++---
crates/supabase-auth/src/claims.rs | 48 +++++++++++++++++++
crates/supabase-auth/src/server/mod.rs | 18 +++++++
packages/supabase/src/billing.ts | 28 +++++++++--
30 files changed, 245 insertions(+), 95 deletions(-)
diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs
index 6d6d255438..bddebf68b0 100644
--- a/apps/api/src/main.rs
+++ b/apps/api/src/main.rs
@@ -110,8 +110,8 @@ async fn app() -> Router {
)
.build();
- let auth_state_pro =
- AuthState::new(&env.supabase.supabase_url).with_required_entitlement("hyprnote_pro");
+ let auth_state_pro = AuthState::new(&env.supabase.supabase_url)
+ .with_required_entitlements(vec!["hyprnote_pro".into(), "hyprnote_lite".into()]);
let auth_state_basic = AuthState::new(&env.supabase.supabase_url);
let auth_state_support = AuthState::new(&env.supabase.supabase_url);
diff --git a/apps/api/src/rate_limit.rs b/apps/api/src/rate_limit.rs
index 8e8b869ddb..543ca6f457 100644
--- a/apps/api/src/rate_limit.rs
+++ b/apps/api/src/rate_limit.rs
@@ -66,7 +66,7 @@ pub async fn rate_limit(
}
if let Some(auth) = request.extensions().get::() {
- let limiter = if auth.claims.is_pro() {
+ let limiter = if auth.claims.is_paid() {
&state.limiter_pro
} else {
&state.limiter_free
diff --git a/apps/desktop/src/ai/hooks/useLLMConnection.ts b/apps/desktop/src/ai/hooks/useLLMConnection.ts
index 09aa79ded5..2fd2fc7253 100644
--- a/apps/desktop/src/ai/hooks/useLLMConnection.ts
+++ b/apps/desktop/src/ai/hooks/useLLMConnection.ts
@@ -97,11 +97,11 @@ export const useLLMConnection = (): LLMConnectionResult => {
modelId: current_llm_model,
providerConfig,
session: auth?.session,
- isPro: billing.isPro,
+ isPaid: billing.isPaid,
}),
[
auth,
- billing.isPro,
+ billing.isPaid,
current_llm_model,
current_llm_provider,
providerConfig,
@@ -119,14 +119,14 @@ const resolveLLMConnection = (params: {
modelId: string | undefined;
providerConfig: AIProviderStorage | undefined;
session: { access_token: string } | null | undefined;
- isPro: boolean;
+ isPaid: boolean;
}): LLMConnectionResult => {
const {
providerId: rawProviderId,
modelId,
providerConfig,
session,
- isPro,
+ isPaid,
} = params;
if (!rawProviderId) {
@@ -166,7 +166,7 @@ const resolveLLMConnection = (params: {
const context: ProviderEligibilityContext = {
isAuthenticated: !!session,
- isPro,
+ isPaid,
config: { base_url: baseUrl, api_key: apiKey },
};
diff --git a/apps/desktop/src/auth/billing.tsx b/apps/desktop/src/auth/billing.tsx
index 132cd6f914..0a3d69247e 100644
--- a/apps/desktop/src/auth/billing.tsx
+++ b/apps/desktop/src/auth/billing.tsx
@@ -60,7 +60,7 @@ export function BillingProvider({ children }: { children: ReactNode }) {
const isReady = !claimsQuery.isPending;
const canTrialQuery = useQuery({
- enabled: !!auth?.session && !billing.isPro,
+ enabled: !!auth?.session && !billing.isPaid,
queryKey: [auth?.session?.user.id ?? "", "canStartTrial"],
queryFn: async () => {
const headers = auth?.getHeaders();
@@ -78,10 +78,10 @@ export function BillingProvider({ children }: { children: ReactNode }) {
const canStartTrial = useMemo(
() => ({
- data: billing.isPro ? false : (canTrialQuery.data ?? false),
+ data: billing.isPaid ? false : (canTrialQuery.data ?? false),
isPending: canTrialQuery.isPending,
}),
- [billing.isPro, canTrialQuery.data, canTrialQuery.isPending],
+ [billing.isPaid, canTrialQuery.data, canTrialQuery.isPending],
);
const upgradeToPro = useCallback(async () => {
diff --git a/apps/desktop/src/calendar/components/oauth/provider-content.tsx b/apps/desktop/src/calendar/components/oauth/provider-content.tsx
index b0d5cf7bbe..a219533659 100644
--- a/apps/desktop/src/calendar/components/oauth/provider-content.tsx
+++ b/apps/desktop/src/calendar/components/oauth/provider-content.tsx
@@ -22,8 +22,8 @@ import { buildWebAppUrl } from "~/shared/utils";
export function OAuthProviderContent({ config }: { config: CalendarProvider }) {
const auth = useAuth();
- const { isPro, upgradeToPro } = useBillingAccess();
- const { data: connections, isError } = useConnections(isPro);
+ const { isPaid, upgradeToPro } = useBillingAccess();
+ const { data: connections, isError } = useConnections(isPaid);
const providerConnections = useMemo(
() =>
connections?.filter(
@@ -57,14 +57,14 @@ export function OAuthProviderContent({ config }: { config: CalendarProvider }) {
);
}
- if (!isPro) {
+ if (!isPaid) {
return (
);
diff --git a/apps/desktop/src/calendar/components/sidebar.tsx b/apps/desktop/src/calendar/components/sidebar.tsx
index 7008cd5ce2..82dd175d78 100644
--- a/apps/desktop/src/calendar/components/sidebar.tsx
+++ b/apps/desktop/src/calendar/components/sidebar.tsx
@@ -78,8 +78,8 @@ function ProviderAccordionItem({
calendar: ReturnType;
}) {
const auth = useAuth();
- const { isPro } = useBillingAccess();
- const { data: connections, isPending, isError } = useConnections(isPro);
+ const { isPaid } = useBillingAccess();
+ const { data: connections, isPending, isError } = useConnections(isPaid);
const providerConnections =
connections?.filter(
(connection) => connection.integration_id === provider.nangoIntegrationId,
@@ -87,7 +87,7 @@ function ProviderAccordionItem({
const canAddAccount =
!!provider.nangoIntegrationId &&
!!auth.session &&
- isPro &&
+ isPaid &&
!isPending &&
!isError;
const shouldConnectOnClick =
diff --git a/apps/desktop/src/onboarding/account/after-login.tsx b/apps/desktop/src/onboarding/account/after-login.tsx
index 76926c7960..c92b2d1191 100644
--- a/apps/desktop/src/onboarding/account/after-login.tsx
+++ b/apps/desktop/src/onboarding/account/after-login.tsx
@@ -19,8 +19,8 @@ function TrialStatusDisplay({ trialPhase }: { trialPhase: TrialPhase }) {
>
)}
- {trialPhase === "already-pro" && (
-
+ {trialPhase === "already-paid" && (
+
)}
{trialPhase === "already-trialing" && (
diff --git a/apps/desktop/src/onboarding/account/trial.tsx b/apps/desktop/src/onboarding/account/trial.tsx
index aca2108268..bcb2928037 100644
--- a/apps/desktop/src/onboarding/account/trial.tsx
+++ b/apps/desktop/src/onboarding/account/trial.tsx
@@ -16,7 +16,7 @@ import * as settings from "~/store/tinybase/store/settings";
export type TrialPhase =
| "checking"
| "starting"
- | "already-pro"
+ | "already-paid"
| "already-trialing"
| { done: StartTrialReason | "error" };
@@ -60,11 +60,11 @@ export function useTrialFlow(onContinue: () => void) {
useEffect(() => {
if (!auth?.session || !billing.isReady || hasTriggeredRef.current) return;
- if (billing.isPro && !billing.isTrialing) {
+ if (billing.isPaid && !billing.isTrialing) {
hasTriggeredRef.current = true;
void analyticsCommands.event({
event: "trial_flow_skipped",
- properties: { reason: "already_pro" },
+ properties: { reason: "already_paid" },
});
if (store) configureProSettings(store);
setTimeout(onContinue, 1500);
@@ -89,7 +89,7 @@ export function useTrialFlow(onContinue: () => void) {
if (!auth?.session) return null;
if (!billing.isReady) return "checking" as const;
- if (billing.isPro && !billing.isTrialing) return "already-pro" as const;
+ if (billing.isPaid && !billing.isTrialing) return "already-paid" as const;
if (billing.isTrialing) return "already-trialing" as const;
if (mutation.isPending) return "starting" as const;
diff --git a/apps/desktop/src/onboarding/calendar.tsx b/apps/desktop/src/onboarding/calendar.tsx
index fffa6f13ef..4c27fa04c6 100644
--- a/apps/desktop/src/onboarding/calendar.tsx
+++ b/apps/desktop/src/onboarding/calendar.tsx
@@ -244,8 +244,8 @@ function addIntegrationMenus({
function GoogleCalendarProvider({ onSignIn }: { onSignIn: () => void }) {
const auth = useAuth();
- const { isPro, isReady, upgradeToPro } = useBillingAccess();
- const { data: connections, isPending, isError } = useConnections(isPro);
+ const { isPaid, isReady, upgradeToPro } = useBillingAccess();
+ const { data: connections, isPending, isError } = useConnections(isPaid);
const providerConnections = useMemo(
() =>
connections?.filter(
@@ -261,7 +261,7 @@ function GoogleCalendarProvider({ onSignIn }: { onSignIn: () => void }) {
return;
}
- if (!isPro) {
+ if (!isPaid) {
upgradeToPro();
return;
}
@@ -271,7 +271,7 @@ function GoogleCalendarProvider({ onSignIn }: { onSignIn: () => void }) {
undefined,
"connect",
);
- }, [auth.session, isPro, onSignIn, upgradeToPro]);
+ }, [auth.session, isPaid, onSignIn, upgradeToPro]);
if (!GOOGLE_PROVIDER) {
return null;
diff --git a/apps/desktop/src/settings/ai/llm/configure.tsx b/apps/desktop/src/settings/ai/llm/configure.tsx
index 1d1082e404..772d37a96d 100644
--- a/apps/desktop/src/settings/ai/llm/configure.tsx
+++ b/apps/desktop/src/settings/ai/llm/configure.tsx
@@ -103,7 +103,7 @@ function HyprProviderCard({
}
function HyprProviderAutoRow({ highlight }: { highlight?: boolean }) {
- const { isPro, canStartTrial, upgradeToPro } = useBillingAccess();
+ const { isPaid, canStartTrial, upgradeToPro } = useBillingAccess();
const handleSelectProvider = settings.UI.useSetValueCallback(
"current_llm_provider",
@@ -120,13 +120,13 @@ function HyprProviderAutoRow({ highlight }: { highlight?: boolean }) {
);
const handleClick = useCallback(() => {
- if (!isPro) {
+ if (!isPaid) {
upgradeToPro();
} else {
handleSelectProvider("hyprnote");
handleSelectModel("Auto");
}
- }, [isPro, upgradeToPro, handleSelectProvider, handleSelectModel]);
+ }, [isPaid, upgradeToPro, handleSelectProvider, handleSelectModel]);
return (
@@ -137,7 +137,7 @@ function HyprProviderAutoRow({ highlight }: { highlight?: boolean }) {
{
- if (provider === "hyprnote" && !billing.isPro) {
+ if (provider === "hyprnote" && !billing.isPaid) {
billing.upgradeToPro();
return;
}
@@ -186,7 +186,7 @@ export function SelectProviderAndModel() {
provider.requirements,
"pro",
);
- const locked = requiresPro && !billing.isPro;
+ const locked = requiresPro && !billing.isPaid;
return (
{
const eligible =
getProviderSelectionBlockers(provider.requirements, {
isAuthenticated: !!auth?.session,
- isPro: billing.isPro,
+ isPaid: billing.isPaid,
config: { base_url: baseUrl, api_key: apiKey },
}).length === 0;
diff --git a/apps/desktop/src/settings/ai/shared/eligibility.ts b/apps/desktop/src/settings/ai/shared/eligibility.ts
index e398106c32..85037feff0 100644
--- a/apps/desktop/src/settings/ai/shared/eligibility.ts
+++ b/apps/desktop/src/settings/ai/shared/eligibility.ts
@@ -33,7 +33,7 @@ export function getRequiredConfigFields(
export type ProviderEligibilityContext = {
isAuthenticated: boolean;
- isPro: boolean;
+ isPaid: boolean;
config?: { base_url?: string; api_key?: string };
};
@@ -51,7 +51,7 @@ export function getProviderSelectionBlockers(
}
break;
case "requires_entitlement":
- if (req.entitlement === "pro" && !context.isPro) {
+ if (req.entitlement === "pro" && !context.isPaid) {
blockers.push({ code: "requires_entitlement", entitlement: "pro" });
}
break;
diff --git a/apps/desktop/src/settings/ai/shared/hypr-cloud-button.tsx b/apps/desktop/src/settings/ai/shared/hypr-cloud-button.tsx
index 06f62a0872..e7974c50a2 100644
--- a/apps/desktop/src/settings/ai/shared/hypr-cloud-button.tsx
+++ b/apps/desktop/src/settings/ai/shared/hypr-cloud-button.tsx
@@ -14,23 +14,23 @@ export function HyprProviderRow({ children }: { children: React.ReactNode }) {
}
export function HyprCloudCTAButton({
- isPro,
+ isPaid,
canStartTrial,
highlight,
onClick,
}: {
- isPro: boolean;
+ isPaid: boolean;
canStartTrial: boolean | undefined;
highlight?: boolean;
onClick: () => void;
}) {
- const buttonLabel = isPro
+ const buttonLabel = isPaid
? "Ready to use"
: canStartTrial
? "Start Free Trial"
- : "Upgrade to Pro";
+ : "Upgrade";
- const showShimmer = highlight && !isPro;
+ const showShimmer = highlight && !isPaid;
return (