Skip to content
Open
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
17 changes: 14 additions & 3 deletions apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useSubscription } from "@/hooks/useSubscription";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { Play } from "lucide-react";

export default function ProDashboardPage() {
const { isPaidUser, isLoading } = useSubscription();
Expand Down Expand Up @@ -109,17 +111,26 @@ export default function ProDashboardPage() {
soon you'll see all the pro perks here. thanks for investin!
</h1>
{isPaidUser && (
<div className="mt-6">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-center gap-3">
<Link
href="/dashboard/pro/sessions"
className="inline-flex items-center gap-2 px-4 py-2 bg-brand-purple hover:bg-brand-purple-light text-text-primary font-medium rounded-lg transition-colors duration-200 text-sm"
>
<Play className="w-4 h-4" />
Pro Sessions
</Link>
<button
onClick={handleJoinSlack}
disabled={isJoining}
className="px-4 py-2 bg-brand-purple hover:bg-brand-purple-light text-text-primary font-medium rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
className="px-4 py-2 bg-dash-surface border border-dash-border hover:border-brand-purple/50 text-text-primary font-medium rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
>
{isJoining ? "Joining..." : "Join Slack"}
</button>
{error && <p className="text-error-text text-sm mt-2">{error}</p>}
</div>
)}
{error && (
<p className="text-error-text text-sm mt-2 text-center">{error}</p>
)}
</div>
</div>
);
Expand Down
194 changes: 194 additions & 0 deletions apps/web/src/app/(main)/dashboard/pro/sessions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"use client";

import { useEffect, useState } from "react";

import { ArrowLeft, CheckCircle2, ExternalLink, Play } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";

import { useSubscription } from "@/hooks/useSubscription";
import { proSessions, type ProSession } from "@/data/pro-sessions";
Comment on lines +9 to +10
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pro session data should be protected server-side, not exposed in client bundle.

Currently, proSessions is imported from a static data file, which means the session data (titles, YouTube URLs, topics) is embedded in the client-side JavaScript bundle. Non-paid users can extract this data by inspecting the bundle, bypassing the subscription gate.

As noted in the PR objectives, the reviewer apsinghdev recommended storing sessions in a database and serving them via an authenticated API endpoint. This would ensure only paid users can access the session data.

Recommended approach:

  1. Store sessions in the database (using the Prisma schema suggested in PR comments)
  2. Create a protected tRPC endpoint to fetch sessions
  3. Fetch sessions client-side after subscription verification
  4. Handle loading/error states appropriately

Would you like me to generate a shell script to verify if the session data is exposed in the production bundle, or help draft the database schema and tRPC endpoint implementation?

Also applies to: 177-180

🤖 Prompt for AI Agents
In apps/web/src/app/(main)/dashboard/pro/sessions/page.tsx around lines 9-10
(and similarly lines 177-180), the current import of proSessions embeds
protected session data into the client bundle; remove the static import and
instead persist sessions in the database (using the suggested Prisma schema),
implement a protected tRPC endpoint that returns sessions only for
authenticated/paid users, call that endpoint from the client after verifying the
user's subscription (use the existing useSubscription hook or server-side
session check), and implement proper loading and error states; ensure the page
no longer includes the proSessions static file so sensitive titles/YouTube URLs
are not shipped to the client.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bruhhh

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huamanraj I know, I know! 😅 Security issues can be a pain to refactor.

If you'd like, I can help generate the Prisma schema and tRPC endpoint setup to move this to the database. Just let me know! Otherwise, feel free to ping me if you have questions while implementing it.


🧠 Learnings used
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Extract reusable logic into custom hooks

Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Extract reusable logic into custom hooks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client[procedureType] is not a function do you know why this issue ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@huamanraj Let me check your tRPC setup to diagnose the issue.


🏁 Script executed:

#!/bin/bash

# Find tRPC router definitions
echo "=== tRPC Router Definitions ==="
rg -n -A5 "createTRPCRouter|router\(" --type=ts -g '!node_modules/**' -g '!.next/**'

echo -e "\n=== tRPC Client Setup/Initialization ==="
rg -n -A5 "createTRPCClient|createTRPCNext|api\." --type=ts -g '!node_modules/**' -g '!.next/**' | head -100

echo -e "\n=== tRPC Context Setup ==="
rg -n -A3 "createTRPCContext|createContext" --type=ts -g '!node_modules/**' -g '!.next/**'

echo -e "\n=== Recent trpc imports and usage ==="
fd -e ts -e tsx "trpc" --type f -x cat {}

echo -e "\n=== Looking for procedureType usage ==="
rg -n "procedureType" --type=ts -g '!node_modules/**' -g '!.next/**'

Length of output: 7957


