Skip to content

Commit d4a3663

Browse files
committed
Stripe cleanup
1 parent eabe780 commit d4a3663

File tree

19 files changed

+229
-151
lines changed

19 files changed

+229
-151
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@ OPENAI_API_KEY="<OPENAI_API_KEY>"
2828

2929
# Public URL of the web app. In preview URLs it should be `PUBLIC_WEB_URL=https://$NEXT_PUBLIC_VERCEL_URL`.
3030
PUBLIC_WEB_URL="<YOUR_PUBLIC_WEB_URL>"
31+
32+
# Stripe
33+
STRIPE_SECRET_KEY="<STRIPE_SECRET_KEY>"
34+
STRIPE_WEBHOOK_SECRET="<STRIPE_WEBHOOK_SECRET>"
35+
STRIPE_WEBHOOK_PORT="<STRIPE_WEBHOOK_PORT>"

apps/stripe-listener/package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
{
2-
"name": "@acme/stripe-listener",
2+
"name": "@acme/stripe",
33
"version": "0.0.0",
44
"private": true,
55
"type": "module",
66
"scripts": {
77
"clean": "git clean -xdf .cache .turbo node_modules tsconfig.tsbuildinfo",
8-
"dev": "stripe listen --forward-to localhost:3000/api/auth/stripe/webhook",
9-
"typecheck": "echo 'No TypeScript files to check in stripe-listener'"
8+
"dev": "pnpm with-env sh -c 'stripe listen --forward-to localhost:$STRIPE_WEBHOOK_PORT/api/auth/stripe/webhook'",
9+
"typecheck": "tsc --noEmit",
10+
"with-env": "dotenv -e ../../.env --"
11+
},
12+
"devDependencies": {
13+
"@acme/tsconfig": "workspace:*",
14+
"@t3-oss/env-nextjs": "^0.13.6",
15+
"dotenv-cli": "^8.0.0",
16+
"zod": "^3.25.71"
1017
}
1118
}

apps/stripe-listener/src/env.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createEnv } from "@t3-oss/env-nextjs";
2+
import { z } from "zod";
3+
4+
export const env = createEnv({
5+
/**
6+
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
7+
*/
8+
experimental__runtimeEnv: {
9+
NODE_ENV: process.env.NODE_ENV,
10+
},
11+
/**
12+
* Specify your server-side environment variables schema here.
13+
* This way you can ensure the app isn't built with invalid env vars.
14+
*/
15+
server: {
16+
STRIPE_WEBHOOK_PORT: z.string().transform(Number).default("3000"),
17+
},
18+
shared: {
19+
NODE_ENV: z
20+
.enum(["development", "production", "test"])
21+
.default("development"),
22+
},
23+
skipValidation:
24+
!!process.env.CI || process.env.npm_lifecycle_event === "lint",
25+
});

apps/stripe-listener/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tooling/tsconfig/tsconfig.nodejs.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src"
6+
},
7+
"include": ["src/**/*"],
8+
"exclude": ["node_modules", "dist"]
9+
}

apps/web/src/app/(app)/(billing)/subscription/_components/subscription-manage-button.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client";
22

33
import type { ActiveSubscription } from "@acme/api";
4-
import { usePathname } from "next/navigation";
4+
import { useMutation } from "@tanstack/react-query";
5+
import { redirect, usePathname } from "next/navigation";
56
import type { ComponentProps } from "react";
6-
import { useState } from "react";
77
import { toast } from "sonner";
8-
import { authClient } from "~/auth/client";
98
import { Button } from "~/components/ui/button";
9+
import { useTRPC } from "~/trpc/react";
1010

