diff --git a/frontend/edu-connect/package-lock.json b/frontend/edu-connect/package-lock.json index 3204ae3..7b3da1e 100644 --- a/frontend/edu-connect/package-lock.json +++ b/frontend/edu-connect/package-lock.json @@ -8,15 +8,25 @@ "name": "edu-connect", "version": "0.1.0", "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-navigation-menu": "^1.2.3", "@radix-ui/react-progress": "^1.1.1", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-toast": "^1.2.4", + "@radix-ui/react-tooltip": "^1.1.6", + "@tanstack/react-table": "^8.20.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "js-cookie": "^3.0.5", @@ -512,6 +522,34 @@ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.4.tgz", + "integrity": "sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.4", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", @@ -561,6 +599,66 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz", + "integrity": "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz", + "integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", @@ -617,6 +715,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz", + "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "^2.6.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -728,6 +862,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", @@ -972,6 +1115,38 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.2.tgz", + "integrity": "sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", @@ -1077,6 +1252,29 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz", + "integrity": "sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", @@ -1125,6 +1323,74 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz", + "integrity": "sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz", + "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -1301,6 +1567,39 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/frontend/edu-connect/package.json b/frontend/edu-connect/package.json index a6726bb..ddb34ff 100644 --- a/frontend/edu-connect/package.json +++ b/frontend/edu-connect/package.json @@ -3,21 +3,31 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -H 0.0.0.0", "build": "next build", - "start": "next start", + "start": "next start -H 0.0.0.0", "lint": "next lint" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-navigation-menu": "^1.2.3", "@radix-ui/react-progress": "^1.1.1", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-toast": "^1.2.4", + "@radix-ui/react-tooltip": "^1.1.6", + "@tanstack/react-table": "^8.20.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "js-cookie": "^3.0.5", diff --git a/frontend/edu-connect/src/app/(admin)/admin/dashboard/page.tsx b/frontend/edu-connect/src/app/(admin)/admin/dashboard/page.tsx new file mode 100644 index 0000000..1367006 --- /dev/null +++ b/frontend/edu-connect/src/app/(admin)/admin/dashboard/page.tsx @@ -0,0 +1,20 @@ +'use client' +import CommonContainer from "@/components/(dashboard)/elements/CommonContainer" +import AppContextProvider from "@/helpers/context/AppContextProvider" +import AdminView from "@/views/AdminView" + +const DashBoardIndexPage = () => { + return( + <> + + + +

hello

