From eeb72f41d132e1b4e95e286126de2d026180f3b1 Mon Sep 17 00:00:00 2001 From: Kai Kim Date: Mon, 1 Sep 2025 13:55:45 -0400 Subject: [PATCH 01/37] Added the courses UI from the old website design to the new website, updated hover animations for courses cards --- src/app/courses/page.tsx | 262 ++++++++++++++++++++++++++++++--------- src/lib/courses.ts | 168 +++++++++++++++++++++++++ 2 files changed, 373 insertions(+), 57 deletions(-) create mode 100644 src/lib/courses.ts diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 09523b7..3933af5 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -1,10 +1,18 @@ "use client"; -import { useEffect } from "react"; +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Users, BookOpen, Video, Globe, Zap, Award, Star, Clock } from "lucide-react"; +import Link from "next/link"; +import { getAllCourses, type Course } from "@/lib/courses"; import AOS from "aos"; import "aos/dist/aos.css"; -export default function ComingSoonPage() { +export default function CoursesPage() { + const [courses, setCourses] = useState([]); + useEffect(() => { AOS.init({ duration: 1000, @@ -13,70 +21,210 @@ export default function ComingSoonPage() { offset: 50, delay: 0, }); + loadCourses(); }, []); + const loadCourses = async () => { + try { + const coursesData = await getAllCourses(); + setCourses(coursesData); + } catch (error) { + console.error("Failed to load courses:", error); + } + }; + return ( -
-
-
-
- {/* Left Section - Mission Statement */} -
-
-

- Coming Soon -

-

- We're working hard to bring you something amazing. Our - team is putting the finishing touches on this page to ensure - we deliver the best possible experience. Check back soon to - see what we've been working on! -

+
+
+
+

+ Explore Our Courses & Boot Camps +

+

+ Learn tech skills live on Zoom with expert mentors and volunteers. +

+ +
+
+
+
+ + Expert Mentors +
+
+ + Hands-on Learning +
+
+
+
+ +
+
+
+

Why Join?

+
+ +
+
+
+
+

Accessible Learning

- {/* Right Section - Placeholder Content */} -
-
-
-
- - - -
-

- Something Great is Coming -

-

- We're preparing something special for you. Stay tuned - for updates! -

-
- -
-

- "The best things in life are worth waiting for" -

-
+
+
+ +
+

Real Mentorship

+
+ +
+
+
+

Practical Skills

+ +
+
+ +
+

Community Impact

+
+
+
+
+ +
+
+
+

Available Courses & Boot Camps

+

+ Interactive, mentor-led courses designed to build practical skills and empower communities. +

+
+ + {/* Course Grid */} +
+ {courses.map((course, index) => ( + + +
+ + +
+ + {course.rating} + ({course.reviews}) +
+
+ {course.title} +

{course.description}

+
+ + +
+
+ + {course.duration} +
+
+ + + {course.students.toLocaleString()} students + +
+
+ + {course.level} +
+
+ +
+
+
{course.price}
+
{course.originalPrice}
+
+
+ {course.schedule.format} • {course.schedule.commitment} +
+
+
+ + +
+ + + + + + +
+
+
+ ))} +
+
+
+ +
+
+

Transform Your Future Today

+

+ Join thousands of successful graduates who have launched their tech careers with ReEnvision. Start your + journey today and unlock endless opportunities in technology. +

+
+ +
-
+
); -} +} \ No newline at end of file diff --git a/src/lib/courses.ts b/src/lib/courses.ts new file mode 100644 index 0000000..31ef5a4 --- /dev/null +++ b/src/lib/courses.ts @@ -0,0 +1,168 @@ +export interface Course { + id: string; + title: string; + description: string; + duration: string; + level: string; + price: string; + originalPrice: string; + rating: number; + reviews: number; + students: number; + schedule: { + format: string; + commitment: string; + }; +} + +export const getAllCourses = async (): Promise => { + // Simulate the API call delay + await new Promise(resolve => setTimeout(resolve, 100)); + + return [ + { + id: "web-development-bootcamp", + title: "Full-Stack Web Development Bootcamp", + description: "Learn modern web development with HTML, CSS, JavaScript, React, and Node.js. Build real-world projects and get job-ready skills.", + duration: "12 weeks", + level: "Beginner to Intermediate", + price: "Free", + originalPrice: "$2,499", + rating: 4.8, + reviews: 1247, + students: 15420, + schedule: { + format: "Live Online", + commitment: "20 hours/week" + } + }, + { + id: "python-data-science", + title: "Python for Data Science & Analytics", + description: "Master Python programming for data analysis, visualization, and machine learning. Work with real datasets and build predictive models.", + duration: "10 weeks", + level: "Beginner", + price: "Free", + originalPrice: "$1,899", + rating: 4.9, + reviews: 892, + students: 12350, + schedule: { + format: "Live Online", + commitment: "15 hours/week" + } + }, + { + id: "digital-marketing", + title: "Digital Marketing & Social Media", + description: "Learn digital marketing strategies, SEO, social media management, and content creation. Build your personal brand and grow businesses online.", + duration: "8 weeks", + level: "Beginner", + price: "Free", + originalPrice: "$1,299", + rating: 4.7, + reviews: 634, + students: 8920, + schedule: { + format: "Live Online", + commitment: "12 hours/week" + } + }, + { + id: "cybersecurity-fundamentals", + title: "Cybersecurity Fundamentals", + description: "Learn essential cybersecurity concepts, threat detection, and protection strategies. Understand how to secure networks and data.", + duration: "6 weeks", + level: "Beginner", + price: "Free", + originalPrice: "$1,599", + rating: 4.6, + reviews: 445, + students: 6780, + schedule: { + format: "Live Online", + commitment: "10 hours/week" + } + }, + { + id: "mobile-app-development", + title: "Mobile App Development with React Native", + description: "Build cross-platform mobile apps using React Native. Learn to create iOS and Android apps with a single codebase.", + duration: "10 weeks", + level: "Intermediate", + price: "Free", + originalPrice: "$2,199", + rating: 4.8, + reviews: 567, + students: 7890, + schedule: { + format: "Live Online", + commitment: "18 hours/week" + } + }, + { + id: "ui-ux-design", + title: "UI/UX Design & Prototyping", + description: "Learn user interface and user experience design principles. Create beautiful, functional designs using modern tools like Figma.", + duration: "8 weeks", + level: "Beginner", + price: "Free", + originalPrice: "$1,799", + rating: 4.7, + reviews: 723, + students: 9560, + schedule: { + format: "Live Online", + commitment: "14 hours/week" + } + }, + { + id: "cloud-computing-aws", + title: "Cloud Computing with AWS", + description: "Master Amazon Web Services (AWS) cloud platform. Learn to deploy, manage, and scale applications in the cloud.", + duration: "12 weeks", + level: "Intermediate", + price: "Free", + originalPrice: "$2,799", + rating: 4.9, + reviews: 389, + students: 5430, + schedule: { + format: "Live Online", + commitment: "16 hours/week" + } + }, + { + id: "blockchain-basics", + title: "Blockchain & Cryptocurrency Basics", + description: "Understand blockchain technology, smart contracts, and cryptocurrency. Learn about DeFi, NFTs, and the future of digital finance.", + duration: "6 weeks", + level: "Beginner", + price: "Free", + originalPrice: "$1,499", + rating: 4.5, + reviews: 298, + students: 4120, + schedule: { + format: "Live Online", + commitment: "8 hours/week" + } + }, + { + id: "artificial-intelligence", + title: "Introduction to Artificial Intelligence", + description: "Explore AI concepts, machine learning algorithms, and neural networks. Build your first AI models and understand the future of technology.", + duration: "10 weeks", + level: "Intermediate", + price: "Free", + originalPrice: "$2,299", + rating: 4.8, + reviews: 456, + students: 6230, + schedule: { + format: "Live Online", + commitment: "15 hours/week" + } + } + ]; +}; From caf843efb21162c1f6f0b014d9d2bcf76651685a Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 12:19:24 +0000 Subject: [PATCH 02/37] change background color of take action --- src/app/courses/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 3933af5..0ecf0c3 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -200,7 +200,7 @@ export default function CoursesPage() {
-
+

Transform Your Future Today

From 72a5a2fbd2a5745307bde8dc7283eac99c340d7f Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 12:28:37 +0000 Subject: [PATCH 03/37] change text colors --- src/app/courses/page.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 0ecf0c3..76880f7 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -202,8 +202,8 @@ export default function CoursesPage() {

-

Transform Your Future Today

-

+

Transform Your Future Today

+

Join thousands of successful graduates who have launched their tech careers with ReEnvision. Start your journey today and unlock endless opportunities in technology.

@@ -215,13 +215,6 @@ export default function CoursesPage() { > Schedule Free Consultation -
From ca5ba72e8bd4171d7753f3b69346efc7a68e1ae5 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 12:30:01 +0000 Subject: [PATCH 04/37] remove extra button in hero section --- src/app/page.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 575db6d..78c27ca 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -228,16 +228,8 @@ export default function HomePage() { data-aos="fade-up" data-aos-delay="400" > - + - - - From 5f1af4e7e208afdb932175dfc0dff0dbd75e42f6 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 13:57:03 +0000 Subject: [PATCH 05/37] remove call to action --- src/app/courses/page.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 76880f7..97bac14 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -199,25 +199,6 @@ export default function CoursesPage() {
- -
-
-

Transform Your Future Today

-

- Join thousands of successful graduates who have launched their tech careers with ReEnvision. Start your - journey today and unlock endless opportunities in technology. -

-
- -
-
-
); } \ No newline at end of file From 3810e774f44c3129c4be69d57ce81fd4a9bead0f Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 14:39:26 +0000 Subject: [PATCH 06/37] edit courses card --- src/app/courses/page.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 97bac14..357f28e 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -115,7 +115,7 @@ export default function CoursesPage() {

Available Courses & Boot Camps

- Interactive, mentor-led courses designed to build practical skills and empower communities. + Interactive, mentor-led courses designed to build technical skills and help empower communities.

@@ -129,17 +129,7 @@ export default function CoursesPage() { data-aos-delay={index * 100} > -
- - -
- - {course.rating} - ({course.reviews}) -
-
+ {course.title}

{course.description}

From 1a19954fb7846fcf34f9ee5c418bc7c436b0a32d Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 14:51:56 +0000 Subject: [PATCH 07/37] lint --- src/app/actions/create-checkout.ts | 50 ------------------------------ src/app/courses/page.tsx | 3 +- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 src/app/actions/create-checkout.ts diff --git a/src/app/actions/create-checkout.ts b/src/app/actions/create-checkout.ts deleted file mode 100644 index 23fccd3..0000000 --- a/src/app/actions/create-checkout.ts +++ /dev/null @@ -1,50 +0,0 @@ -"use server"; - -import Stripe from "stripe"; - -export async function createCheckoutSession( - amount: number, - customerName: string, -) { - const stripeSecretKey = process.env.STRIPE_SECRET_KEY; - - if (!stripeSecretKey) { - throw new Error("STRIPE_SECRET_KEY is not set in environment variables"); - } - - const stripe = new Stripe(stripeSecretKey, { - apiVersion: "2025-06-30.basil", - }); - - try { - const session = await stripe.checkout.sessions.create({ - payment_method_types: ["card"], - line_items: [ - { - price_data: { - currency: "usd", - product_data: { - name: "Donation to ReEnvision", - description: - "Help us keep education free and accessible for everyone", - }, - unit_amount: Math.round(amount * 100), // Convert to cents - }, - quantity: 1, - }, - ], - mode: "payment", - success_url: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"}/donation/success`, - cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"}/donate`, - customer_email: undefined, // You can add email field if needed - metadata: { - donor_name: customerName, - }, - }); - - return { sessionId: session.id, url: session.url }; - } catch (error) { - console.error("Error creating checkout session:", error); - throw new Error("Failed to create checkout session"); - } -} diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 357f28e..2cdb55b 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -3,8 +3,7 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Users, BookOpen, Video, Globe, Zap, Award, Star, Clock } from "lucide-react"; +import { Users, BookOpen, Video, Globe, Zap, Award, Clock } from "lucide-react"; import Link from "next/link"; import { getAllCourses, type Course } from "@/lib/courses"; import AOS from "aos"; From 7e77db310a48a269e4f4d12d88f0ccd6ac13b85b Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 15:01:52 +0000 Subject: [PATCH 08/37] update to eslint due to next lint deprecation --- eslint.config.mjs | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 3a95b32..ec2cd0c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,15 @@ const compat = new FlatCompat({ }); const eslintConfig = [ + { + ignores: [ + "node_modules/**", + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ], + }, ...compat.extends( "next/core-web-vitals", "next/typescript", diff --git a/package.json b/package.json index 46acdcd..9dae020 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "cross-env NODE_ENV=development next dev --turbopack", "build": "next build", "start": "next start", - "lint": "prettier . --write && next lint", + "lint": "prettier . --write && eslint .", "db:push": "npx drizzle-kit push" }, "dependencies": { From 5ce1643cd4dd7d613b81b33a794993bbe74a5f75 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 15:03:02 +0000 Subject: [PATCH 09/37] lint again --- .gitignore | 4 -- src/app/courses/page.tsx | 99 ++++++++++++++++++++++++++++++---------- src/app/layout.tsx | 2 +- src/lib/courses.ts | 67 +++++++++++++++------------ 4 files changed, 115 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index 4af9b43..e3a7542 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,3 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts .env*.local - -# email crap - -email.js \ No newline at end of file diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index 2cdb55b..ee9390b 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -2,7 +2,13 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Users, BookOpen, Video, Globe, Zap, Award, Clock } from "lucide-react"; import Link from "next/link"; import { getAllCourses, type Course } from "@/lib/courses"; @@ -61,11 +67,15 @@ export default function CoursesPage() {
- Expert Mentors + + Expert Mentors +
- Hands-on Learning + + Hands-on Learning +
@@ -74,36 +84,62 @@ export default function CoursesPage() {
-

Why Join?

+

+ Why Join? +

-
+
-

Accessible Learning

+

+ Accessible Learning +

-
+
-

Real Mentorship

+

+ Real Mentorship +

-
+
-

Practical Skills

+

+ Practical Skills +

-
+
-

Community Impact

+

+ Community Impact +

@@ -112,9 +148,12 @@ export default function CoursesPage() {
-

Available Courses & Boot Camps

+

+ Available Courses & Boot Camps +

- Interactive, mentor-led courses designed to build technical skills and help empower communities. + Interactive, mentor-led courses designed to build technical skills + and help empower communities.

@@ -128,16 +167,21 @@ export default function CoursesPage() { data-aos-delay={index * 100} > - - {course.title} -

{course.description}

+ + {course.title} + +

+ {course.description} +

- {course.duration} + + {course.duration} +
@@ -147,14 +191,20 @@ export default function CoursesPage() {
- {course.level} + + {course.level} +
-
{course.price}
-
{course.originalPrice}
+
+ {course.price} +
+
+ {course.originalPrice} +
{course.schedule.format} • {course.schedule.commitment} @@ -173,7 +223,10 @@ export default function CoursesPage() { View Details - +
); -} \ No newline at end of file +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e208a3b..d1ccefd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,7 @@ import Header from "@/components/Header"; import Footer from "@/components/Footer"; import AuthProvider from "@/components/AuthProvider"; import { Analytics } from "@vercel/analytics/next"; -import { SpeedInsights } from "@vercel/speed-insights/next" +import { SpeedInsights } from "@vercel/speed-insights/next"; // Configure EB Garamond const ebGaramond = EB_Garamond({ diff --git a/src/lib/courses.ts b/src/lib/courses.ts index 31ef5a4..c8334fa 100644 --- a/src/lib/courses.ts +++ b/src/lib/courses.ts @@ -18,12 +18,13 @@ export interface Course { export const getAllCourses = async (): Promise => { // Simulate the API call delay await new Promise(resolve => setTimeout(resolve, 100)); - + return [ { id: "web-development-bootcamp", title: "Full-Stack Web Development Bootcamp", - description: "Learn modern web development with HTML, CSS, JavaScript, React, and Node.js. Build real-world projects and get job-ready skills.", + description: + "Learn modern web development with HTML, CSS, JavaScript, React, and Node.js. Build real-world projects and get job-ready skills.", duration: "12 weeks", level: "Beginner to Intermediate", price: "Free", @@ -33,13 +34,14 @@ export const getAllCourses = async (): Promise => { students: 15420, schedule: { format: "Live Online", - commitment: "20 hours/week" - } + commitment: "20 hours/week", + }, }, { id: "python-data-science", title: "Python for Data Science & Analytics", - description: "Master Python programming for data analysis, visualization, and machine learning. Work with real datasets and build predictive models.", + description: + "Master Python programming for data analysis, visualization, and machine learning. Work with real datasets and build predictive models.", duration: "10 weeks", level: "Beginner", price: "Free", @@ -49,13 +51,14 @@ export const getAllCourses = async (): Promise => { students: 12350, schedule: { format: "Live Online", - commitment: "15 hours/week" - } + commitment: "15 hours/week", + }, }, { id: "digital-marketing", title: "Digital Marketing & Social Media", - description: "Learn digital marketing strategies, SEO, social media management, and content creation. Build your personal brand and grow businesses online.", + description: + "Learn digital marketing strategies, SEO, social media management, and content creation. Build your personal brand and grow businesses online.", duration: "8 weeks", level: "Beginner", price: "Free", @@ -65,13 +68,14 @@ export const getAllCourses = async (): Promise => { students: 8920, schedule: { format: "Live Online", - commitment: "12 hours/week" - } + commitment: "12 hours/week", + }, }, { id: "cybersecurity-fundamentals", title: "Cybersecurity Fundamentals", - description: "Learn essential cybersecurity concepts, threat detection, and protection strategies. Understand how to secure networks and data.", + description: + "Learn essential cybersecurity concepts, threat detection, and protection strategies. Understand how to secure networks and data.", duration: "6 weeks", level: "Beginner", price: "Free", @@ -81,13 +85,14 @@ export const getAllCourses = async (): Promise => { students: 6780, schedule: { format: "Live Online", - commitment: "10 hours/week" - } + commitment: "10 hours/week", + }, }, { id: "mobile-app-development", title: "Mobile App Development with React Native", - description: "Build cross-platform mobile apps using React Native. Learn to create iOS and Android apps with a single codebase.", + description: + "Build cross-platform mobile apps using React Native. Learn to create iOS and Android apps with a single codebase.", duration: "10 weeks", level: "Intermediate", price: "Free", @@ -97,13 +102,14 @@ export const getAllCourses = async (): Promise => { students: 7890, schedule: { format: "Live Online", - commitment: "18 hours/week" - } + commitment: "18 hours/week", + }, }, { id: "ui-ux-design", title: "UI/UX Design & Prototyping", - description: "Learn user interface and user experience design principles. Create beautiful, functional designs using modern tools like Figma.", + description: + "Learn user interface and user experience design principles. Create beautiful, functional designs using modern tools like Figma.", duration: "8 weeks", level: "Beginner", price: "Free", @@ -113,13 +119,14 @@ export const getAllCourses = async (): Promise => { students: 9560, schedule: { format: "Live Online", - commitment: "14 hours/week" - } + commitment: "14 hours/week", + }, }, { id: "cloud-computing-aws", title: "Cloud Computing with AWS", - description: "Master Amazon Web Services (AWS) cloud platform. Learn to deploy, manage, and scale applications in the cloud.", + description: + "Master Amazon Web Services (AWS) cloud platform. Learn to deploy, manage, and scale applications in the cloud.", duration: "12 weeks", level: "Intermediate", price: "Free", @@ -129,13 +136,14 @@ export const getAllCourses = async (): Promise => { students: 5430, schedule: { format: "Live Online", - commitment: "16 hours/week" - } + commitment: "16 hours/week", + }, }, { id: "blockchain-basics", title: "Blockchain & Cryptocurrency Basics", - description: "Understand blockchain technology, smart contracts, and cryptocurrency. Learn about DeFi, NFTs, and the future of digital finance.", + description: + "Understand blockchain technology, smart contracts, and cryptocurrency. Learn about DeFi, NFTs, and the future of digital finance.", duration: "6 weeks", level: "Beginner", price: "Free", @@ -145,13 +153,14 @@ export const getAllCourses = async (): Promise => { students: 4120, schedule: { format: "Live Online", - commitment: "8 hours/week" - } + commitment: "8 hours/week", + }, }, { id: "artificial-intelligence", title: "Introduction to Artificial Intelligence", - description: "Explore AI concepts, machine learning algorithms, and neural networks. Build your first AI models and understand the future of technology.", + description: + "Explore AI concepts, machine learning algorithms, and neural networks. Build your first AI models and understand the future of technology.", duration: "10 weeks", level: "Intermediate", price: "Free", @@ -161,8 +170,8 @@ export const getAllCourses = async (): Promise => { students: 6230, schedule: { format: "Live Online", - commitment: "15 hours/week" - } - } + commitment: "15 hours/week", + }, + }, ]; }; From 9c0d508a96b1be776dc88fd266f3e11c86ccfe69 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Tue, 2 Sep 2025 15:07:48 +0000 Subject: [PATCH 10/37] update to latest nextjs --- package-lock.json | 147 +++++++++++++++++++--------------------------- package.json | 16 +++-- 2 files changed, 70 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2cc4094..bb82722 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,14 +27,14 @@ "dotenv": "^17.2.1", "drizzle-orm": "^0.44.4", "lucide-react": "^0.525.0", - "next": "15.3.5", + "next": "15.5.2", "next-auth": "^4.24.11", "next-themes": "^0.4.6", "nodemailer": "^6.10.1", "particles.js": "^2.0.0", "pg": "^8.16.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "19.1.1", + "react-dom": "19.1.1", "sonner": "^2.0.7", "stripe": "^18.3.0", "tailwind-merge": "^3.3.1" @@ -45,12 +45,12 @@ "@types/aos": "^3.0.7", "@types/node": "^20", "@types/pg": "^8.15.5", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", "cross-env": "^10.0.0", "drizzle-kit": "^0.31.4", "eslint": "^9", - "eslint-config-next": "15.3.5", + "eslint-config-next": "15.5.2", "eslint-plugin-import": "^2.32.0", "prettier": "^3.6.2", "tailwindcss": "^4", @@ -1824,15 +1824,15 @@ } }, "node_modules/@next/env": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.5.tgz", - "integrity": "sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz", + "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.5.tgz", - "integrity": "sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.2.tgz", + "integrity": "sha512-lkLrRVxcftuOsJNhWatf1P2hNVfh98k/omQHrCEPPriUypR6RcS13IvLdIrEvkm9AH2Nu2YpR5vLqBuy6twH3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1840,9 +1840,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.5.tgz", - "integrity": "sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz", + "integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==", "cpu": [ "arm64" ], @@ -1856,9 +1856,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.5.tgz", - "integrity": "sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz", + "integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==", "cpu": [ "x64" ], @@ -1872,9 +1872,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.5.tgz", - "integrity": "sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz", + "integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==", "cpu": [ "arm64" ], @@ -1888,9 +1888,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.5.tgz", - "integrity": "sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz", + "integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==", "cpu": [ "arm64" ], @@ -1904,9 +1904,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.5.tgz", - "integrity": "sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz", + "integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==", "cpu": [ "x64" ], @@ -1920,9 +1920,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.5.tgz", - "integrity": "sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz", + "integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==", "cpu": [ "x64" ], @@ -1936,9 +1936,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.5.tgz", - "integrity": "sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz", + "integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==", "cpu": [ "arm64" ], @@ -1952,9 +1952,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.5.tgz", - "integrity": "sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz", + "integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==", "cpu": [ "x64" ], @@ -2494,12 +2494,6 @@ "@supabase/storage-js": "^2.10.4" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2873,9 +2867,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz", - "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2883,9 +2877,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", - "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", "devOptional": true, "license": "MIT", "peerDependencies": { @@ -4008,17 +4002,6 @@ "dev": true, "license": "MIT" }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5105,13 +5088,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.5.tgz", - "integrity": "sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.2.tgz", + "integrity": "sha512-3hPZghsLupMxxZ2ggjIIrat/bPniM2yRpsVPVM40rp8ZMzKWOJp2CGWn7+EzoV2ddkUr5fxNfHpF+wU1hGt/3g==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.3.5", + "@next/eslint-plugin-next": "15.5.2", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -7159,15 +7142,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/next/-/next-15.3.5.tgz", - "integrity": "sha512-RkazLBMMDJSJ4XZQ81kolSpwiCt907l0xcgcpF4xC2Vml6QVcPNXW0NQRwQ80FFtSn7UM52XN0anaw8TEJXaiw==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz", + "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==", "license": "MIT", "dependencies": { - "@next/env": "15.3.5", - "@swc/counter": "0.1.3", + "@next/env": "15.5.2", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -7179,19 +7160,19 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.3.5", - "@next/swc-darwin-x64": "15.3.5", - "@next/swc-linux-arm64-gnu": "15.3.5", - "@next/swc-linux-arm64-musl": "15.3.5", - "@next/swc-linux-x64-gnu": "15.3.5", - "@next/swc-linux-x64-musl": "15.3.5", - "@next/swc-win32-arm64-msvc": "15.3.5", - "@next/swc-win32-x64-msvc": "15.3.5", - "sharp": "^0.34.1" + "@next/swc-darwin-arm64": "15.5.2", + "@next/swc-darwin-x64": "15.5.2", + "@next/swc-linux-arm64-gnu": "15.5.2", + "@next/swc-linux-arm64-musl": "15.5.2", + "@next/swc-linux-x64-gnu": "15.5.2", + "@next/swc-linux-x64-musl": "15.5.2", + "@next/swc-win32-arm64-msvc": "15.5.2", + "@next/swc-win32-x64-msvc": "15.5.2", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -8512,14 +8493,6 @@ "node": ">= 0.4" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/package.json b/package.json index 9dae020..c3e8d25 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,14 @@ "dotenv": "^17.2.1", "drizzle-orm": "^0.44.4", "lucide-react": "^0.525.0", - "next": "15.3.5", + "next": "15.5.2", "next-auth": "^4.24.11", "next-themes": "^0.4.6", "nodemailer": "^6.10.1", "particles.js": "^2.0.0", "pg": "^8.16.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "19.1.1", + "react-dom": "19.1.1", "sonner": "^2.0.7", "stripe": "^18.3.0", "tailwind-merge": "^3.3.1" @@ -47,17 +47,21 @@ "@types/aos": "^3.0.7", "@types/node": "^20", "@types/pg": "^8.15.5", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", "cross-env": "^10.0.0", "drizzle-kit": "^0.31.4", "eslint": "^9", - "eslint-config-next": "15.3.5", + "eslint-config-next": "15.5.2", "eslint-plugin-import": "^2.32.0", "prettier": "^3.6.2", "tailwindcss": "^4", "tsx": "^4.20.3", "tw-animate-css": "^1.3.5", "typescript": "^5" + }, + "overrides": { + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9" } } From 741a216b0bdcb44d15ea4e7e0c7d3d2b1251dd46 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Wed, 3 Sep 2025 13:52:01 +0000 Subject: [PATCH 11/37] add courses flow --- TODO | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 2f4fa7f..d2c2fb6 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,30 @@ -add a signout button too -fix carrousel bug thing \ No newline at end of file +======================== TO DO LIST ======================== + +fix carrousel bug thing + +======================== Ideas ======================== + +Key: +- is see +> is do ++ is any api requests or backend +| end + +--------------- courses flow --------------- + +> course is published on admin dashboard +- user sees course on the courses catalog +> user clicks on course +- user sees details +- user decides they want to take it +> user clicks on enroll button ++ enroll button runs a function to create a checkout ++ a secure paypal checkout with designated price and product name gets generated +- user sees paypal payment portal +> user pays ++ payment gets logged into paypal and recieves a receipt with remind code +- user sees remind code +> user joins remind +> admin approves user into class after verifying that they payed on the paypal +| user gets to see meeting links and other details on remind + From aeaee248a5ad3eb5c064cec3d992da49d15150a8 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 4 Sep 2025 13:51:43 +0000 Subject: [PATCH 12/37] remove stripe --- package-lock.json | 52 +++++++++++++++-------------------------------- package.json | 1 - 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb82722..0ae9980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "react": "19.1.1", "react-dom": "19.1.1", "sonner": "^2.0.7", - "stripe": "^18.3.0", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -4025,6 +4024,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4038,6 +4038,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4717,6 +4718,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4848,6 +4850,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4857,6 +4860,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4894,6 +4898,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5706,6 +5711,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5739,6 +5745,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5868,6 +5875,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5946,6 +5954,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6986,6 +6995,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7301,6 +7311,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7850,21 +7861,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8339,6 +8335,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8358,6 +8355,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8374,6 +8372,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8392,6 +8391,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8661,26 +8661,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stripe": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.3.0.tgz", - "integrity": "sha512-FkxrTUUcWB4CVN2yzgsfF/YHD6WgYHduaa7VmokCy5TLCgl5UNJkwortxcedrxSavQ8Qfa4Ir4JxcbIYiBsyLg==", - "license": "MIT", - "dependencies": { - "qs": "^6.11.0" - }, - "engines": { - "node": ">=12.*" - }, - "peerDependencies": { - "@types/node": ">=12.x.x" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", diff --git a/package.json b/package.json index c3e8d25..53a4343 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "react": "19.1.1", "react-dom": "19.1.1", "sonner": "^2.0.7", - "stripe": "^18.3.0", "tailwind-merge": "^3.3.1" }, "devDependencies": { From c9749c80f9171d0b764a5141abe8aad57d963da5 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 4 Sep 2025 14:38:34 +0000 Subject: [PATCH 13/37] update todo --- TODO | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index d2c2fb6..ec7f1c2 100644 --- a/TODO +++ b/TODO @@ -1,17 +1,25 @@ ======================== TO DO LIST ======================== -fix carrousel bug thing +- fix carrousel bug thing +- create database table, schema, and drizzle thing to host the courses +- create backend that communicates with database for courses +- create backend for admmin dashboard that adds courses +- replace signout with a profile picture that when you click on it, you get access to settings and a logout button + + ======================== Ideas ======================== + +--------------- courses flow --------------- + Key: + - is see > is do + is any api requests or backend | end ---------------- courses flow --------------- - > course is published on admin dashboard - user sees course on the courses catalog > user clicks on course @@ -28,3 +36,10 @@ Key: > admin approves user into class after verifying that they payed on the paypal | user gets to see meeting links and other details on remind + +--------------- account settings --------------- + +- profile picture +- Name +- email (will require reverif) +- password (will require old password) From 563c92ef5d2aa3ab26e5f5010b86b96f8f0e986d Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 4 Sep 2025 14:41:01 +0000 Subject: [PATCH 14/37] update todo --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index ec7f1c2..9c0ea37 100644 --- a/TODO +++ b/TODO @@ -39,7 +39,7 @@ Key: --------------- account settings --------------- -- profile picture +- profile picture (default generated by jdenticons) - Name - email (will require reverif) - password (will require old password) From 09a0a1f42d9958db4aee173adc51c8f5a2653fcd Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 25 Sep 2025 14:42:26 +0000 Subject: [PATCH 15/37] add psql and nano --- .idx/dev.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idx/dev.nix b/.idx/dev.nix index e5ca7d7..b8172bc 100644 --- a/.idx/dev.nix +++ b/.idx/dev.nix @@ -3,6 +3,8 @@ packages = [ pkgs.nodejs_20 pkgs.gh + pkgs.nano + pkgs.postgresql ]; idx.extensions = [ From 648d13516457d08689bbd220e385b7c8d8ba4853 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 25 Sep 2025 14:43:39 +0000 Subject: [PATCH 16/37] trick drizzle migration into thinking it migrated successfully and also update schema --- drizzle/0000_flimsy_gorilla_man.sql | 1 + drizzle/meta/0000_snapshot.json | 314 ++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 13 ++ src/db/schema.ts | 35 +++- 4 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 drizzle/0000_flimsy_gorilla_man.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json diff --git a/drizzle/0000_flimsy_gorilla_man.sql b/drizzle/0000_flimsy_gorilla_man.sql new file mode 100644 index 0000000..a483e14 --- /dev/null +++ b/drizzle/0000_flimsy_gorilla_man.sql @@ -0,0 +1 @@ +-- This migration is intentionally left empty to sync with the existing database. \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..e97bd3a --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,314 @@ +{ + "id": "319dcb5f-b8a9-47c5-8ef1-93c66dbe00a8", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.courses": { + "name": "courses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "course_name": { + "name": "course_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "courses_image": { + "name": "courses_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "course_description": { + "name": "course_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_price": { + "name": "course_price", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_purchased": { + "name": "time_purchased", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "customers_course_id_courses_id_fk": { + "name": "customers_course_id_courses_id_fk", + "tableFrom": "customers", + "tableTo": "courses", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_title": { + "name": "event_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "event_desc": { + "name": "event_desc", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "events_created_by_users_id_fk": { + "name": "events_created_by_users_id_fk", + "tableFrom": "events", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reset_key": { + "name": "reset_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reset_key_expires": { + "name": "reset_key_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_reset": { + "name": "last_reset", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_verification_key": { + "name": "email_verification_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verification_key_expires": { + "name": "email_verification_key_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_banned": { + "name": "is_banned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..12a220f --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1758810869702, + "tag": "0000_flimsy_gorilla_man", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index fd584bd..2f09443 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -5,13 +5,14 @@ import { pgTable, text, varchar, + numeric, } from "drizzle-orm/pg-core"; export const usersTable = pgTable("users", { - id: text().notNull().primaryKey(), - email: varchar({ length: 255 }).notNull().unique(), - password: text().notNull(), - name: text().notNull(), + id: text("id").notNull().primaryKey(), + email: varchar("email", { length: 255 }).notNull().unique(), + password: text("password").notNull(), + name: text("name").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), resetKey: text("reset_key"), resetKeyExpires: timestamp("reset_key_expires"), @@ -24,7 +25,7 @@ export const usersTable = pgTable("users", { }); export const eventsTable = pgTable("events", { - id: text() + id: text("id") .notNull() .primaryKey() .$defaultFn(() => cuid()), @@ -36,3 +37,27 @@ export const eventsTable = pgTable("events", { createdBy: text("created_by").references(() => usersTable.id), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); + +export const coursesTable = pgTable("courses", { + id: text("id") + .notNull() + .primaryKey() + .$defaultFn(() => cuid()), + course_name: text("course_name").notNull(), + courses_image: text("courses_image"), + course_description: text("course_description").notNull(), + course_price: numeric("course_price").notNull(), + created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updated_at: timestamp("updated_at").notNull().defaultNow(), +}); + +export const customersTable = pgTable("customers", { + id: text("id") + .notNull() + .primaryKey() + .$defaultFn(() => cuid()), + customer_name: text("customer_name").notNull(), + customer_email: text("customer_email").notNull(), + course_id: text("course_id").references(() => coursesTable.id), + time_purchased: timestamp("time_purchased", { withTimezone: true }).notNull().defaultNow(), +}); From 511f89c5914acb6df80d618373a3d20025a3aed1 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 25 Sep 2025 14:51:20 +0000 Subject: [PATCH 17/37] update upload backendd --- src/app/api/upload/route.ts | 125 +++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index c1951d9..9b4f2fe 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,43 +1,42 @@ +import { db } from "@/db/index"; +import { coursesTable } from "@/db/schema"; +import { NextResponse } from "next/server"; + const API_URL = process.env.SUPABASE_URL; const API_KEY = process.env.SUPABASE_ANON_KEY; export async function POST(request: Request) { try { const formData = await request.formData(); - const file = formData.get("file") as File; + const file = formData.get("file") as File | null; + const uploadType = formData.get("uploadType") as string | null; - if (!file) { - return Response.json({ error: "No file provided" }, { status: 400 }); - } + // ========== NEW: COURSE UPLOAD LOGIC ========== + if (uploadType === 'course') { + const course_name = formData.get("course_name") as string; + const course_description = formData.get("course_description") as string; + // Use parseFloat to handle numeric conversion correctly + const course_price_str = formData.get("course_price") as string; + const course_price = parseFloat(course_price_str); - // Validate file type - if (!file.type.includes("jpeg") && !file.type.includes("jpg")) { - return Response.json( - { error: "Only JPEG files are allowed" }, - { status: 400 }, - ); - } + if (!file || !course_name || !course_description || isNaN(course_price)) { + return NextResponse.json({ error: "Missing required fields: file, course_name, course_description, and a valid course_price are all required." }, { status: 400 }); + } - // Validate file size (400KB max) - if (file.size > 400 * 1024) { - return Response.json( - { error: "File size must be less than 400KB" }, - { status: 400 }, - ); - } + // Corrected bucket name to 'courses' + const bucketName = 'courses'; - // Generate filename with current date - const today = new Date().toISOString().split("T")[0]; - const timestamp = Date.now(); - const filename = `${today}-${timestamp}.jpg`; + if (!file.type.includes("jpeg") && !file.type.includes("jpg") && !file.type.includes("png")) { + return NextResponse.json({ error: "Only JPG or PNG files are allowed for course images" }, { status: 400 }); + } + if (file.size > 400 * 1024) { + return NextResponse.json({ error: "File size must be less than 400KB" }, { status: 400 }); + } - // Convert file to buffer - const buffer = await file.arrayBuffer(); + const filename = `${Date.now()}-${file.name.replace(/\s/g, '_')}`; + const buffer = await file.arrayBuffer(); - // Upload to storage - const response = await fetch( - `${API_URL}/storage/v1/object/event-images/${filename}`, - { + const uploadResponse = await fetch(`${API_URL}/storage/v1/object/${bucketName}/${filename}`, { method: "POST", headers: { apikey: API_KEY || "", @@ -45,19 +44,69 @@ export async function POST(request: Request) { "Content-Type": file.type, }, body: buffer, - }, - ); + }); - if (!response.ok) { - throw new Error("Failed to upload image"); - } + if (!uploadResponse.ok) { + const errorBody = await uploadResponse.json(); + console.error("Supabase Storage Error:", errorBody); + throw new Error(`Failed to upload course image. Status: ${uploadResponse.status}`); + } + + const publicUrl = `${API_URL}/storage/v1/object/public/${bucketName}/${filename}`; + + const newCourse = await db + .insert(coursesTable) + .values({ + course_name, + course_description, + course_price: course_price.toString(), // Convert back to string for numeric DB type + courses_image: publicUrl, + }) + .returning(); + + return NextResponse.json({ message: "Course created successfully", course: newCourse[0] }, { status: 201 }); + + } else { + // ========== ORIGINAL: EVENT IMAGE UPLOAD LOGIC ========== + + if (!file) { + return NextResponse.json({ error: "No file provided" }, { status: 400 }); + } + + if (!file.type.includes("jpeg") && !file.type.includes("jpg")) { + return NextResponse.json({ error: "Only JPEG files are allowed" }, { status: 400 }); + } + + if (file.size > 400 * 1024) { + return NextResponse.json({ error: "File size must be less than 400KB" }, { status: 400 }); + } - // Get public URL - const publicUrl = `${API_URL}/storage/v1/object/public/event-images/${filename}`; + const today = new Date().toISOString().split("T")[0]; + const timestamp = Date.now(); + const filename = `${today}-${timestamp}.jpg`; + const buffer = await file.arrayBuffer(); + + const response = await fetch(`${API_URL}/storage/v1/object/event-images/${filename}`, { + method: "POST", + headers: { + apikey: API_KEY || "", + Authorization: `Bearer ${API_KEY}`, + "Content-Type": file.type, + }, + body: buffer, + }); + + if (!response.ok) { + throw new Error("Failed to upload image"); + } + + const publicUrl = `${API_URL}/storage/v1/object/public/event-images/${filename}`; + return NextResponse.json({ url: publicUrl }); + } - return Response.json({ url: publicUrl }); } catch (error) { - console.error("Upload error:", error); - return Response.json({ error: "Failed to upload image" }, { status: 500 }); + console.error("API Error:", error); + const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; + return NextResponse.json({ error: "Failed to process request", details: errorMessage }, { status: 500 }); } } From b37527d51f3c2dfbcf385c1ab78c3fb643f4caeb Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 25 Sep 2025 14:52:37 +0000 Subject: [PATCH 18/37] Revert "trick drizzle migration into thinking it migrated successfully" This reverts commit 648d13516457d08689bbd220e385b7c8d8ba4853. --- drizzle/0000_flimsy_gorilla_man.sql | 1 - drizzle/meta/0000_snapshot.json | 314 ---------------------------- drizzle/meta/_journal.json | 13 -- src/db/schema.ts | 35 +--- 4 files changed, 5 insertions(+), 358 deletions(-) delete mode 100644 drizzle/0000_flimsy_gorilla_man.sql delete mode 100644 drizzle/meta/0000_snapshot.json delete mode 100644 drizzle/meta/_journal.json diff --git a/drizzle/0000_flimsy_gorilla_man.sql b/drizzle/0000_flimsy_gorilla_man.sql deleted file mode 100644 index a483e14..0000000 --- a/drizzle/0000_flimsy_gorilla_man.sql +++ /dev/null @@ -1 +0,0 @@ --- This migration is intentionally left empty to sync with the existing database. \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index e97bd3a..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,314 +0,0 @@ -{ - "id": "319dcb5f-b8a9-47c5-8ef1-93c66dbe00a8", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.courses": { - "name": "courses", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "course_name": { - "name": "course_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "courses_image": { - "name": "courses_image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "course_description": { - "name": "course_description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_price": { - "name": "course_price", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customers": { - "name": "customers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "customer_name": { - "name": "customer_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_email": { - "name": "customer_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_id": { - "name": "course_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_purchased": { - "name": "time_purchased", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "customers_course_id_courses_id_fk": { - "name": "customers_course_id_courses_id_fk", - "tableFrom": "customers", - "tableTo": "courses", - "columnsFrom": [ - "course_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.events": { - "name": "events", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "image_url": { - "name": "image_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "event_title": { - "name": "event_title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "event_date": { - "name": "event_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "event_desc": { - "name": "event_desc", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "events_created_by_users_id_fk": { - "name": "events_created_by_users_id_fk", - "tableFrom": "events", - "tableTo": "users", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "email": { - "name": "email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "reset_key": { - "name": "reset_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reset_key_expires": { - "name": "reset_key_expires", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_reset": { - "name": "last_reset", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "email_verification_key": { - "name": "email_verification_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verification_key_expires": { - "name": "email_verification_key_expires", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "is_admin": { - "name": "is_admin", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_banned": { - "name": "is_banned", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json deleted file mode 100644 index 12a220f..0000000 --- a/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1758810869702, - "tag": "0000_flimsy_gorilla_man", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index 2f09443..fd584bd 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -5,14 +5,13 @@ import { pgTable, text, varchar, - numeric, } from "drizzle-orm/pg-core"; export const usersTable = pgTable("users", { - id: text("id").notNull().primaryKey(), - email: varchar("email", { length: 255 }).notNull().unique(), - password: text("password").notNull(), - name: text("name").notNull(), + id: text().notNull().primaryKey(), + email: varchar({ length: 255 }).notNull().unique(), + password: text().notNull(), + name: text().notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), resetKey: text("reset_key"), resetKeyExpires: timestamp("reset_key_expires"), @@ -25,7 +24,7 @@ export const usersTable = pgTable("users", { }); export const eventsTable = pgTable("events", { - id: text("id") + id: text() .notNull() .primaryKey() .$defaultFn(() => cuid()), @@ -37,27 +36,3 @@ export const eventsTable = pgTable("events", { createdBy: text("created_by").references(() => usersTable.id), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); - -export const coursesTable = pgTable("courses", { - id: text("id") - .notNull() - .primaryKey() - .$defaultFn(() => cuid()), - course_name: text("course_name").notNull(), - courses_image: text("courses_image"), - course_description: text("course_description").notNull(), - course_price: numeric("course_price").notNull(), - created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), - updated_at: timestamp("updated_at").notNull().defaultNow(), -}); - -export const customersTable = pgTable("customers", { - id: text("id") - .notNull() - .primaryKey() - .$defaultFn(() => cuid()), - customer_name: text("customer_name").notNull(), - customer_email: text("customer_email").notNull(), - course_id: text("course_id").references(() => coursesTable.id), - time_purchased: timestamp("time_purchased", { withTimezone: true }).notNull().defaultNow(), -}); From 0ae0b8de479cbe4d334ea77b2301b65f9966a548 Mon Sep 17 00:00:00 2001 From: thayeeboi890 Date: Thu, 25 Sep 2025 15:43:31 +0000 Subject: [PATCH 19/37] get backend for uploading courses functioning --- src/app/api/courses/route.ts | 31 +++ src/app/api/upload/route.ts | 136 +++++-------- src/app/dashboard/page.tsx | 10 + src/components/admin/course-management.tsx | 219 +++++++++++++++++++++ src/db/schema.ts | 35 +++- 5 files changed, 337 insertions(+), 94 deletions(-) create mode 100644 src/app/api/courses/route.ts create mode 100644 src/components/admin/course-management.tsx diff --git a/src/app/api/courses/route.ts b/src/app/api/courses/route.ts new file mode 100644 index 0000000..2f4f769 --- /dev/null +++ b/src/app/api/courses/route.ts @@ -0,0 +1,31 @@ +import db from "@/db/database"; +import { coursesTable } from "@/db/schema"; +import { NextResponse } from "next/server"; + +export async function POST(req: Request) { + try { + // We expect the image to be uploaded separately first, and the URL passed in the body. + const { course_name, course_description, course_price, courses_image } = await req.json(); + + if (!course_name || !course_description || !course_price || !courses_image) { + return NextResponse.json({ error: "Missing required fields. All fields are required, including the image URL." }, { status: 400 }); + } + + const newCourse = await db + .insert(coursesTable) + .values({ + course_name, + course_description, + course_price, + courses_image, // This is the public URL from the storage + }) + .returning(); + + return NextResponse.json({ message: "Course created successfully", course: newCourse[0] }, { status: 201 }); + } catch (error) { + console.error("Error creating course:", error); + // Check for a specific known error, e.g., unique constraint violation, if applicable + // if (error.code === '23505') { ... } + return NextResponse.json({ error: "Internal Server Error", details: error instanceof Error ? error.message : "Unknown error" }, { status: 500 }); + } +} diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 9b4f2fe..3b93389 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,112 +1,70 @@ -import { db } from "@/db/index"; -import { coursesTable } from "@/db/schema"; -import { NextResponse } from "next/server"; - const API_URL = process.env.SUPABASE_URL; -const API_KEY = process.env.SUPABASE_ANON_KEY; +const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; export async function POST(request: Request) { try { const formData = await request.formData(); - const file = formData.get("file") as File | null; - const uploadType = formData.get("uploadType") as string | null; - - // ========== NEW: COURSE UPLOAD LOGIC ========== - if (uploadType === 'course') { - const course_name = formData.get("course_name") as string; - const course_description = formData.get("course_description") as string; - // Use parseFloat to handle numeric conversion correctly - const course_price_str = formData.get("course_price") as string; - const course_price = parseFloat(course_price_str); - - if (!file || !course_name || !course_description || isNaN(course_price)) { - return NextResponse.json({ error: "Missing required fields: file, course_name, course_description, and a valid course_price are all required." }, { status: 400 }); - } - - // Corrected bucket name to 'courses' - const bucketName = 'courses'; - - if (!file.type.includes("jpeg") && !file.type.includes("jpg") && !file.type.includes("png")) { - return NextResponse.json({ error: "Only JPG or PNG files are allowed for course images" }, { status: 400 }); - } - if (file.size > 400 * 1024) { - return NextResponse.json({ error: "File size must be less than 400KB" }, { status: 400 }); - } + const file = formData.get("file") as File; + const uploadType = formData.get("uploadType") as string || 'event'; // Default to 'event' for backward compatibility - const filename = `${Date.now()}-${file.name.replace(/\s/g, '_')}`; - const buffer = await file.arrayBuffer(); - - const uploadResponse = await fetch(`${API_URL}/storage/v1/object/${bucketName}/${filename}`, { - method: "POST", - headers: { - apikey: API_KEY || "", - Authorization: `Bearer ${API_KEY}`, - "Content-Type": file.type, - }, - body: buffer, - }); - - if (!uploadResponse.ok) { - const errorBody = await uploadResponse.json(); - console.error("Supabase Storage Error:", errorBody); - throw new Error(`Failed to upload course image. Status: ${uploadResponse.status}`); - } - - const publicUrl = `${API_URL}/storage/v1/object/public/${bucketName}/${filename}`; - - const newCourse = await db - .insert(coursesTable) - .values({ - course_name, - course_description, - course_price: course_price.toString(), // Convert back to string for numeric DB type - courses_image: publicUrl, - }) - .returning(); - - return NextResponse.json({ message: "Course created successfully", course: newCourse[0] }, { status: 201 }); + if (!file) { + return Response.json({ error: "No file provided" }, { status: 400 }); + } - } else { - // ========== ORIGINAL: EVENT IMAGE UPLOAD LOGIC ========== + // Validate file type + if (!file.type.includes("jpeg") && !file.type.includes("jpg")) { + return Response.json( + { error: "Only JPEG files are allowed" }, + { status: 400 }, + ); + } - if (!file) { - return NextResponse.json({ error: "No file provided" }, { status: 400 }); - } + // Validate file size (400KB max) + if (file.size > 400 * 1024) { + return Response.json( + { error: "File size must be less than 400KB" }, + { status: 400 }, + ); + } - if (!file.type.includes("jpeg") && !file.type.includes("jpg")) { - return NextResponse.json({ error: "Only JPEG files are allowed" }, { status: 400 }); - } + // Determine the storage bucket based on the upload type + const storagePath = uploadType === 'course' ? 'courses' : 'event-images'; - if (file.size > 400 * 1024) { - return NextResponse.json({ error: "File size must be less than 400KB" }, { status: 400 }); - } + // Generate filename + const today = new Date().toISOString().split("T")[0]; + const timestamp = Date.now(); + const filename = `${today}-${timestamp}.jpg`; - const today = new Date().toISOString().split("T")[0]; - const timestamp = Date.now(); - const filename = `${today}-${timestamp}.jpg`; - const buffer = await file.arrayBuffer(); + // Convert file to buffer + const buffer = await file.arrayBuffer(); - const response = await fetch(`${API_URL}/storage/v1/object/event-images/${filename}`, { + // Upload to the determined storage bucket using the SERVICE_KEY + const response = await fetch( + `${API_URL}/storage/v1/object/${storagePath}/${filename}`, + { method: "POST", headers: { - apikey: API_KEY || "", - Authorization: `Bearer ${API_KEY}`, + apikey: SERVICE_KEY || "", + Authorization: `Bearer ${SERVICE_KEY}`, "Content-Type": file.type, }, body: buffer, - }); + }, + ); - if (!response.ok) { - throw new Error("Failed to upload image"); - } - - const publicUrl = `${API_URL}/storage/v1/object/public/event-images/${filename}`; - return NextResponse.json({ url: publicUrl }); + if (!response.ok) { + const errorBody = await response.json(); + console.error("Supabase Upload Error:", errorBody); + throw new Error(errorBody.message || "Failed to upload image to storage."); } + // Get public URL from the correct path + const publicUrl = `${API_URL}/storage/v1/object/public/${storagePath}/${filename}`; + + return Response.json({ url: publicUrl }); } catch (error) { - console.error("API Error:", error); - const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; - return NextResponse.json({ error: "Failed to process request", details: errorMessage }, { status: 500 }); + console.error("Upload error:", error); + const message = error instanceof Error ? error.message : "An unknown error occurred."; + return Response.json({ error: `Failed to upload image: ${message}` }, { status: 500 }); } } diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index e454898..bd0236b 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,4 +1,5 @@ import EventManagement from "@/components/admin/event-management"; +import CourseManagement from "@/components/admin/course-management"; // Import the new component import { checkAuth } from "@/lib/check-auth"; import { authOptions } from "@/lib/auth.config"; import { getServerSession } from "next-auth/next"; @@ -17,7 +18,16 @@ export default async function Page() { Welcome, administrator! You have special access privileges.

+ + {/* Existing Event Management */} + + {/* Spacer and Divider */} +
+ + {/* New Course Management */} + +
); } diff --git a/src/components/admin/course-management.tsx b/src/components/admin/course-management.tsx new file mode 100644 index 0000000..c47f10e --- /dev/null +++ b/src/components/admin/course-management.tsx @@ -0,0 +1,219 @@ +"use client"; + +import React, { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Plus } from "lucide-react"; +import { toast } from "sonner"; + +export default function CourseManagement() { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [uploading, setUploading] = useState(false); + + // Form state + const [courseName, setCourseName] = useState(""); + const [courseDescription, setCourseDescription] = useState(""); + const [coursePrice, setCoursePrice] = useState(""); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(null); + + const resetForm = () => { + setCourseName(""); + setCourseDescription(""); + setCoursePrice(""); + setImageFile(null); + setImagePreview(null); + }; + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) { + resetForm(); + return; + } + + // Validate file type (JPEG only) + if (!file.type.includes("jpeg") && !file.type.includes("jpg")) { + toast.error("Only JPEG files are allowed."); + e.target.value = ""; // Clear the file input + return; + } + + // Validate file size (400KB max) + if (file.size > 400 * 1024) { + toast.error("File size must be less than 400KB."); + e.target.value = ""; // Clear the file input + return; + } + + setImageFile(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + + const handleSaveCourse = async () => { + if (!courseName || !courseDescription || !coursePrice || !imageFile) { + toast.error("All fields, including the image, are required."); + return; + } + + setUploading(true); + let imageUrl = ""; + + try { + // Step 1: Upload the image + const imageFormData = new FormData(); + imageFormData.append("file", imageFile); + imageFormData.append("uploadType", "course"); // Specify the upload type for the backend + + const uploadRes = await fetch("/api/upload", { + method: "POST", + body: imageFormData, + }); + + if (!uploadRes.ok) { + const errorData = await uploadRes.json(); + throw new Error(errorData.error || "Failed to upload image."); + } + + const uploadData = await uploadRes.json(); + imageUrl = uploadData.url; // Assumes the backend returns { url: "..." } + + if (!imageUrl) { + throw new Error("Image URL was not returned from the upload."); + } + + // Step 2: Create the course with the returned image URL + const courseRes = await fetch("/api/courses", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + course_name: courseName, + course_description: courseDescription, + course_price: coursePrice, + courses_image: imageUrl, + }), + }); + + if (!courseRes.ok) { + const errorData = await courseRes.json(); + throw new Error(errorData.error || "Failed to create course."); + } + + toast.success("Course created successfully!"); + setIsDialogOpen(false); + resetForm(); + // Optionally, you could trigger a refetch of courses here if displaying them + } catch (error) { + const msg = error instanceof Error ? error.message : "An unknown error occurred."; + console.error("Error creating course:", msg); + toast.error(`Error: ${msg}`); + } finally { + setUploading(false); + } + }; + + return ( +
+
+

Course Management

+ + + + + + + Add a New Course + +
+
+ + setCourseName(e.target.value)} + placeholder="e.g., Introduction to Web Development" + /> +
+
+ + setCoursePrice(e.target.value)} + placeholder="e.g., 99.99" + /> +
+
+ + + {imagePreview && ( +
+ Image Preview +
+ )} +
+
+ +