Skip to content

Commit 883c23b

Browse files
committed
feat: image list
1 parent 12633f8 commit 883c23b

File tree

13 files changed

+123
-96
lines changed

13 files changed

+123
-96
lines changed

apps/web/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
/.pnp
66
.pnp.js
77

8+
/public/uploads/
9+
810
# testing
911
/coverage
1012

apps/web/@/molecules/upload/AssetsManagement.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,50 @@
1+
import { useEffect, useState } from "react"
2+
3+
import { Image, TListImageResponse } from "database"
4+
15
import SearchBar from "../nav/search-bar"
26
import ImageList from "./ImageList"
3-
import UploadImageButton from "./UploadImageButton"
47

58
const AssetManagement = () => {
9+
const [images, setImages] = useState<Image[]>([])
10+
11+
useEffect(() => {
12+
const fetchImages = async () => {
13+
try {
14+
const response = await fetch("/api/protected/images")
15+
const data: TListImageResponse = await response.json()
16+
17+
setImages(data?.data?.data?.data)
18+
} catch (error) {
19+
console.error("Error fetching images:", error)
20+
}
21+
}
22+
23+
fetchImages()
24+
}, [])
25+
26+
const handleSelect = (id: string) => {
27+
// Handle image selection
28+
console.log("Selected image:", id)
29+
}
30+
31+
const handleDelete = async (id: string) => {
32+
try {
33+
await fetch(`/api/images/${id}`, { method: "DELETE" })
34+
setImages(images.filter((image) => image.id !== id))
35+
} catch (error) {
36+
console.error("Error deleting image:", error)
37+
}
38+
}
39+
640
return (
741
<div className="gap-4">
8-
<div className="flex gap-4">
42+
<div className="flex gap-4 px-1 py-1">
943
<SearchBar />
10-
<UploadImageButton />
44+
<div>filter...</div>
1145
</div>
1246

13-
<ImageList />
47+
<ImageList images={images} />
1448
</div>
1549
)
1650
}

apps/web/@/molecules/upload/ImageList.tsx

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,35 @@
1-
import React, { useEffect, useState } from "react"
1+
import React from "react"
2+
import Image from "next/image"
23

4+
import { Image as ImageType } from "database"
35
import { TrashIcon } from "lucide-react"
46
import { Button } from "ui"
57

