Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 49 additions & 29 deletions apps/desktop/src/settings/general/account.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import {
CheckCircle2,
Construction,
Puzzle,
RefreshCw,
Sparkle,
Expand Down Expand Up @@ -41,10 +42,10 @@ import * as settings from "~/store/tinybase/store/settings";
const WEB_APP_BASE_URL = env.VITE_APP_URL ?? "http://localhost:3000";
const ACCOUNT_FEATURES = [
{
label: "Pro AI models",
label: "Cloud Services",
icon: Sparkle,
comingSoon: false,
benefit: "Use premium hosted models without managing API keys.",
benefit:
"Get hosted transcription and language models without managing API keys.",
accent: {
icon: "text-blue-900",
label: "text-blue-950",
Expand All @@ -53,7 +54,6 @@ const ACCOUNT_FEATURES = [
{
label: "Integrations",
icon: Puzzle,
comingSoon: true,
benefit: "Connect tools and pull context into Char with less busywork.",
accent: {
icon: "text-purple-700",
Expand Down Expand Up @@ -151,8 +151,8 @@ export function SettingsAccount() {
<div className="flex flex-col gap-2">
<h3 className="text-sm font-medium">Sign in to Char</h3>
<div className="text-sm text-neutral-600">
Sign in to unlock powerful AI models, sync across devices,
personalization, and workflow integrations.
Sign in to unlock cloud transcription and AI models, plus Pro
features like integrations and sharing.
</div>
</div>
<button
Expand Down Expand Up @@ -466,7 +466,7 @@ function PlanTierList({
{tier.price}
</span>
{tier.period && (
<span className="text-sm text-neutral-500">
<span className="ml-1 text-sm text-neutral-500">
{tier.period}
</span>
)}
Expand All @@ -478,18 +478,47 @@ function PlanTierList({
</div>

<div className="mb-3 flex flex-col gap-1">
{tier.features.map((f) => (
<div key={f} className="flex items-start gap-1.5">
<CheckCircle2 className="mt-0.5 size-3.5 shrink-0 text-green-700" />
<span className="text-xs text-neutral-700">{f}</span>
</div>
))}
{tier.notIncluded.map((f) => (
<div key={f} className="flex items-start gap-1.5">
<XCircle className="mt-0.5 size-3.5 shrink-0 text-neutral-300" />
<span className="text-xs text-neutral-400">{f}</span>
</div>
))}
{tier.features.map((feature) => {
const Icon =
feature.included === true
? CheckCircle2
: feature.included === "partial"
? Construction
: XCircle;
const hoverTitle =
feature.included === "partial"
? "Currently in development"
: undefined;

return (
<div
key={feature.label}
className="flex items-start gap-1.5"
title={hoverTitle}
>
<Icon
className={cn([
"mt-0.5 size-3.5 shrink-0",
feature.included === true
? "text-green-700"
: feature.included === "partial"
? "text-yellow-600"
: "text-red-500",
])}
/>
<span
className={cn([
"text-xs",
feature.included === false
? "text-neutral-700"
: "text-neutral-900",
])}
>
{feature.label}
</span>
</div>
);
})}
</div>

<div className="mt-auto">{renderAction(action, false)}</div>
Expand Down Expand Up @@ -555,13 +584,7 @@ function FeatureSpotlight() {
return () => window.clearInterval(interval);
}, [isPaused]);

const {
label,
icon: Icon,
comingSoon,
benefit,
accent,
} = ACCOUNT_FEATURES[activeIndex];
const { label, icon: Icon, benefit, accent } = ACCOUNT_FEATURES[activeIndex];

return (
<div className="group relative flex w-full max-w-[220px] min-w-[180px] items-center justify-center p-2">
Expand Down Expand Up @@ -632,9 +655,6 @@ function FeatureSpotlight() {
<p className={cn(["text-sm font-medium", accent.label])}>
{label}
</p>
{comingSoon ? (
<span className="text-xs text-neutral-400">Soon</span>
) : null}
</div>
<p className="mt-1 text-xs leading-[1.45] text-neutral-600">
{benefit}
Expand Down
124 changes: 53 additions & 71 deletions apps/web/src/routes/_view/pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { CheckCircle2, MinusCircle, XCircle } from "lucide-react";
import { CheckCircle2, Construction, XCircle } from "lucide-react";

import { cn } from "@hypr/utils";

Expand All @@ -19,8 +19,6 @@ interface PricingPlan {
label: string;
included: boolean | "partial";
tooltip?: string;
comingSoon?: boolean;
partiallyImplemented?: boolean;
}>;
}

Expand All @@ -33,27 +31,22 @@ const pricingPlans: PricingPlan[] = [
features: [
{ label: "On-device Transcription", included: true },
{ label: "Save Audio Recordings", included: true },
{ label: "Audio Player with Transcript Tracking", included: true },
{ label: "Bring Your Own Key (STT & LLM)", included: true },
{ label: "Export to PDF, TXT, Markdown", included: true },
{ label: "Audio Player", included: true },
{ label: "Bring Your Own Key", included: true },
{ label: "Export to Various Formats", included: true },
{
label: "Local-first Data Architecture",
label: "Custom Default Folder",
included: true,
tooltip:
"Filesystem-based by default: notes and transcripts are stored on your device first.",
},
{
label: "Custom Content Base Location",
included: true,
tooltip: "Move your default content folder to any location you prefer.",
tooltip: "Move your default folder location to anywhere you prefer.",
},
{ label: "Templates", included: true },
{ label: "Shortcuts", included: true },
{ label: "Chat", included: true },
{ label: "Integrations", included: false },
{ label: "Contacts View", included: true },
{ label: "Calendar View", included: true },
{ label: "Transcript Editor", included: "partial" },
{ label: "Templates", included: "partial" },
{ label: "Shortcuts", included: "partial" },
{ label: "Cloud Services (STT & LLM)", included: false },
{ label: "Cloud Sync", included: false },
{ label: "Shareable Links", included: false },
{ label: "Speaker Identification", included: false },
],
},
{
Expand All @@ -63,17 +56,15 @@ const pricingPlans: PricingPlan[] = [
yearly: null,
},
description:
"Cloud AI without the complexity. No API keys needed — just sign in and go.",
"Unlimited cloud transcription and AI models without the complexity. No API keys needed — just sign in and go.",
features: [
{ label: "Everything in Free", included: true },
{ label: "Cloud Services (STT & LLM)", included: true },
{
label: "Integrations",
included: true,
tooltip:
"Google Calendar is available now. Additional integrations are in progress.",
},
{ label: "Speaker Identification", included: "partial" },
{ label: "Change Playback Rates", included: false },
{ label: "Integrations", included: false },
{ label: "Advanced Templates", included: false },
{ label: "Folders View", included: false },
{ label: "Cloud Sync", included: false },
{ label: "Shareable Links", included: false },
],
Expand All @@ -85,35 +76,33 @@ const pricingPlans: PricingPlan[] = [
yearly: 250,
},
description:
"No API keys needed. Get cloud services, advanced sharing, and team features out of the box.",
"Everything in Lite, plus advanced sharing and team features out of the box.",
popular: true,
features: [
{ label: "Everything in Free", included: true },
{ label: "Audio Player with Playback Rates", included: true },
{
label: "Speaker Identification",
included: "partial",
partiallyImplemented: true,
},
{ label: "Advanced Templates", included: true },
{ label: "Everything in Lite", included: true },
{ label: "Change Playback Rates", included: true },
{
label: "Integrations",
included: true,
tooltip:
"Google Calendar is available now. Additional integrations are in progress.",
},
{ label: "Cloud Services (STT & LLM)", included: true },
{ label: "Advanced Templates", included: "partial" },
{ label: "Folders View", included: "partial" },
{
label: "Connect to OpenClaw",
included: "partial",
tooltip: "Select which notes to sync",
},
{
label: "Cloud Sync",
included: true,
included: "partial",
tooltip: "Select which notes to sync",
comingSoon: true,
},
{
label: "Shareable Links",
included: true,
included: "partial",
tooltip: "DocSend-like: view tracking, expiration, revocation",
comingSoon: true,
},
],
},
Expand Down Expand Up @@ -200,17 +189,18 @@ function PricingCard({ plan }: { plan: PricingPlan }) {
${plan.price.monthly}
</span>
<span className="text-neutral-600">/month</span>
{plan.price.yearly != null ? (
<span className="text-sm text-neutral-600">
or ${plan.price.yearly}/year
</span>
) : null}
</div>
{plan.price.yearly != null ? (
<div className="text-sm text-neutral-600">
or ${plan.price.yearly}/year
</div>
) : (
<div className="text-sm text-neutral-400">Monthly only</div>
)}
</div>
) : (
<div className="font-serif text-4xl text-stone-700">Free</div>
<div className="flex items-baseline gap-2">
<span className="font-serif text-4xl text-stone-700">$0</span>
<span className="text-neutral-600">per month</span>
</div>
)}
</div>
</div>
Expand All @@ -221,49 +211,41 @@ function PricingCard({ plan }: { plan: PricingPlan }) {
feature.included === true
? CheckCircle2
: feature.included === "partial"
? MinusCircle
? Construction
: XCircle;
const hoverTitle =
feature.included === "partial"
? "Currently in development"
: undefined;

return (
<div key={idx} className="flex items-start gap-3">
<div
key={idx}
className="flex items-start gap-3"
title={hoverTitle}
>
<IconComponent
className={cn([
"mt-0.5 size-4.5 shrink-0",
feature.included === true
? "text-green-700"
: feature.included === "partial"
? "text-yellow-600"
: "text-neutral-300",
: "text-red-500",
])}
/>
<div className="flex-1">
<div className="flex items-center gap-2">
<span
className={cn([
"text-sm",
feature.included === true
? "text-neutral-900"
: feature.included === "partial"
? "text-neutral-700"
: "text-neutral-400",
feature.included === false
? "text-neutral-700"
: "text-neutral-900",
])}
>
{feature.label}
</span>
{(feature.comingSoon || feature.partiallyImplemented) && (
<span
className={cn([
"rounded-full px-2 py-0.5 text-xs font-medium",
feature.partiallyImplemented
? "bg-yellow-100 text-yellow-800"
: "bg-neutral-200 text-neutral-500",
])}
>
{feature.partiallyImplemented
? "Partially Implemented"
: "Coming Soon"}
</span>
)}
</div>
{feature.tooltip && (
<div className="mt-0.5 text-xs text-neutral-500 italic">
Expand Down Expand Up @@ -330,7 +312,7 @@ function FAQSection() {
{
question: "What value does an account unlock?",
answer:
"An account unlocks Char's cloud layer: powerful AI models, sync across devices, personalization, and integrations that streamline your workflow.",
"A paid plan unlocks Char's cloud layer. Lite gives you hosted transcription, speaker identification, and language models, while Pro adds advanced templates, integrations, sync across devices, and shareable links.",
},
{
question: "What's included in shareable links?",
Expand Down
Loading
Loading