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 "User" ADD COLUMN "completedSteps" JSONB;
19 changes: 10 additions & 9 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ enum SubscriptionStatus {
}

model User {
id String @id @default(cuid())
email String @unique
firstName String
authMethod String
createdAt DateTime @default(now())
lastLogin DateTime @updatedAt
accounts Account[]
payments Payment[]
subscriptions Subscription[]
id String @id @default(cuid())
email String @unique
firstName String
authMethod String
createdAt DateTime @default(now())
lastLogin DateTime @updatedAt
completedSteps Json?
accounts Account[]
payments Payment[]
subscriptions Subscription[]
}

model Account {
Expand Down
23 changes: 23 additions & 0 deletions apps/api/src/routers/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { router, publicProcedure, protectedProcedure } from "../trpc.js";
import { userService } from "../services/user.service.js";
import { z } from "zod";

export const userRouter = router({
// get the total count of users
Expand All @@ -12,4 +13,26 @@ export const userRouter = router({
const userId = ctx.user.id;
return await userService.checkSubscriptionStatus(ctx.db.prisma, userId);
}),

// get user's completed steps
getCompletedSteps: protectedProcedure.query(async ({ ctx }: any) => {
const userId = ctx.user.id;
return await userService.getCompletedSteps(ctx.db.prisma, userId);
}),

// update user's completed steps
updateCompletedSteps: protectedProcedure
.input(
z.object({
completedSteps: z.array(z.string()),
})
)
.mutation(async ({ ctx, input }: any) => {
const userId = ctx.user.id;
return await userService.updateCompletedSteps(
ctx.db.prisma,
userId,
input.completedSteps
);
}),
});
39 changes: 39 additions & 0 deletions apps/api/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,43 @@ export const userService = {
: null,
};
},

/**
* Get user's completed steps
*/
async getCompletedSteps(
prisma: ExtendedPrismaClient | PrismaClient,
userId: string
) {
const user = await prisma.user.findUnique({
where: { id: userId },
select: { completedSteps: true },
});

if (!user) {
throw new Error("User not found");
}

const completedSteps = user.completedSteps as string[] | null;
return completedSteps || [];
},

