changed a lot

This commit is contained in:
Harivansh Rathi 2024-11-25 01:24:37 -05:00
parent ef9ccf22d3
commit 28901128ff
20 changed files with 1794 additions and 526 deletions

View file

@ -0,0 +1,84 @@
import * as React from "react"
import { useDropzone } from "react-dropzone"
import { Cloud, File, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
interface FileUploadProps {
onUpload: (files: File[]) => void
value?: File[]
onChange?: (files: File[]) => void
className?: string
}
export function FileUpload({ onUpload, value = [], onChange, className }: FileUploadProps) {
const [files, setFiles] = React.useState<File[]>(value)
const onDrop = React.useCallback((acceptedFiles: File[]) => {
setFiles(prev => [...prev, ...acceptedFiles])
onChange?.(acceptedFiles)
onUpload(acceptedFiles)
}, [onChange, onUpload])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': [],
'application/pdf': [],
'application/msword': [],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [],
}
})
const removeFile = (fileToRemove: File) => {
const newFiles = files.filter(file => file !== fileToRemove)
setFiles(newFiles)
onChange?.(newFiles)
}
return (
<div className={className}>
<div
{...getRootProps()}
className={cn(
"border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors",
isDragActive && "border-primary",
className
)}
>
<input {...getInputProps()} />
<div className="flex flex-col items-center gap-2">
<Cloud className="h-10 w-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
Drag & drop files here, or click to select files
</p>
<Button variant="secondary" size="sm">
Select Files
</Button>
</div>
</div>
{files.length > 0 && (
<div className="mt-4 space-y-2">
{files.map((file, index) => (
<div
key={index}
className="flex items-center gap-2 rounded-md border p-2"
>
<File className="h-4 w-4 text-muted-foreground" />
<span className="text-sm flex-1 truncate">{file.name}</span>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => removeFile(file)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
)}
</div>
)
}

View file

@ -1,55 +1,21 @@
'use client'
import { useState, useEffect } from 'react'
import { Sparkles } from 'lucide-react'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
const IframeWithSkeleton = () => {
const [iframeLoaded, setIframeLoaded] = useState(false);
useEffect(() => {
const iframe = document.getElementById('youtube-iframe') as HTMLIFrameElement;
if (iframe) {
const handleIframeLoad = () => {
setIframeLoaded(true);
};
iframe.addEventListener('load', handleIframeLoad);
return () => {
iframe.removeEventListener('load', handleIframeLoad);
};
}
}, []);
return (
<>
{!iframeLoaded && <Skeleton className="w-full max-w-2xl h-auto aspect-video" />}
<iframe
id="youtube-iframe"
src="https://www.youtube.com/embed/Q6jDdtbkMIU?si=YtgU89RhYiwt5-U5"
title="YouTube Video Player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
className={`w-full max-w-2xl h-auto aspect-video rounded-[6px] ${iframeLoaded ? '' : 'hidden'}`}
></iframe>
</>
);
};
export const Header = () => {
return (
<div className="space-y-20 mt-32">
<div className="mx-auto grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="flex flex-col justify-center text-center lg:text-left ">
<div className="mx-auto grid grid-cols-1 gap-8">
<div className="flex flex-col justify-center text-center">
<h2 className="text-4xl font-extrabold sm:text-5xl">
Clone. Build. Ship.
</h2>
<p className="mt-4 text-lg text-foreground">
Build your SaaS faster with our fully customizable template.
</p>
<div className="flex justify-center lg:justify-start items-center mt-4">
<div className="flex justify-center items-center mt-4">
<Link href="/overview">
<Button className="gap-2">
<Sparkles className="h-5 w-5" />
@ -58,9 +24,6 @@ export const Header = () => {
</Link>
</div>
</div>
<div className="flex items-center justify-center rounded-lg overflow-hidden">
<IframeWithSkeleton />
</div>
</div>
</div>
)

View file

@ -1,26 +1,26 @@
'use client'
import Link from 'next/link'
import { ModeToggle } from '@/components/mode-toggle'
import Image from 'next/image'
import { UserButton } from '@/components/user-button'
import { MobileSidebar } from '@/components/mobile-sidebar'
import { Logo } from '@/components/logo'
import { usePathname } from 'next/navigation'
export const navPages = [
{
title: 'Dashboard',
link: '/dashboard'
},
{
title: 'Pricing',
link: '/#pricing'
},
{
title: 'Items',
link: '/#items'
}
]
export const Navbar = () => {
const pathname = usePathname()
// Don't show navbar on home page
if (pathname === '/') return null
return (
<nav className="top-0 w-full z-50 transition">
<div className="max-w-6xl mx-auto px-6 py-4">

View file

@ -0,0 +1,91 @@
import * as React from "react"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Button } from "@/components/ui/button"
import { PlusCircle } from "lucide-react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function ProjectSelect() {
const [open, setOpen] = React.useState(false)
const [projectName, setProjectName] = React.useState("")
// This would be fetched from your backend
const projects = [
{ id: "1", name: "Marketing Website" },
{ id: "2", name: "Mobile App" },
{ id: "3", name: "Dashboard Redesign" },
]
const handleCreateProject = () => {
// Handle project creation here
setOpen(false)
setProjectName("")
}
return (
<div className="flex items-center gap-2">
<Select>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="Select project" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Projects</SelectLabel>
{projects.map((project) => (
<SelectItem key={project.id} value={project.id}>
{project.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="icon">
<PlusCircle className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
<DialogDescription>
Add a new project to organize your tasks.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">Project name</Label>
<Input
id="name"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
placeholder="Enter project name"
/>
</div>
</div>
<DialogFooter>
<Button onClick={handleCreateProject}>Create Project</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}

View file

@ -0,0 +1,101 @@
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { Toggle } from "@/components/ui/toggle"
import {
Bold,
Italic,
List,
ListOrdered,
Quote,
Heading2,
Code,
} from "lucide-react"
import { cn } from "@/lib/utils"
const MenuBar = ({ editor }: { editor: any }) => {
if (!editor) {
return null
}
return (
<div className="border-b flex flex-wrap gap-1 p-1">
<Toggle
size="sm"
pressed={editor.isActive('bold')}
onPressedChange={() => editor.chain().focus().toggleBold().run()}
>
<Bold className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('italic')}
onPressedChange={() => editor.chain().focus().toggleItalic().run()}
>
<Italic className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('heading')}
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
>
<Heading2 className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('bulletList')}
onPressedChange={() => editor.chain().focus().toggleBulletList().run()}
>
<List className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('orderedList')}
onPressedChange={() => editor.chain().focus().toggleOrderedList().run()}
>
<ListOrdered className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('blockquote')}
onPressedChange={() => editor.chain().focus().toggleBlockquote().run()}
>
<Quote className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('code')}
onPressedChange={() => editor.chain().focus().toggleCode().run()}
>
<Code className="h-4 w-4" />
</Toggle>
</div>
)
}
interface RichTextEditorProps {
content?: string
onChange?: (content: string) => void
className?: string
}
export function RichTextEditor({ content, onChange, className }: RichTextEditorProps) {
const editor = useEditor({
extensions: [
StarterKit,
],
content,
onUpdate: ({ editor }) => {
onChange?.(editor.getHTML())
},
})
return (
<div className={cn("border rounded-md", className)}>
<MenuBar editor={editor} />
<EditorContent
editor={editor}
className="prose prose-sm dark:prose-invert max-w-none p-4"
/>
</div>
)
}

54
components/task-card.tsx Normal file
View file

@ -0,0 +1,54 @@
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { Card } from "@/components/ui/card"
import { cn } from "@/lib/utils"
interface TaskCardProps {
id: string
title: string
dueDate: string
progress: number
status: string
}
export function TaskCard({ id, title, dueDate, progress, status }: TaskCardProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: id,
data: {
type: "Task",
task: { id, title, dueDate, progress, status },
},
})
const style = {
transform: CSS.Transform.toString(transform),
transition,
}
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<Card
className={cn(
"p-3 cursor-move hover:border-primary/50 transition-colors",
isDragging && "opacity-50 border-dashed"
)}
>
<h5 className="font-medium">{title}</h5>
<p className="text-sm text-muted-foreground mt-1">Due: {dueDate}</p>
<div className="mt-3 h-1.5 w-full bg-secondary rounded-full">
<div
className="h-full bg-primary rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
</Card>
</div>
)
}

