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-tooltip": "^1.0.7",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"aws-sdk": "^2.1475.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user