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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ OPENAI_API_KEY="<OPENAI_API_KEY>"

# Public URL of the web app. In preview URLs it should be `PUBLIC_WEB_URL=https://$NEXT_PUBLIC_VERCEL_URL`.
PUBLIC_WEB_URL="<YOUR_PUBLIC_WEB_URL>"

# Stripe credentials for billing.
# You will need the Stripe CLI for local webhooks: https://docs.stripe.com/stripe-cli
# Grab the API keys from here: https://dashboard.stripe.com/<account_id>/test/apikeys
STRIPE_SECRET_KEY="<STRIPE_SECRET_KEY>"
STRIPE_WEBHOOK_SECRET="<STRIPE_WEBHOOK_SECRET>"
STRIPE_WEBHOOK_PORT="<STRIPE_WEBHOOK_PORT>"
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ env:
FORCE_COLOR: 3
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# Stripe environment variables
STRIPE_SECRET_KEY: 'sk_test_placeholder_for_build'
STRIPE_WEBHOOK_SECRET: 'whsec_build_placeholder'

jobs:
check:
Expand Down
11 changes: 0 additions & 11 deletions apps/stripe-listener/package.json

This file was deleted.

18 changes: 18 additions & 0 deletions apps/stripe/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@acme/stripe",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"clean": "git clean -xdf .cache .turbo node_modules tsconfig.tsbuildinfo",
"dev": "pnpm with-env sh -c 'stripe listen --forward-to localhost:$STRIPE_WEBHOOK_PORT/api/auth/stripe/webhook'",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"devDependencies": {
"@acme/tsconfig": "workspace:*",
"@t3-oss/env-nextjs": "^0.13.6",
"dotenv-cli": "^8.0.0",
"zod": "^3.25.71"
}
}
25 changes: 25 additions & 0 deletions apps/stripe/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
experimental__runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
},
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {
STRIPE_WEBHOOK_PORT: z.string().transform(Number).default("3000"),
},
shared: {
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
skipValidation:
!!process.env.CI || process.env.npm_lifecycle_event === "lint",
});
9 changes: 9 additions & 0 deletions apps/stripe/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tooling/tsconfig/tsconfig.nodejs.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
File renamed without changes.

This file was deleted.

53 changes: 0 additions & 53 deletions apps/web/src/app/(app)/(billing)/subscription/page.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,43 +1,31 @@
"use client";

import type { ActiveSubscription } from "@acme/api";
import type { Subscription } from "@acme/api";
import { CreditCard } from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
DropdownMenuItem,
DropdownMenuSeparator,
} from "~/components/ui/dropdown-menu";

export const AppSidebarManageSubscription = ({
activeSubscription,
}: {
activeSubscription: ActiveSubscription;
}) => {
const pathname = usePathname();
type AppSidebarManageSubscriptionProps = {
subscription: Subscription;
};

if (!activeSubscription) {
export function AppSidebarManageSubscription({
subscription,
}: AppSidebarManageSubscriptionProps) {
if (!subscription) {
return null;
}

const isOnSubscriptionPage = pathname === "/subscription";

return (
<>
<DropdownMenuSeparator />
<DropdownMenuItem asChild className={"w-full cursor-pointer"}>
{isOnSubscriptionPage ? (
<div className="flex items-center gap-2">
<CreditCard />
Subscription
</div>
) : (
<Link href="/subscription" className="flex items-center gap-2">
<CreditCard />
Subscription
</Link>
)}
<Link href="/subscription" className="flex items-center gap-2">
<CreditCard />
Subscription
</Link>
</DropdownMenuItem>
</>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export function AppSidebarUserSettings() {
setCancelUrl(pathname);

// Add a small delay to allow the dropdown to close before navigation
setTimeout(() => {
requestAnimationFrame(() => {
router.push("/auth/settings");
}, 150);
});
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import { AppSidebarUserSettings } from "./app-sidebar-user-settings";
import { AppSidebarUserSignOut } from "./app-sidebar-user-sign-out";

export async function AppSidebarUser() {
const user = await getUser();
const activeSubscription = await api.subscription.getActiveSubscription();
const [user, subscription] = await Promise.all([
getUser(),
api.subscription.getSubscription(),
]);

return (
<SidebarMenu>
Expand Down Expand Up @@ -74,9 +76,7 @@ export async function AppSidebarUser() {
<DropdownMenuSeparator />
<AppSidebarUserSettings />
<AppSidebarUserSignOut />
<AppSidebarManageSubscription
activeSubscription={activeSubscription}
/>
<AppSidebarManageSubscription subscription={subscription} />
</AppSidebarUserMenu>
</DropdownMenu>
</SidebarMenuItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
"use client";

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

export const SubscriptionModal = ({
children,
}: {
children: React.ReactNode;
}) => {
const pathname = usePathname();
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);

// Use effect to prevent hydration mismatch and flashing
useEffect(() => {
setIsOpen(pathname === "/subscription");
}, [pathname]);
const [isOpen, setIsOpen] = useState(true);

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

return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<BillingErrorBoundary>{children}</BillingErrorBoundary>
{children}
</Dialog>
);
};
35 changes: 5 additions & 30 deletions apps/web/src/app/(app)/@billingModal/(.)subscription/page.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,26 @@
import { CreditCard } from "lucide-react";
import { redirect } from "next/navigation";
import {
DialogContent,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import { api } from "~/trpc/server";
import { SubscriptionInfo } from "../../_components/billing/subscription-info";
import { SubscriptionManageButton } from "../../(billing)/subscription/_components/subscription-manage-button";
import { SubscriptionView } from "../../../(dashboard)/subscription/_components/subscription-view";
import { SubscriptionModal } from "./_components/subscription-modal";

export default async function SubscriptionBillingModalPage() {
const subscription = await api.subscription.getActiveSubscription();
const subscription = await api.subscription.getSubscription();
if (!subscription) {
return redirect("/");
return redirect("/journal");
}

// Format the cancellation date if subscription is set to cancel at period end
const cancelDate =
subscription.cancelAtPeriodEnd && subscription.periodEnd
? new Date(subscription.periodEnd).toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
})
: null;

return (
<SubscriptionModal>
<DialogContent>
<DialogHeader>
<DialogTitle>Manage Subscription</DialogTitle>
<DialogTitle className="capitalize">Plan Summary</DialogTitle>
</DialogHeader>
<SubscriptionInfo activeSubscription={subscription} />
{/* Action Button */}
<div className="flex justify-end">
{cancelDate ? (
<SubscriptionManageButton activeSubscription={subscription}>
Don't cancel subscription
</SubscriptionManageButton>
) : (
<SubscriptionManageButton activeSubscription={subscription}>
<CreditCard />
Manage Subscription
</SubscriptionManageButton>
)}
</div>
<SubscriptionView subscription={subscription} />
</DialogContent>
</SubscriptionModal>
);
Expand Down
Loading