+
+
+
+ + ) +} + +export default DashBoardIndexPage \ No newline at end of file diff --git a/frontend/edu-connect/src/app/(admin)/admin/layout.tsx b/frontend/edu-connect/src/app/(admin)/admin/layout.tsx new file mode 100644 index 0000000..2d6a812 --- /dev/null +++ b/frontend/edu-connect/src/app/(admin)/admin/layout.tsx @@ -0,0 +1,22 @@ +import { Toaster } from "@/components/(dashboard)/ui/toaster" +import AppContextProvider from "@/helpers/context/AppContextProvider" +import { Suspense } from "react" + +const RootLayout :React.FC<{ + children :React.ReactNode +}> = ({ + children +}) => { + return( + <> + + + {children} + + + + + ) +} + +export default RootLayout \ No newline at end of file diff --git a/frontend/edu-connect/src/app/_partials/CommunitySection.tsx b/frontend/edu-connect/src/app/_partials/CommunitySection.tsx index 533e493..69e4724 100644 --- a/frontend/edu-connect/src/app/_partials/CommunitySection.tsx +++ b/frontend/edu-connect/src/app/_partials/CommunitySection.tsx @@ -1,7 +1,13 @@ import CommunityImage from '@/assets/img/community-collboration.jpg' +import { defaultFetcher } from '@/helpers/fetch.helper' +import { APP_BASE_URL } from '@/utils/constants' import Image from 'next/image' +import useSWR from 'swr' const CommunitySection = () => { + + const {data : AuthorCount} = useSWR(APP_BASE_URL+'' , defaultFetcher); + const { data : TotalCourses } = useSWR(APP_BASE_URL + '' , defaultFetcher) return(
@@ -13,7 +19,7 @@ const CommunitySection = () => {

-

500+

+

{}+

Expert Contributors

diff --git a/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx b/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx index c9985e1..b3d5b93 100644 --- a/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx +++ b/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx @@ -1,7 +1,11 @@ import React from 'react'; import CourseCard from '@/components/elements/CourseCard'; +import useSWR from 'swr'; +import { APP_BASE_URL } from '@/utils/constants'; +import { defaultFetcher } from '@/helpers/fetch.helper'; const FeaturedCourses = () => { + const { data : CourseList } = useSWR(APP_BASE_URL + '' , defaultFetcher) const courses = [ { id: 1, @@ -71,6 +75,7 @@ const FeaturedCourses = () => { return (
+

Featured Courses

diff --git a/frontend/edu-connect/src/app/_partials/HeroSection.tsx b/frontend/edu-connect/src/app/_partials/HeroSection.tsx index d4d2d24..6888b7e 100644 --- a/frontend/edu-connect/src/app/_partials/HeroSection.tsx +++ b/frontend/edu-connect/src/app/_partials/HeroSection.tsx @@ -1,7 +1,11 @@ import LearnWithUS from '@/assets/img/learn-with-us.jpg' +import { defaultFetcher } from '@/helpers/fetch.helper'; +import { APP_BASE_URL } from '@/utils/constants'; import Image from 'next/image'; +import useSWR from "swr" const HeroSection: React.FC = () => { + const {data : TotalUserCount } = useSWR(APP_BASE_URL + '/api/user/count' , defaultFetcher) return (
@@ -36,7 +40,7 @@ const HeroSection: React.FC = () => { />
-

10,000+

+

{}+

Active Learners

diff --git a/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx b/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx index ab761ef..16a4ea8 100644 --- a/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx +++ b/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx @@ -1,12 +1,85 @@ -import { useState } from 'react'; +import { FormEvent, useContext, useEffect, useState } from 'react'; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { EyeIcon, EyeOffIcon } from 'lucide-react'; +import Link from 'next/link'; +import { routes } from '@/lib/routes'; +import { useToast } from '@/hooks/use-toast'; +import { AuthContext } from '@/helpers/context/AuthProvider'; +import { setEduConnectAccessToken } from '@/helpers/token.helper'; +import { useRouter } from 'next/navigation'; export default function LoginForm() { + const router = useRouter() const [showPassword, setShowPassword] = useState(false); + const [loading ,setLoading] = useState(false) + const [inputValues , setInputValues] = useState>({}); + const [error , setError] = useState>({}); + + + const { fetchUser , user } = useContext(AuthContext); + const { toast } = useToast() + + const handleFormSubmit = async (e : FormEvent) :Promise => { + e.preventDefault(); + + setLoading(false) + const formData = new FormData(e.currentTarget) + Object.entries(inputValues).forEach(([key, value]) => { + formData.append(key, value); + }); + + try{ + const res = await fetch(`${process.env.NEXT_PUBLIC_EDU_CONNECT_HOST}/api/session/create` , { + method : 'POST' , + body : formData + }) + + const data : any = await res.json(); + + if(res.status == 200){ + toast({ + title : 'Sucessfully logged in !', + description : `${data?.message}`, + variant : 'success' + }) + + setInputValues({}) + fetchUser(data?.session_key) + setEduConnectAccessToken(data?.session_key) + router.push(routes.INDEX_PAGE) + }else{ + setError(data?.error) + toast({ + title : 'Error logging in !', + description : `${data?.message}`, + variant : 'success' + }) + } + }catch(e : any){ + toast({ + title : 'Error logging in !', + description : `${error?.message}`, + variant : 'success' + }) + }finally{ + setLoading(false) + } + + } + + useEffect(() => { + setError({}) + setInputValues({}) + },[]) + + useEffect(() => { + if(user){ + router.push(routes.INDEX_PAGE) + } + },[router]) return (
@@ -18,7 +91,7 @@ export default function LoginForm() { -
+
setInputValues((prev : any) => ({...prev , email : e.target.value}))} />
@@ -36,6 +110,8 @@ export default function LoginForm() { type={showPassword ? "text" : "password"} placeholder="Enter your password" className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500 pr-10" + onChange={(e) => setInputValues((prev : any) => ({...prev , password : e.target.value}))} + />
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx new file mode 100644 index 0000000..cd86274 --- /dev/null +++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Label } from "@/components/ui/label"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Check, X } from "lucide-react"; + +const QuizGenerator = () => { + // Submitted questions history + const [submittedQuestions] = useState([ + { + id: 1, + question: "What is React's Virtual DOM?", + options: [ + "A complete DOM copy", + "A lightweight copy of the DOM", + "A browser feature", + "A routing system" + ], + selectedOption: 0, // User selected + correctOption: 1, // Correct answer + }, + { + id: 2, + question: "What hook manages side effects?", + options: [ + "useState", + "useReducer", + "useEffect", + "useContext" + ], + selectedOption: 2, + correctOption: 2, + }, + ]); + + // Unsubmitted questions + const [pendingQuestions, setPendingQuestions] = useState([ + { + id: 1, + question: "Which of these is a state management library?", + options: ["Redux", "Axios", "Lodash", "Moment"], + selectedOption: null + }, + { + id: 2, + question: "What does CSS stand for?", + options: [ + "Computer Style Sheets", + "Cascading Style Sheets", + "Creative Style Sheets", + "Colorful Style Sheets" + ], + selectedOption: null + }, + ]); + + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); + + const handleOptionSelect = (value) => { + const updatedQuestions = [...pendingQuestions]; + updatedQuestions[currentQuestionIndex].selectedOption = parseInt(value); + setPendingQuestions(updatedQuestions); + }; + + const generateQuiz = () => { + // Reset selections and shuffle pending questions + const shuffledQuestions = [...pendingQuestions].map(q => ({ + ...q, + selectedOption: null + })); + setPendingQuestions(shuffledQuestions); + setCurrentQuestionIndex(0); + }; + + const handleNext = () => { + if (currentQuestionIndex < pendingQuestions.length - 1) { + setCurrentQuestionIndex(prev => prev + 1); + } + }; + + const handlePrevious = () => { + if (currentQuestionIndex > 0) { + setCurrentQuestionIndex(prev => prev - 1); + } + }; + + return ( +
+ {/* Submitted Questions History */} +
+ + + Quiz History + + + + {submittedQuestions.map((q, index) => ( +
+
+

{q.question}

+
+ {q.options.map((option, i) => ( +
+ {option} + {i === q.correctOption && ( + + )} + {i === q.selectedOption && i !== q.correctOption && ( + + )} +
+ ))} +
+
+ {index < submittedQuestions.length - 1 && } +
+ ))} +
+
+
+ + {/* Pending Questions Interface */} +
+ + + {pendingQuestions.length > 0 ? ( + + + + Question {currentQuestionIndex + 1} of {pendingQuestions.length} + + + +
+

+ {pendingQuestions[currentQuestionIndex].question} +

+ + + {pendingQuestions[currentQuestionIndex].options.map((option, i) => ( +
+ + +
+ ))} +
+ +
+ +
+
+
+
+ ) : ( + + + No pending questions available + + + )} +
+
+
+ ); +}; + +export default QuizGenerator; \ No newline at end of file diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx index 4272a22..f30e86c 100644 --- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx +++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx @@ -4,34 +4,36 @@ import { CourseData } from "@/helpers/apiSchema/course.schema" import PDFFlipBook from "./CoursePDFHolder" import CustomPDFViewer from "./TryPDF" import DiscussionSection from "@/components/elements/DiscussionForm" +import QuizGenerator from "./CourseGenerateQuestions" const CourseMedia : React.FC<{courseData : CourseData}> = ({courseData}) => { return(
-
- {/* Main Content */} -
- - -

Course Content

- -
-
-
+
+ {/* Main Content */} +
+ + +

Course Content

+ +
+
+
- {/* Sidebar */} -
- - -

Discussions

- -
-
+ {/* Sidebar */} +
+ + +

Discussions

+ +
+
+ +
- -
-
+
+
) } diff --git a/frontend/edu-connect/src/app/user/profile/_partials/UserTabContent.tsx b/frontend/edu-connect/src/app/user/profile/_partials/UserTabContent.tsx index 7c98285..46a442c 100644 --- a/frontend/edu-connect/src/app/user/profile/_partials/UserTabContent.tsx +++ b/frontend/edu-connect/src/app/user/profile/_partials/UserTabContent.tsx @@ -1,40 +1,203 @@ -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" +import { useContext, useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { AuthContext } from "@/helpers/context/AuthProvider"; +import { APP_BASE_URL } from "@/utils/constants"; +import { fetchHeader } from "@/helpers/fetch.helper"; +import { useToast } from "@/hooks/use-toast"; +import { getEduConnectAccessToken } from "@/helpers/token.helper"; +import { Loader } from "lucide-react"; const UserTabContent = () => { - return( + + const { user , fetchUser } = useContext(AuthContext); + const { toast } = useToast(); + const [loading , setLoading] = useState(false) + // Centralized state for all form inputs + const [inputValues, setInputValues] = useState({ + profile_picture: user?.profile_picture, + firstName: user?.firstName, + lastName: user?.lastName, + username: user?.username, + dob: user?.dob, + bio: user?.bio, + }); + const [error , setError] = useState>({}) + + // Handle input changes for text, date, and textarea fields + const handleInputChange = (field: string, value: string) => { + setInputValues((prev) => ({ + ...prev, + [field]: value, + })); + }; + + // Function to handle profile picture upload + const handleUploadPicture = async(event: React.ChangeEvent) : Promise => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => { + setInputValues((prev) => ({ + ...prev, + profile_picture: reader.result as string, + })); + }; + const formData = new FormData(); + formData.append('profile_picture' , file !) + try{ + const res = await fetch(APP_BASE_URL + '/api/profile/update-profile-picture' , { + headers : fetchHeader(), + method : 'PATCH', + body : formData + }) + const data = await res.json() + if(res.status == 200){ + toast({ + title : 'Sucessfully updated profile picture', + description : `${data?.message}`, + variant : 'success' + }) + fetchUser(getEduConnectAccessToken() !) + }else{ + toast({ + title : 'Error updatingprofile image', + description : `${data?.message}`, + variant : 'destructive' + }) + } + }catch(error:any){ + toast({ + title : 'Error updatingprofile image', + description : `${error?.message}`, + variant : 'destructive' + }) + } + reader.readAsDataURL(file); + } + }; + + // Function to handle the Save Changes button click + const handleSaveChanges = async() :Promise => { + + setLoading(true); + setError({}); + + const formData = new FormData(); + + Object.entries(inputValues).forEach(([key, value] : [key : string , value : any]) => { + if(key == 'profile_picture'){ + if(value instanceof File){ + formData.append(key, value); + } + }else{ + formData.append(key, value); + } + }); + + try{ + const res = await fetch(APP_BASE_URL + '/api/profile/me' , { + headers : fetchHeader(), + body : formData, + method : 'PUT' + }) + const data = await res.json() + if(res.status == 200){ + toast({ + title : 'Sucessfully Updated profile', + description : `${data?.message}`, + variant : 'success' + }) + fetchUser(getEduConnectAccessToken() !) + }else{ + toast({ + title : 'Sucessfully Updated profile', + description : `${data?.message}`, + variant : 'success' + }) + } + }catch(e : any){ + toast({ + title : 'Sucessfully Updated profile', + description : `${error?.message}`, + variant : 'success' + }) + }finally{ + setLoading(false) + } + }; + + useEffect(() => { + setInputValues( + { + profile_picture: user?.profile_picture, + firstName: user?.firstName, + lastName: user?.lastName, + username: user?.username, + dob: user?.dob?.split('T')?.at(0), + bio: user?.bio, + } + ) + },[user]) + + return ( - - Profile -

- Manage your personal information and profile settings. -

-
+ + Profile +

+ Manage your personal information and profile settings. +

+
- {/* Profile Picture */}
- No image + {inputValues.profile_picture ? ( + Profile + ) : ( + No image + )} +
+
+ +
-
- {/* Email */} {/* Name */}
- + handleInputChange("firstName", e.target.value)} + />
- + handleInputChange("lastName", e.target.value)} + />
@@ -42,42 +205,68 @@ const UserTabContent = () => {
- + handleInputChange("username", e.target.value)} + error={error?.username} + />

- This is your public display name. You can only change this once every 30 days. + This is your public display name. You can only change this once + every 30 days.

- handleInputChange("dob", e.target.value)} + error={error?.dob} />
- - - {/* Bio */}
-