Skip to content

Commit 25fb047

Browse files
committed
wip
1 parent 6ca92f3 commit 25fb047

File tree

3 files changed

+164
-249
lines changed

3 files changed

+164
-249
lines changed
Lines changed: 48 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { openUrl } from "@tauri-apps/plugin-opener";
2-
import { Check, ExternalLinkIcon } from "lucide-react";
2+
import { ExternalLinkIcon } from "lucide-react";
33
import { type ReactNode, useCallback } from "react";
4+
import type Stripe from "stripe";
45

56
import { Button } from "@hypr/ui/components/ui/button";
67

78
import { useAuth } from "../../auth";
8-
import { type BillingAccess, useBillingAccess } from "../../billing";
9+
import { useBillingAccess } from "../../billing";
910
import { env } from "../../env";
1011

1112
const WEB_APP_BASE_URL = env.VITE_APP_URL ?? "http://localhost:3000";
@@ -20,74 +21,91 @@ export function SettingsAccount() {
2021
openUrl(`${WEB_APP_BASE_URL}/app/account`);
2122
}, []);
2223

23-
const handleUpgrade = useCallback(() => {
24-
openUrl(`${WEB_APP_BASE_URL}/app/checkout?period=monthly`);
25-
}, []);
26-
2724
const handleSignIn = useCallback(() => {
2825
auth?.signIn();
2926
}, [auth]);
3027

3128
if (!isAuthenticated) {
3229
return (
3330
<Container
34-
title="Sign in to manage your account"
35-
description="Sign in with your Hyprnote account to access billing and plan details."
31+
title="Sign in to Hyprnote"
32+
description="Hyprnote account is required to access pro plan."
3633
action={
3734
<Button onClick={handleSignIn}>
38-
<span>Sign in</span>
35+
<span>Get Started</span>
3936
</Button>
4037
}
41-
>
42-
<p className="text-sm text-neutral-600">
43-
The desktop app links directly to your web account for settings and
44-
billing changes.
45-
</p>
46-
</Container>
38+
></Container>
4739
);
4840
}
4941

5042
const hasStripeCustomer = !!billing.data?.stripe_customer;
51-
const userEmail = auth?.session?.user?.email;
52-
const userId = auth?.session?.user?.id;
5343

5444
return (
5545
<div className="flex flex-col gap-4">
5646
<Container
5747
title="Your Account"
5848
description="Redirect to the web app to manage your account."
5949
action={
60-
<Button variant="outline" onClick={handleOpenAccount}>
61-
<span>Open</span>
50+
<Button
51+
variant="outline"
52+
onClick={handleOpenAccount}
53+
className="w-[100px] flex flex-row gap-1.5"
54+
>
55+
<span className="text-sm">Open</span>
6256
<ExternalLinkIcon className="text-neutral-600" />
6357
</Button>
6458
}
65-
>
66-
<AccountDetails email={userEmail} userId={userId} />
67-
</Container>
59+
></Container>
6860

6961
<Container
7062
title="Plan & Billing"
7163
description="View your current plan and manage billing on the web."
7264
action={
7365
hasStripeCustomer ? (
74-
<Button variant="outline" onClick={handleOpenAccount}>
75-
<span>Open</span>
76-
<ExternalLinkIcon className="text-neutral-600" />
66+
<Button
67+
variant="outline"
68+
onClick={handleOpenAccount}
69+
className="w-[100px] flex flex-row gap-1.5"
70+
>
71+
<span className="text-sm">Manage</span>
72+
<ExternalLinkIcon className="text-neutral-600" size={12} />
7773
</Button>
7874
) : undefined
7975
}
8076
>
81-
<SettingsBilling
82-
billing={billing}
83-
onManage={handleOpenAccount}
84-
onUpgrade={handleUpgrade}
85-
/>
77+
{billing.data?.stripe_subscription && (
78+
<SubscriptionDetails
79+
subscription={billing.data.stripe_subscription}
80+
/>
81+
)}
8682
</Container>
8783
</div>
8884
);
8985
}
9086

