Merge branch 'main' of https://hackethon.ai/hack/HackXlbef/FreeBug
commit
b8bbe33815
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 3.9 MiB |
@ -0,0 +1 @@ |
|||||||
|
declare module 'js-cookie'; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@ |
|||||||
|
import CommunityImage from '@/assets/img/community-collboration.jpg' |
||||||
|
import Image from 'next/image' |
||||||
|
|
||||||
|
const CommunitySection = () => { |
||||||
|
return( |
||||||
|
<div className="bg-gray-50 border-t"> |
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16"> |
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center"> |
||||||
|
<div> |
||||||
|
<h2 className="text-3xl font-bold mb-6 text-purple-700">Join Our Growing Community</h2> |
||||||
|
<p className="text-gray-600 mb-8"> |
||||||
|
Connect with fellow researchers, educators, and learners. Share your knowledge, collaborate on courses, and participate in meaningful discussions. |
||||||
|
</p> |
||||||
|
<div className="grid grid-cols-2 gap-4 text-center"> |
||||||
|
<div className="bg-white p-4 rounded-lg shadow-sm border border-purple-700"> |
||||||
|
<p className="text-2xl font-bold text-purple-600">500+</p> |
||||||
|
<p className="text-gray-600">Expert Contributors</p> |
||||||
|
</div> |
||||||
|
<div className="bg-white p-4 rounded-lg shadow-sm border border-purple-700"> |
||||||
|
<p className="text-2xl font-bold text-purple-600">1000+</p> |
||||||
|
<p className="text-gray-600">Research-Based Courses</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="relative overlay-purple"> |
||||||
|
<Image src={CommunityImage} alt="Community collaboration" className="rounded-lg shadow-lg" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CommunitySection |
@ -0,0 +1,104 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import CourseCard from '@/components/elements/CourseCard'; |
||||||
|
|
||||||
|
const FeaturedCourses = () => { |
||||||
|
const courses = [ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
title: 'Foundation course to under stand about software', |
||||||
|
category: 'Data & Tech', |
||||||
|
lessons: 23, |
||||||
|
duration: '1hr 30 min', |
||||||
|
price: 32.00, |
||||||
|
originalPrice: 89.00, |
||||||
|
instructor: { |
||||||
|
name : 'Micie John', |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
rating: 4.5, |
||||||
|
reviews: 44, |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
title: 'Nidhies course to under stand about softwere', |
||||||
|
category: 'Mechanical', |
||||||
|
lessons: 29, |
||||||
|
duration: '2hr 30 min', |
||||||
|
price: 32.00, |
||||||
|
originalPrice: 89.00, |
||||||
|
instructor: { |
||||||
|
name : 'Micie John', |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
rating: 4.5, |
||||||
|
reviews: 44, |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 3, |
||||||
|
title: 'Minws course to under stand about solution', |
||||||
|
category: 'Development', |
||||||
|
lessons: 25, |
||||||
|
duration: '1hr 40 min', |
||||||
|
price: 40.00, |
||||||
|
originalPrice: 89.00, |
||||||
|
instructor: { |
||||||
|
name : 'Micie John', |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
rating: 4.5, |
||||||
|
reviews: 44, |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 3, |
||||||
|
title: 'Minws course to under stand about solution', |
||||||
|
category: 'Development', |
||||||
|
lessons: 25, |
||||||
|
duration: '1hr 40 min', |
||||||
|
price: 40.00, |
||||||
|
originalPrice: 89.00, |
||||||
|
instructor: { |
||||||
|
name : 'Micie John', |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
}, |
||||||
|
rating: 4.5, |
||||||
|
reviews: 44, |
||||||
|
image: 'https://dummyimage.com/300' |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="max-w-7xl mx-auto p-6"> |
||||||
|
<div className="flex justify-between items-center my-12"> |
||||||
|
<div> |
||||||
|
<h1 className="text-3xl font-bold">Featured Courses</h1> |
||||||
|
<h2 className="text-xl text-purple-700">View all featured courses</h2> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> |
||||||
|
|
||||||
|
{ |
||||||
|
courses?.map((course : Record<string,any> , index) => { |
||||||
|
return( |
||||||
|
<CourseCard |
||||||
|
key={index} |
||||||
|
id={course.id} |
||||||
|
image ={course?.image} |
||||||
|
title ={course?.title} |
||||||
|
category = {course?.category} |
||||||
|
lessons = {course?.lessons} |
||||||
|
instructor = {course?.instructor} |
||||||
|
duration={course?.duration} |
||||||
|
/> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default FeaturedCourses; |
@ -0,0 +1,66 @@ |
|||||||
|
|
||||||
|
type Feature = { |
||||||
|
title : string ,
|
||||||
|
description : string |
||||||
|
icon : React.ReactNode |
||||||
|
} |
||||||
|
const FeaturesSection = () => { |
||||||
|
|
||||||
|
const features: Feature[] = [ |
||||||
|
{ |
||||||
|
title: 'Research-Based Learning', |
||||||
|
description: 'Create and access courses developed by leading researchers and educators in their fields.', |
||||||
|
icon: ( |
||||||
|
<div className="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center"> |
||||||
|
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Knowledge Sharing', |
||||||
|
description: 'Join a vibrant community where educators and learners share insights and resources.', |
||||||
|
icon: ( |
||||||
|
<div className="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center"> |
||||||
|
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Interactive Assessments', |
||||||
|
description: 'Validate learning through comprehensive quizzes and assessments designed by experts.', |
||||||
|
icon: ( |
||||||
|
<div className="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center"> |
||||||
|
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
return( |
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-24"> |
||||||
|
<div className="text-center mb-16"> |
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">Why Choose EduConnect?</h2> |
||||||
|
<p className=" max-w-2xl mx-auto"> |
||||||
|
Our platform connects researchers, educators, and learners in a dynamic environment focused on quality education and knowledge sharing. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8"> |
||||||
|
{features.map((feature, index) => ( |
||||||
|
<div key={index} className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow border border-purple-700"> |
||||||
|
{feature.icon} |
||||||
|
<h3 className="text-xl font-semibold mt-4 mb-2">{feature.title}</h3> |
||||||
|
<p className="text-gray-600">{feature.description}</p> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default FeaturesSection |
@ -0,0 +1,49 @@ |
|||||||
|
import LearnWithUS from '@/assets/img/learn-with-us.jpg' |
||||||
|
import Image from 'next/image'; |
||||||
|
|
||||||
|
const HeroSection: React.FC = () => { |
||||||
|
return ( |
||||||
|
<div className="bg-gradient-to-br from-purple-900 to-indigo-900 text-white"> |
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-24"> |
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center"> |
||||||
|
<div> |
||||||
|
<div className="text-purple-300 mb-4"> |
||||||
|
RESEARCH-POWERED LEARNING PLATFORM |
||||||
|
</div> |
||||||
|
<h1 className="text-4xl md:text-6xl font-bold mb-6"> |
||||||
|
Connect, Learn, and Grow Together |
||||||
|
</h1> |
||||||
|
<p className="mb-8 text-gray-300 text-lg"> |
||||||
|
Join a community where researchers and educators create engaging |
||||||
|
courses, share knowledge, and help learners validate their |
||||||
|
understanding through interactive assessments. |
||||||
|
</p> |
||||||
|
<div className="flex gap-4"> |
||||||
|
<button className="bg-white text-gray-900 px-6 py-3 rounded-md font-medium"> |
||||||
|
Explore Courses |
||||||
|
</button> |
||||||
|
<button className="text-white border border-white px-6 py-3 rounded-md font-medium"> |
||||||
|
Create Course → |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="relative"> |
||||||
|
<div className='overlay-purple'> |
||||||
|
<Image |
||||||
|
src={LearnWithUS} |
||||||
|
alt="Collaborative learning" |
||||||
|
className="rounded-lg shadow-xl" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="absolute -bottom-4 -right-4 bg-purple-700 text-white p-4 rounded-lg shadow-lg"> |
||||||
|
<p className="font-bold">10,000+</p> |
||||||
|
<p className="text-sm">Active Learners</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default HeroSection |
@ -0,0 +1,82 @@ |
|||||||
|
import { 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'; |
||||||
|
|
||||||
|
export default function LoginForm() { |
||||||
|
const [showPassword, setShowPassword] = useState(false); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex items-center justify-center min-h-[60vh]"> |
||||||
|
<Card className="w-full max-w-md shadow-lg border-0"> |
||||||
|
<CardHeader className="space-y-3"> |
||||||
|
<CardTitle className="text-2xl font-bold text-purple-600">Login</CardTitle> |
||||||
|
<CardDescription className="text-gray-600"> |
||||||
|
Enter your email and password to login to your account |
||||||
|
</CardDescription> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<form className="space-y-6"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="email" className="text-gray-700">Email</Label> |
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email" |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="password" className="text-gray-700">Password</Label> |
||||||
|
<div className="relative"> |
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
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" |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
onClick={() => setShowPassword(!showPassword)} |
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" |
||||||
|
> |
||||||
|
{showPassword ? ( |
||||||
|
<EyeOffIcon className="h-5 w-5" /> |
||||||
|
) : ( |
||||||
|
<EyeIcon className="h-5 w-5" /> |
||||||
|
)} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="flex items-center justify-between text-sm"> |
||||||
|
<label className="flex items-center space-x-2 text-gray-600"> |
||||||
|
<input type="checkbox" className="rounded border-gray-300 text-purple-600 focus:ring-purple-500" /> |
||||||
|
<span>Remember me</span> |
||||||
|
</label> |
||||||
|
<a href="#" className="text-purple-600 hover:text-purple-700"> |
||||||
|
Forgot password? |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full h-11 bg-purple-600 hover:bg-purple-700 text-white font-semibold" |
||||||
|
> |
||||||
|
Login |
||||||
|
</Button> |
||||||
|
|
||||||
|
<div className="text-center text-sm text-gray-600"> |
||||||
|
Don't have an account?{' '} |
||||||
|
<a href="#" className="text-purple-600 hover:text-purple-700 font-semibold"> |
||||||
|
Register |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
'use client' |
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import LoginForm from "./_partials/LoginForm" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
|
||||||
|
const LoginPage : React.FC = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
<div className="py-16 bg-gray-50"> |
||||||
|
<LoginForm /> |
||||||
|
</div> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default LoginPage |
@ -0,0 +1,186 @@ |
|||||||
|
import { ChangeEvent, FormEvent, 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'; |
||||||
|
|
||||||
|
export default function SignupForm() { |
||||||
|
const [showPassword, setShowPassword] = useState(false); |
||||||
|
const [formData, setFormData] = useState({ |
||||||
|
email: '', |
||||||
|
firstName: '', |
||||||
|
lastName: '', |
||||||
|
username: '', |
||||||
|
password: '', |
||||||
|
bio: '', |
||||||
|
dob: '', |
||||||
|
profile_picture: null |
||||||
|
}); |
||||||
|
const [error , setError] = useState<Record<string,any>>({}) |
||||||
|
|
||||||
|
const handleInputChange = (e : ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { |
||||||
|
const { id, value } = e.target; |
||||||
|
setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
[id]: value |
||||||
|
})); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = (e : FormEvent<HTMLFormElement>) => { |
||||||
|
e.preventDefault(); |
||||||
|
console.log('Form submitted:', formData); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex items-center justify-center min-h-[80vh]"> |
||||||
|
<Card className="w-full max-w-lg shadow-lg border border-purple-700"> |
||||||
|
<CardHeader className="space-y-3"> |
||||||
|
<CardTitle className="text-2xl font-bold text-purple-600">Create Account</CardTitle> |
||||||
|
<CardDescription className="text-gray-600"> |
||||||
|
Fill in your details to create your account |
||||||
|
</CardDescription> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<form onSubmit={handleSubmit} className="space-y-6"> |
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="firstName" className="text-gray-700">First Name</Label> |
||||||
|
<Input
|
||||||
|
id="firstName" |
||||||
|
value={formData.firstName} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
placeholder="Enter your first name" |
||||||
|
error={error?.firstName} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="lastName" className="text-gray-700">Last Name</Label> |
||||||
|
<Input
|
||||||
|
id="lastName" |
||||||
|
value={formData.lastName} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
placeholder="Enter your last name" |
||||||
|
error={error?.lastName} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="email" className="text-gray-700">Email</Label> |
||||||
|
<Input
|
||||||
|
id="email" |
||||||
|
type="email" |
||||||
|
value={formData.email} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
placeholder="Enter your email" |
||||||
|
error={error?.email} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="username" className="text-gray-700">Username</Label> |
||||||
|
<Input
|
||||||
|
id="username" |
||||||
|
value={formData.username} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
placeholder="Choose a username" |
||||||
|
error={error?.username} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="password" className="text-gray-700">Password</Label> |
||||||
|
<div className="relative"> |
||||||
|
<Input
|
||||||
|
id="password" |
||||||
|
type={showPassword ? "text" : "password"} |
||||||
|
value={formData.password} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500 pr-10" |
||||||
|
placeholder="Create a strong password" |
||||||
|
error={error?.password} |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
onClick={() => setShowPassword(!showPassword)} |
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" |
||||||
|
> |
||||||
|
{showPassword ? ( |
||||||
|
<EyeOffIcon className="h-5 w-5" /> |
||||||
|
) : ( |
||||||
|
<EyeIcon className="h-5 w-5" /> |
||||||
|
)} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="dob" className="text-gray-700">Date of Birth</Label> |
||||||
|
<Input
|
||||||
|
id="dob" |
||||||
|
type="date" |
||||||
|
value={formData.dob} |
||||||
|
onChange={handleInputChange} |
||||||
|
error={error?.dob} |
||||||
|
className="h-11 px-4 bg-gray-50/50 border-gray-200 focus:border-purple-500 focus:ring-purple-500" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="bio" className="text-gray-700">Bio</Label> |
||||||
|
<textarea
|
||||||
|
id="bio" |
||||||
|
value={formData.bio} |
||||||
|
onChange={handleInputChange} |
||||||
|
className="w-full px-4 py-2 bg-gray-50/50 border-gray-200 rounded-md focus:border-purple-500 focus:ring-purple-500" |
||||||
|
rows={3} |
||||||
|
placeholder="Tell us about yourself" |
||||||
|
/> |
||||||
|
{ |
||||||
|
error?.bio && error?.bio.map((err : any) => { |
||||||
|
return( |
||||||
|
<span className='text-red-500'>{err}</span> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="profile_picture" className="text-gray-700">Profile Picture</Label> |
||||||
|
<div className="flex items-center space-x-2"> |
||||||
|
<Button |
||||||
|
type="button" |
||||||
|
variant="outline" |
||||||
|
className="h-11 px-4 border-gray-200 hover:bg-gray-50" |
||||||
|
> |
||||||
|
<Upload className="h-5 w-5 mr-2" /> |
||||||
|
Upload Photo |
||||||
|
</Button> |
||||||
|
<span className="text-sm text-gray-500">No file chosen</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full h-11 bg-purple-600 hover:bg-purple-700 text-white font-semibold" |
||||||
|
> |
||||||
|
Create Account |
||||||
|
</Button> |
||||||
|
|
||||||
|
<div className="text-center text-sm text-gray-600"> |
||||||
|
Already have an account?{' '} |
||||||
|
<a href="#" className="text-purple-600 hover:text-purple-700 font-semibold"> |
||||||
|
Login |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
'use client' |
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
import RegisterForm from "./_partials/registerForm" |
||||||
|
|
||||||
|
export default function Component() { |
||||||
|
return ( |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
<div className="py-6"> |
||||||
|
<RegisterForm /> |
||||||
|
</div> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
|
||||||
|
) |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
'use client'; |
||||||
|
import React from 'react'; |
||||||
|
import { Input } from "@/components/ui/input"; |
||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { Label } from "@/components/ui/label"; |
||||||
|
import { Textarea } from "@/components/ui/textarea"; |
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; |
||||||
|
import { |
||||||
|
Select, |
||||||
|
SelectContent, |
||||||
|
SelectItem, |
||||||
|
SelectTrigger, |
||||||
|
SelectValue, |
||||||
|
} from "@/components/ui/select"; |
||||||
|
import { TabsContent } from '@/components/ui/tabs'; |
||||||
|
|
||||||
|
const CourseActionFormTabContent :React.FC = () => { |
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { |
||||||
|
e.preventDefault(); |
||||||
|
// Handle form submission
|
||||||
|
const formData = new FormData(e.currentTarget); |
||||||
|
console.log('Form submitted:', Object.fromEntries(formData)); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle className="text-2xl">Create New Course</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<form onSubmit={handleSubmit} className="space-y-6"> |
||||||
|
{/* Course Title */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="title">Course Title</Label> |
||||||
|
<Input |
||||||
|
id="title" |
||||||
|
name="title" |
||||||
|
placeholder="Enter course title" |
||||||
|
required |
||||||
|
className="w-full" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Course Category */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="category">Category</Label> |
||||||
|
<Select name="category"> |
||||||
|
<SelectTrigger> |
||||||
|
<SelectValue placeholder="Select a category" /> |
||||||
|
</SelectTrigger> |
||||||
|
<SelectContent> |
||||||
|
<SelectItem value="programming">Programming</SelectItem> |
||||||
|
<SelectItem value="design">Design</SelectItem> |
||||||
|
<SelectItem value="business">Business</SelectItem> |
||||||
|
<SelectItem value="marketing">Marketing</SelectItem> |
||||||
|
</SelectContent> |
||||||
|
</Select> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Course Description */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="description">Description</Label> |
||||||
|
<Textarea |
||||||
|
id="description" |
||||||
|
name="description" |
||||||
|
placeholder="Enter course description" |
||||||
|
className="min-h-32" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Estimated Learning Hours */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="estimatedHours">Estimated Learning Hours</Label> |
||||||
|
<Input |
||||||
|
id="estimatedHours" |
||||||
|
name="estimatedHours" |
||||||
|
type="number" |
||||||
|
min="1" |
||||||
|
placeholder="Enter estimated hours" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Required Pages */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="requiredPages">Required Pages</Label> |
||||||
|
<Input |
||||||
|
id="requiredPages" |
||||||
|
name="requiredPages" |
||||||
|
type="number" |
||||||
|
min="1" |
||||||
|
placeholder="Enter number of required pages" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* PDF Upload */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="coursePdf">Course PDF</Label> |
||||||
|
<Input |
||||||
|
id="coursePdf" |
||||||
|
name="coursePdf" |
||||||
|
type="file" |
||||||
|
accept=".pdf" |
||||||
|
className="cursor-pointer" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Submit Button */} |
||||||
|
<div className="pt-4"> |
||||||
|
<Button type="submit" className="w-full"> |
||||||
|
Create Course |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default CourseActionFormTabContent; |
@ -0,0 +1,9 @@ |
|||||||
|
const MyCoursesTabContent : React.FC = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
|
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default MyCoursesTabContent |
@ -0,0 +1,46 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; |
||||||
|
import MyCoursesActionContent from './MyCoursesTabContent'; |
||||||
|
import CourseActionFormTabContent from './CoursesActionModal'; |
||||||
|
|
||||||
|
const MyCoursesWrapper = () => { |
||||||
|
return ( |
||||||
|
<div className="container mx-auto py-8"> |
||||||
|
<Tabs defaultValue="general" className="flex gap-6 min-h-[50vh]"> |
||||||
|
<div className="min-w-[240px]"> |
||||||
|
<TabsList className="flex flex-col h-auto bg-transparent p-0"> |
||||||
|
<TabsTrigger
|
||||||
|
value="generali" |
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md " |
||||||
|
> |
||||||
|
Profile |
||||||
|
</TabsTrigger> |
||||||
|
<TabsTrigger
|
||||||
|
value="email"
|
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md" |
||||||
|
> |
||||||
|
Account |
||||||
|
</TabsTrigger> |
||||||
|
<TabsTrigger
|
||||||
|
value="password"
|
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md" |
||||||
|
> |
||||||
|
Security |
||||||
|
</TabsTrigger> |
||||||
|
</TabsList> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="flex-1"> |
||||||
|
<TabsContent value="general" className="mt-0"> |
||||||
|
{/* <MyCoursesActionContent /> */} |
||||||
|
<CourseActionFormTabContent /> |
||||||
|
</TabsContent> |
||||||
|
{/* <UserEmailUpdateTabContent /> |
||||||
|
<UserPasswordUpdateTabContent /> */} |
||||||
|
</div> |
||||||
|
</Tabs> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default MyCoursesWrapper; |
@ -0,0 +1,19 @@ |
|||||||
|
'use client' |
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import MyCoursesWrapper from "./_partials/myCoursesTabWrapper" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
|
||||||
|
|
||||||
|
const MyCourses : React.FC = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
<MyCoursesWrapper /> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default MyCourses |
@ -1,101 +1,21 @@ |
|||||||
import Image from "next/image"; |
'use client' |
||||||
|
|
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider"; |
||||||
|
import CommonView from "@/views/CommonView"; |
||||||
|
import HeroSection from "./_partials/HeroSection"; |
||||||
|
import FeaturesSection from "./_partials/FeaturesSection"; |
||||||
|
import CommunitySection from "./_partials/CommunitySection"; |
||||||
|
import FeaturedCourses from "./_partials/FeaturedCourses"; |
||||||
|
|
||||||
export default function Home() { |
export default function Home() { |
||||||
return ( |
return ( |
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> |
<AppContextProvider> |
||||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> |
<CommonView> |
||||||
<Image |
<HeroSection /> |
||||||
className="dark:invert" |
<FeaturedCourses /> |
||||||
src="https://nextjs.org/icons/next.svg" |
<FeaturesSection /> |
||||||
alt="Next.js logo" |
<CommunitySection /> |
||||||
width={180} |
</CommonView> |
||||||
height={38} |
</AppContextProvider> |
||||||
priority |
|
||||||
/> |
|
||||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> |
|
||||||
<li className="mb-2"> |
|
||||||
Get started by editing{" "} |
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> |
|
||||||
src/app/page.tsx |
|
||||||
</code> |
|
||||||
. |
|
||||||
</li> |
|
||||||
<li>Save and see your changes instantly.</li> |
|
||||||
</ol> |
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row"> |
|
||||||
<a |
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" |
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
> |
|
||||||
<Image |
|
||||||
className="dark:invert" |
|
||||||
src="https://nextjs.org/icons/vercel.svg" |
|
||||||
alt="Vercel logomark" |
|
||||||
width={20} |
|
||||||
height={20} |
|
||||||
/> |
|
||||||
Deploy now |
|
||||||
</a> |
|
||||||
<a |
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" |
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
> |
|
||||||
Read our docs |
|
||||||
</a> |
|
||||||
</div> |
|
||||||
</main> |
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> |
|
||||||
<a |
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
> |
|
||||||
<Image |
|
||||||
aria-hidden |
|
||||||
src="https://nextjs.org/icons/file.svg" |
|
||||||
alt="File icon" |
|
||||||
width={16} |
|
||||||
height={16} |
|
||||||
/> |
|
||||||
Learn |
|
||||||
</a> |
|
||||||
<a |
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
> |
|
||||||
<Image |
|
||||||
aria-hidden |
|
||||||
src="https://nextjs.org/icons/window.svg" |
|
||||||
alt="Window icon" |
|
||||||
width={16} |
|
||||||
height={16} |
|
||||||
/> |
|
||||||
Examples |
|
||||||
</a> |
|
||||||
<a |
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
> |
|
||||||
<Image |
|
||||||
aria-hidden |
|
||||||
src="https://nextjs.org/icons/globe.svg" |
|
||||||
alt="Globe icon" |
|
||||||
width={16} |
|
||||||
height={16} |
|
||||||
/> |
|
||||||
Go to nextjs.org → |
|
||||||
</a> |
|
||||||
</footer> |
|
||||||
</div> |
|
||||||
); |
); |
||||||
} |
} |
||||||
|
@ -0,0 +1,47 @@ |
|||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
CardHeader, |
||||||
|
CardTitle, |
||||||
|
} from "@/components/ui/card"; |
||||||
|
import { Input } from "@/components/ui/input"; |
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; |
||||||
|
|
||||||
|
const UserEmailUpdateTabContent = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<TabsContent value="email" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Email Settings</CardTitle> |
||||||
|
<p className="text-sm text-gray-500"> |
||||||
|
Manage your email addresses and verification status. |
||||||
|
</p> |
||||||
|
</CardHeader> |
||||||
|
<CardContent className="space-y-6"> |
||||||
|
|
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Primary Email Address</label> |
||||||
|
<div className="p-4 border rounded-lg space-y-4"> |
||||||
|
<div className="flex items-center justify-between"> |
||||||
|
<span>your@email.com</span> |
||||||
|
<span className="text-sm text-green-600">✓ Verified</span> |
||||||
|
{ |
||||||
|
false &&
|
||||||
|
<Button>Add & Verify</Button> |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default UserEmailUpdateTabContent |
@ -0,0 +1,46 @@ |
|||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
CardHeader, |
||||||
|
CardTitle, |
||||||
|
} from "@/components/ui/card"; |
||||||
|
import { Input } from "@/components/ui/input"; |
||||||
|
import { TabsContent } from "@/components/ui/tabs"; |
||||||
|
|
||||||
|
const UserPasswordUpdateTabContent = () => { |
||||||
|
return( |
||||||
|
<TabsContent value="password" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Update Password</CardTitle> |
||||||
|
<p className="text-sm text-gray-500"> |
||||||
|
Change your password to keep your account secure. |
||||||
|
</p> |
||||||
|
</CardHeader> |
||||||
|
<CardContent className="space-y-6"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Current Password</label> |
||||||
|
<Input type="password" required /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">New Password</label> |
||||||
|
<Input type="password" required/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Confirm New Password</label> |
||||||
|
<Input type="password" required/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mx-auto w-fit"> |
||||||
|
<Button className="mt-6 w-full md:min-w-[600px] min-w-[300px] bg-purple-700">Save Changes</Button> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default UserPasswordUpdateTabContent |
@ -0,0 +1,83 @@ |
|||||||
|
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" |
||||||
|
|
||||||
|
const UserTabContent = () => { |
||||||
|
return( |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Profile</CardTitle> |
||||||
|
<p className="text-sm text-gray-500"> |
||||||
|
Manage your personal information and profile settings. |
||||||
|
</p> |
||||||
|
</CardHeader> |
||||||
|
<CardContent className="space-y-6"> |
||||||
|
|
||||||
|
{/* Profile Picture */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Profile Picture</label> |
||||||
|
<div className="flex items-center gap-4"> |
||||||
|
<div className="h-16 w-16 rounded-full bg-gray-100 flex items-center justify-center"> |
||||||
|
<span className="text-gray-500 text-sm">No image</span> |
||||||
|
</div> |
||||||
|
<Button variant="outline">Upload Image</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/* Email */} |
||||||
|
|
||||||
|
{/* Name */} |
||||||
|
<div className="grid grid-cols-2 gap-4"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">First Name</label> |
||||||
|
<Input defaultValue="John" /> |
||||||
|
</div> |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Last Name</label> |
||||||
|
<Input defaultValue="Doe" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Username */} |
||||||
|
<div className="grid grid-cols-2 gap-4"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Username</label> |
||||||
|
<Input defaultValue="johndoe123" /> |
||||||
|
<p className="text-sm text-gray-500"> |
||||||
|
This is your public display name. You can only change this once every 30 days. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Date of Birth</label> |
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
defaultValue="1990-01-01" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Bio */} |
||||||
|
<div className="space-y-2"> |
||||||
|
<label className="text-sm font-medium">Bio</label> |
||||||
|
<Textarea
|
||||||
|
defaultValue="This is a test user." |
||||||
|
placeholder="Tell us about yourself" |
||||||
|
/> |
||||||
|
<p className="text-sm text-gray-500"> |
||||||
|
You can @mention other users and organizations to link to them. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Update Button */} |
||||||
|
<div className="mx-auto w-fit"> |
||||||
|
<Button className="mt-6 w-full md:min-w-[600px] min-w-[300px] bg-purple-700">Save Changes</Button> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default UserTabContent |
@ -0,0 +1,96 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { |
||||||
|
Card, |
||||||
|
CardContent, |
||||||
|
CardHeader, |
||||||
|
CardTitle, |
||||||
|
} from "@/components/ui/card"; |
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; |
||||||
|
import UserTabContent from './UserTabContent'; |
||||||
|
import UserEmailUpdateTabContent from './UserEmailUpdateTabContent'; |
||||||
|
import UserPasswordUpdateTabContent from './UserPasswordUpdateTabContent'; |
||||||
|
|
||||||
|
const ProfileSettings = () => { |
||||||
|
return ( |
||||||
|
<div className="container mx-auto py-8"> |
||||||
|
<Tabs defaultValue="general" className="flex gap-6 min-h-[50vh]"> |
||||||
|
<div className="min-w-[240px]"> |
||||||
|
<TabsList className="flex flex-col h-auto bg-transparent p-0"> |
||||||
|
<TabsTrigger
|
||||||
|
value="general"
|
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md " |
||||||
|
> |
||||||
|
Profile |
||||||
|
</TabsTrigger> |
||||||
|
<TabsTrigger
|
||||||
|
value="email"
|
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md" |
||||||
|
> |
||||||
|
Account |
||||||
|
</TabsTrigger> |
||||||
|
<TabsTrigger
|
||||||
|
value="password"
|
||||||
|
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md" |
||||||
|
> |
||||||
|
Security |
||||||
|
</TabsTrigger> |
||||||
|
</TabsList> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="flex-1"> |
||||||
|
<TabsContent value="general" className="mt-0"> |
||||||
|
<UserTabContent /> |
||||||
|
</TabsContent> |
||||||
|
<UserEmailUpdateTabContent /> |
||||||
|
<UserPasswordUpdateTabContent /> |
||||||
|
|
||||||
|
<TabsContent value="account" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Account</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<p>Account settings coming soon...</p> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
|
||||||
|
<TabsContent value="appearance" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Appearance</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<p>Appearance settings coming soon...</p> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
|
||||||
|
<TabsContent value="notifications" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Notifications</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<p>Notification settings coming soon...</p> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
|
||||||
|
<TabsContent value="display" className="mt-0"> |
||||||
|
<Card> |
||||||
|
<CardHeader> |
||||||
|
<CardTitle>Display</CardTitle> |
||||||
|
</CardHeader> |
||||||
|
<CardContent> |
||||||
|
<p>Display settings coming soon...</p> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</TabsContent> |
||||||
|
</div> |
||||||
|
</Tabs> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ProfileSettings; |
@ -0,0 +1,19 @@ |
|||||||
|
'use client' |
||||||
|
|
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import ProfileSettings from "./_partials/profile" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
|
||||||
|
const UserProfileIndex = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
<ProfileSettings /> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default UserProfileIndex |
After Width: | Height: | Size: 522 KiB |
After Width: | Height: | Size: 301 KiB |
@ -0,0 +1,13 @@ |
|||||||
|
import { ElementType } from 'react'; |
||||||
|
|
||||||
|
export interface NavItem { |
||||||
|
title: string; |
||||||
|
href: string; |
||||||
|
description: string; |
||||||
|
icon: ElementType; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ListItemProps extends React.ComponentPropsWithoutRef<"a"> { |
||||||
|
icon?: ElementType; |
||||||
|
title?: string; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
import { Button } from "@/components/ui/button" |
||||||
|
|
||||||
|
const HeaderFeatureSection : React.FC = () => { |
||||||
|
return( |
||||||
|
<div className="flex"> |
||||||
|
<Button> |
||||||
|
Login |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default HeaderFeatureSection |
@ -0,0 +1,7 @@ |
|||||||
|
const Login : React.FC = () => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
|
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
import { Input } from "@/components/ui/input" |
||||||
|
import { Search } from "lucide-react"; |
||||||
|
import { useState } from "react"; |
||||||
|
|
||||||
|
const SearchBar = () => { |
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>(''); |
||||||
|
|
||||||
|
const handleSearch = (e: React.FormEvent) => { |
||||||
|
e.preventDefault(); |
||||||
|
// Handle search logic here
|
||||||
|
console.log('Searching for:', searchQuery); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
return( |
||||||
|
<> |
||||||
|
<form
|
||||||
|
onSubmit={handleSearch} |
||||||
|
className="hidden sm:flex flex-1 mx-4 max-w-md relative" |
||||||
|
> |
||||||
|
<div className="relative w-full"> |
||||||
|
<Input |
||||||
|
type="search" |
||||||
|
placeholder="Search courses..." |
||||||
|
className="w-full pl-10 bg-purple-700/10 border-white/20 text-white placeholder:text-white/50 focus:ring-[#9333EA] focus:border-[#9333EA]" |
||||||
|
value={searchQuery} |
||||||
|
onChange={(e) => setSearchQuery(e.target.value)} |
||||||
|
/> |
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/50" /> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
<div className="sm:hidden px-4 pb-4"> |
||||||
|
<form onSubmit={handleSearch} className="relative"> |
||||||
|
<Input |
||||||
|
type="search" |
||||||
|
placeholder="Search courses..." |
||||||
|
className="w-full pl-10 bg-white/10 border-white/20 text-white placeholder:text-white/50 focus:ring-[#9333EA] focus:border-[#9333EA]" |
||||||
|
value={searchQuery} |
||||||
|
onChange={(e) => setSearchQuery(e.target.value)} |
||||||
|
/> |
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/50" /> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default SearchBar |
@ -0,0 +1,34 @@ |
|||||||
|
|
||||||
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "@/components/ui/dropdown-menu" |
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" |
||||||
|
import Link from "next/link" |
||||||
|
import { Button } from "@/components/ui/button" |
||||||
|
|
||||||
|
export default function Component() { |
||||||
|
return ( |
||||||
|
<DropdownMenu> |
||||||
|
<DropdownMenuTrigger asChild> |
||||||
|
<Avatar className="h-9 w-9"> |
||||||
|
<AvatarImage src="/placeholder-user.jpg" alt="@shadcn" /> |
||||||
|
<AvatarFallback>JP</AvatarFallback> |
||||||
|
<span className="sr-only">Toggle user menu</span> |
||||||
|
</Avatar> |
||||||
|
</DropdownMenuTrigger> |
||||||
|
<DropdownMenuContent className="w-56"> |
||||||
|
<DropdownMenuItem className="font-bold text-lg">John Doe</DropdownMenuItem> |
||||||
|
<DropdownMenuSeparator /> |
||||||
|
<DropdownMenuItem asChild> |
||||||
|
<Link href="#" className="block w-full text-left" prefetch={false}> |
||||||
|
Profile |
||||||
|
</Link> |
||||||
|
</DropdownMenuItem> |
||||||
|
<DropdownMenuSeparator /> |
||||||
|
<DropdownMenuItem asChild> |
||||||
|
<Button variant="outline" className="block w-full text-left"> |
||||||
|
Logout |
||||||
|
</Button> |
||||||
|
</DropdownMenuItem> |
||||||
|
</DropdownMenuContent> |
||||||
|
</DropdownMenu> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { Menu, X } from "lucide-react"; |
||||||
|
import { DesktopNav } from '../Navbar/desktopNav'; |
||||||
|
import { MobileNav } from '../Navbar/mobileNav'; |
||||||
|
import SearchBar from './_partials/searchBar'; |
||||||
|
|
||||||
|
const Header = () => { |
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false); |
||||||
|
|
||||||
|
const toggleMenu = () => { |
||||||
|
setIsMenuOpen(!isMenuOpen); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<header className="relative bg-zinc-900"> |
||||||
|
<div className="container mx-auto px-4"> |
||||||
|
<div className="flex items-center justify-between py-4"> |
||||||
|
{/* Logo */} |
||||||
|
<h1 className="brand-logo text-purple-700 font-bold text-2xl">EDU CONNECT</h1> |
||||||
|
<SearchBar /> |
||||||
|
<div className='flex gap-8'> |
||||||
|
<DesktopNav /> |
||||||
|
<div className="hidden md:block space-x-4"> |
||||||
|
<Button className='bg-purple-700'> |
||||||
|
Login |
||||||
|
</Button> |
||||||
|
<Button className='bg-purple-700'> |
||||||
|
Register |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Hamburger Menu Button */} |
||||||
|
<button |
||||||
|
onClick={toggleMenu} |
||||||
|
className="md:hidden p-2 rounded-md hover:bg-gray-100" |
||||||
|
> |
||||||
|
{isMenuOpen ? ( |
||||||
|
<X className="h-6 w-6" /> |
||||||
|
) : ( |
||||||
|
<Menu className="h-6 w-6" /> |
||||||
|
)} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Mobile Navigation */} |
||||||
|
<MobileNav isOpen={isMenuOpen} /> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Header; |
@ -0,0 +1,53 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Link from "next/link"; |
||||||
|
import { NavigationMenu,NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; |
||||||
|
import { Book, Home, User } from "lucide-react"; |
||||||
|
|
||||||
|
export const DesktopNav = () => { |
||||||
|
return ( |
||||||
|
<div className="hidden md:block"> |
||||||
|
<NavigationMenu> |
||||||
|
<NavigationMenuList> |
||||||
|
{/* <NavigationMenuItem> |
||||||
|
<NavigationMenuTrigger className='bg-zinc-900 text-white hover:bg-zinc-600'> |
||||||
|
<Home className="mr-2 h-4 w-4" /> |
||||||
|
Menu |
||||||
|
</NavigationMenuTrigger> |
||||||
|
<NavigationMenuContent> |
||||||
|
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]"> |
||||||
|
{navigationItems.map((item : Record<string,any> , index : number) => ( |
||||||
|
<ListItem |
||||||
|
key={item.title} |
||||||
|
title={item.title} |
||||||
|
href={item.href} |
||||||
|
icon={item.icon} |
||||||
|
> |
||||||
|
{item.description} |
||||||
|
</ListItem> |
||||||
|
))} |
||||||
|
</ul> |
||||||
|
</NavigationMenuContent> |
||||||
|
</NavigationMenuItem> */} |
||||||
|
|
||||||
|
<NavigationMenuItem> |
||||||
|
<Link href="/docs" legacyBehavior passHref> |
||||||
|
<NavigationMenuLink className={navigationMenuTriggerStyle()}> |
||||||
|
<Home className="mr-2 h-4 w-4" /> |
||||||
|
Home |
||||||
|
</NavigationMenuLink> |
||||||
|
</Link> |
||||||
|
</NavigationMenuItem> |
||||||
|
|
||||||
|
<NavigationMenuItem> |
||||||
|
<Link href="/profile" legacyBehavior passHref> |
||||||
|
<NavigationMenuLink className={navigationMenuTriggerStyle()}> |
||||||
|
<Book className="mr-2 h-4 w-4" /> |
||||||
|
All Courses |
||||||
|
</NavigationMenuLink> |
||||||
|
</Link> |
||||||
|
</NavigationMenuItem> |
||||||
|
</NavigationMenuList> |
||||||
|
</NavigationMenu> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,32 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { NavigationMenuLink } from "@/components/ui/navigation-menu"; |
||||||
|
import { cn } from "@/lib/utils"; |
||||||
|
import { ListItemProps } from '../Header/_partials/header.type'; |
||||||
|
|
||||||
|
export const ListItem = React.forwardRef<React.ElementRef<"a">, ListItemProps>( |
||||||
|
({ className, title, children, icon: Icon, ...props }, ref) => { |
||||||
|
return ( |
||||||
|
<li> |
||||||
|
<NavigationMenuLink asChild> |
||||||
|
<a |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-zinc-500 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground bg-zinc-900 text-white", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<div className="flex items-center text-sm font-medium leading-none"> |
||||||
|
{Icon && <Icon className="mr-2 h-4 w-4" />} |
||||||
|
{title} |
||||||
|
</div> |
||||||
|
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground mt-2"> |
||||||
|
{children} |
||||||
|
</p> |
||||||
|
</a> |
||||||
|
</NavigationMenuLink> |
||||||
|
</li> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
ListItem.displayName = "ListItem"; |
@ -0,0 +1,34 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Link from "next/link"; |
||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { navigationItems } from './navRoutes'; |
||||||
|
|
||||||
|
interface MobileNavProps { |
||||||
|
isOpen: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const MobileNav: React.FC<MobileNavProps> = ({ isOpen }) => { |
||||||
|
if (!isOpen) return null; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="md:hidden absolute top-full left-0 right-0 bg-white border-t shadow-lg"> |
||||||
|
<nav className="px-4 py-2"> |
||||||
|
{navigationItems.map((item :Record<string,any> , index : number) => ( |
||||||
|
<Link |
||||||
|
key={item.title} |
||||||
|
href={item.href} |
||||||
|
className="flex items-center px-4 py-3 hover:bg-gray-100 rounded-md" |
||||||
|
> |
||||||
|
<item.icon className="mr-3 h-5 w-5" /> |
||||||
|
<span>{item.title}</span> |
||||||
|
</Link> |
||||||
|
))} |
||||||
|
<div className="px-4 py-3"> |
||||||
|
<Button className="w-full"> |
||||||
|
Login |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</nav> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,41 @@ |
|||||||
|
import { Home, Settings, Book, FileText, Bell, User } from "lucide-react"; |
||||||
|
import { NavItem } from "../Header/_partials/header.type"; |
||||||
|
|
||||||
|
export const navigationItems: NavItem[] = [ |
||||||
|
{ |
||||||
|
title: "Dashboard", |
||||||
|
href: "/dashboard", |
||||||
|
description: "Overview of your account and key metrics", |
||||||
|
icon: Home |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Settings", |
||||||
|
href: "/settings", |
||||||
|
description: "Manage your account preferences and configuration", |
||||||
|
icon: Settings |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Documentation", |
||||||
|
href: "/docs", |
||||||
|
description: "Detailed guides and API references", |
||||||
|
icon: Book |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Reports", |
||||||
|
href: "/reports", |
||||||
|
description: "View and generate activity reports", |
||||||
|
icon: FileText |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Notifications", |
||||||
|
href: "/notifications", |
||||||
|
description: "Manage your alerts and notifications", |
||||||
|
icon: Bell |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Profile", |
||||||
|
href: "/profile", |
||||||
|
description: "View and edit your profile information", |
||||||
|
icon: User |
||||||
|
}, |
||||||
|
]; |
@ -0,0 +1,19 @@ |
|||||||
|
const CommonContainer = ({ |
||||||
|
children, |
||||||
|
title, |
||||||
|
className |
||||||
|
} : { |
||||||
|
children : React.ReactNode |
||||||
|
title? : string |
||||||
|
className? : string |
||||||
|
}) => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
|
||||||
|
<title>{title ?? 'EDCUCONNECT'}</title> |
||||||
|
<div className={`container py-4 px-4 sm:px-8 mx-auto ${className}`}>{children}</div> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
export default CommonContainer |
||||||
|
|
@ -0,0 +1,73 @@ |
|||||||
|
import { BookOpen, Clock, Heart } from "lucide-react" |
||||||
|
import { Card, CardContent } from "../ui/card" |
||||||
|
|
||||||
|
|
||||||
|
interface CourseInterface { |
||||||
|
id : string , |
||||||
|
image :string | null
|
||||||
|
title : string |
||||||
|
category : string |
||||||
|
lessons : string |
||||||
|
duration : string |
||||||
|
instructor : { |
||||||
|
image : string |
||||||
|
name : string |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const CourseCard : React.FC<CourseInterface> = ({ |
||||||
|
id, |
||||||
|
image, |
||||||
|
title, |
||||||
|
category, |
||||||
|
lessons , |
||||||
|
instructor, |
||||||
|
duration |
||||||
|
}) => { |
||||||
|
return( |
||||||
|
<Card key={id} className="overflow-hidden"> |
||||||
|
<div className="relative"> |
||||||
|
|
||||||
|
<img |
||||||
|
src={image!} |
||||||
|
alt={title} |
||||||
|
className="w-full h-48 object-cover" |
||||||
|
/> |
||||||
|
<span className="absolute top-4 left-4 bg-purple-700/90 text-white px-3 py-1 rounded-full text-sm"> |
||||||
|
{category} |
||||||
|
</span> |
||||||
|
<button className="absolute top-4 right-4 p-2 bg-white/90 rounded-full hover:bg-white"> |
||||||
|
<Heart className="w-4 h-4 fill-red-500 text-red-500" /> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<CardContent className="p-6"> |
||||||
|
<div className="flex gap-4 text-sm text-gray-600 mb-3"> |
||||||
|
<div className="flex items-center gap-1"> |
||||||
|
<BookOpen className="w-4 h-4" /> |
||||||
|
<span>{lessons} Lesson</span> |
||||||
|
</div> |
||||||
|
<div className="flex items-center gap-1"> |
||||||
|
<Clock className="w-4 h-4" /> |
||||||
|
<span>{duration}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<h3 className="text-lg font-semibold mb-4">{title}</h3> |
||||||
|
<div className="flex justify-between items-center"> |
||||||
|
<div className="flex items-center gap-2"> |
||||||
|
<img |
||||||
|
src={instructor?.image} |
||||||
|
alt={instructor?.name} |
||||||
|
className="w-8 h-8 rounded-full" |
||||||
|
/> |
||||||
|
<span className="text-sm">{instructor?.name}</span> |
||||||
|
</div> |
||||||
|
<div className="flex items-center gap-2"> |
||||||
|
<span className="text-purple-700 font-semibold text-sm px-4 py-1 bg-purple-200 rounded-2xl">Free</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseCard |
@ -0,0 +1,109 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { Card, CardContent } from "@/components/ui/card"; |
||||||
|
import { Button } from "@/components/ui/button"; |
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; |
||||||
|
import { Textarea } from "@/components/ui/textarea"; |
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"; |
||||||
|
import { MessageCircle, ThumbsUp, Reply } from "lucide-react"; |
||||||
|
|
||||||
|
const DiscussionSection = () => { |
||||||
|
const [comment, setComment] = useState(''); |
||||||
|
const [discussions, setDiscussions] = useState([ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
user: 'Sarah Chen', |
||||||
|
avatar: '/api/placeholder/32/32', |
||||||
|
content: 'This is really interesting! I particularly liked the section about state management.', |
||||||
|
likes: 12, |
||||||
|
replies: 2, |
||||||
|
timestamp: '2 hours ago' |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
user: 'Alex Kim', |
||||||
|
avatar: '/api/placeholder/32/32', |
||||||
|
content: 'Great explanation! Could you elaborate more on the useEffect implementation?', |
||||||
|
likes: 8, |
||||||
|
replies: 1, |
||||||
|
timestamp: '1 hour ago' |
||||||
|
} |
||||||
|
]); |
||||||
|
|
||||||
|
const handleSubmit = () => { |
||||||
|
if (comment.trim()) { |
||||||
|
const newComment = { |
||||||
|
id: discussions.length + 1, |
||||||
|
user: 'Current User', |
||||||
|
avatar: '/api/placeholder/32/32', |
||||||
|
content: comment, |
||||||
|
likes: 0, |
||||||
|
replies: 0, |
||||||
|
timestamp: 'Just now' |
||||||
|
}; |
||||||
|
setDiscussions([...discussions, newComment]); |
||||||
|
setComment(''); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Card className="w-full max-w-2xl mx-auto"> |
||||||
|
<CardContent className="p-6"> |
||||||
|
<div className="mb-6"> |
||||||
|
<div className="flex items-start gap-4"> |
||||||
|
<Avatar className="w-8 h-8"> |
||||||
|
<AvatarImage src="/api/placeholder/32/32" alt="User" /> |
||||||
|
<AvatarFallback>U</AvatarFallback> |
||||||
|
</Avatar> |
||||||
|
<div className="flex-1"> |
||||||
|
<Textarea |
||||||
|
placeholder="Add to the discussion..." |
||||||
|
value={comment} |
||||||
|
onChange={(e) => setComment(e.target.value)} |
||||||
|
className="min-h-24 mb-2" |
||||||
|
/> |
||||||
|
<Button onClick={handleSubmit} className="float-right"> |
||||||
|
Post Comment |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<ScrollArea className="h-96"> |
||||||
|
<div className="space-y-6"> |
||||||
|
{discussions.map((discussion) => ( |
||||||
|
<div key={discussion.id} className="flex gap-4"> |
||||||
|
<Avatar className="w-8 h-8"> |
||||||
|
<AvatarImage src={discussion.avatar} alt={discussion.user} /> |
||||||
|
<AvatarFallback>{discussion.user[0]}</AvatarFallback> |
||||||
|
</Avatar> |
||||||
|
<div className="flex-1"> |
||||||
|
<div className="flex items-center gap-2 mb-1"> |
||||||
|
<span className="font-medium">{discussion.user}</span> |
||||||
|
<span className="text-sm text-gray-500">{discussion.timestamp}</span> |
||||||
|
</div> |
||||||
|
<p className="text-gray-700 mb-2">{discussion.content}</p> |
||||||
|
<div className="flex items-center gap-4"> |
||||||
|
<button className="flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700"> |
||||||
|
<ThumbsUp className="w-4 h-4" /> |
||||||
|
{discussion.likes} |
||||||
|
</button> |
||||||
|
<button className="flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700"> |
||||||
|
<Reply className="w-4 h-4" /> |
||||||
|
Reply |
||||||
|
</button> |
||||||
|
<button className="flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700"> |
||||||
|
<MessageCircle className="w-4 h-4" /> |
||||||
|
{discussion.replies} replies |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</ScrollArea> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default DiscussionSection; |
@ -0,0 +1,50 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as AvatarPrimitive from "@radix-ui/react-avatar" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Avatar = React.forwardRef< |
||||||
|
React.ElementRef<typeof AvatarPrimitive.Root>, |
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<AvatarPrimitive.Root |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
Avatar.displayName = AvatarPrimitive.Root.displayName |
||||||
|
|
||||||
|
const AvatarImage = React.forwardRef< |
||||||
|
React.ElementRef<typeof AvatarPrimitive.Image>, |
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<AvatarPrimitive.Image |
||||||
|
ref={ref} |
||||||
|
className={cn("aspect-square h-full w-full", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
AvatarImage.displayName = AvatarPrimitive.Image.displayName |
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef< |
||||||
|
React.ElementRef<typeof AvatarPrimitive.Fallback>, |
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<AvatarPrimitive.Fallback |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"flex h-full w-full items-center justify-center rounded-full bg-muted", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName |
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback } |
@ -0,0 +1,36 @@ |
|||||||
|
import * as React from "react" |
||||||
|
import { cva, type VariantProps } from "class-variance-authority" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const badgeVariants = cva( |
||||||
|
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", |
||||||
|
{ |
||||||
|
variants: { |
||||||
|
variant: { |
||||||
|
default: |
||||||
|
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", |
||||||
|
secondary: |
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", |
||||||
|
destructive: |
||||||
|
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", |
||||||
|
outline: "text-foreground", |
||||||
|
}, |
||||||
|
}, |
||||||
|
defaultVariants: { |
||||||
|
variant: "default", |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
export interface BadgeProps |
||||||
|
extends React.HTMLAttributes<HTMLDivElement>, |
||||||
|
VariantProps<typeof badgeVariants> {} |
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) { |
||||||
|
return ( |
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} /> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { Badge, badgeVariants } |
@ -0,0 +1,76 @@ |
|||||||
|
import * as React from "react" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Card = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"rounded-xl border bg-card text-card-foreground shadow", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
Card.displayName = "Card" |
||||||
|
|
||||||
|
const CardHeader = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
CardHeader.displayName = "CardHeader" |
||||||
|
|
||||||
|
const CardTitle = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
CardTitle.displayName = "CardTitle" |
||||||
|
|
||||||
|
const CardDescription = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className={cn("text-sm text-muted-foreground", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
CardDescription.displayName = "CardDescription" |
||||||
|
|
||||||
|
const CardContent = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> |
||||||
|
)) |
||||||
|
CardContent.displayName = "CardContent" |
||||||
|
|
||||||
|
const CardFooter = React.forwardRef< |
||||||
|
HTMLDivElement, |
||||||
|
React.HTMLAttributes<HTMLDivElement> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className={cn("flex items-center p-6 pt-0", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
CardFooter.displayName = "CardFooter" |
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } |
@ -0,0 +1,201 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" |
||||||
|
import { Check, ChevronRight, Circle } from "lucide-react" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const DropdownMenu = DropdownMenuPrimitive.Root |
||||||
|
|
||||||
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger |
||||||
|
|
||||||
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group |
||||||
|
|
||||||
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal |
||||||
|
|
||||||
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub |
||||||
|
|
||||||
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup |
||||||
|
|
||||||
|
const DropdownMenuSubTrigger = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { |
||||||
|
inset?: boolean |
||||||
|
} |
||||||
|
>(({ className, inset, children, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.SubTrigger |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", |
||||||
|
inset && "pl-8", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{children} |
||||||
|
<ChevronRight className="ml-auto" /> |
||||||
|
</DropdownMenuPrimitive.SubTrigger> |
||||||
|
)) |
||||||
|
DropdownMenuSubTrigger.displayName = |
||||||
|
DropdownMenuPrimitive.SubTrigger.displayName |
||||||
|
|
||||||
|
const DropdownMenuSubContent = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.SubContent |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
DropdownMenuSubContent.displayName = |
||||||
|
DropdownMenuPrimitive.SubContent.displayName |
||||||
|
|
||||||
|
const DropdownMenuContent = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> |
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.Portal> |
||||||
|
<DropdownMenuPrimitive.Content |
||||||
|
ref={ref} |
||||||
|
sideOffset={sideOffset} |
||||||
|
className={cn( |
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", |
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
</DropdownMenuPrimitive.Portal> |
||||||
|
)) |
||||||
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName |
||||||
|
|
||||||
|
const DropdownMenuItem = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { |
||||||
|
inset?: boolean |
||||||
|
} |
||||||
|
>(({ className, inset, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.Item |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", |
||||||
|
inset && "pl-8", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName |
||||||
|
|
||||||
|
const DropdownMenuCheckboxItem = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> |
||||||
|
>(({ className, children, checked, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.CheckboxItem |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
checked={checked} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> |
||||||
|
<DropdownMenuPrimitive.ItemIndicator> |
||||||
|
<Check className="h-4 w-4" /> |
||||||
|
</DropdownMenuPrimitive.ItemIndicator> |
||||||
|
</span> |
||||||
|
{children} |
||||||
|
</DropdownMenuPrimitive.CheckboxItem> |
||||||
|
)) |
||||||
|
DropdownMenuCheckboxItem.displayName = |
||||||
|
DropdownMenuPrimitive.CheckboxItem.displayName |
||||||
|
|
||||||
|
const DropdownMenuRadioItem = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> |
||||||
|
>(({ className, children, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.RadioItem |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> |
||||||
|
<DropdownMenuPrimitive.ItemIndicator> |
||||||
|
<Circle className="h-2 w-2 fill-current" /> |
||||||
|
</DropdownMenuPrimitive.ItemIndicator> |
||||||
|
</span> |
||||||
|
{children} |
||||||
|
</DropdownMenuPrimitive.RadioItem> |
||||||
|
)) |
||||||
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName |
||||||
|
|
||||||
|
const DropdownMenuLabel = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { |
||||||
|
inset?: boolean |
||||||
|
} |
||||||
|
>(({ className, inset, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.Label |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"px-2 py-1.5 text-sm font-semibold", |
||||||
|
inset && "pl-8", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName |
||||||
|
|
||||||
|
const DropdownMenuSeparator = React.forwardRef< |
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, |
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<DropdownMenuPrimitive.Separator |
||||||
|
ref={ref} |
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName |
||||||
|
|
||||||
|
const DropdownMenuShortcut = ({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => { |
||||||
|
return ( |
||||||
|
<span |
||||||
|
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" |
||||||
|
|
||||||
|
export { |
||||||
|
DropdownMenu, |
||||||
|
DropdownMenuTrigger, |
||||||
|
DropdownMenuContent, |
||||||
|
DropdownMenuItem, |
||||||
|
DropdownMenuCheckboxItem, |
||||||
|
DropdownMenuRadioItem, |
||||||
|
DropdownMenuLabel, |
||||||
|
DropdownMenuSeparator, |
||||||
|
DropdownMenuShortcut, |
||||||
|
DropdownMenuGroup, |
||||||
|
DropdownMenuPortal, |
||||||
|
DropdownMenuSub, |
||||||
|
DropdownMenuSubContent, |
||||||
|
DropdownMenuSubTrigger, |
||||||
|
DropdownMenuRadioGroup, |
||||||
|
} |
@ -1,22 +1,48 @@ |
|||||||
import * as React from "react" |
import * as React from "react"; |
||||||
|
import { cn } from "@/lib/utils"; |
||||||
|
|
||||||
import { cn } from "@/lib/utils" |
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { |
||||||
|
error?: string[]; |
||||||
|
label?: string; |
||||||
|
helperText?: string; |
||||||
|
required?: boolean; |
||||||
|
} |
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( |
const Input = React.forwardRef<HTMLInputElement, InputProps>( |
||||||
({ className, type, ...props }, ref) => { |
({ className, type, error, label, helperText, required = false, ...props }, ref) => { |
||||||
return ( |
return ( |
||||||
|
<div className="w-full space-y-2"> |
||||||
|
{label && ( |
||||||
|
<div className="flex text-sm font-medium text-gray-700"> |
||||||
|
{label} |
||||||
|
{required && <span className="text-red-500 ml-1">*</span>} |
||||||
|
</div> |
||||||
|
)} |
||||||
<input |
<input |
||||||
type={type} |
type={type} |
||||||
className={cn( |
className={cn( |
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", |
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", |
||||||
|
error && error.length > 0 && "border-red-500 focus-visible:ring-red-500", |
||||||
className |
className |
||||||
)} |
)} |
||||||
ref={ref} |
ref={ref} |
||||||
{...props} |
{...props} |
||||||
/> |
/> |
||||||
|
{helperText && ( |
||||||
|
<p className="text-sm text-muted-foreground"> |
||||||
|
{helperText} |
||||||
|
</p> |
||||||
|
)} |
||||||
|
{error && error.map((message, index) => ( |
||||||
|
<p key={index} className="text-sm font-medium text-red-500"> |
||||||
|
{message} |
||||||
|
</p> |
||||||
|
))} |
||||||
|
</div> |
||||||
) |
) |
||||||
} |
} |
||||||
) |
) |
||||||
|
|
||||||
Input.displayName = "Input" |
Input.displayName = "Input" |
||||||
|
|
||||||
export { Input } |
export { Input } |
@ -0,0 +1,26 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label" |
||||||
|
import { cva, type VariantProps } from "class-variance-authority" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const labelVariants = cva( |
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" |
||||||
|
) |
||||||
|
|
||||||
|
const Label = React.forwardRef< |
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>, |
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & |
||||||
|
VariantProps<typeof labelVariants> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<LabelPrimitive.Root |
||||||
|
ref={ref} |
||||||
|
className={cn(labelVariants(), className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
Label.displayName = LabelPrimitive.Root.displayName |
||||||
|
|
||||||
|
export { Label } |
@ -0,0 +1,28 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Progress = React.forwardRef< |
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>, |
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> |
||||||
|
>(({ className, value, ...props }, ref) => ( |
||||||
|
<ProgressPrimitive.Root |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<ProgressPrimitive.Indicator |
||||||
|
className="h-full w-full flex-1 bg-primary transition-all" |
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} |
||||||
|
/> |
||||||
|
</ProgressPrimitive.Root> |
||||||
|
)) |
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName |
||||||
|
|
||||||
|
export { Progress } |
@ -0,0 +1,48 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef< |
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>, |
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> |
||||||
|
>(({ className, children, ...props }, ref) => ( |
||||||
|
<ScrollAreaPrimitive.Root |
||||||
|
ref={ref} |
||||||
|
className={cn("relative overflow-hidden", className)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> |
||||||
|
{children} |
||||||
|
</ScrollAreaPrimitive.Viewport> |
||||||
|
<ScrollBar /> |
||||||
|
<ScrollAreaPrimitive.Corner /> |
||||||
|
</ScrollAreaPrimitive.Root> |
||||||
|
)) |
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName |
||||||
|
|
||||||
|
const ScrollBar = React.forwardRef< |
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, |
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> |
||||||
|
>(({ className, orientation = "vertical", ...props }, ref) => ( |
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar |
||||||
|
ref={ref} |
||||||
|
orientation={orientation} |
||||||
|
className={cn( |
||||||
|
"flex touch-none select-none transition-colors", |
||||||
|
orientation === "vertical" && |
||||||
|
"h-full w-2.5 border-l border-l-transparent p-[1px]", |
||||||
|
orientation === "horizontal" && |
||||||
|
"h-2.5 flex-col border-t border-t-transparent p-[1px]", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> |
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar> |
||||||
|
)) |
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName |
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar } |
@ -0,0 +1,159 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select" |
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root |
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group |
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value |
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> |
||||||
|
>(({ className, children, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.Trigger |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{children} |
||||||
|
<SelectPrimitive.Icon asChild> |
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" /> |
||||||
|
</SelectPrimitive.Icon> |
||||||
|
</SelectPrimitive.Trigger> |
||||||
|
)) |
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName |
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.ScrollUpButton |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"flex cursor-default items-center justify-center py-1", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<ChevronUp className="h-4 w-4" /> |
||||||
|
</SelectPrimitive.ScrollUpButton> |
||||||
|
)) |
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName |
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.ScrollDownButton |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"flex cursor-default items-center justify-center py-1", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<ChevronDown className="h-4 w-4" /> |
||||||
|
</SelectPrimitive.ScrollDownButton> |
||||||
|
)) |
||||||
|
SelectScrollDownButton.displayName = |
||||||
|
SelectPrimitive.ScrollDownButton.displayName |
||||||
|
|
||||||
|
const SelectContent = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> |
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => ( |
||||||
|
<SelectPrimitive.Portal> |
||||||
|
<SelectPrimitive.Content |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", |
||||||
|
position === "popper" && |
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", |
||||||
|
className |
||||||
|
)} |
||||||
|
position={position} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<SelectScrollUpButton /> |
||||||
|
<SelectPrimitive.Viewport |
||||||
|
className={cn( |
||||||
|
"p-1", |
||||||
|
position === "popper" && |
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" |
||||||
|
)} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</SelectPrimitive.Viewport> |
||||||
|
<SelectScrollDownButton /> |
||||||
|
</SelectPrimitive.Content> |
||||||
|
</SelectPrimitive.Portal> |
||||||
|
)) |
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName |
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.Label |
||||||
|
ref={ref} |
||||||
|
className={cn("px-2 py-1.5 text-sm font-semibold", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName |
||||||
|
|
||||||
|
const SelectItem = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> |
||||||
|
>(({ className, children, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.Item |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> |
||||||
|
<SelectPrimitive.ItemIndicator> |
||||||
|
<Check className="h-4 w-4" /> |
||||||
|
</SelectPrimitive.ItemIndicator> |
||||||
|
</span> |
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> |
||||||
|
</SelectPrimitive.Item> |
||||||
|
)) |
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName |
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef< |
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>, |
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<SelectPrimitive.Separator |
||||||
|
ref={ref} |
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName |
||||||
|
|
||||||
|
export { |
||||||
|
Select, |
||||||
|
SelectGroup, |
||||||
|
SelectValue, |
||||||
|
SelectTrigger, |
||||||
|
SelectContent, |
||||||
|
SelectLabel, |
||||||
|
SelectItem, |
||||||
|
SelectSeparator, |
||||||
|
SelectScrollUpButton, |
||||||
|
SelectScrollDownButton, |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root |
||||||
|
|
||||||
|
const TabsList = React.forwardRef< |
||||||
|
React.ElementRef<typeof TabsPrimitive.List>, |
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<TabsPrimitive.List |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName |
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef< |
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>, |
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<TabsPrimitive.Trigger |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName |
||||||
|
|
||||||
|
const TabsContent = React.forwardRef< |
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>, |
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> |
||||||
|
>(({ className, ...props }, ref) => ( |
||||||
|
<TabsPrimitive.Content |
||||||
|
ref={ref} |
||||||
|
className={cn( |
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
)) |
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName |
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent } |
@ -0,0 +1,22 @@ |
|||||||
|
import * as React from "react" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const Textarea = React.forwardRef< |
||||||
|
HTMLTextAreaElement, |
||||||
|
React.ComponentProps<"textarea"> |
||||||
|
>(({ className, ...props }, ref) => { |
||||||
|
return ( |
||||||
|
<textarea |
||||||
|
className={cn( |
||||||
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", |
||||||
|
className |
||||||
|
)} |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
}) |
||||||
|
Textarea.displayName = "Textarea" |
||||||
|
|
||||||
|
export { Textarea } |
@ -0,0 +1,14 @@ |
|||||||
|
export interface User { |
||||||
|
firstName: string; |
||||||
|
lastName: string; |
||||||
|
email: string; |
||||||
|
id: string; |
||||||
|
pfpName: string; |
||||||
|
bio: string; |
||||||
|
date_of_birth: string; |
||||||
|
} |
||||||
|
|
||||||
|
export interface Admin extends User { |
||||||
|
role: string; |
||||||
|
permissions: string[]; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import { AuthProvider } from "./AuthProvider" |
||||||
|
|
||||||
|
const AppContextProvider = ({ |
||||||
|
children |
||||||
|
} : { |
||||||
|
children : React.ReactNode |
||||||
|
}) => { |
||||||
|
return( |
||||||
|
<AuthProvider> |
||||||
|
{children} |
||||||
|
</AuthProvider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default AppContextProvider |
@ -0,0 +1,93 @@ |
|||||||
|
"use client"; |
||||||
|
|
||||||
|
import React, { ReactNode, createContext, useEffect, useState } from "react"; |
||||||
|
import Cookies from "js-cookie"; |
||||||
|
import { User } from "../apiSchema/User"; |
||||||
|
import { getEduConnectAccessToken } from "../token.helper"; |
||||||
|
|
||||||
|
interface AuthContextProps { |
||||||
|
user: User | null; |
||||||
|
loading: boolean; |
||||||
|
fetchUser: (token: string) => void; |
||||||
|
logout: () => void; |
||||||
|
} |
||||||
|
|
||||||
|
interface AuthProviderProps { |
||||||
|
children: ReactNode; |
||||||
|
} |
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextProps>({ |
||||||
|
user: null, |
||||||
|
loading: true, |
||||||
|
fetchUser: () => {}, |
||||||
|
logout: () => {}, |
||||||
|
}); |
||||||
|
|
||||||
|
const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => { |
||||||
|
|
||||||
|
|
||||||
|
const [user, setUser] = useState<User | null>(null); |
||||||
|
const [loading, setLoading] = useState(true); |
||||||
|
|
||||||
|
const fetchUser = async () => { |
||||||
|
|
||||||
|
|
||||||
|
try { |
||||||
|
// Fetch user data
|
||||||
|
const userResponse = await fetch(`${process.env.NEXT_PUBLIC_EDU_CONNECT_HOST}/api/get-user-by-token`, { |
||||||
|
method: "POST", |
||||||
|
headers: { |
||||||
|
Authorization: `Bearer ${getEduConnectAccessToken()}`, |
||||||
|
Accept: "Application/json", |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const userData = await userResponse.json(); |
||||||
|
setUser(userData.data); |
||||||
|
// router.replace(routes.SELECT_COMPANY_INDEX)
|
||||||
|
} catch (error) { |
||||||
|
console.error("Fetch error:", error); |
||||||
|
// router.replace(routes.ADMIN_LOGIN);
|
||||||
|
} finally { |
||||||
|
setLoading(false); // Only set loading to false on initial load
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!user) { |
||||||
|
fetchUser(); |
||||||
|
} |
||||||
|
}, []); |
||||||
|
|
||||||
|
|
||||||
|
const logout = async () => { |
||||||
|
try { |
||||||
|
await fetch(`${process.env.NEXT_PUBLIC_IDP_HOST}/api/logout`, { |
||||||
|
method: "POST", |
||||||
|
headers: { |
||||||
|
Authorization: `Bearer ${getEduConnectAccessToken()}`, |
||||||
|
}, |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
console.error("Logout error:", error); |
||||||
|
} finally { |
||||||
|
Cookies.remove("HRMS_ACCESS_TOKEN", { domain: process.env.APP_DOMAIN }); |
||||||
|
setUser(null); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<AuthContext.Provider |
||||||
|
value={{ |
||||||
|
user, |
||||||
|
loading: !user && loading, |
||||||
|
fetchUser: fetchUser, |
||||||
|
logout, |
||||||
|
}} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</AuthContext.Provider> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export { AuthContext, AuthProvider }; |
@ -0,0 +1,16 @@ |
|||||||
|
import Cookie from 'js-cookie' |
||||||
|
|
||||||
|
export const getEduConnectAccessToken = () : string | null => { |
||||||
|
return Cookie.get('EDU_CONNECT_ACCESS_TOKEN') |
||||||
|
} |
||||||
|
|
||||||
|
export const setEduConnectAccessToken = (token : string) : void => { |
||||||
|
Cookie.set('EDU_CONNECT_ACCESS_TOKEN' , token) |
||||||
|
} |
||||||
|
|
||||||
|
export const removeEduConnectAccessToken = () : void => { |
||||||
|
Cookie.remove('EDU_CONNECT_ACCESS_TOKEN') |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
export const routes = { |
||||||
|
INDEX_PAGE : '/' ,
|
||||||
|
LOGIN_ROUTES : '/login' |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import Footer from "@/components/common/Footer/footer" |
||||||
|
import Header from "@/components/common/Header/header" |
||||||
|
|
||||||
|
const CommonView = ({ |
||||||
|
children |
||||||
|
} : { |
||||||
|
children : React.ReactNode |
||||||
|
}) => { |
||||||
|
return( |
||||||
|
<div className="min-h-screen flex flex-col justify-between"> |
||||||
|
<Header /> |
||||||
|
<main> |
||||||
|
{children} |
||||||
|
</main> |
||||||
|
<Footer /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CommonView |
Loading…
Reference in new issue