@huamanraj I can see your tRPC setup. The error client[procedureType] is not a function typically happens when you try to access tRPC procedures dynamically using bracket notation, which doesn't work with the React Query client.

Can you share the code where you're getting this error? It will help me give you a precise fix.

In the meantime, here are common mistakes and correct patterns:

❌ Incorrect (causes the error):

// This doesn't work with createTRPCReact
const procedureType = 'query';
const result = trpc.sessions.getAll[procedureType]();

✅ Correct patterns from your codebase:

For queries (fetching data):

// Client component
const { data, isLoading } = trpc.sessions.getAll.useQuery();

For mutations (creating/updating):

// Client component
const mutation = trpc.sessions.create.useMutation({
  onSuccess: () => {
    // handle success
  }
});

mutation.mutate({ title: "Session 1" });

Your router structure should look like:

// apps/api/src/routers/sessions.ts
export const sessionsRouter = router({
  getAll: protectedProcedure.query(async ({ ctx }) => {
    // return sessions from DB
  }),
  create: protectedProcedure
    .input(z.object({ /* schema */ }))
    .mutation(async ({ input, ctx }) => {
      // create session
    }),
});

Then add it to your app router:

// apps/api/src/routers/_app.ts
export const appRouter = router({
  // ... existing routers
  sessions: sessionsRouter, // add this
});

Show me your code and I'll help fix it! 🚀


🧠 Learnings used
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Extract reusable logic into custom hooks

Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Extract reusable logic into custom hooks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everywhere i am getting this error

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Checking tRPC Package Versions ==="
cat package.json | jq '.dependencies + .devDependencies | to_entries | map(select(.key | contains("trpc"))) | from_entries'

echo -e "\n=== tRPC Client Initialization ==="
fd "trpc.ts" --type f -x echo "File: {}" \; -x cat {}

echo -e "\n=== tRPC Provider Setup ==="
rg -n "TRPCReactProvider|trpc.Provider" --type=tsx --type=ts -C3

echo -e "\n=== Root Layout Provider Wrapping ==="
cat apps/web/src/app/layout.tsx 2>/dev/null || echo "Layout file not found"

echo -e "\n=== API Export and Type Export ==="
rg -n "export.*api|export type AppRouter" --type=ts -g '**/trpc.ts' -g '**/api/src/**' -C2

echo -e "\n=== Recent Git Changes to tRPC Files ==="
git log --oneline --name-only -10 -- "**/trpc.ts" "**/api.ts" "**/*router*" | head -50

echo -e "\n=== Checking for Dynamic Procedure Access ==="
rg -n "\[procedureType\]|\[.*Type\]" --type=ts --type=tsx -C2 -g '!node_modules/**'

Length of output: 5972


🏁 Script executed:

#!/bin/bash

echo "=== TRPCProvider Implementation ==="
cat apps/web/src/providers/trpc-provider.tsx 2>/dev/null || cat apps/web/src/providers/trpc-provider.ts 2>/dev/null || echo "Provider file not found in providers/"

echo -e "\n=== Check all provider files ==="
fd "trpc" --type f -e tsx -e ts apps/web/src -x echo "File: {}" \; -x cat {}

echo -e "\n=== Apps Web Package.json Dependencies ==="
cat apps/web/package.json | jq '{dependencies, devDependencies}'

echo -e "\n=== Apps API Package.json Dependencies ==="
cat apps/api/package.json | jq '{dependencies, devDependencies}'

echo -e "\n=== How trpc is being used in components ==="
rg -n "trpc\." --type=ts -g 'apps/web/**' -C2 | head -100

Length of output: 11925


🏁 Script executed:

#!/bin/bash

echo "=== Check the appRouter definition ==="
cat apps/api/src/routers/_app.ts

echo -e "\n=== Check what routers exist ==="
fd ".*\.ts$" apps/api/src/routers --type f -x echo "File: {}" \; -x head -20 {}

Length of output: 4748


@huamanraj I found the issue! You have a version mismatch between your tRPC packages that's causing the error everywhere.

The Problem

Your packages are out of sync:

  • Web app (apps/web): @trpc/server@^11.5.1, @trpc/client@^11.6.0, @trpc/react-query@^11.6.0
  • API (apps/api): @trpc/server@^11.7.2

This version mismatch breaks the tRPC protocol communication between client and server, causing the client[procedureType] is not a function error.

The Fix

Align all tRPC packages to the same version. Run these commands from your project root:

# Update web app packages
cd apps/web
npm install @trpc/client@latest @trpc/react-query@latest @trpc/server@latest

# Update API packages  
cd ../api
npm install @trpc/server@latest

# Go back to root and reinstall everything
cd ../..
npm install

Additional Cleanup

