Skip to content

Commit

Permalink
Merge pull request #23 from kscalelabs/second_milestone_serhii
Browse files Browse the repository at this point in the history
feature_image_upload
  • Loading branch information
Serhii Ofii authored Sep 9, 2024
2 parents 400d57e + 00ffa3c commit 8550111
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 83 deletions.
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"react-bootstrap-icons": "^1.11.4",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-images-uploading": "^3.1.7",
"react-scripts": "5.0.1",
"typescript": "^4.3.0",
"web-vitals": "^2.1.4"
Expand Down
26 changes: 17 additions & 9 deletions frontend/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { AxiosInstance } from "axios";
import { Collection } from "types/model";

export interface Image {
filename: string;
s3_url: string;
}
import { Collection, Image } from "types/model";

export interface CollectionCreateFragment {
title: string;
description: string;
}
export class api {
export class Api {
public api: AxiosInstance;

constructor(api: AxiosInstance) {
Expand All @@ -20,7 +15,7 @@ export class api {
const response = await this.api.get(`/`);
return response.data.message;
}
public async handleUpload(formData: FormData): Promise<Image> {
public async handleUpload(formData: FormData): Promise<Image | null> {
try {
const response = await this.api.post("/upload/", formData, {
headers: {
Expand All @@ -30,7 +25,7 @@ export class api {
return response.data;
} catch (error) {
console.error("Error uploading the file", error);
return { s3_url: "", filename: "" };
return null;
}
}
public async createCollection(
Expand All @@ -51,4 +46,17 @@ export class api {
const response = await this.api.get(`/get_collections`);
return response.data;
}
public async uploadImage(file: File, collection_id: string): Promise<Image> {
const response = await this.api.post("/upload", {
file,
id: collection_id,
});
return response.data;
}
public async getImages(collection_id: string): Promise<Array<Image>> {
const response = await this.api.get(
`/get_images?collection_id=${collection_id}`,
);
return response.data;
}
}
18 changes: 11 additions & 7 deletions frontend/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ export const signup = async (data: SignupData): Promise<Response> => {
const response = await axios.post(`${API_URL}/signup`, data);
return response.data;
};
export const read_me = async (token: string): Promise<Response> => {
const response = await axios.get(`${API_URL}/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
export const read_me = async (token: string): Promise<Response | null> => {
try {
const response = await axios.get(`${API_URL}/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch {
return null;
}
};
export const signin = async (data: SigninData): Promise<Response> => {
const response = await axios.post(`${API_URL}/signin`, data);
Expand Down
124 changes: 124 additions & 0 deletions frontend/src/components/UploadContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { FC, useState } from "react";
import { XCircleFill } from "react-bootstrap-icons";
import ImageUploading, { ImageListType } from "react-images-uploading";

interface UploadContentProps {
onUpload: (file: File) => Promise<void>; // Updated to handle a single file
}

const UploadContent: FC<UploadContentProps> = ({ onUpload }) => {
const [images, setImages] = useState<ImageListType>([]);
const [uploading, setUploading] = useState<boolean>(false);
// const [uploadIndex, setUploadIndex] = useState<number>(0);

const maxNumber = 10; // Set the maximum number of files allowed

const handleUpload = async () => {
if (images.length === 0) return;

setUploading(true);
for (let i = 0; i < images.length; i++) {
const file = images[i].file as File;
try {
await onUpload(file); // Upload each file one by one
// Optionally, handle success feedback here
} catch (error) {
console.error(`Failed to upload file ${file.name}:`, error);
// Optionally, handle failure feedback here
}
}
setImages([]); // Clear images after uploading
setUploading(false);
};

const onChange = (imageList: ImageListType) => {
setImages(imageList);
};

return (
<div className="p-6 bg-white dark:bg-gray-800 rounded-lg">
<ImageUploading
multiple
value={images}
onChange={onChange}
maxNumber={maxNumber}
dataURLKey="data_url"
>
{({
imageList,
onImageUpload,
onImageRemoveAll,
onImageRemove,
isDragging,
dragProps,
}) => (
<div className="upload__image-wrapper">
{/* Dropzone Area */}
<div
className={`border-2 border-dashed p-5 rounded-lg flex flex-col items-center justify-center h-64 transition-colors duration-300 ${
isDragging
? "border-green-500 bg-green-100 dark:border-green-600 dark:bg-green-900"
: "border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-800"
}`}
onClick={onImageUpload} // Click to upload images
{...dragProps} // Drag-n-Drop props
>
<p className="text-gray-500 dark:text-gray-400">
Drag & drop images here, or click to select files
</p>
</div>

{/* Display uploaded images below the dropzone */}
<div className="mt-5 grid grid-cols-3 gap-4 w-full">
{imageList.length > 0 ? (
imageList.map((image, index) => (
<div key={index} className="relative text-center">
<img
src={image["data_url"]}
alt=""
className="w-full h-32 object-cover rounded-lg border border-gray-300 dark:border-gray-700"
/>
<button
className="absolute top-2 right-2 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 transition-colors duration-200"
onClick={() => onImageRemove(index)}
>
<XCircleFill size={24} />
</button>
</div>
))
) : (
<p className="text-gray-500 dark:text-gray-400">
No images uploaded yet.
</p>
)}
</div>

{/* Optional: Remove all button */}
{imageList.length > 0 && (
<div className="flex gap-4">
{/* Upload Button */}
<button
className={`mt-3 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-500 transition-colors duration-200 ${
uploading ? "cursor-not-allowed opacity-50" : ""
}`}
onClick={handleUpload}
disabled={uploading || images.length === 0}
>
{uploading ? "Uploading..." : "Upload Images"}
</button>
<button
className="mt-3 bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-500 transition-colors duration-200"
onClick={onImageRemoveAll}
>
Remove All Images
</button>
</div>
)}
</div>
)}
</ImageUploading>
</div>
);
};

export default UploadContent;
33 changes: 33 additions & 0 deletions frontend/src/components/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FC, ReactNode } from "react";
import { X } from "react-bootstrap-icons"; // Using react-bootstrap-icons for the close button

interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}

const Modal: FC<ModalProps> = ({ isOpen, onClose, children }) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-70 dark:bg-gray-900 dark:bg-opacity-80">
{/* Modal container */}
<div className="bg-white p-8 rounded-2xl shadow-xl max-w-3xl w-full relative border border-gray-200 dark:bg-gray-800 dark:border-gray-700 dark:text-white">
{/* Close button in the top right corner */}
<button
className="absolute top-4 right-4 text-gray-600 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400 transition-colors duration-200"
onClick={onClose}
aria-label="Close Modal"
>
<X size={28} /> {/* Close icon from react-bootstrap-icons */}
</button>

{/* Modal content */}
<div className="p-6">{children}</div>
</div>
</div>
);
};

export default Modal;
9 changes: 6 additions & 3 deletions frontend/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ const AuthProvider = ({ children }: { children: ReactNode }) => {
const token = localStorage.getItem("token");
if (token) {
const fetch_data = async (token: string) => {
const response = await read_me(token);
console.log(response);
if (response) setAuth(response);
try {
const response = await read_me(token);
if (response) setAuth(response);
} catch {
return;
}
};
fetch_data(token);
} else signout();
Expand Down
Loading

0 comments on commit 8550111

Please sign in to comment.