87+
function SubscriptionDetails({
88+
subscription,
89+
}: {
90+
subscription: Stripe.Subscription;
91+
}) {
92+
const {
93+
status,
94+
items: {
95+
data: [{ current_period_end, current_period_start }],
96+
},
97+
} = subscription;
98+
99+
return (
100+
<div className="flex flex-row gap-1 text-xs text-neutral-600">
101+
<span className="capitalize">{status}:</span>
102+
<span>{new Date(current_period_start * 1000).toLocaleDateString()}</span>
103+
<span>~</span>
104+
<span>{new Date(current_period_end * 1000).toLocaleDateString()}</span>
105+
</div>
106+
);
107+
}
108+
91109
function Container({
92110
title,
93111
description,
@@ -112,166 +130,3 @@ function Container({
112130
</section>
113131
);
114132
}
115-
116-
function AccountDetails({
117-
email,
118-
userId,
119-
}: {
120-
email?: string | null;
121-
userId?: string | null;
122-
}) {
123-
return (
124-
<div className="flex flex-col gap-3">
125-
<div>
126-
<p className="text-xs uppercase text-neutral-500">Email</p>
127-
<p className="text-sm text-neutral-900">
128-
{email ?? "Email unavailable"}
129-
</p>
130-
</div>
131-
132-
{userId ? (
133-
<div>
134-
<p className="text-xs uppercase text-neutral-500">User ID</p>
135-
<p className="font-mono text-xs text-neutral-500 break-all">
136-
{userId}
137-
</p>
138-
</div>
139-
) : null}
140-
</div>
141-
);
142-
}
143-
144-
function SettingsBilling({
145-
billing,
146-
onManage,
147-
onUpgrade,
148-
}: {
149-
billing: BillingAccess;
150-
onManage: () => void;
151-
onUpgrade: () => void;
152-
}) {
153-
if (billing.isPending && !billing.data) {
154-
return (
155-
<div className="text-sm text-neutral-600">Loading billing details...</div>
156-
);
157-
}
158-
159-
const billingData = billing.data;
160-
const planId: PlanId = billingData?.isPro ? "pro" : "free";
161-
const plan = PLANS[planId];
162-
const hasStripeCustomer = !!billingData?.stripe_customer;
163-
const subscriptionStatus = billingData?.stripe_subscription?.status;
164-
const showErrorBanner = billing.isError;
165-
const errorMessage =
166-
billing.error instanceof Error
167-
? billing.error.message
168-
: "Unable to load billing details.";
169-
170-
return (
171-
<div className="space-y-4">
172-
{showErrorBanner && (
173-
<div className="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
174-
<span>{errorMessage}</span>
175-
<Button
176-
variant="outline"
177-
size="sm"
178-
onClick={() => billing.refetch()}
179-
disabled={billing.isRefetching}
180-
>
181-
Retry
182-
</Button>
183-
</div>
184-
)}
185-
186-
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
187-
<div className="flex-1">
188-
<p className="text-xs uppercase text-neutral-500">Active plan</p>
189-
<p className="text-lg font-semibold text-neutral-900">{plan.name}</p>
190-
<p className="text-sm text-neutral-600">{plan.description}</p>
191-
{subscriptionStatus && planId === "pro" ? (
192-
<p className="text-xs text-neutral-500">
193-
Subscription status:{" "}
194-
{formatSubscriptionStatus(subscriptionStatus)}
195-
</p>
196-
) : null}
197-
</div>
198-
199-
<div className="sm:w-auto">
200-
<PlanActions
201-
planId={planId}
202-
hasStripeCustomer={hasStripeCustomer}
203-
onManage={onManage}
204-
onUpgrade={onUpgrade}
205-
/>
206-
</div>
207-
</div>
208-
209-
<ul className="space-y-2">
210-
{plan.features.map((feature) => (
211-
<li
212-
key={feature}
213-
className="flex items-start gap-2 text-sm text-neutral-700"
214-
>
215-
<Check size={16} className="mt-0.5 text-emerald-500 shrink-0" />
216-
<span>{feature}</span>
217-
</li>
218-
))}
219-
</ul>
220-
</div>
221-
);
222-
}
223-
224-
function PlanActions({
225-
planId,
226-
hasStripeCustomer,
227-
onManage,
228-
onUpgrade,
229-
}: {
230-
planId: PlanId;
231-
hasStripeCustomer: boolean;
232-
onManage: () => void;
233-
onUpgrade: () => void;
234-
}) {
235-
if (planId === "pro" && hasStripeCustomer) {
236-
return (
237-
<Button variant="outline" onClick={onManage} className="w-full sm:w-auto">
238-
Manage billing
239-
</Button>
240-
);
241-
}
242-
243-
return (
244-
<Button onClick={onUpgrade} className="w-full sm:w-auto">
245-
Upgrade to Pro
246-
</Button>
247-
);
248-
}
249-
250-
type PlanId = "free" | "pro";
251-
252-
interface BillingPlan {
253-
id: PlanId;
254-
name: string;
255-
description: string;
256-
features: string[];
257-
}
258-
259-
const PLANS: Record<PlanId, BillingPlan> = {
260-
free: {
261-
id: "free",
262-
name: "Free",
263-
description: "Local transcription with manual exports.",
264-
features: ["Local transcription", "Copy and PDF export"],
265-
},
266-
pro: {
267-
id: "pro",
268-
name: "Pro",
269-
description: "Cloud transcription, collaboration, and sharing features.",
270-
features: ["Cloud transcription", "Shareable links"],
271-
},
272-
};
273-
274-
function formatSubscriptionStatus(status: string) {
275-
const normalized = status.replace(/_/g, " ");
276-
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
277-
}

apps/desktop/src/components/settings/ai/stt/health.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useQueries } from "@tanstack/react-query";
33
import { useBillingAccess } from "../../../../billing";
44
import { useConfigValues } from "../../../../config/use-config";
55
import { useSTTConnection } from "../../../../hooks/useSTTConnection";
6-
import * as main from "../../../../store/tinybase/main";
76
import { AvailabilityHealth, ConnectionHealth } from "../shared/health";
87
import {
98
type ProviderId,
@@ -27,6 +26,7 @@ function useConnectionHealth(): Parameters<typeof ConnectionHealth>[0] {
2726
const isCloud =
2827
current_stt_provider === "hyprnote" || current_stt_model === "cloud";
2928

29+
console.log(conn);
3030
if (isCloud) {
3131
return conn
3232
? { status: "success" }
@@ -68,11 +68,6 @@ function useAvailability():
6868
] as const);
6969
const billing = useBillingAccess();
7070

71-
const configuredProviders = main.UI.useResultTable(
72-
main.QUERIES.sttProviders,
73-
main.STORE_ID,
74-
);
75-
7671
const [p2, p3, tinyEn, smallEn] = useQueries({
7772
queries: [
7873
sttModelQueries.isDownloaded("am-parakeet-v2"),
@@ -123,16 +118,6 @@ function useAvailability():
123118
return { available: true };
124119
}
125120

126-
const config = configuredProviders[providerId] as
127-
| main.AIProviderStorage
128-
| undefined;
129-
if (!config) {
130-
return {
131-
available: false,
132-
message: "Provider not configured. Please configure the provider below.",
133-
};
134-
}
135-
136121
if (providerId === "custom") {
137122
return { available: true };
138123
}

0 commit comments

Comments
 (0)