Compare commits
2 Commits
7a5ff71010
...
43b82377e9
Author | SHA1 | Date |
---|---|---|
|
43b82377e9 | 6 months ago |
|
6d9cb1bbb3 | 6 months ago |
@ -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() { |
||||
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)]"> |
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> |
||||
<Image |
||||
className="dark:invert" |
||||
src="https://nextjs.org/icons/next.svg" |
||||
alt="Next.js logo" |
||||
width={180} |
||||
height={38} |
||||
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> |
||||
<AppContextProvider> |
||||
<CommonView> |
||||
<HeroSection /> |
||||
<FeaturedCourses /> |
||||
<FeaturesSection /> |
||||
<CommunitySection /> |
||||
</CommonView> |
||||
</AppContextProvider> |
||||
); |
||||
} |
||||
|
@ -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">>( |
||||
({ className, type, ...props }, ref) => { |
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>( |
||||
({ className, type, error, label, helperText, required = false, ...props }, ref) => { |
||||
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 |
||||
type={type} |
||||
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 |
||||
)} |
||||
ref={ref} |
||||
{...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" |
||||
|
||||
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