diff --git a/frontend/edu-connect/src/app/(admin)/admin/users/_partials/UserTable.tsx b/frontend/edu-connect/src/app/(admin)/admin/users/_partials/UserTable.tsx new file mode 100644 index 0000000..8f154e5 --- /dev/null +++ b/frontend/edu-connect/src/app/(admin)/admin/users/_partials/UserTable.tsx @@ -0,0 +1,137 @@ +import DataTable from "@/components/(dashboard)/common/DataTable/DataTable" +import { Button } from "@/components/(dashboard)/ui/button" +import { Avatar, AvatarImage } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { routes } from "@/lib/routes" +import { ColumnDef } from "@tanstack/react-table" +import { ArrowUpDown } from "lucide-react" +import Link from "next/link" + +const UserTable :React.FC<{ + mutate : () => void + userData : Array + isLoading : boolean +}> = ({ + mutate , + userData , + isLoading +}) => { + + const columns: ColumnDef[] = [ + { + accessorKey: "sn", + header: "SN", + cell: ({ row }) => ( +
{row.index + 1}
+ ), + }, + { + id: 'name', + accessorFn: (row: any) => row.original?.firstName, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ + + +

{row?.original?.firstName}

+
+ ), + }, + { + id: 'email', + accessorFn: (row: any) => row.original?.email, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.original?.email}
+ ), + }, + { + id: 'dateOfBirth', + accessorFn: (row: any) => row.original?.dateOfBirth, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.original?.dateOfBirth}
+ ), + }, + { + id: 'isActivated', + accessorFn: (row: any) => row.original?.isActivated, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.original?.isActivated ? Active : Deactive}
+ ), + }, + { + id: 'last_online_at', + accessorFn: (row: any) => row.original?.lastOnline, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.original?.lastOnline ?? '-'} +
+ ), + }, +] + return( + <> + + + ) +} + +export default UserTable \ No newline at end of file diff --git a/frontend/edu-connect/src/app/(admin)/admin/users/page.tsx b/frontend/edu-connect/src/app/(admin)/admin/users/page.tsx new file mode 100644 index 0000000..b3a9777 --- /dev/null +++ b/frontend/edu-connect/src/app/(admin)/admin/users/page.tsx @@ -0,0 +1,56 @@ +'use client' +import UserTabContent from "@/app/user/profile/_partials/UserTabContent" +import BreadCrumbNav from "@/components/(dashboard)/common/BreadCumbNav/BreadCrumbNav" +import DataTable from "@/components/(dashboard)/common/DataTable/DataTable" +import ContentContainer from "@/components/(dashboard)/elements/ContentContainer" +import { PageHeading } from "@/components/(dashboard)/ui/title" +import CommonContainer from "@/components/elements/CommonContainer" +import AppContextProvider from "@/helpers/context/AppContextProvider" +import { defaultFetcher } from "@/helpers/fetch.helper" +import { routes } from "@/lib/routes" +import { APP_BASE_URL } from "@/utils/constants" +import AdminView from "@/views/AdminView" +import useSWR from "swr" +import UserTable from "./_partials/UserTable" + +const UsersIndexPage = () => { + const UserListURL = `${APP_BASE_URL}/api/admin/stats/userDetail` + const { data : UsersList , mutate , isLoading} = useSWR(UserListURL , defaultFetcher); + + + console.log(UsersList) + + return( + <> + + + + Users + + +
+ +
+
+
+
+
+ + ) +} + +export default UsersIndexPage + 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 16a4ea8..bf2a3dc 100644 --- a/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx +++ b/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx @@ -47,7 +47,7 @@ export default function LoginForm() { }) setInputValues({}) - fetchUser(data?.session_key) + await fetchUser(data?.session_key) setEduConnectAccessToken(data?.session_key) router.push(routes.INDEX_PAGE) }else{ @@ -55,14 +55,14 @@ export default function LoginForm() { toast({ title : 'Error logging in !', description : `${data?.message}`, - variant : 'success' + variant : 'destructive' }) } }catch(e : any){ toast({ title : 'Error logging in !', description : `${error?.message}`, - variant : 'success' + variant : 'destructive' }) }finally{ setLoading(false) @@ -82,7 +82,7 @@ export default function LoginForm() { },[router]) return ( -
+
Login @@ -107,23 +107,12 @@ export default function LoginForm() {
setInputValues((prev : any) => ({...prev , password : e.target.value}))} /> -
diff --git a/frontend/edu-connect/src/app/auth/register/_partials/registerForm.tsx b/frontend/edu-connect/src/app/auth/register/_partials/registerForm.tsx index 5df628a..89b1e97 100644 --- a/frontend/edu-connect/src/app/auth/register/_partials/registerForm.tsx +++ b/frontend/edu-connect/src/app/auth/register/_partials/registerForm.tsx @@ -1,13 +1,22 @@ -import { ChangeEvent, FormEvent, 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, Upload } from 'lucide-react'; +import { EyeIcon, EyeOffIcon, Upload, X } 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 SignupForm() { + const router = useRouter(); const [showPassword, setShowPassword] = useState(false); - const [formData, setFormData] = useState({ + const [loading, setLoading] = useState(false); + const [previewUrl, setPreviewUrl] = useState(null); + const [inputValues, setInputValues] = useState({ email: '', firstName: '', lastName: '', @@ -15,23 +24,131 @@ export default function SignupForm() { password: '', bio: '', dob: '', - profile_picture: null + profile_picture: null as File | null }); - const [error , setError] = useState>({}) + const [error, setError] = useState>({}); - const handleInputChange = (e : ChangeEvent) => { + const { fetchUser, user } = useContext(AuthContext); + const { toast } = useToast(); + + const handleInputChange = (e: React.ChangeEvent) => { const { id, value } = e.target; - setFormData(prev => ({ + setInputValues(prev => ({ ...prev, [id]: value })); }; - const handleSubmit = (e : FormEvent) => { - e.preventDefault(); - console.log('Form submitted:', formData); + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + if (file.size > 5 * 1024 * 1024) { + setError(prev => ({ + ...prev, + profile_picture: ['File size should be less than 5MB'] + })); + return; + } + if (!['image/jpeg', 'image/png', 'image/gif'].includes(file.type)) { + setError(prev => ({ + ...prev, + profile_picture: ['Please upload an image file (JPG, PNG, or GIF)'] + })); + return; + } + + // Create preview URL + const objectUrl = URL.createObjectURL(file); + setPreviewUrl(objectUrl); + + setInputValues(prev => ({ + ...prev, + profile_picture: file + })); + + // Clear any existing errors + setError(prev => ({ + ...prev, + profile_picture: null + })); + } + }; + + const removeImage = () => { + setInputValues(prev => ({ + ...prev, + profile_picture: null + })); + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + setPreviewUrl(null); + } }; + const handleSubmit = async(e : FormEvent) : Promise => { + e.preventDefault(); + const formData = new FormData(e.currentTarget) + Object.entries(inputValues).forEach(([key, value]) => { + if(value){ + formData.append(key, value); + } + }); + + try{ + const res = await fetch(`${process.env.NEXT_PUBLIC_EDU_CONNECT_HOST}/api/profile/register` , { + method : 'POST' , + body : formData + }) + + const data : any = await res.json(); + + if(res.status == 201){ + toast({ + title : 'Sucessfully registered in !', + description : `${data?.message}`, + variant : 'success' + }) + + setInputValues({ + email: '', + firstName: '', + lastName: '', + username: '', + password: '', + bio: '', + dob: '', + profile_picture: null as File | null + }) + + router.push(routes.LOGIN_ROUTES) + }else{ + setError(data?.error) + toast({ + title : 'Error signing in !', + description : `${data?.error}`, + variant : 'destructive' + }) + } + }catch(e : any){ + toast({ + title : 'Error signing in !', + description : `${error?.message}`, + variant : 'destructive' + }) + }finally{ + setLoading(false) + } + } + + // Clean up preview URL when component unmounts + useEffect(() => { + return () => { + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + }; + }, [previewUrl]); + return (
@@ -44,24 +161,79 @@ export default function SignupForm() {
+
+ +
+ {previewUrl ? ( +
+ Profile preview + +
+ ) : ( +
+
+ +
+
+ )} +
+ + +
+ {error?.profile_picture && ( +

{error.profile_picture}

+ )} +
+
+
+
+
@@ -73,24 +245,30 @@ export default function SignupForm() { +
+
@@ -99,23 +277,15 @@ export default function SignupForm() { - +
@@ -124,59 +294,46 @@ export default function SignupForm() { +