6-
interface Image {
7-
id: string
8-
url: string
8+
type ImageListProps = {
9+
images: ImageType[]
910
}
1011

11-
const ImageList: React.FC = () => {
12-
const [images, setImages] = useState<Image[]>([])
13-
14-
useEffect(() => {
15-
// Fetch images from API
16-
const fetchImages = async () => {
17-
try {
18-
const response = await fetch("/api/images/list") // Replace with your API endpoint
19-
const data = await response.json()
20-
setImages(data)
21-
} catch (error) {
22-
console.error("Error fetching images:", error)
23-
}
24-
}
25-
26-
fetchImages()
27-
}, [])
28-
29-
const handleSelect = (id: string) => {
30-
// Handle image selection
31-
console.log("Selected image:", id)
32-
}
33-
34-
const handleDelete = async (id: string) => {
35-
try {
36-
await fetch(`/api/images/${id}`, { method: "DELETE" })
37-
setImages(images.filter((image) => image.id !== id))
38-
} catch (error) {
39-
console.error("Error deleting image:", error)
40-
}
41-
}
42-
12+
const ImageList: React.FC<ImageListProps> = ({ images }) => {
4313
return (
44-
<div className="grid grid-cols-5 gap-2">
45-
{images.map((image) => (
14+
<div className="mt-2 grid grid-cols-5 gap-3">
15+
{images?.map((image) => (
4616
<div
4717
key={image.id}
4818
className="group relative"
4919
>
50-
<img
51-
src={image.url}
20+
<Image
21+
src={`${process.env.NEXT_PUBLIC_FRONTEND_URL}${image.url}`}
5222
alt={`Image ${image.id}`}
53-
className="h-40 w-40 cursor-pointer object-cover"
54-
onClick={() => handleSelect(image.id)}
23+
width={90}
24+
height={90}
25+
className="h-[90px] w-[90px] cursor-pointer object-cover"
26+
// onClick={() => handleSelect(image.id)}
5527
/>
5628
<Button
5729
variant="destructive"
5830
size="icon"
5931
className="absolute right-1 top-1 opacity-0 transition-opacity group-hover:opacity-100"
60-
onClick={() => handleDelete(image.id)}
32+
// onClick={() => handleDelete(image.id)}
6133
>
6234
<TrashIcon className="h-4 w-4" />
6335
</Button>

apps/web/@/molecules/upload/UploadImageButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ const UploadImageButton = () => {
5252
multiple={false}
5353
/>
5454
<LoadingButton
55-
variant="outline"
55+
variant="default"
5656
onClick={handleButtonClick}
5757
// loading={loading}
58+
className="gap-1"
5859
>
5960
<Upload size={16} />
6061
Upload

apps/web/@/molecules/upload/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "ui"
1212

1313
import AssetManagement from "./AssetsManagement"
14+
import UploadImageButton from "./UploadImageButton"
1415

1516
interface UploadProps {
1617
children: ReactNode
@@ -29,12 +30,15 @@ const Upload: React.FC<UploadProps> = ({ children }) => {
2930
// onOpenChange={handleOpenChange}
3031
>
3132
<DialogTrigger asChild>{children}</DialogTrigger>
32-
<DialogContent className="top-[200px] w-full max-w-[800px]">
33-
<DialogHeader>
34-
<DialogTitle>Asset Management</DialogTitle>
33+
<DialogContent className="w-full max-w-[800px]">
34+
<DialogHeader className="flex flex-row items-center gap-4">
35+
<DialogTitle className="mb-0">Asset Management</DialogTitle>
36+
<UploadImageButton />
3537
</DialogHeader>
3638

37-
<AssetManagement />
39+
<div className="h-[300px] overflow-scroll">
40+
<AssetManagement />
41+
</div>
3842

3943
<DialogFooter>
4044
<Button>Select</Button>

apps/web/app/[lang]/(public)/search/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,28 @@ export async function generateMetadata({ searchParams }): Promise<Metadata> {
1717
// TODO: Hight light matching
1818
// TODO: Load more
1919
export default async function Page({ searchParams }) {
20+
// TODO: Add pagination
2021
const posts = await getPosts({
2122
searchParams,
2223
})
2324

2425
return (
2526
<div className="">
2627
<h1 className="flex-1 text-xl font-extrabold">
27-
{`${posts?.total} results for`}
28+
{`${posts?.data?.total} results for`}
2829
<span className="px-2 text-2xl">{`"${searchParams?.search}"`}</span>
2930
</h1>
3031

3132
<SearchBar />
3233

3334
<Filter className="mt-3" />
3435

35-
{posts?.data?.length === 0 ? (
36+
{posts?.data?.data?.length === 0 ? (
3637
<NoItemFounded />
3738
) : (
3839
<div className="mt-4">
3940
<div className="mt-4">
40-
{posts?.data?.map((post) => (
41+
{posts?.data?.data?.map((post) => (
4142
<PostItem
4243
key={post.id}
4344
post={post}

apps/web/app/api/protected/images/list/routet.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

apps/web/app/api/protected/images/route.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,44 @@ import fs from "fs/promises"
22
import path from "path"
33
import { NextRequest } from "next/server"
44

5-
import { createImage, getImage } from "database"
5+
import { createImage, getImage, getImages } from "database"
66
import sharp from "sharp"
77
import { v4 as uuidv4 } from "uuid"
88

99
import { getServerSession } from "@/utils/auth"
1010

11+
// GET /api/protected/images/list
12+
// GET /api/protected/images/list?page=1&limit=10
13+
// GET /api/protected/images/list?page=1&limit=10&userId=1
14+
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test
15+
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test&mime=image/jpeg
16+
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test&mime=image/jpeg&sort=createdAt:desc
1117
export async function GET(request: NextRequest, { params }: { params: { imageId: string } }) {
1218
try {
13-
const image = await getImage(params.imageId)
19+
const session = await getServerSession()
1420

15-
if (!image)
21+
if (!session) {
1622
return Response.json({
17-
status: 404,
23+
status: 401,
1824
data: undefined,
25+
message: "Unauthorized",
26+
})
27+
}
28+
29+
const images = await getImages({
30+
userId: session?.user?.id,
31+
})
32+
33+
if (!images)
34+
return Response.json({
35+
status: 404,
36+
data: [],
1937
message: "Image not found",
2038
})
2139

2240
return Response.json({
2341
status: 200,
24-
data: image,
42+
data: images,
2543
})
2644
} catch (error) {
2745
// TODO: Log error

apps/web/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ export const config = {
3434
// Skip all internal paths (_next)
3535
// "/((?!_next).*)",
3636
// Optional: only run on root (/) URL
37-
"/((?!api/|_next/|_proxy/|asset|_static|_vercel|[\\w-]+\\.\\w+).*)",
37+
"/((?!api/|_next/|_proxy/|asset|_static|_vercel|uploads|[\\w-]+\\.\\w+).*)",
3838
],
3939
}

apps/web/next.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ const withNextIntl = createNextIntlPlugin();
77
const nextConfig = {
88
reactStrictMode: true,
99
transpilePackages: ["ui", "database"],
10+
images: {
11+
remotePatterns: [
12+
{
13+
protocol: 'https',
14+
hostname: '**',
15+
},
16+
{
17+
protocol: 'http',
18+
hostname: 'localhost',
19+
},
20+
],
21+
},
1022
webpack: (config) => {
1123
config.module.rules.push({
1224
test: /\.svg$/i,

0 commit comments

Comments
 (0)