Compare commits
No commits in common. '716e5cffb4a6a47736b4015ef5d6fd4d0a8b76f4' and '4169e265ab810c26e0578a4a26e3c336b5777c85' have entirely different histories.
716e5cffb4
...
4169e265ab
@ -1,137 +0,0 @@ |
|||||||
import DataTable from "@/components/(dashboard)/common/DataTable/DataTable" |
|
||||||
import { Button } from "@/components/(dashboard)/ui/button" |
|
||||||
import { Avatar, AvatarImage } from "@/components/ui/avatar" |
|
||||||
import { Badge } from "@/components/ui/badge" |
|
||||||
import { routes } from "@/lib/routes" |
|
||||||
import { ColumnDef } from "@tanstack/react-table" |
|
||||||
import { ArrowUpDown } from "lucide-react" |
|
||||||
import Link from "next/link" |
|
||||||
|
|
||||||
const UserTable :React.FC<{ |
|
||||||
mutate : () => void |
|
||||||
userData : Array<any> |
|
||||||
isLoading : boolean |
|
||||||
}> = ({ |
|
||||||
mutate ,
|
|
||||||
userData ,
|
|
||||||
isLoading |
|
||||||
}) => { |
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [ |
|
||||||
{ |
|
||||||
accessorKey: "sn", |
|
||||||
header: "SN", |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div className="capitalize">{row.index + 1}</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: 'name', |
|
||||||
accessorFn: (row: any) => row.original?.firstName, |
|
||||||
header: ({ column }) => ( |
|
||||||
<Button |
|
||||||
variant="ghost" |
|
||||||
className="!px-0" |
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
|
||||||
> |
|
||||||
Name |
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
|
||||||
</Button> |
|
||||||
), |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div className="capitalize flex gap-2"> |
|
||||||
<Avatar className="h-8 w-8"> |
|
||||||
<AvatarImage
|
|
||||||
src={row.original?.profilePicture ?? 'no image path'}
|
|
||||||
alt={row.original?.firstName}
|
|
||||||
/> |
|
||||||
</Avatar> |
|
||||||
<p>{row?.original?.firstName}</p> |
|
||||||
</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: 'email', |
|
||||||
accessorFn: (row: any) => row.original?.email, |
|
||||||
header: ({ column }) => ( |
|
||||||
<Button |
|
||||||
variant="ghost" |
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
|
||||||
className="!px-0" |
|
||||||
> |
|
||||||
Email |
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
|
||||||
</Button> |
|
||||||
), |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div>{row.original?.email}</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: 'dateOfBirth', |
|
||||||
accessorFn: (row: any) => row.original?.dateOfBirth, |
|
||||||
header: ({ column }) => ( |
|
||||||
<Button |
|
||||||
variant="ghost" |
|
||||||
className="!px-0" |
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
|
||||||
> |
|
||||||
DateOfBirth |
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
|
||||||
</Button> |
|
||||||
), |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div className="capitalize">{row.original?.dateOfBirth}</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: 'isActivated', |
|
||||||
accessorFn: (row: any) => row.original?.isActivated, |
|
||||||
header: ({ column }) => ( |
|
||||||
<Button |
|
||||||
variant="ghost" |
|
||||||
className="!px-0" |
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
|
||||||
> |
|
||||||
IsActive |
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
|
||||||
</Button> |
|
||||||
), |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div>{row.original?.isActivated ? <Badge variant={'success'}>Active</Badge> : <Badge variant={'destructive'}>Deactive</Badge>}</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: 'last_online_at', |
|
||||||
accessorFn: (row: any) => row.original?.lastOnline, |
|
||||||
header: ({ column }) => ( |
|
||||||
<Button |
|
||||||
variant="ghost" |
|
||||||
className="!px-0" |
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} |
|
||||||
> |
|
||||||
lastOnline |
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" /> |
|
||||||
</Button> |
|
||||||
), |
|
||||||
cell: ({ row }) => ( |
|
||||||
<div> |
|
||||||
{row.original?.lastOnline ?? '-'}
|
|
||||||
</div> |
|
||||||
), |
|
||||||
}, |
|
||||||
] |
|
||||||
return( |
|
||||||
<> |
|
||||||
<DataTable
|
|
||||||
data={userData} |
|
||||||
columns={columns} |
|
||||||
mutate={mutate} |
|
||||||
searchKey="username" |
|
||||||
isLoading={isLoading} |
|
||||||
/> |
|
||||||
</> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default UserTable |
|
@ -1,56 +0,0 @@ |
|||||||
'use client' |
|
||||||
import UserTabContent from "@/app/user/profile/_partials/UserTabContent" |
|
||||||
import BreadCrumbNav from "@/components/(dashboard)/common/BreadCumbNav/BreadCrumbNav" |
|
||||||
import DataTable from "@/components/(dashboard)/common/DataTable/DataTable" |
|
||||||
import ContentContainer from "@/components/(dashboard)/elements/ContentContainer" |
|
||||||
import { PageHeading } from "@/components/(dashboard)/ui/title" |
|
||||||
import CommonContainer from "@/components/elements/CommonContainer" |
|
||||||
import AppContextProvider from "@/helpers/context/AppContextProvider" |
|
||||||
import { defaultFetcher } from "@/helpers/fetch.helper" |
|
||||||
import { routes } from "@/lib/routes" |
|
||||||
import { APP_BASE_URL } from "@/utils/constants" |
|
||||||
import AdminView from "@/views/AdminView" |
|
||||||
import useSWR from "swr" |
|
||||||
import UserTable from "./_partials/UserTable" |
|
||||||
|
|
||||||
const UsersIndexPage = () => { |
|
||||||
const UserListURL = `${APP_BASE_URL}/api/admin/stats/userDetail` |
|
||||||
const { data : UsersList , mutate , isLoading} = useSWR(UserListURL , defaultFetcher); |
|
||||||
|
|
||||||
|
|
||||||
console.log(UsersList) |
|
||||||
|
|
||||||
return( |
|
||||||
<> |
|
||||||
<AppContextProvider> |
|
||||||
<AdminView> |
|
||||||
<CommonContainer> |
|
||||||
<PageHeading>Users</PageHeading> |
|
||||||
<BreadCrumbNav breadCrumbItems={[ |
|
||||||
{ |
|
||||||
title : 'Dashboard', |
|
||||||
href : routes.DASHBOARD_ROUTE |
|
||||||
}, |
|
||||||
{ |
|
||||||
title : 'Users', |
|
||||||
href : routes.USER_INDEX_PAGE |
|
||||||
}, |
|
||||||
]}/> |
|
||||||
<ContentContainer> |
|
||||||
<div> |
|
||||||
<UserTable
|
|
||||||
userData={UsersList?.users} |
|
||||||
mutate={mutate} |
|
||||||
isLoading={isLoading} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
</ContentContainer> |
|
||||||
</CommonContainer> |
|
||||||
</AdminView> |
|
||||||
</AppContextProvider> |
|
||||||
</> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default UsersIndexPage |
|
||||||
|
|
@ -1,179 +0,0 @@ |
|||||||
import { useState, useEffect } from 'react'; |
|
||||||
import { Card, CardContent } from "@/components/ui/card"; |
|
||||||
import { Button } from "@/components/ui/button"; |
|
||||||
import { Upload, X, AlertCircle } from 'lucide-react'; |
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert"; |
|
||||||
|
|
||||||
interface BannerUploadProps { |
|
||||||
onImageChange?: (file: File | null) => void; |
|
||||||
defaultImage?: string; |
|
||||||
maxSizeMB?: number; |
|
||||||
} |
|
||||||
|
|
||||||
export default function BannerImageUpload({
|
|
||||||
onImageChange,
|
|
||||||
defaultImage, |
|
||||||
maxSizeMB = 5
|
|
||||||
}: BannerUploadProps) { |
|
||||||
const [previewUrl, setPreviewUrl] = useState<string | null>(defaultImage || null); |
|
||||||
const [error, setError] = useState<string | null>(null); |
|
||||||
const [isDragging, setIsDragging] = useState(false); |
|
||||||
|
|
||||||
const acceptedTypes = ['image/jpeg', 'image/png', 'image/webp']; |
|
||||||
const maxSize = maxSizeMB * 1024 * 1024; // Convert MB to bytes
|
|
||||||
|
|
||||||
const validateFile = (file: File): string | null => { |
|
||||||
if (!acceptedTypes.includes(file.type)) { |
|
||||||
return 'Please upload a valid image file (JPG, PNG, or WebP)'; |
|
||||||
} |
|
||||||
if (file.size > maxSize) { |
|
||||||
return `File size should be less than ${maxSizeMB}MB`; |
|
||||||
} |
|
||||||
return null; |
|
||||||
}; |
|
||||||
|
|
||||||
const handleFile = (file: File) => { |
|
||||||
const validationError = validateFile(file); |
|
||||||
if (validationError) { |
|
||||||
setError(validationError); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Clear any existing error
|
|
||||||
setError(null); |
|
||||||
|
|
||||||
// Create preview URL
|
|
||||||
const objectUrl = URL.createObjectURL(file); |
|
||||||
setPreviewUrl(objectUrl); |
|
||||||
|
|
||||||
// Notify parent component
|
|
||||||
if (onImageChange) { |
|
||||||
onImageChange(file); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { |
|
||||||
const file = e.target.files?.[0]; |
|
||||||
if (file) { |
|
||||||
handleFile(file); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => { |
|
||||||
e.preventDefault(); |
|
||||||
setIsDragging(false); |
|
||||||
|
|
||||||
const file = e.dataTransfer.files?.[0]; |
|
||||||
if (file) { |
|
||||||
handleFile(file); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { |
|
||||||
e.preventDefault(); |
|
||||||
setIsDragging(true); |
|
||||||
}; |
|
||||||
|
|
||||||
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => { |
|
||||||
e.preventDefault(); |
|
||||||
setIsDragging(false); |
|
||||||
}; |
|
||||||
|
|
||||||
const removeBanner = () => { |
|
||||||
if (previewUrl) { |
|
||||||
URL.revokeObjectURL(previewUrl); |
|
||||||
} |
|
||||||
setPreviewUrl(null); |
|
||||||
setError(null); |
|
||||||
if (onImageChange) { |
|
||||||
onImageChange(null); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Cleanup preview URL when component unmounts
|
|
||||||
useEffect(() => { |
|
||||||
return () => { |
|
||||||
if (previewUrl && previewUrl !== defaultImage) { |
|
||||||
URL.revokeObjectURL(previewUrl); |
|
||||||
} |
|
||||||
}; |
|
||||||
}, [previewUrl, defaultImage]); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="space-y-4 col-span-2"> |
|
||||||
<label>Upload Course Banner</label> |
|
||||||
<div |
|
||||||
className={`relative w-full h-64 rounded-lg overflow-hidden border-2 border-dashed transition-colors
|
|
||||||
${isDragging ? 'border-purple-500 bg-purple-50' : 'border-gray-300 bg-gray-50'} |
|
||||||
${error ? 'border-red-300' : ''}`}
|
|
||||||
onDrop={handleDrop} |
|
||||||
onDragOver={handleDragOver} |
|
||||||
onDragLeave={handleDragLeave} |
|
||||||
> |
|
||||||
{previewUrl ? ( |
|
||||||
<> |
|
||||||
<img |
|
||||||
src={previewUrl} |
|
||||||
alt="Banner preview" |
|
||||||
className="w-full h-full object-cover" |
|
||||||
/> |
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-40 opacity-0 hover:opacity-100 transition-opacity"> |
|
||||||
<div className="absolute inset-0 flex items-center justify-center space-x-4"> |
|
||||||
<Button |
|
||||||
type="button" |
|
||||||
variant="outline" |
|
||||||
className="bg-white hover:bg-gray-100" |
|
||||||
onClick={() => document.getElementById('banner-upload')?.click()} |
|
||||||
> |
|
||||||
<Upload className="h-5 w-5 mr-2" /> |
|
||||||
Change Banner |
|
||||||
</Button> |
|
||||||
<Button |
|
||||||
type="button" |
|
||||||
variant="destructive" |
|
||||||
onClick={removeBanner} |
|
||||||
> |
|
||||||
<X className="h-5 w-5 mr-2" /> |
|
||||||
Remove Banner |
|
||||||
</Button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</> |
|
||||||
) : ( |
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center"> |
|
||||||
<Upload className="h-12 w-12 text-gray-400 mb-4" /> |
|
||||||
<div className="text-center"> |
|
||||||
<Button |
|
||||||
type="button" |
|
||||||
variant="outline" |
|
||||||
className="mb-2" |
|
||||||
onClick={() => document.getElementById('banner-upload')?.click()} |
|
||||||
> |
|
||||||
Choose File |
|
||||||
</Button> |
|
||||||
<p className="text-sm text-gray-500">or drag and drop your image here</p> |
|
||||||
<p className="text-xs text-gray-400 mt-2"> |
|
||||||
Supports: JPG, PNG, WebP (max {maxSizeMB}MB) |
|
||||||
</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
)} |
|
||||||
|
|
||||||
<input |
|
||||||
id="banner-upload" |
|
||||||
type="file" |
|
||||||
className="hidden" |
|
||||||
accept={acceptedTypes.join(',')} |
|
||||||
onChange={handleFileSelect} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
|
|
||||||
{error && ( |
|
||||||
<Alert variant="destructive"> |
|
||||||
<AlertCircle className="h-4 w-4" /> |
|
||||||
<AlertDescription>{error}</AlertDescription> |
|
||||||
</Alert> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
export const CourseCategoryOptionHelper = (data: Record<string, string>[]) : any => { |
|
||||||
return data?.length > 0
|
|
||||||
? data.map((item) => ({ |
|
||||||
value: item?.id?.toString() ? item?.id?.toString() : item?.idx, |
|
||||||
label: item?.title ? item.title : item?.name, |
|
||||||
})) |
|
||||||
: []; |
|
||||||
} |
|
@ -1,74 +0,0 @@ |
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" |
|
||||||
import { defaultFetcher } from "@/helpers/fetch.helper" |
|
||||||
import { APP_BASE_URL } from "@/utils/constants" |
|
||||||
import Image from "next/image" |
|
||||||
import useSWR from "swr" |
|
||||||
|
|
||||||
const EnrolledCourseTabContent : React.FC = () => { |
|
||||||
const { data } = useSWR(APP_BASE_URL + '/' , defaultFetcher); |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Card className='border border-purple-700/20 shadow shadow-purple-700/40'> |
|
||||||
<CardHeader> |
|
||||||
<CardTitle className="text-2xl text-purple-700">Enrolled Courses List </CardTitle> |
|
||||||
</CardHeader> |
|
||||||
<CardContent> |
|
||||||
{ |
|
||||||
data?.length && data.map((course : Record<string,any> , index : number) => { |
|
||||||
return( |
|
||||||
<> |
|
||||||
<Card |
|
||||||
key={course.id} |
|
||||||
className="hover:shadow-lg transition-shadow" |
|
||||||
> |
|
||||||
<CardContent className="p-6 flex gap-4"> |
|
||||||
<div > |
|
||||||
<Image src="https://placehold.jp/400x250.png"alt="placeholder" width={400} height={250} className="w-[400] h-auto rounded-2xl"/> |
|
||||||
</div> |
|
||||||
<div> |
|
||||||
<div className="flex items-start justify-between"> |
|
||||||
<div className="space-y-2"> |
|
||||||
<h3 className="text-xl font-semibold text-purple-700"> |
|
||||||
{course.title} |
|
||||||
</h3> |
|
||||||
<p className="text-gray-600">{course.description}</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap gap-4 text-sm text-gray-500"> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Published:</span> |
|
||||||
<span className="ml-1">{course.publishedDate}</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Enrolled:</span> |
|
||||||
<span className="ml-1"> |
|
||||||
{course.enrolledStudent} students |
|
||||||
</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Active Discussions:</span> |
|
||||||
<span className="ml-1"> |
|
||||||
{course.activeDiscussionCount} |
|
||||||
</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Published by:</span> |
|
||||||
<span className="ml-1">{course.publishedBy}</span> |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</CardContent> |
|
||||||
</Card> |
|
||||||
</> |
|
||||||
) |
|
||||||
}) |
|
||||||
} |
|
||||||
</CardContent> |
|
||||||
</Card> |
|
||||||
</> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default EnrolledCourseTabContent |
|
@ -1,74 +0,0 @@ |
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" |
|
||||||
import { defaultFetcher } from "@/helpers/fetch.helper" |
|
||||||
import { APP_BASE_URL } from "@/utils/constants" |
|
||||||
import Image from "next/image" |
|
||||||
import useSWR from "swr" |
|
||||||
|
|
||||||
const MyCoursesListTabContent = () => { |
|
||||||
const { data } = useSWR(APP_BASE_URL + '/' , defaultFetcher); |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Card className='border border-purple-700/20 shadow shadow-purple-700/40'> |
|
||||||
<CardHeader> |
|
||||||
<CardTitle className="text-2xl text-purple-700">My Courses</CardTitle> |
|
||||||
</CardHeader> |
|
||||||
<CardContent> |
|
||||||
{ |
|
||||||
data?.length && data.map((course : Record<string,any> , index : number) => { |
|
||||||
return( |
|
||||||
<> |
|
||||||
<Card |
|
||||||
key={course.id} |
|
||||||
className="hover:shadow-lg transition-shadow" |
|
||||||
> |
|
||||||
<CardContent className="p-6 flex gap-4"> |
|
||||||
<div > |
|
||||||
<Image src="https://placehold.jp/400x250.png"alt="placeholder" width={400} height={250} className="w-[400] h-auto rounded-2xl"/> |
|
||||||
</div> |
|
||||||
<div> |
|
||||||
<div className="flex items-start justify-between"> |
|
||||||
<div className="space-y-2"> |
|
||||||
<h3 className="text-xl font-semibold text-purple-700"> |
|
||||||
{course.title} |
|
||||||
</h3> |
|
||||||
<p className="text-gray-600">{course.description}</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap gap-4 text-sm text-gray-500"> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Published:</span> |
|
||||||
<span className="ml-1">{course.publishedDate}</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Enrolled:</span> |
|
||||||
<span className="ml-1"> |
|
||||||
{course.enrolledStudent} students |
|
||||||
</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Active Discussions:</span> |
|
||||||
<span className="ml-1"> |
|
||||||
{course.activeDiscussionCount} |
|
||||||
</span> |
|
||||||
</span> |
|
||||||
<span className="flex items-center"> |
|
||||||
<span className="font-medium">Published by:</span> |
|
||||||
<span className="ml-1">{course.publishedBy}</span> |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</CardContent> |
|
||||||
</Card> |
|
||||||
</> |
|
||||||
) |
|
||||||
}) |
|
||||||
} |
|
||||||
</CardContent> |
|
||||||
</Card> |
|
||||||
</> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default MyCoursesListTabContent |
|
Before Width: | Height: | Size: 14 KiB |
@ -1,59 +0,0 @@ |
|||||||
import * as React from "react" |
|
||||||
import { cva, type VariantProps } from "class-variance-authority" |
|
||||||
|
|
||||||
import { cn } from "@/lib/utils" |
|
||||||
|
|
||||||
const alertVariants = cva( |
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", |
|
||||||
{ |
|
||||||
variants: { |
|
||||||
variant: { |
|
||||||
default: "bg-background text-foreground", |
|
||||||
destructive: |
|
||||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", |
|
||||||
}, |
|
||||||
}, |
|
||||||
defaultVariants: { |
|
||||||
variant: "default", |
|
||||||
}, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
const Alert = React.forwardRef< |
|
||||||
HTMLDivElement, |
|
||||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> |
|
||||||
>(({ className, variant, ...props }, ref) => ( |
|
||||||
<div |
|
||||||
ref={ref} |
|
||||||
role="alert" |
|
||||||
className={cn(alertVariants({ variant }), className)} |
|
||||||
{...props} |
|
||||||
/> |
|
||||||
)) |
|
||||||
Alert.displayName = "Alert" |
|
||||||
|
|
||||||
const AlertTitle = React.forwardRef< |
|
||||||
HTMLParagraphElement, |
|
||||||
React.HTMLAttributes<HTMLHeadingElement> |
|
||||||
>(({ className, ...props }, ref) => ( |
|
||||||
<h5 |
|
||||||
ref={ref} |
|
||||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)} |
|
||||||
{...props} |
|
||||||
/> |
|
||||||
)) |
|
||||||
AlertTitle.displayName = "AlertTitle" |
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef< |
|
||||||
HTMLParagraphElement, |
|
||||||
React.HTMLAttributes<HTMLParagraphElement> |
|
||||||
>(({ className, ...props }, ref) => ( |
|
||||||
<div |
|
||||||
ref={ref} |
|
||||||
className={cn("text-sm [&_p]:leading-relaxed", className)} |
|
||||||
{...props} |
|
||||||
/> |
|
||||||
)) |
|
||||||
AlertDescription.displayName = "AlertDescription" |
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription } |
|
Loading…
Reference in new issue