1111
export const SubscriptionManageButton = ({
1212
activeSubscription,
@@ -17,35 +17,32 @@ export const SubscriptionManageButton = ({
1717
activeSubscription: ActiveSubscription;
1818
} & ComponentProps<typeof Button>) => {
1919
const pathname = usePathname();
20-
const { data: sessionData } = authClient.useSession();
21-
const [isLoading, setIsLoading] = useState(false);
20+
const trpc = useTRPC();
21+
22+
const { mutateAsync: openBillingPortal, isPending } = useMutation(
23+
trpc.subscription.openBillingPortal.mutationOptions({
24+
onError: (error) => {
25+
toast.error(error.message || "Failed to open billing portal");
26+
},
27+
}),
28+
);
2229

2330
if (!activeSubscription) {
2431
return null;
2532
}
2633

2734
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
28-
setIsLoading(true);
29-
30-
if (!sessionData?.user?.id) {
31-
toast.error("Please sign in to manage your subscription");
32-
return;
33-
}
34-
35-
const result = await authClient.subscription.billingPortal({
36-
locale: "en",
37-
referenceId: sessionData.user.id,
35+
const res = await openBillingPortal({
3836
returnUrl: pathname,
3937
});
40-
41-
if (result.error) {
42-
console.error("Billing portal error:", result.error);
43-
toast.error("Failed to open billing portal. Please try again.");
44-
}
45-
4638
// Call the custom onClick handler if provided
4739
onClick?.(event);
48-
setIsLoading(false);
40+
41+
if (res.url) {
42+
redirect(res.url);
43+
} else {
44+
toast.error("Failed to create billing portal session");
45+
}
4946
};
5047

5148
return (
@@ -54,7 +51,7 @@ export const SubscriptionManageButton = ({
5451
size="sm"
5552
className="rounded-lg bg-black text-white"
5653
variant="outline"
57-
disabled={isLoading}
54+
disabled={isPending}
5855
{...buttonProps}
5956
>
6057
{children}

apps/web/src/app/(app)/@appSidebar/_components/app-sidebar-user-settings.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export function AppSidebarUserSettings() {
1414
setCancelUrl(pathname);
1515

1616
// Add a small delay to allow the dropdown to close before navigation
17-
setTimeout(() => {
17+
requestAnimationFrame(() => {
1818
router.push("/auth/settings");
19-
}, 150);
19+
});
2020
};
2121

2222
return (
Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
"use client";
22

3-
import { usePathname, useRouter } from "next/navigation";
4-
import { useEffect, useState } from "react";
3+
import { useRouter } from "next/navigation";
4+
import { useState } from "react";
55
import { Dialog } from "~/components/ui/dialog";
6-
import { BillingErrorBoundary } from "../../_components/billing-error-boundary";
76

87
export const SubscriptionModal = ({
98
children,
109
}: {
1110
children: React.ReactNode;
1211
}) => {
13-
const pathname = usePathname();
1412
const router = useRouter();
15-
const [isOpen, setIsOpen] = useState(false);
16-
17-
// Use effect to prevent hydration mismatch and flashing
18-
useEffect(() => {
19-
setIsOpen(pathname === "/subscription");
20-
}, [pathname]);
13+
const [isOpen, setIsOpen] = useState(true);
2114

2215
const handleOpenChange = (open: boolean) => {
2316
if (!open) {
@@ -28,7 +21,7 @@ export const SubscriptionModal = ({
2821

2922
return (
3023
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
31-
<BillingErrorBoundary>{children}</BillingErrorBoundary>
24+
{children}
3225
</Dialog>
3326
);
3427
};

apps/web/src/app/(app)/@billingModal/_components/billing-error-boundary.tsx

Lines changed: 0 additions & 63 deletions
This file was deleted.

apps/web/src/app/(app)/@billingModal/error.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect } from "react";
44
import { Button } from "~/components/ui/button";
55
import {
6+
Dialog,
67
DialogContent,
78
DialogHeader,
89
DialogTitle,
@@ -20,26 +21,28 @@ export default function BillingModalError({
2021
}, [error]);
2122

2223
return (
23-
<DialogContent>
24-
<DialogHeader>
25-
<DialogTitle>Billing Error</DialogTitle>
26-
</DialogHeader>
27-
<div className="space-y-4">
28-
<p className="text-muted-foreground">
29-
We encountered an error while loading your billing information.
30-
</p>
31-
<div className="flex justify-end space-x-2">
32-
<Button
33-
variant="outline"
34-
onClick={() => {
35-
window.location.href = "/";
36-
}}
37-
>
38-
Go Home
39-
</Button>
40-
<Button onClick={reset}>Try Again</Button>
24+
<Dialog open={true}>
25+
<DialogContent>
26+
<DialogHeader>
27+
<DialogTitle>Billing Error</DialogTitle>
28+
</DialogHeader>
29+
<div className="space-y-4">
30+
<p className="text-muted-foreground">
31+
We encountered an error while loading your billing information.
32+
</p>
33+
<div className="flex justify-end space-x-2">
34+
<Button
35+
variant="outline"
36+
onClick={() => {
37+
window.location.href = "/";
38+
}}
39+
>
40+
Go Home
41+
</Button>
42+
<Button onClick={reset}>Try Again</Button>
43+
</div>
4144
</div>
42-
</div>
43-
</DialogContent>
45+
</DialogContent>
46+
</Dialog>
4447
);
4548
}

apps/web/src/app/(app)/@chatSidebar/default.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function ChatSidebar() {
2121
side="right"
2222
collapsible="offcanvas"
2323
variant="floating"
24-
className="sticky top-0 z-10 h-svh"
24+
className="sticky top-0 h-svh"
2525
defaultWidth={CHAT_SIDEBAR_DEFAULT_WIDTH}
2626
minWidth={CHAT_SIDEBAR_MIN_WIDTH}
2727
maxWidth={CHAT_SIDEBAR_WIDTH_MAX}

0 commit comments

Comments
 (0)