mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add polar as better-auth plugin (#578)
This commit is contained in:
@@ -4,6 +4,10 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polar, checkout, portal } from "@polar-sh/better-auth";
|
||||
import { polarClient } from "./payments";
|
||||
{{/if}}
|
||||
import prisma from "../db";
|
||||
|
||||
export const auth = betterAuth<BetterAuthOptions>({
|
||||
@@ -28,9 +32,35 @@ export const auth = betterAuth<BetterAuthOptions>({
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [
|
||||
polar({
|
||||
client: polarClient,
|
||||
createCustomerOnSignUp: true,
|
||||
enableCustomerPortal: true,
|
||||
use: [
|
||||
checkout({
|
||||
products: [
|
||||
{
|
||||
productId: "your-product-id",
|
||||
slug: "pro",
|
||||
},
|
||||
],
|
||||
successUrl: process.env.POLAR_SUCCESS_URL,
|
||||
authenticatedUsersOnly: true,
|
||||
}),
|
||||
portal(),
|
||||
],
|
||||
}),
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
expo(),
|
||||
{{/if}}
|
||||
],
|
||||
{{else}}
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
, plugins: [expo()]
|
||||
plugins: [expo()],
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
@@ -42,6 +72,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polar, checkout, portal } from "@polar-sh/better-auth";
|
||||
import { polarClient } from "./payments";
|
||||
{{/if}}
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema/auth";
|
||||
|
||||
@@ -68,9 +102,35 @@ export const auth = betterAuth<BetterAuthOptions>({
|
||||
httpOnly: true,
|
||||
},
|
||||
},
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [
|
||||
polar({
|
||||
client: polarClient,
|
||||
createCustomerOnSignUp: true,
|
||||
enableCustomerPortal: true,
|
||||
use: [
|
||||
checkout({
|
||||
products: [
|
||||
{
|
||||
productId: "your-product-id",
|
||||
slug: "pro",
|
||||
},
|
||||
],
|
||||
successUrl: process.env.POLAR_SUCCESS_URL,
|
||||
authenticatedUsersOnly: true,
|
||||
}),
|
||||
portal(),
|
||||
],
|
||||
}),
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
expo(),
|
||||
{{/if}}
|
||||
],
|
||||
{{else}}
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
plugins: [expo()],
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
@@ -80,6 +140,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polar, checkout, portal } from "@polar-sh/better-auth";
|
||||
import { polarClient } from "./payments";
|
||||
{{/if}}
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema/auth";
|
||||
import { env } from "cloudflare:workers";
|
||||
@@ -109,9 +173,32 @@ export const auth = betterAuth<BetterAuthOptions>({
|
||||
httpOnly: true,
|
||||
},
|
||||
},
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [
|
||||
polar({
|
||||
client: polarClient,
|
||||
createCustomerOnSignUp: true,
|
||||
enableCustomerPortal: true,
|
||||
use: [
|
||||
checkout({
|
||||
products: [
|
||||
{
|
||||
productId: "your-product-id",
|
||||
slug: "pro",
|
||||
},
|
||||
],
|
||||
successUrl: env.POLAR_SUCCESS_URL,
|
||||
authenticatedUsersOnly: true,
|
||||
}),
|
||||
portal(),
|
||||
],
|
||||
}),
|
||||
],
|
||||
{{else}}
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
plugins: [expo()],
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@@ -122,6 +209,10 @@ import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polar, checkout, portal } from "@polar-sh/better-auth";
|
||||
import { polarClient } from "./payments";
|
||||
{{/if}}
|
||||
import { client } from "../db";
|
||||
|
||||
export const auth = betterAuth<BetterAuthOptions>({
|
||||
@@ -141,9 +232,35 @@ export const auth = betterAuth<BetterAuthOptions>({
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [
|
||||
polar({
|
||||
client: polarClient,
|
||||
createCustomerOnSignUp: true,
|
||||
enableCustomerPortal: true,
|
||||
use: [
|
||||
checkout({
|
||||
products: [
|
||||
{
|
||||
productId: "your-product-id",
|
||||
slug: "pro",
|
||||
},
|
||||
],
|
||||
successUrl: process.env.POLAR_SUCCESS_URL,
|
||||
authenticatedUsersOnly: true,
|
||||
}),
|
||||
portal(),
|
||||
],
|
||||
}),
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
expo(),
|
||||
{{/if}}
|
||||
],
|
||||
{{else}}
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
, plugins: [expo()]
|
||||
plugins: [expo()],
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
@@ -153,6 +270,10 @@ import { betterAuth, type BetterAuthOptions } from "better-auth";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polar, checkout, portal } from "@polar-sh/better-auth";
|
||||
import { polarClient } from "./payments";
|
||||
{{/if}}
|
||||
|
||||
export const auth = betterAuth<BetterAuthOptions>({
|
||||
database: "", // Invalid configuration
|
||||
@@ -171,9 +292,35 @@ export const auth = betterAuth<BetterAuthOptions>({
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [
|
||||
polar({
|
||||
client: polarClient,
|
||||
createCustomerOnSignUp: true,
|
||||
enableCustomerPortal: true,
|
||||
use: [
|
||||
checkout({
|
||||
products: [
|
||||
{
|
||||
productId: "your-product-id",
|
||||
slug: "pro",
|
||||
},
|
||||
],
|
||||
successUrl: process.env.POLAR_SUCCESS_URL,
|
||||
authenticatedUsersOnly: true,
|
||||
}),
|
||||
portal(),
|
||||
],
|
||||
}),
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
expo(),
|
||||
{{/if}}
|
||||
],
|
||||
{{else}}
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
, plugins: [expo()]
|
||||
plugins: [expo()],
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
@@ -11,9 +11,28 @@ definePageMeta({
|
||||
const { $orpc } = useNuxtApp()
|
||||
|
||||
const session = $authClient.useSession()
|
||||
{{#if (eq payments "polar")}}
|
||||
const customerState = ref<any>(null)
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery($orpc.privateData.queryOptions())
|
||||
const privateData = useQuery({
|
||||
...$orpc.privateData.queryOptions(),
|
||||
enabled: computed(() => !!session.value?.data?.user)
|
||||
})
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq payments "polar")}}
|
||||
onMounted(async () => {
|
||||
if (session.value?.data) {
|
||||
const { data } = await $authClient.customer.state()
|
||||
customerState.value = data
|
||||
}
|
||||
})
|
||||
|
||||
const hasProSubscription = computed(() =>
|
||||
customerState.value?.activeSubscriptions?.length! > 0
|
||||
)
|
||||
{{/if}}
|
||||
|
||||
</script>
|
||||
@@ -27,7 +46,22 @@ const privateData = useQuery($orpc.privateData.queryOptions())
|
||||
{{#if (eq api "orpc")}}
|
||||
<div v-if="privateData.status.value === 'pending'">Loading private data...</div>
|
||||
<div v-else-if="privateData.status.value === 'error'">Error loading private data: \{{ privateData.error.value?.message }}</div>
|
||||
<p v-else-if="privateData.data.value">Private Data: \{{ privateData.data.value.message }}</p>
|
||||
<p v-else-if="privateData.data.value">API: \{{ privateData.data.value.message }}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p class="mb-2">Plan: \{{ hasProSubscription ? "Pro" : "Free" }}</p>
|
||||
<UButton
|
||||
v-if="hasProSubscription"
|
||||
@click="() => { $authClient.customer.portal() }"
|
||||
>
|
||||
Manage Subscription
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else
|
||||
@click="() => { $authClient.checkout({ slug: 'pro' }) }"
|
||||
>
|
||||
Upgrade to Pro
|
||||
</UButton>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
const config = useRuntimeConfig()
|
||||
const serverUrl = config.public.serverURL
|
||||
|
||||
const authClient = createAuthClient({
|
||||
baseURL: serverUrl
|
||||
})
|
||||
|
||||
return {
|
||||
provide: {
|
||||
authClient: authClient
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polarClient } from "@polar-sh/better-auth";
|
||||
{{/if}}
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const config = useRuntimeConfig();
|
||||
const serverUrl = config.public.serverURL;
|
||||
|
||||
const authClient = createAuthClient({
|
||||
baseURL: serverUrl,
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [polarClient()],
|
||||
{{/if}}
|
||||
});
|
||||
|
||||
return {
|
||||
provide: {
|
||||
authClient: authClient,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,4 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polarClient } from "@polar-sh/better-auth";
|
||||
{{/if}}
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL:
|
||||
@@ -7,4 +10,7 @@ export const authClient = createAuthClient({
|
||||
{{else}}
|
||||
import.meta.env.VITE_SERVER_URL,
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [polarClient()]
|
||||
{{/if}}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { trpc } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
|
||||
export default function Dashboard({
|
||||
{{#if (eq payments "polar")}}
|
||||
customerState,
|
||||
{{/if}}
|
||||
session
|
||||
}: {
|
||||
{{#if (eq payments "polar")}}
|
||||
customerState: ReturnType<typeof authClient.customer.state>;
|
||||
{{/if}}
|
||||
session: typeof authClient.$Infer.Session;
|
||||
}) {
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq payments "polar")}}
|
||||
const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;
|
||||
console.log("Active subscriptions:", customerState?.activeSubscriptions);
|
||||
{{/if}}
|
||||
|
||||
return (
|
||||
<>
|
||||
{{#if (eq api "orpc")}}
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
|
||||
{hasProSubscription ? (
|
||||
<Button onClick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
{{/if}}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +1,37 @@
|
||||
"use client"
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { trpc } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import Dashboard from "./dashboard";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default function Dashboard() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
export default async function DashboardPage() {
|
||||
const session = await authClient.getSession({
|
||||
fetchOptions: {
|
||||
headers: await headers()
|
||||
}
|
||||
});
|
||||
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
if (!session.data) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!session && !isPending) {
|
||||
router.push("/login");
|
||||
}
|
||||
}, [session, isPending]);
|
||||
{{#if (eq payments "polar")}}
|
||||
const { data: customerState, error } = await authClient.customer.state({
|
||||
fetchOptions: {
|
||||
headers: await headers()
|
||||
}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
if (isPending) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session?.user.name}</p>
|
||||
{{#if (eq api "orpc")}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session.data.user.name}</p>
|
||||
<Dashboard
|
||||
session={session.data}
|
||||
{{#if (eq payments "polar")}}
|
||||
customerState={customerState}
|
||||
{{/if}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { orpc } from "@/utils/orpc";
|
||||
@@ -8,12 +9,15 @@ import { trpc } from "@/utils/trpc";
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
{{/if}}
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function Dashboard() {
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
const navigate = useNavigate();
|
||||
{{#if (eq payments "polar")}}
|
||||
const [customerState, setCustomerState] = useState<any>(null);
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
@@ -26,18 +30,48 @@ export default function Dashboard() {
|
||||
if (!session && !isPending) {
|
||||
navigate("/login");
|
||||
}
|
||||
}, [session, isPending]);
|
||||
}, [session, isPending, navigate]);
|
||||
|
||||
{{#if (eq payments "polar")}}
|
||||
useEffect(() => {
|
||||
async function fetchCustomerState() {
|
||||
if (session) {
|
||||
const { data } = await authClient.customer.state();
|
||||
setCustomerState(data);
|
||||
}
|
||||
}
|
||||
|
||||
fetchCustomerState();
|
||||
}, [session]);
|
||||
{{/if}}
|
||||
|
||||
if (isPending) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
{{#if (eq payments "polar")}}
|
||||
const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;
|
||||
console.log("Active subscriptions:", customerState?.activeSubscriptions);
|
||||
{{/if}}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session?.user.name}</p>
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
|
||||
{hasProSubscription ? (
|
||||
<Button onClick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { orpc } from "@/utils/orpc";
|
||||
@@ -8,44 +9,61 @@ import { trpc } from "@/utils/trpc";
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
{{/if}}
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { useEffect } from "react";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
component: RouteComponent,
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const session = await authClient.getSession();
|
||||
if (!session.data) {
|
||||
redirect({
|
||||
to: "/login",
|
||||
throw: true
|
||||
});
|
||||
}
|
||||
{{#if (eq payments "polar")}}
|
||||
const {data: customerState} = await authClient.customer.state()
|
||||
return { session, customerState };
|
||||
{{else}}
|
||||
return { session };
|
||||
{{/if}}
|
||||
}
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
const { session{{#if (eq payments "polar")}}, customerState{{/if}} } = Route.useRouteContext();
|
||||
|
||||
const navigate = Route.useNavigate();
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
const hasProSubscription = customerState?.activeSubscriptions?.length! > 0
|
||||
console.log("Active subscriptions:", customerState?.activeSubscriptions)
|
||||
{{/if}}
|
||||
|
||||
useEffect(() => {
|
||||
if (!session && !isPending) {
|
||||
navigate({
|
||||
to: "/login",
|
||||
});
|
||||
}
|
||||
}, [session, isPending]);
|
||||
|
||||
if (isPending) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session?.user.name}</p>
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session.data?.user.name}</p>
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
|
||||
{hasProSubscription ? (
|
||||
<Button onClick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "trpc")}}
|
||||
import { useTRPC } from "@/utils/trpc";
|
||||
@@ -7,48 +8,62 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
{{/if}}
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { useEffect } from "react";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
component: RouteComponent,
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const session = await authClient.getSession();
|
||||
if (!session.data) {
|
||||
redirect({
|
||||
to: "/login",
|
||||
throw: true
|
||||
});
|
||||
}
|
||||
{{#if (eq payments "polar")}}
|
||||
const {data: customerState} = await authClient.customer.state()
|
||||
return { session, customerState };
|
||||
{{else}}
|
||||
return { session };
|
||||
{{/if}}
|
||||
}
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const navigate = Route.useNavigate();
|
||||
{{#if (eq api "trpc")}}
|
||||
const trpc = useTRPC();
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
{{/if}}
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
const { session{{#if (eq payments "polar")}}, customerState{{/if}} } = Route.useRouteContext();
|
||||
|
||||
{{#if (eq api "trpc")}}
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const trpc = useTRPC();
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
|
||||
useEffect(() => {
|
||||
if (!session && !isPending) {
|
||||
navigate({
|
||||
to: "/login",
|
||||
});
|
||||
}
|
||||
}, [session, isPending]);
|
||||
{{#if (eq payments "polar")}}
|
||||
const hasProSubscription = customerState?.activeSubscriptions?.length! > 0
|
||||
console.log("Active subscriptions:", customerState?.activeSubscriptions)
|
||||
{{/if}}
|
||||
|
||||
if (isPending) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session?.user.name}</p>
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session.data?.user.name}</p>
|
||||
{{#if ( or (eq api "orpc") (eq api "trpc"))}}
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
|
||||
{hasProSubscription ? (
|
||||
<Button onClick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { createAuthClient } from "better-auth/solid";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: import.meta.env.VITE_SERVER_URL,
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createAuthClient } from "better-auth/solid";
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polarClient } from "@polar-sh/better-auth";
|
||||
{{/if}}
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: import.meta.env.VITE_SERVER_URL,
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [polarClient()]
|
||||
{{/if}}
|
||||
});
|
||||
@@ -3,42 +3,65 @@ import { authClient } from "@/lib/auth-client";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
import { useQuery } from "@tanstack/solid-query";
|
||||
{{/if}}
|
||||
import { createFileRoute } from "@tanstack/solid-router";
|
||||
import { createEffect, Show } from "solid-js";
|
||||
import { createFileRoute, redirect } from "@tanstack/solid-router";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
component: RouteComponent,
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const session = await authClient.getSession();
|
||||
if (!session.data) {
|
||||
redirect({
|
||||
to: "/login",
|
||||
throw: true,
|
||||
});
|
||||
}
|
||||
{{#if (eq payments "polar")}}
|
||||
const { data: customerState } = await authClient.customer.state();
|
||||
return { session, customerState };
|
||||
{{else}}
|
||||
return { session };
|
||||
{{/if}}
|
||||
},
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const session = authClient.useSession();
|
||||
const navigate = Route.useNavigate();
|
||||
const context = Route.useRouteContext();
|
||||
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(() => orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
const session = context().session;
|
||||
{{#if (eq payments "polar")}}
|
||||
const customerState = context().customerState;
|
||||
{{/if}}
|
||||
|
||||
createEffect(() => {
|
||||
if (!session().data && !session().isPending) {
|
||||
navigate({
|
||||
to: "/login",
|
||||
});
|
||||
}
|
||||
});
|
||||
{{#if (eq api "orpc")}}
|
||||
const privateData = useQuery(() => orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Show when={session().isPending}>
|
||||
<div>Loading...</div>
|
||||
</Show>
|
||||
{{#if (eq payments "polar")}}
|
||||
const hasProSubscription = () =>
|
||||
customerState?.activeSubscriptions?.length! > 0;
|
||||
{{/if}}
|
||||
|
||||
<Show when={!session().isPending && session().data}>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session().data?.user.name}</p>
|
||||
{{#if (eq api "orpc")}}
|
||||
<p>privateData: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {session.data?.user.name}</p>
|
||||
{{#if (eq api "orpc")}}
|
||||
<p>API: {privateData.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {hasProSubscription() ? "Pro" : "Free"}</p>
|
||||
{hasProSubscription() ? (
|
||||
<button onClick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={async () => await authClient.checkout({ slug: "pro" })}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
)}
|
||||
{{/if}}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { PUBLIC_SERVER_URL } from "$env/static/public";
|
||||
import { createAuthClient } from "better-auth/svelte";
|
||||
{{#if (eq payments "polar")}}
|
||||
import { polarClient } from "@polar-sh/better-auth";
|
||||
{{/if}}
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: PUBLIC_SERVER_URL,
|
||||
{{#if (eq payments "polar")}}
|
||||
plugins: [polarClient()]
|
||||
{{/if}}
|
||||
});
|
||||
@@ -6,7 +6,9 @@
|
||||
import { orpc } from '$lib/orpc';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
{{/if}}
|
||||
import { get } from 'svelte/store';
|
||||
{{#if (eq payments "polar")}}
|
||||
let customerState: any = null;
|
||||
{{/if}}
|
||||
|
||||
const sessionQuery = authClient.useSession();
|
||||
|
||||
@@ -15,10 +17,17 @@
|
||||
{{/if}}
|
||||
|
||||
onMount(() => {
|
||||
const { data: session, isPending } = get(sessionQuery);
|
||||
const { data: session, isPending } = $sessionQuery;
|
||||
if (!session && !isPending) {
|
||||
goto('/login');
|
||||
}
|
||||
{{#if (eq payments "polar")}}
|
||||
if (session) {
|
||||
authClient.customer.state().then(({ data }) => {
|
||||
customerState = data;
|
||||
});
|
||||
}
|
||||
{{/if}}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -30,7 +39,19 @@
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome {$sessionQuery.data.user.name}</p>
|
||||
{{#if (eq api "orpc")}}
|
||||
<p>privateData: {$privateDataQuery.data?.message}</p>
|
||||
<p>API: {$privateDataQuery.data?.message}</p>
|
||||
{{/if}}
|
||||
{{#if (eq payments "polar")}}
|
||||
<p>Plan: {customerState?.activeSubscriptions?.length > 0 ? "Pro" : "Free"}</p>
|
||||
{#if customerState?.activeSubscriptions?.length > 0}
|
||||
<button onclick={async () => await authClient.customer.portal()}>
|
||||
Manage Subscription
|
||||
</button>
|
||||
{:else}
|
||||
<button onclick={async () => await authClient.checkout({ slug: "pro" })}>
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
{/if}
|
||||
{{/if}}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
],
|
||||
{{/if}}
|
||||
"devDependencies": {
|
||||
"tsdown": "^0.14.1",
|
||||
"tsdown": "^0.15.1",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
[install]
|
||||
{{#if (includes frontend "nuxt")}}
|
||||
# linker = "isolated" # Commented out for Nuxt compatibility
|
||||
{{else}}
|
||||
linker = "isolated"
|
||||
{{/if}}
|
||||
|
||||
@@ -17,6 +17,10 @@ import appCss from "../index.css?url";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import type { ConvexQueryClient } from "@convex-dev/react-query";
|
||||
import type { ConvexReactClient } from "convex/react";
|
||||
{{else}}
|
||||
{{#if (or (eq api "trpc") (eq api "orpc"))}}
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
import Loader from "@/components/loader";
|
||||
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/router-plugin": "^1.109.2",
|
||||
"@tanstack/solid-form": "^1.9.0",
|
||||
"@tanstack/solid-router": "^1.110.0",
|
||||
"lucide-solid": "^0.507.0",
|
||||
"solid-js": "^1.9.4",
|
||||
"tailwindcss": "^4.0.6",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tanstack/router-plugin": "^1.131.44",
|
||||
"@tanstack/solid-form": "^1.20.0",
|
||||
"@tanstack/solid-router": "^1.131.44",
|
||||
"lucide-solid": "^0.544.0",
|
||||
"solid-js": "^1.9.9",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"zod": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.0.2",
|
||||
"vite-plugin-solid": "^2.11.2"
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-solid": "^2.11.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Polar } from "@polar-sh/sdk";
|
||||
|
||||
export const polarClient = new Polar({
|
||||
accessToken: process.env.POLAR_ACCESS_TOKEN,
|
||||
server: "sandbox",
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const checkout_id = route.query.checkout_id as string
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Payment Successful!</h1>
|
||||
<p v-if="checkout_id">Checkout ID: \{{ checkout_id }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
export default async function SuccessPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ checkout_id: string }>
|
||||
}) {
|
||||
const params = await searchParams;
|
||||
const checkout_id = params.checkout_id;
|
||||
|
||||
return (
|
||||
<div className="px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
export default function SuccessPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const checkout_id = searchParams.get("checkout_id");
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/success")({
|
||||
component: SuccessPage,
|
||||
validateSearch: (search) => ({
|
||||
checkout_id: search.checkout_id as string,
|
||||
}),
|
||||
});
|
||||
|
||||
function SuccessPage() {
|
||||
const { checkout_id } = useSearch({ from: "/success" });
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/success")({
|
||||
component: SuccessPage,
|
||||
validateSearch: (search) => ({
|
||||
checkout_id: search.checkout_id as string,
|
||||
}),
|
||||
});
|
||||
|
||||
function SuccessPage() {
|
||||
const { checkout_id } = useSearch({ from: "/success" });
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
{checkout_id && <p>Checkout ID: {checkout_id}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createFileRoute } from "@tanstack/solid-router";
|
||||
import { Show } from "solid-js";
|
||||
|
||||
export const Route = createFileRoute("/success")({
|
||||
component: SuccessPage,
|
||||
validateSearch: (search) => ({
|
||||
checkout_id: search.checkout_id as string,
|
||||
}),
|
||||
});
|
||||
|
||||
function SuccessPage() {
|
||||
const searchParams = Route.useSearch();
|
||||
const checkout_id = searchParams().checkout_id;
|
||||
|
||||
return (
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
<Show when={checkout_id}>
|
||||
<p>Checkout ID: {checkout_id}</p>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
const checkout_id = $page.url.searchParams.get('checkout_id');
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1>Payment Successful!</h1>
|
||||
{#if checkout_id}
|
||||
<p>Checkout ID: {checkout_id}</p>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user