After fixing versions, remove all the as any type casts in your code. They were masking the type errors caused by the version mismatch. Here are the files to clean up:

  1. apps/web/src/hooks/useSubscription.ts (Line 28)

    // ❌ Remove this:
    } = (trpc.user as any).subscriptionStatus.useQuery(undefined, {
    
    // ✅ Replace with:
    } = trpc.user.subscriptionStatus.useQuery(undefined, {
  2. apps/web/src/components/payment/PaymentFlow.tsx (Lines 59, 61)

    // ❌ Remove:
    const createOrderMutation = (trpc.payment as any).createOrder.useMutation();
    const verifyPaymentMutation = (trpc.payment as any).verifyPayment.useMutation();
    
    // ✅ Replace with:
    const createOrderMutation = trpc.payment.createOrder.useMutation();
    const verifyPaymentMutation = trpc.payment.verifyPayment.useMutation();
  3. apps/web/src/app/(main)/dashboard/sheet/page.tsx (Lines 163-170)

    // ❌ Remove type assertions and just use directly:
    const { data: completedSteps } = trpc.user.getCompletedSteps.useQuery(undefined);
    const updateCompletedSteps = trpc.user.updateCompletedSteps.useMutation({
      onSuccess: () => {
        utils.user.getCompletedSteps.invalidate();
      }
    });
  4. apps/web/src/app/api/newsletters/route.ts and apps/web/src/app/api/newsletters/[slug]/route.ts

    // ❌ Remove:
    const subscriptionStatus = await (trpc.user as any).subscriptionStatus.query();
    
    // ✅ Replace with:
    const subscriptionStatus = await trpc.user.subscriptionStatus.query();

Verify the Fix

After updating and removing the type casts, restart your dev servers and the error should be gone! 🎉

Let me know if you still see any issues after this!


🧠 Learnings used
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Extract reusable logic into custom hooks

Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Extract reusable logic into custom hooks


const SessionCard = ({
session,
index,
}: {
session: ProSession;
index: number;
}): JSX.Element | null => {
const [isHovered, setIsHovered] = useState(false);

const handleClick = () => {
window.open(session.youtubeUrl, "_blank", "noopener,noreferrer");
};

return (
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleClick();
}
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
aria-label={`Watch session: ${session.title}`}
className="group relative bg-dash-surface border border-dash-border rounded-xl p-5 cursor-pointer
transition-all duration-300 ease-out
hover:border-brand-purple/50 hover:bg-dash-hover hover:shadow-lg hover:shadow-brand-purple/5
hover:-translate-y-1 active:scale-[0.98]
focus-visible:ring-2 focus-visible:ring-brand-purple/50 focus-visible:outline-none"
style={{
animationDelay: `${index * 50}ms`,
}}
>
{/* Session number badge */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div
className="flex items-center justify-center w-10 h-10 rounded-lg bg-brand-purple/10
group-hover:bg-brand-purple/20 transition-colors duration-300"
>
<span className="text-brand-purple font-bold text-sm">
{String(session.id).padStart(2, "0")}
</span>
</div>
<h3 className="text-text-primary font-semibold text-lg group-hover:text-brand-purple-light transition-colors duration-300">
{session.title}
</h3>
</div>
<div
className={`flex items-center justify-center w-9 h-9 rounded-full
bg-brand-purple/10 group-hover:bg-brand-purple transition-all duration-300
${isHovered ? "scale-110" : "scale-100"}`}
>
<Play
className={`w-4 h-4 transition-all duration-300 ${
isHovered
? "text-white translate-x-0.5"
: "text-brand-purple-light"
}`}
fill={isHovered ? "currentColor" : "none"}
/>
</div>
</div>

{/* Topics covered */}
<div className="space-y-2.5 mb-4">
<p className="text-text-muted text-xs uppercase tracking-wider font-medium">
Topics Covered
</p>
<ul className="space-y-2">
{session.topicsCovered.map((topic, topicIndex) => (
<li
key={topicIndex}
className="flex items-start gap-2.5 text-text-secondary text-sm"
>
<CheckCircle2
className="w-4 h-4 text-brand-purple/70 mt-0.5 flex-shrink-0
group-hover:text-brand-purple transition-colors duration-300"
/>
<span className="group-hover:text-text-primary transition-colors duration-300">
{topic}
</span>
</li>
))}
</ul>
</div>

{/* Watch now indicator */}
<div
className="flex items-center gap-2 text-text-muted text-xs
group-hover:text-brand-purple-light transition-colors duration-300"
>
<ExternalLink className="w-3.5 h-3.5" />
<span>Watch on YouTube</span>
</div>

{/* Hover glow effect */}
<div
className="absolute inset-0 rounded-xl opacity-0 group-hover:opacity-100
transition-opacity duration-500 pointer-events-none"
style={{
background:
"radial-gradient(ellipse at center, rgb(85 25 247 / 0.05) 0%, transparent 70%)",
}}
/>
</div>
);
};

const ProSessionsPage = (): JSX.Element | null => {
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">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 border-2 border-brand-purple border-t-transparent rounded-full animate-spin" />
<p className="text-text-secondary text-sm">Loading sessions...</p>
</div>
</div>
);
}

if (!isPaidUser) {
return null;
}

return (
<div className="w-full min-h-full bg-ox-content">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12">
{/* Header */}
<div className="mb-8 md:mb-12">
{/* Back link */}
<Link
href="/dashboard/pro/dashboard"
className="inline-flex items-center gap-2 text-text-muted hover:text-brand-purple-light
transition-colors duration-200 mb-6 group"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform duration-200" />
<span className="text-sm">Back to Pro Dashboard</span>
</Link>

<div className="flex items-center gap-3 mb-4">
<h1 className="text-2xl md:text-3xl font-bold text-text-primary">
Opensox Pro Sessions
</h1>
</div>
<p className="text-text-secondary text-base md:text-lg max-w-2xl">
Recordings of Opensox Pro session meetings covering advanced open
source strategies, real-world examples, and insider tips to
accelerate your journey.
</p>
</div>

{/* Sessions Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
{proSessions.map((session, index) => (
<SessionCard key={session.id} session={session} index={index} />
))}
</div>

{/* Footer note */}
<div className="mt-12 text-center">
<p className="text-text-muted text-sm">
More sessions coming soon • Stay tuned for updates
</p>
</div>
</div>
</div>
);
};

export default ProSessionsPage;
112 changes: 112 additions & 0 deletions apps/web/src/data/pro-sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Pro Sessions Data
*
* Contains all the Opensox Pro Session YouTube videos with their topics
*/

export interface ProSession {
id: number;
title: string;
youtubeUrl: string;
topicsCovered: string[];
duration?: string;
}

export const proSessions: ProSession[] = [
{
id: 1,
title: "Pro Session 01",
youtubeUrl: "https://www.youtube.com/watch?v=nZOLgP2P8aQ",
topicsCovered: [
"Introduction to Open Source Contributions",
"Setting up your development environment",
"Understanding Git workflow basics",
],
},
{
id: 2,
title: "Pro Session 02",
youtubeUrl: "https://www.youtube.com/watch?v=fp6fiTce-fI",
topicsCovered: [
"Finding your first issue",
"Reading project documentation effectively",
"Communicating with maintainers",
],
},
{
id: 3,
title: "Pro Session 03",
youtubeUrl: "https://www.youtube.com/watch?v=nZOLgP2P8aQ",
topicsCovered: [
"Writing clean pull requests",
"Code review best practices",
"Handling feedback gracefully",
],
},
{
id: 4,
title: "Pro Session 04",
youtubeUrl: "https://www.youtube.com/watch?v=fp6fiTce-fI",
topicsCovered: [
"Building your Open Source portfolio",
"Showcasing contributions on GitHub",
"Networking in the OSS community",
],
},
{
id: 5,
title: "Pro Session 05",
youtubeUrl: "https://www.youtube.com/watch?v=nZOLgP2P8aQ",
topicsCovered: [
"Advanced Git techniques",
"Rebasing and resolving conflicts",
"Cherry-picking commits",
],
},
{
id: 6,
title: "Pro Session 06",
youtubeUrl: "https://www.youtube.com/watch?v=fp6fiTce-fI",
topicsCovered: [
"Understanding CI/CD pipelines",
"Writing effective tests",
"Debugging failed builds",
],
},
{
id: 7,
title: "Pro Session 07",
youtubeUrl: "https://www.youtube.com/watch?v=nZOLgP2P8aQ",
topicsCovered: [
"Contributing to documentation",
"Writing technical content",
"Documentation as code",
],
},
{
id: 8,
title: "Pro Session 08",
youtubeUrl: "https://www.youtube.com/watch?v=fp6fiTce-fI",
topicsCovered: [
"Preparing for GSoC",
"Writing winning proposals",
"Building relationships with mentors",
],
},
{
id: 9,
title: "Pro Session 09",
youtubeUrl: "https://www.youtube.com/watch?v=nZOLgP2P8aQ",
topicsCovered: [
"Landing your first OSS internship",
"Resume tips for developers",
"Leveraging OSS for career growth",
"Landing your first OSS internship",
"Resume tips for developers",
"Leveraging OSS for career growth",
"Landing your first OSS internship",
"Resume tips for developers",
"Leveraging OSS for career growth",
],
},
];