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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Plan" ADD COLUMN "durationMonths" INTEGER NOT NULL DEFAULT 12;
17 changes: 9 additions & 8 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,15 @@ model Subscription {
}

model Plan {
id String @id @default(cuid())
name String
interval String
price Int
currency String @default("INR")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions Subscription[]
id String @id @default(cuid())
name String
interval String
durationMonths Int @default(12)
price Int
currency String @default("INR")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions Subscription[]
}

model WeeklySession {
Expand Down
22 changes: 19 additions & 3 deletions apps/api/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,36 @@ async function main() {
// await prisma.user.deleteMany();
// await prisma.plan.deleteMany();

// Create test plan (1 rupee test plan)
// Create test plan (1 rupee test plan - 1 year)
const testPlan = await prisma.plan.upsert({
where: { id: '385b8215-d70f-473e-81c9-68a673c0d2fc-test' },
update: {},
update: { durationMonths: 12, name: 'Pro' },
create: {
id: '385b8215-d70f-473e-81c9-68a673c0d2fc-test',
name: 'Test Plan',
name: 'Pro',
interval: 'yearly',
durationMonths: 12,
price: 100, // 1 rupee in paise
currency: 'INR',
},
});
console.log('✅ Created test plan:', testPlan.id);

// Create 4-year test plan (dummy plan for the 48-month duration)
const fourYearPlan = await prisma.plan.upsert({
where: { id: '385b8215-d70f-473e-81c9-68a673c0d2fc-4yr-test' },
update: { durationMonths: 48, name: 'Pro+' },
create: {
id: '385b8215-d70f-473e-81c9-68a673c0d2fc-4yr-test',
name: 'Pro+',
interval: 'yearly',
durationMonths: 48,
price: 100, // 1 rupee in paise (test price)
currency: 'INR',
},
});
console.log('✅ Created 4-year test plan:', fourYearPlan.id);

// Create test user
const testUser = await prisma.user.upsert({
where: { email: 'test@example.com' },
Expand Down
31 changes: 31 additions & 0 deletions apps/api/scripts/rename-plan-display-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* one-off: rename Plan.display names to Pro / Pro+ by duration.
* run: npx tsx scripts/rename-plan-display-names.ts
*/
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
const yearly = await prisma.plan.updateMany({
where: { durationMonths: 12 },
data: { name: "Pro" },
});

const fourYear = await prisma.plan.updateMany({
where: { durationMonths: 48 },
data: { name: "Pro+" },
});

console.log(`updated ${yearly.count} yearly plan(s) → Pro`);
console.log(`updated ${fourYear.count} 4-year plan(s) → Pro+`);
}

main()
.catch((error) => {
console.error(error);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
2 changes: 1 addition & 1 deletion apps/api/scripts/seed-pro-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ async function main(): Promise<void> {
let plan = await prisma.plan.findFirst({ where: { name: "Pro (dev seed)" } });
if (!plan) {
plan = await prisma.plan.create({
data: { name: "Pro (dev seed)", interval: "yearly", price: 0, currency: "INR" },
data: { name: "Pro (dev seed)", interval: "yearly", durationMonths: 12, price: 0, currency: "INR" },
});
}

Expand Down
59 changes: 40 additions & 19 deletions apps/api/src/services/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@ function isTransientDbError(error: unknown): boolean {
return false;
}

function resolvePlanDurationMonths(plan: {
durationMonths?: number | null;
interval?: string | null;
}): number {
if (
typeof plan.durationMonths === "number" &&
Number.isFinite(plan.durationMonths) &&
plan.durationMonths > 0
) {
return plan.durationMonths;
}

switch ((plan.interval ?? "").toLowerCase()) {
case "monthly":
return 1;
case "quarterly":
return 3;
case "yearly":
case "annual":
return 12;
default:
return 12;
}
}

interface CreateOrderInput {
amount: number;
currency: string;
Expand Down Expand Up @@ -283,28 +308,24 @@ export const paymentService = {
throw new Error("Plan not found");
}

// Calculate end date - Currently only yearly plan is supported
// Calculate end date from the plan's duration. Prefer the explicit
// durationMonths field; fall back to interpreting the interval string so
// older plans without a duration still resolve to a sensible length.
const startDate = new Date();
const endDate = new Date(startDate);

// Set subscription for 1 year (yearly plan)
endDate.setFullYear(endDate.getFullYear() + 1);

// Future plan intervals (commented out for now):
// switch (plan.interval.toLowerCase()) {
// case "monthly":
// endDate.setMonth(endDate.getMonth() + 1);
// break;
// case "quarterly":
// endDate.setMonth(endDate.getMonth() + 3);
// break;
// case "yearly":
// case "annual":
// endDate.setFullYear(endDate.getFullYear() + 1);
// break;
// default:
// endDate.setFullYear(endDate.getFullYear() + 1);
// }
const durationMonths = resolvePlanDurationMonths(plan);
// Add months with year wraparound, clamping to the last valid day of the
// target month so month-end start dates (e.g. Jan 31, or Feb 29 on a leap
// year) don't overflow into the following month.
const monthsFromYearStart = endDate.getMonth() + durationMonths;
const targetYear =
endDate.getFullYear() + Math.floor(monthsFromYearStart / 12);
const targetMonth = ((monthsFromYearStart % 12) + 12) % 12;
endDate.setFullYear(targetYear, targetMonth, endDate.getDate());
if (endDate.getMonth() !== targetMonth) {
endDate.setDate(0);
}

// Check if user already has an active subscription for this payment
const existingSubscription = await prisma.subscription.findFirst({
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export const userService = {
include: {
plan: true,
},
orderBy: {
startDate: "desc",
},
});

return {
Expand All @@ -40,6 +43,7 @@ export const userService = {
? {
id: subscription.id,
planName: subscription.plan?.name,
durationMonths: subscription.plan?.durationMonths ?? null,
startDate: subscription.startDate,
endDate: subscription.endDate,
status: subscription.status,
Expand Down
Binary file added apps/web/public/assets/jackedaj.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 15 additions & 29 deletions apps/web/src/app/(main)/(landing)/pitch/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@

import React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";

import Footer from "@/components/landing-sections/footer";
import PaymentFlow from "@/components/payment/PaymentFlow";
import PrimaryButton from "@/components/ui/custom-button";
import Header from "@/components/ui/header";

const Pitch = () => {
const pathname = usePathname();
const premiumPlanId = process.env.NEXT_PUBLIC_YEARLY_PREMIUM_PLAN_ID;
const planIdOk =
typeof premiumPlanId === "string" && premiumPlanId.trim().length > 0;
const callbackUrl = `${pathname}#invest`;

return (
<>
<main className="w-full overflow-hidden flex flex-col items-center justify-center relative pt-24 md:pt-28">
Expand Down Expand Up @@ -240,10 +232,18 @@ const Pitch = () => {
</p>
</div>

<div className="border-t border-border pt-8 space-y-4">
<h2 className="text-2xl lg:text-3xl font-bold text-brand-purple-light tracking-tight leading-tight [font-family:Helvetica,Arial,sans-serif]">
but how will you be able to give personal attention and do all
of that if the number of opensox pro members grows in the future?
<div id="my-scale" className="border-t border-border pt-8 space-y-4">
<h2 className="text-2xl lg:text-3xl font-bold tracking-tight leading-tight [font-family:Helvetica,Arial,sans-serif]">
<a
href="/pitch#my-scale"
target="_blank"
rel="noopener noreferrer"
className="text-brand-purple-light decoration-brand-purple-light decoration-1 transition-colors"
>
but how will you be able to give personal attention and do all
of that if the number of opensox pro members grows in the
future?
</a>
</h2>
<p>
i thought about this a lot. and made a hard decision to{" "}
Expand Down Expand Up @@ -315,23 +315,9 @@ const Pitch = () => {
how to invest?
</h2>
<div className="flex justify-center">
{planIdOk ? (
<div className="w-full max-w-[180px]">
<PaymentFlow
planId={premiumPlanId}
planName="Opensox Pro"
description="Annual Subscription"
buttonText="im in"
buttonClassName="w-full"
callbackUrl={callbackUrl}
buttonLocation="pitch_page"
/>
</div>
) : (
<Link href="/pricing" className="w-full max-w-[180px]">
<PrimaryButton classname="w-full">im in</PrimaryButton>
</Link>
)}
<Link href="/pricing#pro-price-card" className="w-full max-w-[180px]">
<PrimaryButton classname="w-full">im in</PrimaryButton>
</Link>
</div>
</div>

Expand Down
Loading