/**
* Update user's completed steps
*/
async updateCompletedSteps(
prisma: ExtendedPrismaClient | PrismaClient,
userId: string,
completedSteps: string[]
) {
const user = await prisma.user.update({
where: { id: userId },
data: {
completedSteps: completedSteps,
},
select: { completedSteps: true },
});

return (user.completedSteps as string[]) || [];
},
};
4 changes: 4 additions & 0 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const nextConfig = {
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
},
],
},
};
Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@vercel/speed-insights": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dompurify": "^3.3.0",
"framer-motion": "^11.15.0",
"geist": "^1.5.1",
"lucide-react": "^0.456.0",
Expand All @@ -41,6 +42,7 @@
"zustand": "^5.0.1"
},
"devDependencies": {
"@types/dompurify": "^3.2.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file added apps/web/public/images/dm.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/doc.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/lv-1.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/module-1.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/ptm.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/sheet-1.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/images/sheet-2.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 15 additions & 15 deletions apps/web/src/app/(main)/(landing)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ const opensoxFeatures = [
const whySub = [
{
content:
"Currently, Opensox 2.0 is in progress (70% done) so till the launch, we are offering premium plan at a discounted price - $49 for the whole year",
"Currently, Opensox 2.0 is in progress (70% done) so till the launch, we are offering Pro plan at a discounted price - $49 for the whole year",
},
{
content:
"This offer is only available for the first 1000 (20 slots booked) users",
},
{
content:
"After the launch, this $49 offer be removed and Opensox premium will be around ~ $120 for whole year ($10/mo.)",
"After the launch, this $49 offer be removed and Opensox Pro will be around ~ $120 for whole year ($10/mo.)",
},
{
content: "The price of the dollar is constantly increasing.",
Expand All @@ -93,17 +93,17 @@ const premiumPlanCard = {
"1:1 session on finding remote jobs and internships in open-source companies.",
"Quick doubts resolution.",
"Personalized guidance for GSoC, LFX, Outreachy, etc",
"Access to premium Slack where you can ask anything anytime.",
"Access to Pro Slack where you can ask anything anytime.",
"Support to enhance skills for open source",
"GSOC proposal, resume reviews, etc.",
"Upcoming premium features",
"Upcoming Pro features",
],
whatYouGetAfterLaunch: [
"Everything mentioned above",
"Advanced tool with premium filters to find open source projects",
"Premium newsletter",
"Advanced tool with Pro filters to find open source projects",
"Pro newsletter",
"30 days opensox challenge sheet",
"Upcoming premium features.",
"Upcoming Pro features.",
],
};

Expand Down Expand Up @@ -202,7 +202,7 @@ const Pricing = () => {
}}
className="text-center text-3xl tracking-tight font-medium"
>
Why should you subscribe to Opensox premium now?
Why should you subscribe to Opensox Pro now?
</motion.h2>
</div>
<div className="w-full border-b border-[#252525]">
Expand Down Expand Up @@ -377,7 +377,7 @@ const SecondaryPricingCard = () => {
<div className="w-full border-dashed border-border-primary px-6 lg:px-10 py-4 ">
<PaymentFlow
planId={planIdOk ? premiumPlanId : ""}
planName="Opensox Premium"
planName="Opensox Pro"
description="Annual Subscription"
buttonText={planIdOk ? "Invest" : "Unavailable"}
buttonClassName={`w-full max-w-[500px] mx-auto font-semibold ${
Expand Down Expand Up @@ -432,7 +432,7 @@ const PremiumTestimonialCard = ({
<p className="text-xl">{username}</p>
{showPremium && (
<div className="bg-gradient-to-b from-[#ad84e7] via-[#986cd6] to-[#432d8e] bg-clip-text text-transparent">
<p className="">Opensox Premium</p>
<p className="">Opensox Pro</p>
</div>
)}
</div>
Expand All @@ -445,14 +445,14 @@ const TestimonialsSection = () => {
id: 1,
username: "Tarun Parmar",
content:
"Getting the Opensox Premium Subscription has been such a game-changer for me. I really like the personal touch in the way the team guides you-it feels like someone is genuinely there to help you navigate. It gave me the initial push I needed and made it so much easier to cut through all the chaos and focus on the right and simple steps. The best part is, it helps you start your open source journey quickly and I know I can reach out to the team anytime. Honestly, it's been an awesome experience so far!",
"Getting the Opensox Pro Subscription has been such a game-changer for me. I really like the personal touch in the way the team guides you-it feels like someone is genuinely there to help you navigate. It gave me the initial push I needed and made it so much easier to cut through all the chaos and focus on the right and simple steps. The best part is, it helps you start your open source journey quickly and I know I can reach out to the team anytime. Honestly, it's been an awesome experience so far!",
column: 1,
},
{
id: 2,
username: "Daksh Yadav",
content:
"My experience with your guidance and opensox has been great. Your tips have really helped in doing my tasks quicker and better. And I would definitely recommend others to opt for opensox premium.",
"My experience with your guidance and opensox has been great. Your tips have really helped in doing my tasks quicker and better. And I would definitely recommend others to opt for opensox Pro.",
column: 1,
},
{
Expand All @@ -462,7 +462,7 @@ const TestimonialsSection = () => {
<div className="space-y-3 text-pretty">
<p>
Okay so there are a few things I genuinely value about OpenSox
Premium, and I&apos;ll focus on the core points because everything
Pro, and I&apos;ll focus on the core points because everything
else is just a natural extension of these.
</p>
<ul className="list-disc space-y-3 pl-6">
Expand Down Expand Up @@ -496,7 +496,7 @@ const TestimonialsSection = () => {
right direction.
</li>
<li>
Overall, I&apos;d absolutely recommend OpenSox Premium to anyone
Overall, I&apos;d absolutely recommend OpenSox Pro to anyone
serious about open source. The personalized guidance is exactly
what most of us hope for, since everyone is at a different stage
of their journey.
Expand Down Expand Up @@ -534,7 +534,7 @@ const TestimonialsSection = () => {

return (
<div className=" text-white ">
<Header title="What our Premium customers say about us" />
<Header title="What our Pro customers say about us" />
<div className="border-b border-[#252525] w-full min-h-[80dvh] grid grid-cols-1 lg:grid-cols-7">
<div className="lg:col-span-2 flex flex-col font-medium divide-y divide-[#252525]">
{groupedTestimonials[1].map((testimonial) => (
Expand Down
64 changes: 64 additions & 0 deletions apps/web/src/app/(main)/dashboard/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import { useSubscription } from "@/hooks/useSubscription";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";

export default function AccountPage() {
const { isPaidUser, isLoading } = useSubscription();

const plan = isPaidUser ? "Pro" : "Free";

return (
<div className="w-full h-full flex flex-col p-6 bg-ox-content">
{isLoading ? (
<div className="flex items-center justify-center h-full">
<span className="text-zinc-400">Loading...</span>
</div>
) : (
<>
<div className="mb-6">
<Link
href="/dashboard/home"
className="inline-flex items-center gap-2 text-ox-purple hover:text-ox-purple-2 transition-colors mb-4"
>
<ArrowLeft className="h-4 w-4" />
<span>Back to Dashboard</span>
</Link>
<h1 className="text-2xl md:text-3xl font-semibold text-white">
Account Settings
</h1>
</div>

<div className="bg-ox-sidebar border border-ox-header rounded-lg p-6 max-w-2xl">
<div className="space-y-4">
<div>
<label className="text-sm text-zinc-400 mb-2 block">Plan</label>
<div className="flex items-center gap-2">
<span className="text-lg font-semibold text-white">
{plan}
</span>
{isPaidUser && (
<span className="px-2 py-0.5 rounded-full bg-ox-purple/20 border border-ox-purple text-ox-purple text-xs font-medium">
Active
</span>
)}
</div>
</div>
{!isPaidUser && (
<div>
<Link
href="/pricing"
className="inline-flex items-center justify-center px-3 py-1.5 bg-ox-purple hover:bg-ox-purple-2 text-white rounded-md transition-colors text-xs font-medium"
>
be a pro
</Link>
</div>
)}
</div>
</div>
</>
)}
</div>
);
}
13 changes: 8 additions & 5 deletions apps/web/src/app/(main)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useFilterStore } from "@/store/useFilterStore";
import { useShowSidebar } from "@/store/useShowSidebar";
import { IconWrapper } from "@/components/ui/IconWrapper";
import { Bars3Icon } from "@heroicons/react/24/outline";
import Link from "next/link";

export default function DashboardLayout({
children,
Expand All @@ -14,19 +15,21 @@ export default function DashboardLayout({
const { showFilters } = useFilterStore();
const { showSidebar, setShowSidebar } = useShowSidebar();
return (
<div className="flex w-screen h-screen bg-[#0a0a0b] overflow-hidden">
<div className="flex w-screen h-screen bg-ox-content overflow-hidden">
{showFilters && <FiltersContainer />}
<aside className={`h-full ${!showSidebar && "hidden xl:block"}`}>
<Sidebar />
</aside>
<div className="flex-1 flex flex-col h-full">
<div className="xl:hidden flex items-center h-16 px-4 border-b border-[#1a1a1d]">
<div className="flex-1 flex flex-col h-full bg-ox-content">
<div className="xl:hidden flex items-center h-16 px-4 border-b border-ox-header bg-ox-content">
<IconWrapper onClick={() => setShowSidebar(true)}>
<Bars3Icon className="size-5 text-ox-purple" />
</IconWrapper>
<h1 className="ml-4 text-lg font-semibold text-ox-white">Opensox</h1>
<Link href="/" className="ml-4 text-lg font-semibold text-ox-white hover:text-ox-purple transition-colors">
Opensox
</Link>
</div>
<main className="flex-1 h-full overflow-auto">
<main className="flex-1 h-full overflow-auto bg-ox-content">
{children}
</main>
</div>
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useSubscription } from "@/hooks/useSubscription";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function ProDashboardPage() {
const { isPaidUser, isLoading } = useSubscription();
const router = useRouter();

useEffect(() => {
if (!isLoading && !isPaidUser) {
router.push("/pricing");
}
}, [isPaidUser, isLoading, router]);

if (isLoading) {
return (
<div className="w-full h-full flex items-center justify-center bg-ox-content">
<p className="text-white">Loading...</p>
</div>
);
}

if (!isPaidUser) {
return null;
}

return (
<div className="w-full h-full flex items-center justify-center bg-ox-content p-6">
<div className="max-w-2xl mx-auto text-center">
<h1 className="text-2xl md:text-3xl font-semibold text-white mb-4">
hi investors, ajeetunc is on the way to deliver the shareholder value.
soon you&apos;ll see all the pro perks here. thanks for investing
</h1>
</div>
</div>
);
}
Loading