-
Notifications
You must be signed in to change notification settings - Fork 180
[Feat] all pro sessions at one place #296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
huamanraj
wants to merge
15
commits into
apsinghdev:main
Choose a base branch
from
huamanraj:feat/pro-sessions-hub
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
2be3239
feat: posthog event tracting for analytics
huamanraj 58cd726
fix: sanitizing the callback URL
huamanraj 956a8b1
chore: add analytics for signin
apsinghdev 4c12ccc
chore: update the msg
apsinghdev 428193a
perf: optimize Hero animations following best practices
ketankauntia 4a9f2ad
feat: added gsoc orgs + tested it
ketankauntia 08b471c
chore: fixes + capitalization consistency of words
ketankauntia ebb397c
chore: add newsletter 2
apsinghdev f0d0391
chore: delete the inc file
apsinghdev b502122
chore: delete useless file
apsinghdev 35bce3f
Merge branch 'apsinghdev:main' into main
huamanraj 88b5a1b
feat: Pro Sessions page
huamanraj b827703
fix: made divs keyboard accessible
huamanraj e5c4ab8
fix: ts errors
huamanraj 9bcbfed
Merge branch 'apsinghdev:main' into feat/pro-sessions-hub
huamanraj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
apps/web/src/app/(main)/dashboard/pro/sessions/page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"; | ||
|
|
||
| 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; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| ], | ||
huamanraj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
huamanraj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ]; | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pro session data should be protected server-side, not exposed in client bundle.
Currently,
proSessionsis 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:
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bruhhh
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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:
Length of output: 7957
@huamanraj I can see your tRPC setup. The error
client[procedureType] is not a functiontypically 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):
✅ Correct patterns from your codebase:
For queries (fetching data):
For mutations (creating/updating):
Your router structure should look like:
Then add it to your app router:
Show me your code and I'll help fix it! 🚀
🧠 Learnings used
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 5972
🏁 Script executed:
Length of output: 11925
🏁 Script executed:
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:
apps/web):@trpc/server@^11.5.1,@trpc/client@^11.6.0,@trpc/react-query@^11.6.0apps/api):@trpc/server@^11.7.2This version mismatch breaks the tRPC protocol communication between client and server, causing the
client[procedureType] is not a functionerror.The Fix
Align all tRPC packages to the same version. Run these commands from your project root:
Additional Cleanup
After fixing versions, remove all the
as anytype casts in your code. They were masking the type errors caused by the version mismatch. Here are the files to clean up:apps/web/src/hooks/useSubscription.ts(Line 28)apps/web/src/components/payment/PaymentFlow.tsx(Lines 59, 61)apps/web/src/app/(main)/dashboard/sheet/page.tsx(Lines 163-170)apps/web/src/app/api/newsletters/route.tsandapps/web/src/app/api/newsletters/[slug]/route.tsVerify 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