Skip to content

Commit

Permalink
feat: upload cv option added
Browse files Browse the repository at this point in the history
  • Loading branch information
sabbirrifat committed Oct 31, 2023
1 parent 714e3fd commit 0e32d71
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 43 deletions.
6 changes: 5 additions & 1 deletion app/api/talents/my-profile/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export async function POST(request: Request) {
rate,
skills,
imageUrl,
cvUrl,
walletAddress,
} = await request.json();

Expand All @@ -47,6 +48,7 @@ export async function POST(request: Request) {
rate,
skills,
image_url,
cv_url,
wallet_address
) VALUES (
${title},
Expand All @@ -63,6 +65,7 @@ export async function POST(request: Request) {
${rate},
${skills},
${imageUrl},
${cvUrl},
${walletAddress}
)
ON CONFLICT (wallet_address) DO UPDATE
Expand All @@ -80,7 +83,8 @@ export async function POST(request: Request) {
about_work = ${aboutWork},
rate = ${rate},
skills = ${skills},
image_url = ${imageUrl}
image_url = ${imageUrl},
cv_url = ${cvUrl},
`;

return new Response(
Expand Down
17 changes: 6 additions & 11 deletions app/api/picture/route.ts → app/api/uploadFile/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { S3, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { v4 as uuidv4 } from "uuid";
import { generateKeyName } from "./utils";

export async function POST(request: Request) {
try {
Expand All @@ -18,24 +18,19 @@ export async function POST(request: Request) {
// Use the existing bucket
const bucketName = process.env.B2_BUCKET || "";

// Extract file extension
const fileExtension = file.type.split("/")[1];

// Upload a new file to the bucket with the file extension
const keyName = `image_${uuidv4()}.${fileExtension}`;
const keyName = generateKeyName(file.type);

// Remove header
const base64Data = file.data.replace(/^data:image\/\w+;base64,/, "");

//const base64Data = file.data.replace(/^data:image\/\w+;base64,/, "");
// Convert base64 to a buffer
const dataBuffer = Buffer.from(base64Data, "base64");

const dataBuffer = Buffer.from(file.data);

const putObjectParams = {
Bucket: bucketName,
Key: keyName,
Body: dataBuffer,
ACL: "public-read",
ContentEncoding: "base64",
ContentType: file.type,
};

Expand All @@ -59,7 +54,7 @@ export async function POST(request: Request) {

const url = `https://${bucketName}.${hostB2}/${keyName}`;

return new Response(JSON.stringify({ imageUrl: url }));
return new Response(JSON.stringify({ fileUrl: url }));
} catch (error) {
console.error(error);
}
Expand Down
13 changes: 13 additions & 0 deletions app/api/uploadFile/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { v4 as uuidv4 } from "uuid";

export const imageTypes = ["image/jpeg", "image/jpg", "image/png"];

export const generateKeyName = (fileType: string) => {
const fileExtension = fileType.split("/")[1];
if (imageTypes.includes(fileType)) {
return `image_${uuidv4()}.${fileExtension}`;
}
if (fileType === "application/pdf") {
return `pdf_${uuidv4()}.${fileExtension}`;
}
}
6 changes: 3 additions & 3 deletions app/components/drag-and-drop-file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import "react-loading-skeleton/dist/skeleton.css";
import FileData from "@interfaces/file-data";

interface Props {
setFile: (file: FileData | false) => void;
file: FileData | false;
setFile: (file: FileData | null) => void;
file: FileData | null;
isRenderedPage: boolean;
setIsRenderedPage: (isRenderedPage: boolean) => void;
invoiceInputValue: React.Ref<HTMLInputElement>;
Expand Down Expand Up @@ -57,7 +57,7 @@ const DragAndDropFile = ({

const { size, type, name } = file;

setFile(false);
setFile(null);

// TODO: move errors files to the modal errors
if (validTypes.indexOf(type) === -1) {
Expand Down
2 changes: 2 additions & 0 deletions app/talents/my-profile/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const resumeUploadSizeLimit =
process.env.NEXT_PUBLIC_RESUME_UPLOAD_SIZE_LIMIT_MB || "20";
121 changes: 93 additions & 28 deletions app/talents/my-profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
"use client";

import { useRef, useState, FormEvent, useEffect, useContext } from "react";
import {
useRef,
useState,
FormEvent,
useEffect,
useContext,
ChangeEvent,
} from "react";

import toast from "react-hot-toast";
import Autosuggest from "react-autosuggest";
Expand All @@ -14,12 +21,14 @@ import { skills } from "@/app/constants/skills";
import { countries } from "@/app/constants/countries";
import LabelOption from "@interfaces/label-option";
import FileData from "@interfaces/file-data";
import { resumeUploadSizeLimit } from "./constants";

export default function MyProfile() {
const invoiceInputValue = useRef(null);
const [isLoading, setIsLoading] = useState(false);
const [imageUrl, setImageUrl] = useState(null);
const [file, setFile] = useState<false | FileData>(false);
const [imageFile, setImageFile] = useState<null | FileData>(null);
const [cvFile, setCvFile] = useState<null | FileData>(null);
const [isRenderedPage, setIsRenderedPage] = useState<boolean>(true);
const [profileData, setProfileData] = useState({
title: "",
Expand All @@ -36,6 +45,7 @@ export default function MyProfile() {
rate: "",
skills: [],
image_url: "",
cv_url: "",
});

const walletAddress = useContext(AddressContext);
Expand Down Expand Up @@ -68,39 +78,76 @@ export default function MyProfile() {
fetchProfileData();
}, [walletAddress]);

useEffect(() => {
if (typeof file === "object" && file !== null) {
const fetchImage = async () => {
setIsLoading(true);

const postImageResponse = await fetch("/api/picture", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(file),
});

if (postImageResponse.ok) {
const { imageUrl } = await postImageResponse.json();

setImageUrl(imageUrl);
} else {
console.error(postImageResponse.statusText);
}
const onCvInputChange = async (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;

setIsLoading(false);
};
const cvFile = e.target.files[0];
const { type, name } = cvFile;

fetchImage();
if (cvFile.size > 1024 * 1024 * Number(resumeUploadSizeLimit)) {
toast.error(`File size should be less than ${resumeUploadSizeLimit} MB`);
return;
}
}, [file]);
const reader = new FileReader();
reader.readAsDataURL(cvFile);

reader.onload = function (event: ProgressEvent<FileReader>) {
// event.target.result contains the ArrayBuffer representation of the file
if (!event.target) return;
const arrayBuffer = event.target.result;
// Do something with the arrayBuffer, for example, send it to the server or process it further
console.log('ArrayBuffer:', arrayBuffer);
setCvFile({
name,
type,
data: arrayBuffer,
});
};
};

const uploadeFileToBucket = async (file: FileData | null) => {
if (!file) return null;
try {
const postImageResponse = await fetch("/api/uploadFile", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(file),
});

if (postImageResponse.ok) {
const { fileUrl } = await postImageResponse.json();
return fileUrl;
} else {
console.error(postImageResponse.statusText);
return null;
}
} catch (error) {
console.error(error);
return null;
}
};

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

setIsLoading(true);

const imageUrl = await uploadeFileToBucket(imageFile);
const cvUrl = await uploadeFileToBucket(cvFile);

if (!imageUrl) {
setIsLoading(false);
toast.error("Image upload failed!");
return;
}
if (!cvUrl) {
setIsLoading(false);
toast.error("CV upload failed!");
return;
}

const formData = new FormData(e.currentTarget);

const dataForm = {
Expand All @@ -118,6 +165,7 @@ export default function MyProfile() {
rate: formData.get("rate"),
skills: selectedSkills,
imageUrl,
cvUrl,
walletAddress,
};

Expand Down Expand Up @@ -230,8 +278,8 @@ export default function MyProfile() {
</div>
) : (
<DragAndDropFile
file={file}
setFile={setFile}
file={imageFile}
setFile={setImageFile}
isRenderedPage={isRenderedPage}
setIsRenderedPage={setIsRenderedPage}
// FIXME: change name of invoiceInputValue to fileInputValue
Expand Down Expand Up @@ -455,6 +503,23 @@ export default function MyProfile() {
defaultValue={profileData?.about_work}
/>
</div>
<div className="mt-4">
<label
htmlFor="cv"
className="inline-block ml-3 text-base text-black form-label"
>
CV*
</label>
<input
className="form-control block w-full px-4 py-2 text-base font-normal text-gray-600 bg-white bg-clip-padding border border-solid border-[#FFC905] rounded-lg hover:shadow-lg transition ease-in-out m-0 focus:text-black focus:bg-white focus:border-[#FF8C05] focus:outline-none"
placeholder="CV"
type="file"
required
name="cv"
accept=".pdf"
onChange={onCvInputChange}
/>
</div>
<div className="flex flex-col gap-4 mt-4 sm:flex-row">
<div className="flex-1">
<label
Expand Down

0 comments on commit 0e32d71

Please sign in to comment.