From 66f8485a71f91eaa05a5c1b0cbb889d5169de015 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Fri, 27 Mar 2026 20:41:35 -0700 Subject: [PATCH 1/4] fix: gate playback rate controls to pro --- apps/desktop/src/audio-player/provider.tsx | 18 ++++- apps/desktop/src/audio-player/timeline.tsx | 84 +++++++++++----------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/apps/desktop/src/audio-player/provider.tsx b/apps/desktop/src/audio-player/provider.tsx index 509e7bd8c2..e53ed4867c 100644 --- a/apps/desktop/src/audio-player/provider.tsx +++ b/apps/desktop/src/audio-player/provider.tsx @@ -14,6 +14,8 @@ import WaveSurfer from "wavesurfer.js"; import { commands as fsSyncCommands } from "@hypr/plugin-fs-sync"; +import { useBillingAccess } from "~/auth/billing"; + type AudioPlayerState = "playing" | "paused" | "stopped"; interface TimeSnapshot { @@ -102,6 +104,7 @@ export function AudioPlayerProvider({ children: ReactNode; }) { const queryClient = useQueryClient(); + const { isPro } = useBillingAccess(); const [container, setContainer] = useState(null); const [wavesurfer, setWavesurfer] = useState(null); const [state, setState] = useState("stopped"); @@ -257,14 +260,27 @@ export function AudioPlayerProvider({ const setPlaybackRate = useCallback( (rate: number) => { + if (!isPro && rate !== 1) { + return; + } if (wavesurfer) { wavesurfer.setPlaybackRate(rate); } setPlaybackRateState(rate); }, - [wavesurfer], + [isPro, wavesurfer], ); + useEffect(() => { + if (isPro || playbackRate === 1) { + return; + } + if (wavesurfer) { + wavesurfer.setPlaybackRate(1); + } + setPlaybackRateState(1); + }, [isPro, playbackRate, wavesurfer]); + const deleteRecordingMutation = useMutation({ mutationFn: async () => { const result = await fsSyncCommands.audioDelete(sessionId); diff --git a/apps/desktop/src/audio-player/timeline.tsx b/apps/desktop/src/audio-player/timeline.tsx index 56b831bf27..053c7c63c5 100644 --- a/apps/desktop/src/audio-player/timeline.tsx +++ b/apps/desktop/src/audio-player/timeline.tsx @@ -5,11 +5,13 @@ import { cn } from "@hypr/utils"; import { useAudioPlayer, useAudioTime } from "./provider"; +import { useBillingAccess } from "~/auth/billing"; import { useNativeContextMenu } from "~/shared/hooks/useNativeContextMenu"; const PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; export function Timeline() { + const { isPro } = useBillingAccess(); const { registerContainer, state, @@ -116,49 +118,51 @@ export function Timeline() { {formatTime(time.total)} -
- - {showRateMenu && ( -
+ - ))} -
- )} -
+ {playbackRate}x + + {showRateMenu && ( +
+ {PLAYBACK_RATES.map((rate) => ( + + ))} +
+ )} + + ) : null}
Date: Fri, 27 Mar 2026 20:42:00 -0700 Subject: [PATCH 2/4] fix: require pro for google calendar connects --- .../src/calendar/components/oauth/provider-content.tsx | 6 +++--- apps/desktop/src/onboarding/calendar.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/calendar/components/oauth/provider-content.tsx b/apps/desktop/src/calendar/components/oauth/provider-content.tsx index 8095f20c14..d94c907953 100644 --- a/apps/desktop/src/calendar/components/oauth/provider-content.tsx +++ b/apps/desktop/src/calendar/components/oauth/provider-content.tsx @@ -21,8 +21,8 @@ import { openIntegrationUrl } from "~/shared/integration"; export function OAuthProviderContent({ config }: { config: CalendarProvider }) { const auth = useAuth(); - const { isPaid, upgradeToPro } = useBillingAccess(); - const { data: connections, isError } = useConnections(isPaid); + const { isPro, upgradeToPro } = useBillingAccess(); + const { data: connections, isError } = useConnections(isPro); const providerConnections = useMemo( () => connections?.filter( @@ -62,7 +62,7 @@ export function OAuthProviderContent({ config }: { config: CalendarProvider }) { ); } - if (!isPaid) { + if (!isPro) { return (
+
+
+ ); +} + function TabContentFolderTopLevel() { return (
diff --git a/apps/desktop/src/session/components/outer-header/folder/index.tsx b/apps/desktop/src/session/components/outer-header/folder/index.tsx index f73768e61d..3044e91b17 100644 --- a/apps/desktop/src/session/components/outer-header/folder/index.tsx +++ b/apps/desktop/src/session/components/outer-header/folder/index.tsx @@ -12,12 +12,14 @@ import { Button } from "@hypr/ui/components/ui/button"; import { SearchableFolderDropdown } from "./searchable-dropdown"; +import { useBillingAccess } from "~/auth/billing"; import { FolderBreadcrumb } from "~/shared/ui/folder-breadcrumb"; import * as main from "~/store/tinybase/store/main"; import { useSessionTitle } from "~/store/zustand/live-title"; import { useTabs } from "~/store/zustand/tabs"; export function FolderChain({ sessionId }: { sessionId: string }) { + const { isPro } = useBillingAccess(); const folderId = main.UI.useCell( "sessions", sessionId, @@ -40,6 +42,20 @@ export function FolderChain({ sessionId }: { sessionId: string }) { main.STORE_ID, ); + if (!isPro) { + return ( + + + + + + + + + + ); + } + return ( diff --git a/apps/desktop/src/session/components/outer-header/folder/searchable-dropdown.tsx b/apps/desktop/src/session/components/outer-header/folder/searchable-dropdown.tsx index 34019522ae..d70336dfaf 100644 --- a/apps/desktop/src/session/components/outer-header/folder/searchable-dropdown.tsx +++ b/apps/desktop/src/session/components/outer-header/folder/searchable-dropdown.tsx @@ -17,6 +17,7 @@ import { DropdownMenuTrigger, } from "@hypr/ui/components/ui/dropdown-menu"; +import { useBillingAccess } from "~/auth/billing"; import { sessionOps } from "~/store/tinybase/persister/session/ops"; import * as main from "~/store/tinybase/store/main"; import { useListener } from "~/stt/contexts"; @@ -201,8 +202,13 @@ function useSessionFolderId(sessionId: string) { } function useMoveDisabledReason(sessionId: string) { + const { isPro } = useBillingAccess(); const sessionMode = useListener((state) => state.getSessionMode(sessionId)); + if (!isPro) { + return "Upgrade to Pro to move notes into folders."; + } + if (sessionMode === "active" || sessionMode === "finalizing") { return "Stop listening before moving this note."; } diff --git a/apps/desktop/src/session/components/outer-header/overflow/index.tsx b/apps/desktop/src/session/components/outer-header/overflow/index.tsx index 0f36bea161..52eb21454b 100644 --- a/apps/desktop/src/session/components/outer-header/overflow/index.tsx +++ b/apps/desktop/src/session/components/outer-header/overflow/index.tsx @@ -17,6 +17,7 @@ import { Listening } from "./listening"; import { Copy, Folder, ShowInFinder } from "./misc"; import { useAudioPlayer } from "~/audio-player"; +import { useBillingAccess } from "~/auth/billing"; import { useHasTranscript } from "~/session/components/shared"; import type { EditorView } from "~/store/zustand/tabs/schema"; @@ -31,6 +32,7 @@ export function OverflowButton({ const [isExportModalOpen, setIsExportModalOpen] = useState(false); const hasTranscript = useHasTranscript(sessionId); const { audioExists } = useAudioPlayer(); + const { isPro } = useBillingAccess(); const openExportModal = () => { setOpen(false); requestAnimationFrame(() => setIsExportModalOpen(true)); @@ -51,7 +53,7 @@ export function OverflowButton({ - + {isPro && } state.live.sessionId); const liveStatus = useListener((state) => state.live.status); const isListening = liveStatus === "active" || liveStatus === "finalizing"; + const { isPro } = useBillingAccess(); const { chat } = useShell(); const newNote = useNewNote(); @@ -1068,13 +1070,19 @@ function useTabsShortcuts() { useHotkeys( "mod+shift+l", - () => openNew({ type: "folders", id: null }), + () => { + if (!isPro) { + return; + } + + openNew({ type: "folders", id: null }); + }, { preventDefault: true, enableOnFormTags: true, enableOnContentEditable: true, }, - [openNew], + [isPro, openNew], ); const newNoteAndListen = useNewNoteAndListen(); diff --git a/apps/desktop/src/sidebar/profile/index.tsx b/apps/desktop/src/sidebar/profile/index.tsx index 7994e91ef0..a473ccf5fd 100644 --- a/apps/desktop/src/sidebar/profile/index.tsx +++ b/apps/desktop/src/sidebar/profile/index.tsx @@ -20,6 +20,7 @@ import { NotificationsMenuContent } from "./notification"; import { MenuItem } from "./shared"; import { useAuth } from "~/auth"; +import { useBillingAccess } from "~/auth/billing"; import { useAutoCloser } from "~/shared/hooks/useAutoCloser"; import * as main from "~/store/tinybase/store/main"; import { useTabs } from "~/store/zustand/tabs"; @@ -38,6 +39,7 @@ export function ProfileSection({ onExpandChange }: ProfileSectionProps = {}) { const openNew = useTabs((state) => state.openNew); const transitionChatMode = useTabs((state) => state.transitionChatMode); const auth = useAuth(); + const { isPro } = useBillingAccess(); const isAuthenticated = !!auth?.session; @@ -146,12 +148,16 @@ export function ProfileSection({ onExpandChange }: ProfileSectionProps = {}) { ]); const menuItems = [ - { - icon: FolderOpenIcon, - label: "Folders", - onClick: handleClickFolders, - badge: ⌘ ⇧ L, - }, + ...(isPro + ? [ + { + icon: FolderOpenIcon, + label: "Folders", + onClick: handleClickFolders, + badge: ⌘ ⇧ L, + }, + ] + : []), { icon: UsersIcon, label: "Contacts",