adding image upload
This commit is contained in:
parent
593bb467ad
commit
e4879ef639
7
.env
Normal file
7
.env
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# S3 data storage
|
||||||
|
S3_BUCKET_NAME="flockstr"
|
||||||
|
MY_AWS_ACCESS_KEY="AKIAT2UDOJMC6K25RFFN"
|
||||||
|
MY_AWS_SECRET_KEY="uCZVvFXTKv5fDX5gh+Wno5EH68ekVPPAClOsWULa"
|
||||||
|
REGION="us-east-1"
|
||||||
|
S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"
|
||||||
|
NEXT_PUBLIC_S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"
|
28
app/api/upload/route.ts
Normal file
28
app/api/upload/route.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { generateV4UploadSignedUrl } from "@/lib/actions/upload";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const BodySchema = z.object({
|
||||||
|
folderName: z.string().optional(),
|
||||||
|
fileType: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// export const runtime = "edge";
|
||||||
|
async function handler(req: Request) {
|
||||||
|
// const session = await getSession();
|
||||||
|
// if (!session?.user.id) {
|
||||||
|
// return new Response("Unauthorized", {
|
||||||
|
// status: 401,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
const rawJson = await req.json();
|
||||||
|
const body = BodySchema.parse(rawJson);
|
||||||
|
const { folderName, fileType } = body;
|
||||||
|
const filename = (`${folderName}/` ?? "") + nanoid();
|
||||||
|
const signedUrl = await generateV4UploadSignedUrl(filename, fileType);
|
||||||
|
|
||||||
|
return NextResponse.json({ ...signedUrl, fileName: filename });
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler as POST };
|
19
lib/actions/upload.ts
Normal file
19
lib/actions/upload.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { s3Client } from "@/lib/clients/s3";
|
||||||
|
|
||||||
|
export async function generateV4UploadSignedUrl(
|
||||||
|
fileName: string,
|
||||||
|
fileType: string,
|
||||||
|
) {
|
||||||
|
const preSignedUrl = await s3Client.getSignedUrl("putObject", {
|
||||||
|
Bucket: process.env.S3_BUCKET_NAME,
|
||||||
|
Key: fileName,
|
||||||
|
ContentType: fileType,
|
||||||
|
Expires: 5 * 60,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PresignedUrl", preSignedUrl);
|
||||||
|
|
||||||
|
return { url: preSignedUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateV4UploadSignedUrl;
|
9
lib/clients/s3.ts
Normal file
9
lib/clients/s3.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import S3 from "aws-sdk/clients/s3";
|
||||||
|
const s3Client = new S3({
|
||||||
|
signatureVersion: "v4",
|
||||||
|
region: process.env.REGION,
|
||||||
|
accessKeyId: process.env.MY_AWS_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.MY_AWS_SECRET_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { s3Client };
|
110
lib/hooks/useImageUpload.tsx
Normal file
110
lib/hooks/useImageUpload.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState, ReactNode, useRef } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { createZodFetcher } from "zod-fetch";
|
||||||
|
|
||||||
|
const fetchWithZod = createZodFetcher();
|
||||||
|
|
||||||
|
const PresignedPostSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
fileName: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const useImageUpload = (folderName?: string) => {
|
||||||
|
const [status, setStatus] = useState<
|
||||||
|
"empty" | "uploading" | "success" | "error"
|
||||||
|
>("empty");
|
||||||
|
const [imageUrl, setImageUrl] = useState<string | null>();
|
||||||
|
const [imagePreview, setImagePreview] = useState<string | null>();
|
||||||
|
|
||||||
|
const uploadImage = async (file: File, folderName?: string) => {
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const presignedPost = await fetchWithZod(
|
||||||
|
// The schema you want to validate with
|
||||||
|
PresignedPostSchema,
|
||||||
|
// Any parameters you would usually pass to fetch
|
||||||
|
"/api/upload",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ folderName, fileType: file.type }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { url, fileName } = presignedPost;
|
||||||
|
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
const result = await fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
body: file,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": file.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
setStatus("success");
|
||||||
|
const imageUrl = `${process.env.NEXT_PUBLIC_S3_BUCKET_URL}/${fileName}`;
|
||||||
|
setImageUrl(imageUrl);
|
||||||
|
setImagePreview(imageUrl);
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
setStatus("error");
|
||||||
|
console.log("ERROR", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onImageChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.currentTarget.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
setStatus("uploading");
|
||||||
|
uploadImage(file, folderName);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = async (readerEvent) => {
|
||||||
|
setImagePreview(readerEvent?.target?.result as string);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageUploadButton = ({ children }: { children: ReactNode }) => {
|
||||||
|
const inputFileRef = useRef<HTMLInputElement>(null);
|
||||||
|
function onButtonClick() {
|
||||||
|
if (inputFileRef.current) {
|
||||||
|
inputFileRef.current!.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button type="button" onClick={onButtonClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
hidden
|
||||||
|
onChange={onImageChange}
|
||||||
|
ref={inputFileRef}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
setStatus("empty");
|
||||||
|
setImageUrl(null);
|
||||||
|
setImagePreview(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
imagePreview,
|
||||||
|
status,
|
||||||
|
imageUrl,
|
||||||
|
ImageUploadButton,
|
||||||
|
clear,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useImageUpload;
|
@ -29,6 +29,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
|
"aws-sdk": "^2.1475.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"cmdk": "^0.2.0",
|
"cmdk": "^0.2.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user