View file

@ -0,0 +1,52 @@
import { useDroppable } from "@dnd-kit/core"
import {
SortableContext,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import { Card } from "@/components/ui/card"
import { TaskCard } from "./task-card"
import { cn } from "@/lib/utils"
import { LucideIcon } from "lucide-react"
interface Task {
id: string
title: string
dueDate: string
progress: number
status: string
}
interface TaskColumnProps {
id: string
title: string
icon: LucideIcon
tasks: Task[]
}
export function TaskColumn({ id, title, icon: Icon, tasks }: TaskColumnProps) {
const { setNodeRef, isOver } = useDroppable({
id,
})
const taskIds = tasks.map(task => task.id)
return (
<Card className={cn("p-4", isOver && "ring-2 ring-primary")}>
<h4 className="font-medium mb-4 flex items-center">
<Icon className="h-4 w-4 mr-2" />
{title}
</h4>
<div ref={setNodeRef} className="space-y-4">
<SortableContext
id={id}
items={taskIds}
strategy={verticalListSortingStrategy}
>
{tasks.map((task) => (
<TaskCard key={task.id} {...task} />
))}
</SortableContext>
</div>
</Card>
)
}

42
components/ui/toggle.tsx Normal file
View file

@ -0,0 +1,42 @@
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground 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=on]:bg-accent data-[state=on]:text-accent-foreground",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-3",
sm: "h-9 px-2.5",
lg: "h-11 px-5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }