Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions web-app/app/(profile)/profile/_components/image-scroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client"

import { useState } from "react"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Plus } from "lucide-react"

type ImageItem = {
id: string
file?: File
url: string
}

export default function ProfileImageScroll() {
const [images, setImages] = useState<ImageItem[]>([])
const [open, setOpen] = useState(false)

return (
<div>
<ScrollArea className="w-140 rounded-md border whitespace-nowrap">
<div className="flex w-max space-x-4 p-4">

{/* Add Image Button */}
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<button className="h-80 w-60 rounded-md bg-muted border-gray-300 hover:bg-neutral-200 border-2 border-dashed flex items-center justify-center transition">
<Plus />
</button>
</DialogTrigger>

<DialogContent>
<DialogHeader>
<DialogTitle>Manage Images</DialogTitle>
</DialogHeader>

<ImageManager
images={images}
setImages={setImages}
onClose={() => setOpen(false)}
/>
</DialogContent>
</Dialog>

{images.map((img) => (
<img
key={img.id}
src={img.url}
alt="Profile"
className="h-80 w-60 rounded-md object-cover"
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
)

type ImageManagerProps = {
images: ImageItem[]
setImages: React.Dispatch<React.SetStateAction<ImageItem[]>>
onClose: () => void
}

function ImageManager({ images, setImages, onClose }: ImageManagerProps) {
const [localImages, setLocalImages] = useState<ImageItem[]>(images)

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return

const newFiles = Array.from(e.target.files)

const newImages = newFiles.map((file) => ({
id: crypto.randomUUID(),
file,
url: URL.createObjectURL(file),
}))

setLocalImages((prev) => [...prev, ...newImages])
}

const removeImage = (id: string) => {
setLocalImages((prev) => prev.filter((img) => img.id !== id))
}

const handleSave = () => {
setImages(localImages)
onClose()
}

return (
<div className="space-y-4">
<input
type="file"
multiple
accept="image/*"
onChange={handleFileChange}
/>

<div className="flex gap-2 flex-wrap">
{localImages.map((img) => (
<div key={img.id} className="relative">
<img
src={img.url}
className="h-24 w-24 rounded-md object-cover"
/>
<button
onClick={() => removeImage(img.id)}
className="absolute top-1 right-1 bg-black text-white text-xs px-1 rounded"
>
</button>
</div>
))}
</div>

<Button onClick={handleSave} className="w-full">
Save
</Button>
</div>
)
}
}
1 change: 1 addition & 0 deletions web-app/app/(profile)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { requireAuth } from "@/lib/auth";
import ProfileImageScroll from "./_components/image-scroll";

export default async function ProfilePage() {
const user = await requireAuth();
Expand Down
58 changes: 58 additions & 0 deletions web-app/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client"

import * as React from "react"
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}

function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}

export { ScrollArea, ScrollBar }