From 02f2c637df434f2049c6c46b81f1754a2ee2c534 Mon Sep 17 00:00:00 2001 From: Igor Silva Date: Sat, 26 Jul 2025 13:22:41 +0100 Subject: [PATCH 1/3] onboarding v0 --- src/components/Onboarding.tsx | 33 ++++++++++ src/components/subscribe/SubscribePage.tsx | 64 -------------------- src/components/subscribe/faq/CreditsFaq.tsx | 4 +- src/components/subscribe/faq/FaqSection.tsx | 5 +- src/routeTree.gen.ts | 21 +++++++ src/routes/__root.tsx | 49 ++++++--------- src/routes/pricing.tsx | 54 +++++++++++++++++ src/routes/subscribe.tsx | 67 ++++++++++++++++++++- 8 files changed, 194 insertions(+), 103 deletions(-) create mode 100644 src/components/Onboarding.tsx delete mode 100644 src/components/subscribe/SubscribePage.tsx create mode 100644 src/routes/pricing.tsx diff --git a/src/components/Onboarding.tsx b/src/components/Onboarding.tsx new file mode 100644 index 0000000..85b0626 --- /dev/null +++ b/src/components/Onboarding.tsx @@ -0,0 +1,33 @@ +import { useAuthActions } from '@convex-dev/auth/react'; + +export function Onboarding() { + // + const { signIn } = useAuthActions(); + + return ( +
+
+

Welcome to Meseeks

+
+ +
+ ); +} diff --git a/src/components/subscribe/SubscribePage.tsx b/src/components/subscribe/SubscribePage.tsx deleted file mode 100644 index 6979f52..0000000 --- a/src/components/subscribe/SubscribePage.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useNavigate } from '@tanstack/react-router'; -import { track } from '@vercel/analytics/react'; -import { api } from 'convex/_generated/api'; -import { useAction } from 'convex/react'; -import { useEffect } from 'react'; -import { toast } from 'sonner'; -import { FaqSection, FounderCard, ProCard } from '~/components/subscribe'; -import { useIsPro } from '~/hooks/useIsPro'; -import { Route } from '~/routes/subscribe'; - -export function SubscribePage({ route }: { route: typeof Route }) { - // - const navigate = useNavigate(); - const startSubscription = useAction(api.subscriptions.public.startSubscription); - const { isPro } = useIsPro(); - - // redirect to balance if already subscribed - useEffect(() => { - if (isPro) { - navigate({ to: '/balance' }); - } - }, [isPro, navigate]); - - const handleSubscribe = async (product: 'pro' | 'founder') => { - track('tap_subscribe', { product }); - try { - const { paymentUrl } = await startSubscription({ product }); - location.href = paymentUrl; - } catch (error) { - console.error(error); - toast.error('Failed to start subscription.'); - } - }; - - track('subscribe', { isPro }); - - // don't render if already subscribed (will redirect) - if (isPro) return null; - - return ( -
-
-

Choose your path

- {/*

- The following offers are available during research preview, and are subject to change. -

*/} -
- -
- - -
- - - -
-

- This is a research preview app. Expect issues. -

-

© 2025 isPro. All rights reserved.

-
-
- ); -} diff --git a/src/components/subscribe/faq/CreditsFaq.tsx b/src/components/subscribe/faq/CreditsFaq.tsx index 32da599..0800d0b 100644 --- a/src/components/subscribe/faq/CreditsFaq.tsx +++ b/src/components/subscribe/faq/CreditsFaq.tsx @@ -12,10 +12,10 @@ export const CreditsFaq = {

1 credit = 1 US Dollar, and we put no margin on the provider price. i.e. if Tavily charges us $0.008/usage, we charge you exactly that. The same goes for any AI usage, you pay exactly - their advertised price. + their advertised price is.

- Pro subscribers get $10 worth of monthly credits that never expire. + Pro subscribers get $10 worth of credits that never expire, every month.

You can top up as much credits as you need, with zero markup from us -{' '} diff --git a/src/components/subscribe/faq/FaqSection.tsx b/src/components/subscribe/faq/FaqSection.tsx index 55c8c88..ebdc564 100644 --- a/src/components/subscribe/faq/FaqSection.tsx +++ b/src/components/subscribe/faq/FaqSection.tsx @@ -1,9 +1,10 @@ +import { type ReactNode } from 'react'; import { QuestionDialog } from '~/components/QuestionDialog'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '~/components/ui/accordion'; import { faqs } from './index'; -export function FaqSection() { +export function FaqSection({ questionComponent }: { questionComponent?: ReactNode }) { // return (

@@ -24,7 +25,7 @@ export function FaqSection() {

Still have questions? We're here to help!

- + {questionComponent || }
); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index d947b39..6ac778e 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as TopUpRouteImport } from './routes/top-up' import { Route as SubscribeRouteImport } from './routes/subscribe' import { Route as SkillsRouteImport } from './routes/skills' import { Route as SchedulesRouteImport } from './routes/schedules' +import { Route as PricingRouteImport } from './routes/pricing' import { Route as BalanceRouteImport } from './routes/balance' import { Route as SplatRouteImport } from './routes/$' import { Route as PolarRouteRouteImport } from './routes/polar/route' @@ -43,6 +44,11 @@ const SchedulesRoute = SchedulesRouteImport.update({ path: '/schedules', getParentRoute: () => rootRouteImport, } as any) +const PricingRoute = PricingRouteImport.update({ + id: '/pricing', + path: '/pricing', + getParentRoute: () => rootRouteImport, +} as any) const BalanceRoute = BalanceRouteImport.update({ id: '/balance', path: '/balance', @@ -93,6 +99,7 @@ export interface FileRoutesByFullPath { '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute '/balance': typeof BalanceRoute + '/pricing': typeof PricingRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute @@ -108,6 +115,7 @@ export interface FileRoutesByTo { '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute '/balance': typeof BalanceRoute + '/pricing': typeof PricingRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute @@ -124,6 +132,7 @@ export interface FileRoutesById { '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute '/balance': typeof BalanceRoute + '/pricing': typeof PricingRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute @@ -141,6 +150,7 @@ export interface FileRouteTypes { | '/polar' | '/$' | '/balance' + | '/pricing' | '/schedules' | '/skills' | '/subscribe' @@ -156,6 +166,7 @@ export interface FileRouteTypes { | '/polar' | '/$' | '/balance' + | '/pricing' | '/schedules' | '/skills' | '/subscribe' @@ -171,6 +182,7 @@ export interface FileRouteTypes { | '/polar' | '/$' | '/balance' + | '/pricing' | '/schedules' | '/skills' | '/subscribe' @@ -187,6 +199,7 @@ export interface RootRouteChildren { PolarRouteRoute: typeof PolarRouteRouteWithChildren SplatRoute: typeof SplatRoute BalanceRoute: typeof BalanceRoute + PricingRoute: typeof PricingRoute SchedulesRoute: typeof SchedulesRoute SkillsRoute: typeof SkillsRoute SubscribeRoute: typeof SubscribeRoute @@ -227,6 +240,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SchedulesRouteImport parentRoute: typeof rootRouteImport } + '/pricing': { + id: '/pricing' + path: '/pricing' + fullPath: '/pricing' + preLoaderRoute: typeof PricingRouteImport + parentRoute: typeof rootRouteImport + } '/balance': { id: '/balance' path: '/balance' @@ -311,6 +331,7 @@ const rootRouteChildren: RootRouteChildren = { PolarRouteRoute: PolarRouteRouteWithChildren, SplatRoute: SplatRoute, BalanceRoute: BalanceRoute, + PricingRoute: PricingRoute, SchedulesRoute: SchedulesRoute, SkillsRoute: SkillsRoute, SubscribeRoute: SubscribeRoute, diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 7767870..35361ad 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,8 +1,7 @@ -import { useAuthActions } from '@convex-dev/auth/react'; import { QueryClient } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/react-router'; -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; +import { HeadContent, Outlet, Scripts, createRootRouteWithContext, useRouter } from '@tanstack/react-router'; +import { TanStackRouterDevtools } from '@tanstack/router-devtools'; import { Analytics } from '@vercel/analytics/react'; import { SpeedInsights } from '@vercel/speed-insights/react'; import { AuthLoading, Authenticated, Unauthenticated } from 'convex/react'; @@ -11,9 +10,9 @@ import { CommandMenuDialog } from '~/components/CommandMenu'; import { FeedbackDialog } from '~/components/FeedbackDialog'; import { Loading } from '~/components/Loading'; import { MainHeader } from '~/components/MainHeader'; +import { Onboarding } from '~/components/Onboarding'; import { RotatingLoadingMessage } from '~/components/RotatingLoadingMessage'; import { ScheduleIterationDialog } from '~/components/ScheduleIterationDialog'; -import { Button } from '~/components/ui/button'; import { Toaster } from '~/components/ui/sonner'; import { useCurrentUser } from '~/hooks/useCurrentUser'; import { FeedbackDialogProvider, useFeedbackDialog } from '~/hooks/useFeedbackDialog'; @@ -113,7 +112,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - + {children}
{children}
@@ -167,34 +166,20 @@ function MainWithFeedback({ children }: { children: React.ReactNode }) { ); } -function AccessDenied() { +function AccessDenied({ children }: { children: React.ReactNode }) { // - const { signIn } = useAuthActions(); - - return ( -
- - -
- ); + const router = useRouter(); + const currentPath = router.state.location.pathname; + + if (currentPath === '/pricing') { + return ( +
+
{children}
+
+ ); + } + + return ; } // TODO: on .webmanifest: diff --git a/src/routes/pricing.tsx b/src/routes/pricing.tsx new file mode 100644 index 0000000..d8c6faa --- /dev/null +++ b/src/routes/pricing.tsx @@ -0,0 +1,54 @@ +import { useAuthActions } from '@convex-dev/auth/react'; +import { createFileRoute } from '@tanstack/react-router'; +import { track } from '@vercel/analytics/react'; +import { MessageCircle } from 'lucide-react'; +import { FaqSection, FounderCard, ProCard } from '~/components/subscribe'; +import { Button } from '~/components/ui/button'; + +export const Route = createFileRoute('/pricing')({ + component: PricingPage, +}); + +function PricingPage() { + // + const { signIn } = useAuthActions(); + + const handleGetStarted = async (product: 'pro' | 'founder') => { + track('tap_get_started_pricing', { product }); + signIn('google', { redirectTo: '/subscribe' }); + }; + + const handleAskQuestion = () => { + track('tap_ask_question_pricing'); + signIn('google', { redirectTo: '/subscribe' }); + }; + + const questionButton = ( + + ); + + return ( +
+
+

Pricing

+
+ +
+ + +
+ + + +
+

+ This is a research preview. Expect issues. +

+

© 2025 isPro. All rights reserved.

+
+
+ ); +} diff --git a/src/routes/subscribe.tsx b/src/routes/subscribe.tsx index 360c189..7a42ce7 100644 --- a/src/routes/subscribe.tsx +++ b/src/routes/subscribe.tsx @@ -1,6 +1,67 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { SubscribePage } from '~/components/subscribe/SubscribePage'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { track } from '@vercel/analytics/react'; +import { api } from 'convex/_generated/api'; +import { useAction } from 'convex/react'; +import { useEffect } from 'react'; +import { toast } from 'sonner'; +import { FaqSection, FounderCard, ProCard } from '~/components/subscribe'; +import { useIsPro } from '~/hooks/useIsPro'; export const Route = createFileRoute('/subscribe')({ - component: () => , + component: RouteComponent, }); + +export function RouteComponent() { + // + const navigate = useNavigate(); + const startSubscription = useAction(api.subscriptions.public.startSubscription); + const { isPro } = useIsPro(); + + // redirect to balance if already subscribed + useEffect(() => { + if (isPro) { + navigate({ to: '/balance' }); + } + }, [isPro, navigate]); + + const handleSubscribe = async (product: 'pro' | 'founder') => { + track('tap_subscribe', { product }); + try { + const { paymentUrl } = await startSubscription({ product }); + location.href = paymentUrl; + } catch (error) { + console.error(error); + toast.error('Failed to start subscription.'); + } + }; + + track('subscribe', { isPro }); + + // don't render if already subscribed (will redirect) + if (isPro) return null; + + return ( +
+
+

Choose your path

+ {/*

+ The following offers are available during research preview, and are subject to change. +

*/} +
+ +
+ + +
+ + + +
+

+ This is a research preview. Expect issues. +

+

© 2025 isPro. All rights reserved.

+
+
+ ); +} From b9667143c1064532c6cb4fe0d86c6bdbb3844484 Mon Sep 17 00:00:00 2001 From: Igor Silva Date: Wed, 13 Aug 2025 16:30:07 +0100 Subject: [PATCH 2/3] order --- src/routes/subscribe.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/subscribe.tsx b/src/routes/subscribe.tsx index 7a42ce7..7a8ca21 100644 --- a/src/routes/subscribe.tsx +++ b/src/routes/subscribe.tsx @@ -50,8 +50,8 @@ export function RouteComponent() {
- +
From 42ccae9cb5970ec1247901316b915815e75e5393 Mon Sep 17 00:00:00 2001 From: Igor Silva Date: Sat, 23 Aug 2025 10:34:37 +0100 Subject: [PATCH 3/3] feat: update Onboarding component to use Button for sign-in --- src/components/Onboarding.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Onboarding.tsx b/src/components/Onboarding.tsx index 85b0626..ac75ac5 100644 --- a/src/components/Onboarding.tsx +++ b/src/components/Onboarding.tsx @@ -1,13 +1,21 @@ import { useAuthActions } from '@convex-dev/auth/react'; +import { Button } from '~/components/ui/button'; export function Onboarding() { // const { signIn } = useAuthActions(); + const handleSignIn = () => { + console.log('signing in'); + signIn('google', { redirectTo: location.href }); + }; + return (
-

Welcome to Meseeks

+