41 Commits

Author SHA1 Message Date
Kshitij 72add0d767 Final commit for frontend branch. 2025-06-26 22:09:38 +05:30
Kshitij c5b26e2b68 Removed info file from frontend branch. 2025-04-19 23:32:49 +05:30
Kshitij 1189e7cb78 Removed package-lock file. 2025-04-19 23:26:31 +05:30
Kshitij 2f6f0ba747 Removed placeholder file. 2025-04-19 23:26:12 +05:30
Kshitij 96f9ddb1d8 Merge branch 'main' into frontend. 2025-04-19 23:13:40 +05:30
Atharva Ombase 2c29597f1d {new_commit_message} 2025-04-19 20:08:15 +05:30
Atharva Ombase 1f6cbf4310 Feat: Update API URL and refactor FileTable component for improved path handling 2025-04-19 20:04:57 +05:30
Atharva Ombase afccce0be3 Feat:Added the upload reducer in the store for re-renders 2025-04-19 18:56:57 +05:30
Atharva Ombase 96fc18ab80 Fix:Removed this unused file 2025-04-19 18:56:22 +05:30
Atharva Ombase febde7dffe Feat:Called UploadStatusSlice for toggling the variable for the re-render 2025-04-19 18:54:38 +05:30
Atharva Ombase c426fecf43 Feat:Added the isUploading in the useEffect for re-rendering file table when new file/folder is uploaded 2025-04-19 18:53:34 +05:30
Atharva Ombase 0adc932e53 Feat:Added slice to re-render file table on every file uplaod and folder creation 2025-04-19 18:52:33 +05:30
Atharva Ombase 54dd5a1fcc Feat:Added logic to redirect the user to login page if user is not logged in 2025-04-19 01:29:07 +05:30
Atharva Ombase d59e8c789c Minor changes 2025-04-19 01:28:06 +05:30
Atharva Ombase 920c793fa6 Feat: Added the logic to save the username and token into the localhost alongside the expiryId. Add Login page component in Authentication module 2025-04-19 01:25:07 +05:30
Atharva Ombase 608435b758 Fix:Fixed the logic because the api endpoints were changed 2025-04-19 01:21:03 +05:30
Atharva Ombase c55dd4b661 Fix:Fixed the bug where dropdown menu was not appearing and removed uneccessary options 2025-04-19 01:19:35 +05:30
Atharva Ombase 5c9e8fedbc Feat:Added username from localhost for fetching 2025-04-19 01:18:21 +05:30
Atharva Ombase 6294066ea7 Fix:Installed axios 2025-04-18 18:11:43 +05:30
vedang29 76f9b00624 Implemented sign in & sign up (with token storage) 2025-04-18 16:33:40 +05:30
Atharva Ombase 5391410609 Fix:Fixed api call for donwloading files 2025-04-16 15:47:11 +05:30
Atharva Ombase a91d7fe8c7 Fix: Removed extra landingpage on the src/pages path 2025-04-16 15:45:50 +05:30
Atharva Ombase 65ca53b224 Fix:Changed project name 2025-04-16 15:43:02 +05:30
Atharva Ombase 9d6387699e Feat:Added provider for the redux 2025-04-16 15:41:07 +05:30
Atharva Ombase f6371faf9a Fix:Changed name of the project 2025-04-16 15:40:14 +05:30
Atharva Ombase 9632450d16 Feat:Added logic to upload the file to the Skycrate 2025-04-16 15:36:49 +05:30
Atharva Ombase e59784bfa9 Feat:Added slice for redux for hdfspath 2025-04-16 15:28:20 +05:30
Atharva Ombase 6aae767aa6 Fix:Changed title of the project 2025-04-16 15:25:40 +05:30
Atharva Ombase df4d3c1990 Fix:deleted api.js and FileUpload.jsx and made new files as needed 2025-04-16 15:25:04 +05:30
Atharva Ombase 4ab49db6af Feat:Added redux for the hdfspath to change the uplaod the file onto folder 2025-04-16 15:22:12 +05:30
Atharva Ombase 00f6e28207 Feat:Added file icons and fixed download errors 2025-04-16 15:19:55 +05:30
Atharva Ombase 1f40b02346 Feat:Added uploadFile.jsx for in UserPages 2025-04-16 15:15:05 +05:30
Kshitij 8c67d9d4c6 Meged Ombase's and Salvi's changes from main branch in frontend. 2025-04-15 11:10:35 +05:30
Kshitij 70f654179a Merged Ombase's file list in main branch with Salvi's file upload. 2025-04-15 11:07:44 +05:30
KaranSalvi b42a53e99b feat:Added upload file functionality 2025-04-15 03:41:16 +05:30
atharvaombase 2ae2002713 Fix:Increased the size of the eye icon in the password field. 2025-03-28 11:03:37 +05:30
atharvaombase 4457823342 Fix:Removed Google login button 2025-03-28 11:01:25 +05:30
atharvaombase 1142ece2fd Feat:Added Link for the Login and signin 2025-03-28 10:48:32 +05:30
atharvaombase 6916142deb Feat:Added redux dependencies 2025-03-28 10:21:07 +05:30
atharvaombase c798c53dcc Feat:Added new store folder for redux 2025-03-28 10:20:28 +05:30
Kshitij 596c6bf573 Added new landing page in sample, designed by Dinesh! 2025-02-27 23:17:50 +05:30
24 changed files with 867 additions and 5878 deletions
-28
View File
@@ -1,28 +0,0 @@
# Info
---
## Work distribution
- Design: Kapil
- Frontend: Shivani, Shriniwas, Ombase, Tejas, Sonali, Dinesh
- Backend: Vedang, Lalit
- DBMS: Lalit
- HDFS: Sonali, Prajakta, Poonam
---
## Description
In this mini project, we'll be creating something similar to Google Drive. There shall be 3 pages, landing, login/registration and main page where all the files uploaded by the user will be shown. Kapil is supposed to design the UI and send it over by Sunday. Based on this design, people in the frontend department shall work on the pages.
Landing page is basically a home page containing small description of the project, features etc. Login/registration page will ask for username/password. Lalit is expected to implement it using MySQL/MongoDB, i.e. he is responsible for user authentication. Once the user is authenticated, they shall be redirected to the main page where they can view their files, and upload/delete them.
Vedang is responsible to developing the backend code in Java for encrypting the uploaded files and decrypting the downloaded files. These files will be stored in Hadoop File System (HDFS) which shall be handled by Sonali, Prajakta and Poonam.
We are planning to make this a good enough project so that we can maybe open source it and make it a part of our resume. Therefore it is important that y'all work sahi se and finish your stuff by the deadlines. We are expected to finish the entire project ✨ before IN-SEM exam ✨
While you are working on the project, note down the things you are doing so that we can provide it to the people doing the documentation.
---
+1
View File
@@ -0,0 +1 @@
VITE_API_URL=http://192.168.29.61:8081
+1 -1
View File
@@ -8,7 +8,7 @@
<link href="/src/styles.css" rel="stylesheet"> <link href="/src/styles.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" />
<title>Drive-thru</title> <title>Skycrate</title>
</head> </head>
-4963
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "drive-thru", "name": "Skycrate",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
@@ -12,9 +12,11 @@
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.6.0", "@reduxjs/toolkit": "^2.6.0",
"@tailwindcss/vite": "^4.0.9", "@tailwindcss/vite": "^4.0.9",
"axios": "^1.8.4",
"lucide-react": "^0.476.0", "lucide-react": "^0.476.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hot-toast": "^2.5.2",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^7.2.0" "react-router-dom": "^7.2.0"
+178 -61
View File
@@ -1,82 +1,198 @@
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { setCurrentPath } from "../store/pathSlice";
import {
FileText,
FileVideo,
FileImage,
FileAudio,
FileArchive,
FileSpreadsheet,
FileType2,
FileCode2,
Presentation,
Folder,
Download,
Trash2,
ArrowLeft,
} from "lucide-react";
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8080";
const FileTable = ({ initialPath }) => { const FileTable = ({ initialPath }) => {
const [currentPath, setCurrentPath] = useState(initialPath || "/"); // Read username dynamically to avoid stale null on first load
const [files, setFiles] = useState([]); const username = localStorage.getItem("username") || "";
const userRoot = `/${username}`;
// Initialize currentPath only once with the correct userRoot
const [currentPath, setCurrentPathState] = useState(
() => initialPath || userRoot
);
const [files, setFiles] = useState([]);
const dispatch = useDispatch();
const isUploading = useSelector((state) => state.upload.isUploading);
// Helpers to parse entry
const getType = (entry) => const getType = (entry) =>
entry.trim().startsWith("📁") ? "Folder" : "File"; entry.trim().startsWith("📁") ? "Folder" : "File";
const getName = (entry) => entry.trim().replace(/^📁\s*|^📄\s*/, ""); const getName = (entry) => entry.trim().replace(/^📁\s*|^📄\s*/, "");
const isFile = (entry) => getType(entry) === "File"; const isFile = (entry) => getType(entry) === "File";
// Fetch and show only top-level entries (indentation = 0) const getIcon = (name, type) => {
if (type === "Folder")
return <Folder className="text-yellow-500 w-5 h-5 mr-2" />;
const ext = name.split(".").pop().toLowerCase();
switch (ext) {
case "txt":
return <FileText className="text-gray-700 w-5 h-5 mr-2" />;
case "mp4":
case "mkv":
return <FileVideo className="text-purple-500 w-5 h-5 mr-2" />;
case "jpg":
case "jpeg":
case "png":
case "gif":
return <FileImage className="text-pink-500 w-5 h-5 mr-2" />;
case "mp3":
case "wav":
return <FileAudio className="text-indigo-500 w-5 h-5 mr-2" />;
case "zip":
case "rar":
case "tar":
case "gz":
return <FileArchive className="text-red-500 w-5 h-5 mr-2" />;
case "csv":
case "xls":
case "xlsx":
return <FileSpreadsheet className="text-green-500 w-5 h-5 mr-2" />;
case "ppt":
case "pptx":
return <Presentation className="text-orange-500 w-5 h-5 mr-2" />;
case "js":
case "html":
case "css":
case "java":
case "py":
case "cpp":
return <FileCode2 className="text-blue-500 w-5 h-5 mr-2" />;
default:
return <FileType2 className="text-gray-500 w-5 h-5 mr-2" />;
}
};
const fetchFiles = async () => { const fetchFiles = async () => {
try { try {
const response = await fetch( const res = await fetch(
`http://192.168.29.61:8080/api/hdfs/listFiles?hdfsPath=${currentPath}` `${API_URL}/api/hdfs/listFiles?hdfsPath=${encodeURIComponent(
currentPath
)}`
); );
const data = await response.json(); const data = await res.json();
// Filter entries: only those without leading spaces
const filtered = data.filter( const filtered = data.filter(
(entry) => entry.match(/^ */)[0].length === 0 (entry) => entry.match(/^ */)[0].length === 0
); );
setFiles(filtered); setFiles(filtered);
} catch (error) { } catch (err) {
console.error("Failed to fetch files:", error); console.error("Failed to fetch files:", err);
setFiles([]); setFiles([]);
} }
}; };
useEffect(() => { const deleteFileOrFolder = async (name, type, e) => {
fetchFiles(); e.stopPropagation();
}, [currentPath]); try {
const hdfsPath = `${currentPath}/${name}`;
const endpoint =
type === "File"
? `${API_URL}/api/hdfs/deleteFile?hdfsPath=${encodeURIComponent(
hdfsPath
)}`
: `${API_URL}/api/hdfs/deleteFolder?hdfsPath=${encodeURIComponent(
hdfsPath
)}`;
// Navigate into a folder const resp = await fetch(endpoint, { method: "DELETE" });
const handleOpenFolder = (folderName) => { if (!resp.ok) console.error("Deletion failed:", await resp.text());
const newPath = fetchFiles();
currentPath === "/" ? `/${folderName}` : `${currentPath}/${folderName}`; } catch (err) {
setCurrentPath(newPath); console.error("Failed to delete:", err);
}
};
useEffect(() => {
dispatch(setCurrentPath(currentPath));
fetchFiles();
}, [currentPath, dispatch, isUploading]);
const handleOpenFolder = (folderName) => {
setCurrentPathState((prev) => `${prev}/${folderName}`);
}; };
// Go up one level
const goBack = () => { const goBack = () => {
if (currentPath === "/") return; if (currentPath === userRoot) return;
const parts = currentPath.split("/").filter(Boolean); const parts = currentPath.split("/").filter(Boolean);
parts.pop(); parts.pop();
setCurrentPath(parts.length === 0 ? "/" : `/${parts.join("/")}`); setCurrentPathState(parts.length === 0 ? userRoot : `/${parts.join("/")}`);
};
const handleFileDownload = async (hdfsPath, name, event) => {
event.stopPropagation();
try {
const authToken = localStorage.getItem("token");
const response = await fetch(
`${API_URL}/api/hdfs/downloadFile?hdfsEncPath=${encodeURIComponent(
hdfsPath
)}&localPath=${name}&username=${username}`,
{
method: "POST",
headers: { Authorization: `Bearer ${authToken}` },
}
);
if (!response.ok) throw new Error(await response.text());
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = name;
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
fetchFiles();
} catch (error) {
console.error("Download failed:", error);
alert("Something went wrong while downloading the file.");
}
}; };
return ( return (
<div className="relative overflow-x-auto shadow-md rounded-lg"> <div className="relative overflow-x-auto rounded-2xl shadow-lg border border-blue-200">
<div className="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-medium"> <div className="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-semibold text-sm">
<span>Path: {currentPath}</span> <span className="truncate max-w-[80%]">Path: {currentPath}</span>
{currentPath !== "/" && ( {currentPath !== userRoot && (
<button onClick={goBack} className="text-blue-600 hover:underline"> <button
Go Back onClick={goBack}
className="flex items-center gap-1 text-blue-600 hover:underline text-sm"
>
<ArrowLeft className="w-4 h-4" />
Go Back
</button> </button>
)} )}
</div> </div>
<table className="w-full text-sm text-left text-black"> <table className="w-full text-sm text-left text-black">
<thead className="text-xs text-black uppercase bg-blue-100"> <thead className="text-xs uppercase bg-blue-50 text-blue-800 border-b border-blue-200">
<tr> <tr>
<th scope="col" className="px-6 py-3"> <th className="px-6 py-3">Name</th>
File Name <th className="px-6 py-3">Actions</th>
</th>
<th scope="col" className="px-6 py-3">
Type
</th>
<th scope="col" className="px-6 py-3">
Action
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{files.length === 0 ? ( {files.length === 0 ? (
<tr> <tr>
<td colSpan="3" className="px-6 py-4 text-gray-600"> <td colSpan="2" className="px-6 py-4 text-gray-500 text-center">
No files found. No files found.
</td> </td>
</tr> </tr>
@@ -84,38 +200,39 @@ const FileTable = ({ initialPath }) => {
files.map((entry, idx) => { files.map((entry, idx) => {
const name = getName(entry); const name = getName(entry);
const type = getType(entry); const type = getType(entry);
const encodedPath = encodeURIComponent(`${currentPath}/${name}`); const hdfsPath = `${currentPath}/${name}`;
const downloadUrl = `http://192.168.29.61:8080/api/hdfs/downloadFile?hdfsPath=${encodedPath}&localPath=E:/testdownload/${name}&kalas=${
currentPath.split("/")[1] || "user"
}`;
return ( return (
<tr <tr
key={idx} key={idx}
className="even:bg-blue-50 odd:bg-white border-b border-blue-200" onClick={
type === "Folder" ? () => handleOpenFolder(name) : undefined
}
className={`even:bg-blue-50 odd:bg-white border-b border-blue-100 transition hover:bg-blue-100 ${
type === "Folder" ? "cursor-pointer" : ""
}`}
> >
<td className="px-6 py-4 font-medium whitespace-nowrap"> <td className="px-6 py-4 font-medium flex items-center">
{getIcon(name, type)}
{name} {name}
</td> </td>
<td className="px-6 py-4">{type}</td> <td className="px-6 py-4 space-x-3">
<td className="px-6 py-4"> {isFile(entry) && (
{isFile(entry) ? (
<a
href={downloadUrl}
className="font-medium text-blue-600 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
Download
</a>
) : (
<button <button
onClick={() => handleOpenFolder(name)} onClick={(e) => handleFileDownload(hdfsPath, name, e)}
className="font-medium text-blue-600 hover:underline" className="text-blue-600 hover:underline inline-flex items-center"
> >
Open <Download className="w-4 h-4 mr-1" />
Download
</button> </button>
)} )}
<button
onClick={(e) => deleteFileOrFolder(name, type, e)}
className="text-red-600 hover:underline inline-flex items-center"
>
<Trash2 className="w-4 h-4 mr-1" />
Delete
</button>
</td> </td>
</tr> </tr>
); );
-68
View File
@@ -1,68 +0,0 @@
import React, { useState } from "react";
import { uploadFileToHDFS } from "../utils/api";
const FileUpload = () => {
const [file, setFile] = useState(null);
const [hdfsPath, setHdfsPath] = useState("/kalas");
const [uploadedFileName, setUploadedFileName] = useState("");
const [username, setUsername] = useState("kalas");
const handleSubmit = (e) => {
e.preventDefault();
if (!file || !uploadedFileName) {
return;
}
uploadFileToHDFS(file, hdfsPath, uploadedFileName, username);
};
return (
<form
onSubmit={handleSubmit}
className="p-5 py-16 border rounded shadow w-full max-w-md"
>
<h2 className="text-lg font-bold mb-2">Upload File Your File</h2>
<input
type="file"
onChange={(e) => {
setFile(e.target.files[0]);
setUploadedFileName(e.target.files[0]?.name || "");
}}
className="mb-2"
/>
{/* <input
type="text"
placeholder="HDFS Path"
value={hdfsPath}
onChange={(e) => setHdfsPath(e.target.value)}
className="border p-2 mb-2 w-full"
/> */}
{/* <input
type="text"
placeholder="Uploaded File Name"
value={uploadedFileName}
onChange={(e) => setUploadedFileName(e.target.value)}
className="border p-2 mb-2 w-full"
/> */}
{/* <input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="border p-2 mb-2 w-full"
/> */}
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded"
>
Upload File
</button>
</form>
);
};
export default FileUpload;
+190
View File
@@ -0,0 +1,190 @@
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import { setIsUploading } from "../store/UploadStatusSlice";
const FileUploadModal = ({ show, onClose, onUploadSuccess }) => {
const currentPath = useSelector((state) => state.path.currentPath);
const dispatch = useDispatch();
const [file, setFile] = useState(null);
const [uploading, setUploading] = useState(false);
const [uploadMessage, setUploadMessage] = useState("");
const [newFolderName, setNewFolderName] = useState("");
const [creatingFolder, setCreatingFolder] = useState(false);
const [folderMessage, setFolderMessage] = useState("");
const username = localStorage.getItem("username");
const API_URL = import.meta.env.VITE_API_URL;
useEffect(() => {
const handleEsc = (e) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", handleEsc);
return () => document.removeEventListener("keydown", handleEsc);
}, [onClose]);
const isFolderNameValid = (name) => {
return /^[a-zA-Z0-9-_ ]+$/.test(name); // disallow special chars like / \ * ? etc.
};
const uploadFileToHDFS = async () => {
if (!file) {
setUploadMessage("⚠️ Please select a file before uploading.");
return;
}
const formData = new FormData();
formData.append("file", file);
formData.append("hdfsPath", currentPath);
formData.append("uploadedFileName", file.name);
formData.append("username", username);
try {
setUploading(true);
setUploadMessage("⏳ Uploading file...");
const response = await fetch(`${API_URL}/api/hdfs/uploadFile`, {
method: "POST",
body: formData,
});
if (!response.ok) {
const errorText = await response.text();
setUploadMessage(`❌ Upload failed: ${errorText}`);
} else {
setUploadMessage("✅ File uploaded successfully!");
dispatch(setIsUploading(true)); // Dispatch the action to set isUploading to true
onUploadSuccess(); // Call the onUploadSuccess prop to notify the parent
setTimeout(() => {
setUploadMessage("");
onClose();
}, 1000);
}
} catch (err) {
console.error(err);
setUploadMessage("❌ An error occurred during upload.");
} finally {
setUploading(false);
}
};
const createFolder = async () => {
if (!newFolderName.trim()) {
setFolderMessage("⚠️ Please enter a folder name.");
return;
}
if (!isFolderNameValid(newFolderName.trim())) {
setFolderMessage("❌ Folder name contains invalid characters.");
return;
}
try {
setCreatingFolder(true);
setFolderMessage("⏳ Creating folder...");
const folderPath =
currentPath === "/" ? "" : currentPath.replace(/\/$/, "");
const newFolderPath = `${folderPath}/${newFolderName}`;
console.log(newFolderPath);
const response = await fetch(
`${API_URL}/api/hdfs/createFolder?hdfsPath=${newFolderPath}`,
{ method: "POST" }
);
if (!response.ok) {
const errorText = await response.text();
setFolderMessage(`❌ Folder creation failed: ${errorText}`);
} else {
setFolderMessage("✅ Folder created successfully!");
dispatch(setIsUploading(true)); // Dispatch the action to set isUploading to true
onUploadSuccess(currentPath); // Call the onUploadSuccess prop after folder creation too
setNewFolderName("");
setTimeout(() => {
setFolderMessage("");
onClose();
}, 1000);
}
} catch (err) {
console.error(err);
setFolderMessage("❌ An error occurred during folder creation.");
} finally {
setCreatingFolder(false);
}
};
if (!show) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black opacity-40" onClick={onClose} />
<div className="relative bg-white rounded-2xl shadow-xl w-full max-w-lg mx-4">
<div className="flex items-center justify-between px-6 py-4 border-b">
<h2 className="text-xl font-semibold text-gray-800">Manage HDFS</h2>
<button
onClick={onClose}
className="text-gray-600 hover:text-gray-900"
>
</button>
</div>
<div className="p-6 space-y-8">
{/* File Upload Section */}
<div className="bg-gray-50 p-4 rounded-lg shadow-inner">
<h3 className="text-lg font-medium text-gray-700 mb-3">
Upload File
</h3>
<input
type="file"
onChange={(e) => setFile(e.target.files[0])}
className="w-full mb-3 text-sm text-gray-600 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
{file && (
<p className="text-sm text-gray-700 mb-2">
Selected file: <strong>{file.name}</strong>
</p>
)}
{uploadMessage && (
<p className="text-sm text-gray-600 mb-3">{uploadMessage}</p>
)}
<button
onClick={uploadFileToHDFS}
disabled={uploading}
className="w-full py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 disabled:opacity-50"
>
{uploading ? "⏳ Uploading..." : "Upload File"}
</button>
</div>
{/* Create Folder Section */}
<div className="bg-gray-50 p-4 rounded-lg shadow-inner">
<h3 className="text-lg font-medium text-gray-700 mb-3">
Create Folder
</h3>
<input
type="text"
placeholder="Folder name"
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
className="w-full mb-3 px-3 py-2 border border-gray-300 rounded-lg placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-200"
/>
{folderMessage && (
<p className="text-sm text-gray-600 mb-3">{folderMessage}</p>
)}
<button
onClick={createFolder}
disabled={creatingFolder}
className="w-full py-2 rounded-lg bg-green-600 text-white font-medium hover:bg-green-700 disabled:opacity-50"
>
{creatingFolder ? "⏳ Creating..." : "Create Folder"}
</button>
</div>
</div>
</div>
</div>
);
};
FileUploadModal.propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onUploadSuccess: PropTypes.func.isRequired,
};
export default FileUploadModal;
+36 -10
View File
@@ -42,22 +42,38 @@ const Footer = () => {
/> />
</svg> </svg>
</div> </div>
<h3 className="text-2xl font-bold text-white">Drive-thru</h3> <h3 className="text-2xl font-bold text-white">Skycrate</h3>
</div> </div>
<p className="text-white/90"> <p className="text-white/90">
Your secure cloud storage solution for all your digital needs. Your secure cloud storage solution for all your digital needs.
</p> </p>
<div className="flex space-x-4"> <div className="flex space-x-4">
<a href="https://facebook.com" target="_blank" rel="noopener noreferrer"> <a
href="https://facebook.com"
target="_blank"
rel="noopener noreferrer"
>
<Facebook className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" /> <Facebook className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" />
</a> </a>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer"> <a
href="https://twitter.com"
target="_blank"
rel="noopener noreferrer"
>
<Twitter className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" /> <Twitter className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" />
</a> </a>
<a href="https://instagram.com" target="_blank" rel="noopener noreferrer"> <a
href="https://instagram.com"
target="_blank"
rel="noopener noreferrer"
>
<Instagram className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" /> <Instagram className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" />
</a> </a>
<a href="https://linkedin.com" target="_blank" rel="noopener noreferrer"> <a
href="https://linkedin.com"
target="_blank"
rel="noopener noreferrer"
>
<Linkedin className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" /> <Linkedin className="w-5 h-5 text-white cursor-pointer hover:text-cyan-200 transition-all duration-200 transform hover:scale-110" />
</a> </a>
</div> </div>
@@ -117,7 +133,8 @@ const Footer = () => {
<div> <div>
<h4 className="font-semibold text-white mb-4">Stay Updated</h4> <h4 className="font-semibold text-white mb-4">Stay Updated</h4>
<p className="text-white/90 mb-4"> <p className="text-white/90 mb-4">
Get exclusive tips, updates on new features, and special offers directly in your inbox. Get exclusive tips, updates on new features, and special offers
directly in your inbox.
</p> </p>
<div className="space-y-4"> <div className="space-y-4">
<input <input
@@ -141,15 +158,24 @@ const Footer = () => {
{/* Bottom Section */} {/* Bottom Section */}
<div className="flex flex-col md:flex-row justify-between items-center text-white/90 text-sm"> <div className="flex flex-col md:flex-row justify-between items-center text-white/90 text-sm">
<p>© {new Date().getFullYear()} Drive-Thru. All rights reserved.</p> <p>© {new Date().getFullYear()} Skycrate. All rights reserved.</p>
<div className="flex gap-4 mt-4 md:mt-0"> <div className="flex gap-4 mt-4 md:mt-0">
<a href="#" className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"> <a
href="#"
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Privacy Policy Privacy Policy
</a> </a>
<a href="#" className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"> <a
href="#"
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Terms of Service Terms of Service
</a> </a>
<a href="#" className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"> <a
href="#"
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Cookie Policy Cookie Policy
</a> </a>
</div> </div>
+78 -233
View File
@@ -1,136 +1,120 @@
import { Link } from "react-router-dom"; import { useState, useEffect, useRef } from "react";
import { Link, useNavigate } from "react-router-dom";
import { toast } from "react-hot-toast";
const Sidebar = () => { const Sidebar = () => {
const navigate = useNavigate(); // Hook for programmatic navigation
const [userMenuOpen, setUserMenuOpen] = useState(false);
const menuRef = useRef();
// Show loading toast and perform logout
const handleLogout = () => {
const loadingToast = toast.loading("Logging out...");
// Simulate a delay (for example, network request)
setTimeout(() => {
// Remove the token from localStorage
localStorage.removeItem("token");
localStorage.removeItem("username");
localStorage.removeItem("expiresIn");
// Redirect user to the homepage
navigate("/");
// Show success toast after logout
toast.update(loadingToast, {
render: "Logged out successfully!",
type: "success",
isLoading: false,
autoClose: 2000,
});
}, 1500);
};
// Close dropdown on outside click
useEffect(() => {
const handleClickOutside = (e) => {
if (menuRef.current && !menuRef.current.contains(e.target)) {
setUserMenuOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return ( return (
<> <>
<nav className="fixed top-0 z-50 h-[60px] w-full bg-white border-b border-gray-200"> <nav className="fixed top-0 z-50 h-[60px] w-full bg-white border-b border-gray-200">
<div className="p-[15px] h-full lg:px-5 lg:pl-3 "> <div className="p-[15px] h-full lg:px-5 lg:pl-3 flex items-center justify-between">
<div className="flex h-full items-center justify-between"> {/* Left Section - Logo & Toggle */}
<div className="flex items-center justify-start rtl:justify-end"> <div className="flex items-center">
<button <button
data-drawer-target="logo-sidebar" data-drawer-target="logo-sidebar"
data-drawer-toggle="logo-sidebar" data-drawer-toggle="logo-sidebar"
aria-controls="logo-sidebar" aria-controls="logo-sidebar"
type="button" type="button"
className="inline-flex items-center p-2 text-lg text-white rounded-lg sm:hidden hover:bg-[#37A0EA] focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-[#37A0EA] dark:focus:ring-gray-600" className="inline-flex items-center p-2 text-lg text-white rounded-lg sm:hidden hover:bg-[#37A0EA] focus:outline-none"
> >
<span className="sr-only">Open sidebar</span> <span className="sr-only">Open sidebar</span>
<svg <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
className="w-6 h-6"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
clipRule="evenodd" clipRule="evenodd"
fillRule="evenodd" fillRule="evenodd"
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
></path> />
</svg> </svg>
</button> </button>
<Link to="/" className="flex ms-2 md:me-24"> <Link to="/" className="flex ms-2 md:me-24">
<img <img src="./image.png" className="h-8 me-3" alt="Skycrate Logo" />
src="./image.png" <span className="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap">
className="h-8 me-3" Skycrate
alt="Drive-thru Logo"
/>
<span className="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
Drive-thru
</span> </span>
</Link> </Link>
</div> </div>
{/* Right Section - Search & User Menu */}
<div className="flex items-center"> <div className="flex items-center">
{" "} {/* Search Bar */}
<div className="flex items-center justify-end mr-40 "> <div className="flex items-center justify-end mr-40"></div>
<input
type="text" {/* User Profile & Dropdown */}
placeholder="Search..." <div className="relative ms-3">
className="w-full border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button <button
type="button" type="button"
className="ml-2 px-4 py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500" onClick={() => setUserMenuOpen((o) => !o)}
> className="flex text-lg bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300"
Search
</button>
</div>
<div className="flex items-center ms-3">
<div>
<button
type="button"
className="flex text-lg bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
aria-expanded="false"
data-dropdown-toggle="dropdown-user"
> >
<span className="sr-only">Open user menu</span> <span className="sr-only">Open user menu</span>
<img <img
className="w-8 h-8 rounded-full" className="w-8 h-8 rounded-full"
src="https://flowbite.com/docs/images/people/profile-picture-5.jpg" src="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
alt="user photo" alt="User Photo"
/> />
</button> </button>
</div>
{userMenuOpen && (
<div <div
className="z-50 hidden my-4 text-base list-none bg-[#1877F2] divide-y divide-gray-100 rounded-sm shadow-sm dark:bg-gray-700 dark:divide-gray-600" ref={menuRef}
id="dropdown-user" className="z-50 absolute right-0 mt-2 w-48 bg-[#1877F2] divide-y divide-gray-100 rounded-sm shadow-sm"
> >
<div className="px-4 py-3" role="none"> <div className="px-4 py-3" role="none">
<p <p className="text-lg text-white" role="none">
className="text-lg text-white dark:text-white" {localStorage.getItem("username")}
role="none"
>
Neil Sims
</p>
<p
className="text-lg font-medium text-white truncate dark:text-gray-300"
role="none"
>
Drive-thru@Drive-thru.com
</p> </p>
</div> </div>
<ul className="py-1" role="none"> <ul className="py-1" role="none">
<li> <li>
<Link <button
to="#" onClick={handleLogout}
className="block px-4 py-2 text-lg text-white hover:bg-[#37A0EA] dark:text-gray-300 dark:hover:bg-[#37A0EA] dark:hover:text-white" className="w-full text-left px-4 py-2 text-lg text-white hover:bg-[#37A0EA]"
role="menuitem" role="menuitem"
> >
Dashboard Log out
</Link> </button>
</li>
<li>
<Link
to="#"
className="block px-4 py-2 text-lg text-white hover:bg-[#37A0EA] dark:text-gray-300 dark:hover:bg-[##37A0EA] dark:hover:text-white"
role="menuitem"
>
Settings
</Link>
</li>
<li>
<Link
to="#"
className="block px-4 py-2 text-lg text-white hover:bg-[#37A0EA] dark:text-gray-300 dark:hover:bg-[#37A0EA] dark:hover:text-white"
role="menuitem"
>
Earnings
</Link>
</li>
<li>
<Link
to="#"
className="block px-4 py-2 text-lg text-white hover:bg-[#37A0EA] dark:text-gray-300 dark:hover:bg-[#37A0EA] dark:hover:text-white"
role="menuitem"
>
Sign out
</Link>
</li> </li>
</ul> </ul>
</div> </div>
</div> )}
</div> </div>
</div> </div>
</div> </div>
@@ -138,167 +122,28 @@ const Sidebar = () => {
<aside <aside
id="logo-sidebar" id="logo-sidebar"
className="fixed top-0 left-0 z-40 w-64 h-screen pt-[60px] transition-transform -translate-x-full bg-[##1877F2] border-r border-gray-200 sm:translate-x-0 dark:bg-[#1877F2] dark:border-gray-700" className="fixed top-0 left-0 z-40 w-64 h-screen pt-[60px] transition-transform -translate-x-full bg-[#1877F2] border-r border-gray-200 sm:translate-x-0"
aria-label="Sidebar" aria-label="Sidebar"
> >
<div className="h-full px-3 pb-4 overflow-y-auto bg-[#1877F2] dark:bg-[#1877F2] custom-scrollbar"> <div className="h-full px-3 pb-4 overflow-y-auto bg-[#1877F2] custom-scrollbar">
<ul className="space-y-2 font-medium"> <ul className="space-y-2 font-medium">
<li> <li>
<Link <Link
to="#" to="#"
className="flex items-center p-2 mt-5 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group" className="flex items-center p-2 mt-5 pt-4 pb-4 text-white rounded-lg hover:bg-[#37A0EA] group"
> >
<svg <svg
className="w-5 h-5 text-white transition duration-75 dark:text-gray-400 group-hover:text-white dark:group-hover:text-white" className="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor"
> >
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.46 4.73L5.82 21z" /> <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.46 4.73L5.82 21z" />
</svg> </svg>
<span className="ms-3">Starred</span> <span className="ms-3">Starred</span>
</Link> </Link>
</li> </li>
<li> {/* ...additional sidebar items... */}
<Link
to="#"
className="flex items-center p-2 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5"
viewBox="0 0 20 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.31763 16.0834C1.93846 16.0834 1.60669 15.9372 1.32231 15.6449C1.03794 15.3527 0.895752 15.0248 0.895752 14.6615V2.33856C0.895752 1.97519 1.03794 1.64737 1.32231 1.3551C1.60669 1.06282 1.93846 0.916687 2.31763 0.916687H8.97674L10.3986 2.33856H18.4322C18.7956 2.33856 19.1234 2.4847 19.4157 2.77697C19.7079 3.06925 19.8541 3.39707 19.8541 3.76044V14.6615C19.8541 15.0248 19.7079 15.3527 19.4157 15.6449C19.1234 15.9372 18.7956 16.0834 18.4322 16.0834H2.31763ZM9.4033 13.0026H16.9866V12.5524C16.9866 11.8888 16.6509 11.3477 15.9795 10.9291C15.308 10.5104 14.3799 10.3011 13.195 10.3011C12.0101 10.3011 11.0819 10.5104 10.4105 10.9291C9.73903 11.3477 9.4033 11.8888 9.4033 12.5524V13.0026ZM13.195 8.87919C13.6689 8.87919 14.0757 8.70935 14.4154 8.36968C14.7551 8.03001 14.9249 7.6232 14.9249 7.14924C14.9249 6.67528 14.7551 6.26847 14.4154 5.9288C14.0757 5.58913 13.6689 5.41929 13.195 5.41929C12.721 5.41929 12.3142 5.58913 11.9745 5.9288C11.6349 6.26847 11.465 6.67528 11.465 7.14924C11.465 7.6232 11.6349 8.03001 11.9745 8.36968C12.3142 8.70935 12.721 8.87919 13.195 8.87919Z"
fill="white"
/>
</svg>
<span className="flex-1 ms-3 whitespace-nowrap">
Shared with me
</span>
</Link>
</li>
<li>
<Link
to="#"
className="flex items-center p-2 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d="M5 3h2v18H5V3zm6 6h2v12h-2V9zm6-4h2v16h-2V5z" />
</svg>
<span className="flex-1 ms-3 whitespace-nowrap">
Statistics
</span>
</Link>
</li>
<li>
<Link
to="#"
className="flex items-center p-2 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5 text-white dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fillRule="evenodd"
d="M3 6a2 2 0 0 1 2-2h5.532a2 2 0 0 1 1.536.72l1.9 2.28H3V6Zm0 3v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9H3Z"
clipRule="evenodd"
/>
</svg>
<span className="flex-1 ms-3 whitespace-nowrap">My files</span>
</Link>
</li>
<li>
<Link
to="#"
className="flex items-center p-2 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5"
viewBox="0 0 20 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.19471 18.9791L7.72075 15.9932C7.42058 15.8826 7.10461 15.7325 6.77284 15.5429C6.44106 15.3534 6.14879 15.1559 5.89601 14.9505L3.09966 16.2302L0.895752 12.3437L3.45513 10.4716C3.42353 10.3294 3.40378 10.1675 3.39588 9.98579C3.38798 9.8041 3.38403 9.64217 3.38403 9.49998C3.38403 9.35779 3.38798 9.19586 3.39588 9.01417C3.40378 8.83249 3.42353 8.67055 3.45513 8.52837L0.895752 6.65623L3.09966 2.76977L5.89601 4.04946C6.14879 3.84408 6.44106 3.64659 6.77284 3.45701C7.10461 3.26743 7.42058 3.12524 7.72075 3.03045L8.19471 0.020813H12.5551L13.0291 3.00675C13.3293 3.11734 13.6492 3.26348 13.9889 3.44516C14.3285 3.62685 14.6168 3.82828 14.8538 4.04946L17.6502 2.76977L19.8541 6.65623L17.2947 8.48097C17.3263 8.63896 17.3461 8.80879 17.354 8.99048C17.3619 9.17216 17.3658 9.34199 17.3658 9.49998C17.3658 9.65797 17.3619 9.82385 17.354 9.99764C17.3461 10.1714 17.3263 10.3373 17.2947 10.4953L19.8541 12.3437L17.6502 16.2302L14.8538 14.9505C14.601 15.1559 14.3127 15.3573 13.9889 15.5548C13.665 15.7523 13.3451 15.8984 13.0291 15.9932L12.5551 18.9791H8.19471ZM10.3749 12.5807C11.228 12.5807 11.9548 12.2805 12.5551 11.6802C13.1555 11.0798 13.4556 10.3531 13.4556 9.49998C13.4556 8.64686 13.1555 7.92012 12.5551 7.31977C11.9548 6.71942 11.228 6.41925 10.3749 6.41925C9.52179 6.41925 8.79506 6.71942 8.19471 7.31977C7.59436 7.92012 7.29419 8.64686 7.29419 9.49998C7.29419 10.3531 7.59436 11.0798 8.19471 11.6802C8.79506 12.2805 9.52179 12.5807 10.3749 12.5807Z"
fill="white"
/>
</svg>
<span className="flex-1 ms-3 whitespace-nowrap">Settings</span>
</Link>
</li>
<li>
<Link
to="#"
className="flex items-center p-2 pt-4 pb-4 text-white rounded-lg dark:text-white hover:bg-[#37A0EA] dark:hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5 text-white dark:text-white self-center"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fillRule="evenodd"
d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z"
clipRule="evenodd"
/>
</svg>
<span className="flex-1 ms-3 whitespace-nowrap">Trash</span>
</Link>
</li>
</ul> </ul>
<div className="mt-6 p-4 rounded-lg text-white">
{/* Cloud Icon + Title */}
<div className="flex items-center">
<svg
className="w-7 h-7 text-white dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M13.383 4.076a6.5 6.5 0 0 0-6.887 3.95A5 5 0 0 0 7 18h3v-4a2 2 0 0 1-1.414-3.414l2-2a2 2 0 0 1 2.828 0l2 2A2 2 0 0 1 14 14v4h4a4 4 0 0 0 .988-7.876 6.5 6.5 0 0 0-5.605-6.048Z" />
<path d="M12.707 9.293a1 1 0 0 0-1.414 0l-2 2a1 1 0 1 0 1.414 1.414l.293-.293V19a1 1 0 1 0 2 0v-6.586l.293.293a1 1 0 0 0 1.414-1.414l-2-2Z" />
</svg>
<span className="ml-2 font-semibold">My Storage</span>
</div>
{/* Usage Text & Progress Bar */}
<p className="mt-2 text-lg">Used: of 100GB</p>
<div className="w-full bg-white rounded-full h-2 mt-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: "24%" }}
></div>
</div>
</div>
</div> </div>
</aside> </aside>
</> </>
+7 -1
View File
@@ -2,9 +2,15 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import "./index.css"; import "./index.css";
import App from "./App.jsx"; import App from "./App.jsx";
import { Provider } from "react-redux";
import { store } from "./store/store";
createRoot(document.getElementById("root")).render( const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<StrictMode> <StrictMode>
<Provider store={store}>
<App /> <App />
</Provider>
</StrictMode> </StrictMode>
); );
+86 -5
View File
@@ -1,14 +1,83 @@
import React from "react"; import { useState, useEffect } from "react";
import { FiEye, FiEyeOff } from "react-icons/fi"; import { FiEye, FiEyeOff } from "react-icons/fi";
import { Link } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import toast from "react-hot-toast"; // Import React Hot Toast
const API_URL = import.meta.env.VITE_API_URL; // Using .env variable
const Login = () => { const Login = () => {
const [showPassword, setShowPassword] = React.useState(false); const [showPassword, setShowPassword] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const navigate = useNavigate(); // For navigation
useEffect(() => {
// Check if token is present in localStorage and redirect to Dashboard
if (localStorage.getItem("token")) {
navigate("/dashboard"); // Redirect to Dashboard
}
}, [navigate]);
const togglePassword = () => { const togglePassword = () => {
setShowPassword(!showPassword); setShowPassword(!showPassword);
}; };
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
// Show loading toast
const toastId = toast.loading("Logging in...");
try {
const response = await fetch(`${API_URL}/api/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
// Dismiss the loading toast after the response
toast.dismiss(toastId);
if (response.ok) {
// On success, store the token in localStorage
localStorage.setItem("token", data.token);
localStorage.setItem("expiresIn", data.expiresIn);
fetch(`${API_URL}/api/hdfs/getUsernameByEmail?email=${email}`)
.then((response) => response.text())
.then((username) => {
localStorage.setItem("username", username);
})
.catch((error) => {
console.error("Error fetching username:", error);
});
// Show success toast
toast.success("Login successful!");
// Redirect to Dashboard
navigate("/dashboard");
} else {
// Show error toast if login fails
toast.error(data.message || "Login failed.");
}
} catch (error) {
// Dismiss the loading toast and show error
toast.dismiss(toastId);
toast.error("An error occurred. Please try again.", error);
} finally {
setLoading(false);
}
};
return ( return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-4"> <div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div className="w-full max-w-md bg-white rounded-4xl shadow-lg p-8"> <div className="w-full max-w-md bg-white rounded-4xl shadow-lg p-8">
@@ -16,6 +85,7 @@ const Login = () => {
Log in Log in
</h1> </h1>
<form onSubmit={handleSubmit}>
<div className="mb-4"> <div className="mb-4">
<div className="flex items-center"> <div className="flex items-center">
<input <input
@@ -23,6 +93,9 @@ const Login = () => {
id="email" id="email"
placeholder="Enter your email" placeholder="Enter your email"
className="w-full border border-gray-300 rounded-l-lg px-4 py-4 focus:outline-none focus:border-blue-500" className="w-full border border-gray-300 rounded-l-lg px-4 py-4 focus:outline-none focus:border-blue-500"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/> />
</div> </div>
</div> </div>
@@ -33,6 +106,9 @@ const Login = () => {
id="password" id="password"
placeholder="Enter your password" placeholder="Enter your password"
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:border-blue-500 pr-10" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:border-blue-500 pr-10"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/> />
<button <button
type="button" type="button"
@@ -51,9 +127,14 @@ const Login = () => {
Forgot password? Forgot password?
</Link> </Link>
</div> </div>
<button className="w-full py-3 bg-gradient-to-r from-[#1877F2] to-[#0E458C] hover:from-[#0E458C] hover:to-[#1877F2] text-white font-semibold rounded-full shadow-md transition duration-300"> <button
Login type="submit"
disabled={loading}
className="w-full py-3 bg-gradient-to-r from-[#1877F2] to-[#0E458C] hover:from-[#0E458C] hover:to-[#1877F2] text-white font-semibold rounded-full shadow-md transition duration-300"
>
{loading ? "Logging In..." : "Login"}
</button> </button>
</form>
<div className="text-center mt-6"> <div className="text-center mt-6">
<p className="text-gray-700"> <p className="text-gray-700">
Dont have an account?{" "} Dont have an account?{" "}
+116 -11
View File
@@ -1,45 +1,138 @@
// eslint-disable-next-line no-unused-vars import { useState } from "react";
import React, { useState } from "react";
import { FiEye, FiEyeOff } from "react-icons/fi"; import { FiEye, FiEyeOff } from "react-icons/fi";
import { Link } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import toast, { Toaster } from "react-hot-toast";
const API_URL = import.meta.env.VITE_API_URL;
const SignUp = () => { const SignUp = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
firstname: "",
lastname: "",
email: "",
password: "",
confirmPassword: "",
});
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [loading, setLoading] = useState(false);
const handleChange = (e) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
if (formData.password !== formData.confirmPassword) {
toast.error("Passwords do not match.");
return;
}
setLoading(true);
const toastId = toast.loading("Registering...");
try {
// 1️⃣ Sign up the user
const signupRes = await fetch(`${API_URL}/api/signup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
firstname: formData.firstname,
lastname: formData.lastname,
email: formData.email,
password: formData.password,
}),
});
const signupData = await signupRes.json();
if (!signupRes.ok) {
toast.error(signupData.message || "Signup failed.", { id: toastId });
return;
}
// 2️⃣ Build username and create HDFS folder
const username =
`${formData.firstname}${formData.lastname}`.toLowerCase();
const folderRes = await fetch(
`${API_URL}/api/hdfs/createFolder?hdfsPath=/${username}`,
{ method: "POST" }
);
if (!folderRes.ok) {
// you might choose to roll back user creation or just notify
toast.error("Failed to create user folder.", { id: toastId });
} else {
toast.success("Successfully registered and folder created!", {
id: toastId,
});
}
// 3️⃣ Redirect to login after a short delay
setTimeout(() => {
navigate("/login");
}, 1500);
} catch (error) {
console.error(error);
toast.error("An error occurred. Please try again.", { id: toastId });
} finally {
setLoading(false);
}
};
return ( return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-6"> <div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
<Toaster position="top-right" />
<div className="w-full max-w-md bg-white rounded-2xl shadow-lg p-8"> <div className="w-full max-w-md bg-white rounded-2xl shadow-lg p-8">
<h1 className="text-2xl font-bold text-gray-900 mb-6">Sign Up</h1> <h1 className="text-2xl font-bold text-gray-900 mb-6">Sign Up</h1>
<form className="space-y-4" onSubmit={handleSubmit}>
{/* Form Fields */}
<div className="space-y-4">
<input <input
type="text" type="text"
name="firstname"
placeholder="First Name" placeholder="First Name"
value={formData.firstname}
onChange={handleChange}
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/> />
<input <input
type="text" type="text"
name="lastname"
placeholder="Last Name" placeholder="Last Name"
value={formData.lastname}
onChange={handleChange}
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/> />
<input <input
type="email" type="email"
name="email"
placeholder="Enter your email" placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/> />
{/* Password Field */} {/* Password Field */}
<div className="relative"> <div className="relative">
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
name="password"
placeholder="Enter your password" placeholder="Enter your password"
value={formData.password}
onChange={handleChange}
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10"
required
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword((v) => !v)}
className="absolute right-3 top-4 text-2xl text-gray-500 hover:text-gray-700" className="absolute right-3 top-4 text-2xl text-gray-500 hover:text-gray-700"
> >
{showPassword ? <FiEyeOff /> : <FiEye />} {showPassword ? <FiEyeOff /> : <FiEye />}
@@ -50,23 +143,35 @@ const SignUp = () => {
<div className="relative"> <div className="relative">
<input <input
type={showConfirmPassword ? "text" : "password"} type={showConfirmPassword ? "text" : "password"}
name="confirmPassword"
placeholder="Confirm your password" placeholder="Confirm your password"
value={formData.confirmPassword}
onChange={handleChange}
className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10" className="w-full border border-gray-300 rounded-lg px-4 py-4 focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10"
required
/> />
<button <button
type="button" type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)} onClick={() => setShowConfirmPassword((v) => !v)}
className="absolute right-3 top-4 text-2xl text-gray-500 hover:text-gray-700" className="absolute right-3 top-4 text-2xl text-gray-500 hover:text-gray-700"
> >
{showConfirmPassword ? <FiEyeOff /> : <FiEye />} {showConfirmPassword ? <FiEyeOff /> : <FiEye />}
</button> </button>
</div> </div>
</div>
{/* Sign Up Button */} {/* Sign Up Button */}
<button className="w-full mt-6 py-3 bg-gradient-to-r from-[#10B981] to-[#07533A] hover:from-[#0E458C] hover:to-[#1877F2] text-white font-semibold rounded-lg shadow-md transition duration-300"> <button
Sign Up type="submit"
disabled={loading}
className={`w-full mt-4 py-3 ${
loading
? "bg-gray-400 cursor-not-allowed"
: "bg-gradient-to-r from-[#10B981] to-[#07533A] hover:from-[#0E458C] hover:to-[#1877F2]"
} text-white font-semibold rounded-lg shadow-md transition duration-300`}
>
{loading ? "Signing Up..." : "Sign Up"}
</button> </button>
</form>
{/* Redirect to Login */} {/* Redirect to Login */}
<p className="text-center mt-4 text-gray-700"> <p className="text-center mt-4 text-gray-700">
-287
View File
@@ -1,287 +0,0 @@
import React from "react";
import Footer from "../components/Footer";
{
/* <img src="vector.png" alt="" className="h-100" /> */
}
const DrivethruLandingPage = () => {
return (
<div className="min-h-screen bg-white overflow-hidden">
<div className="bg-white min-h-screen flex items-center relative">
<div className="container mx-auto px-6 relative z-10">
<div className="flex flex-col md:flex-row items-center">
<div className="hidden md:block md:w-1/2 lg:w-3/5"></div>
<div className="w-full mt-70 md:w-1/2 lg:w-2/5 max-w-lg bg-transparent">
<div className="flex items-center mb-8">
<div className="text-cyan-400 mr-3">
<svg
className="w-12 h-12"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2L2 12L12 22L22 12L12 2Z"
stroke="currentColor"
strokeWidth="3"
fill="none"
/>
</svg>
</div>
<h1 className="text-5xl font-bold text-black">Drive-thru</h1>
</div>
<h2 className="text-2xl font-bold mb-6 text-black">
Store, Access & Share Your Files Anytime, Anywhere!
</h2>
<p className="text-gray-800 mb-10 text-lg">
A simple, secure, and fast cloud storage solution for all your
files. Upload, organize, and access with ease.
</p>
<div className="flex flex-row space-x-4">
<button className="px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white font-medium rounded-full transition-colors duration-200 shadow-md">
Get Started
</button>
<button className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-full transition-colors duration-200 shadow-md">
Login
</button>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white py-16">
<div className="container mx-auto px-6">
<div className="flex flex-col md:flex-row items-center justify-between mb-24">
{" "}
<div className="w-full md:w-1/2 lg:w-2/5 flex justify-center md:justify-end">
<img
src="./He.png"
alt="Person using Drive-thru on laptop"
className="mx h-auto max-w-full"
/>
</div>
{/* Features Card */}
<div className="w-full md:w-1/2 lg:w-3/5 mb-12 md:mb-0">
<div className="bg-blue-100 rounded-3xl p-8 md:p-10 shadow-lg">
<h2 className="text-3xl font-bold mb-8">Key Features</h2>
<div className="space-y-6">
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
"Easy Upload & Access" Drag & drop, instant access.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
"Secure & Private" End-to-end encryption.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
"Seamless Sharing" Share files with one click.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h.5A2.5 2.5 0 0020 5.5v-1.65"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
"Access Anywhere" Works on all devices.
</p>
</div>
</div>
</div>
</div>
</div>
{/* How It Works Section */}
<div className="flex flex-col md:flex-row-reverse items-center justify-between">
{/* Person with Phone Image */}
<div className="w-full md:w-1/2 lg:w-2/5 mb-12 md:mb-0 flex justify-center md:justify-start">
<img
src="./She.png"
alt="Person using Drive-thru on phone"
className="h-auto max-w-full"
/>
</div>
{/* How It Works Card */}
<div className="w-full md:w-1/2 lg:w-3/5">
<div className="bg-blue-100 rounded-3xl p-8 md:p-10 shadow-lg">
<h2 className="text-3xl font-bold mb-8">How It Works</h2>
<div className="space-y-6">
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
Create an account Sign up in seconds.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
Upload files Drag & drop or select from your device.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
></path>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
Manage files Rename, move, or delete easily.
</p>
</div>
<div className="flex items-start">
<div className="text-emerald-500 mr-3 mt-1">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
</div>
<p className="text-lg font-semibold">
Access anytime Open files from any device.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<Footer></Footer>
</div>
);
};
export default DrivethruLandingPage;
+57 -82
View File
@@ -1,109 +1,84 @@
import React from "react"; import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Sidebar from "../../components/Sidebar"; import Sidebar from "../../components/Sidebar";
import FileList from "../../components/FileList"; import FileList from "../../components/FileList";
import FileUpload from "../../components/FileUpload"; import FileUploadModal from "../../components/FileUploadModal";
import { FiPlus } from "react-icons/fi"; import { FiPlus } from "react-icons/fi";
const Dashboard = () => { const Dashboard = () => {
const [files, setFiles] = React.useState([]); const [files, setFiles] = useState([]);
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
const [error, setError] = useState("");
const navigate = useNavigate();
const API_URL = import.meta.env.VITE_API_URL;
const isUserLoggedIn = () => {
const token = localStorage.getItem("token");
const username = localStorage.getItem("username");
const expiresIn = localStorage.getItem("expiresIn");
React.useEffect(() => { if (!token || !username || !expiresIn) return false;
const fetchData = async () => {
const response = await fetch( const expiryTime = new Date(expiresIn).getTime();
"http://192.168.29.61:8080/api/hdfs/listFiles?hdfsPath=/" const now = new Date().getTime();
);
if (now > expiryTime) {
localStorage.clear();
return false;
}
return true;
};
const fetchFiles = async () => {
try {
const response = await fetch(`${API_URL}/api/hdfs/listFiles?hdfsPath=/`);
const data = await response.json(); const data = await response.json();
setFiles(data); setFiles(data);
} catch (error) {
console.error("Failed to fetch files:", error);
setError("Failed to load files. Please try again later.");
}
}; };
fetchData();
}, []); useEffect(() => {
if (!isUserLoggedIn()) {
navigate("/login");
} else {
fetchFiles();
}
}, [navigate]);
return ( return (
<> <>
{/* <!-- Main modal --> */}
<div
id="static-modal"
data-modal-backdrop="static"
tabindex="-1"
aria-hidden="true"
class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full"
>
<div class="relative p-4 w-full max-w-170 h-150 flex items-center justify-center">
{/* <!-- Modal content --> */}
<div class="relative bg-white rounded-lg shadow-sm ">
{/* <!-- Modal header --> */}
<div class="flex items-center justify-between p-2 md:p-5 rounded-t dark:border-gray-600 border-gray-200">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
Static modal
</h3>
<button
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
data-modal-hide="static-modal"
>
<svg
class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
/>
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
{/* <!-- Modal body --> */}
<div class="p-4 md:p-5 space-y-4">
<FileUpload />
</div>
{/* <!-- Modal footer --> */}
{/* <div class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b ">
<button
data-modal-hide="static-modal"
type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
I accept
</button>
<button
data-modal-hide="static-modal"
type="button"
class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
>
Decline
</button>
</div> */}
</div>
</div>
</div>
<Sidebar /> <Sidebar />
<div className="p-4 sm:ml-64"> <div className="p-4 sm:ml-64">
<div className="p-4 border-2 border-gray-200 border-dashed rounded-lg mt-14"> <div className="p-4 border-2 border-gray-200 border-dashed rounded-lg mt-14">
<div className="w-full flex justify-between items-center"> <div className="w-full flex justify-between items-center">
<h1 className="text-2xl font-bold mb-4">Dashboard</h1> <h1 className="text-2xl font-bold mb-4">Dashboard</h1>
<button <button
data-modal-target="static-modal" onClick={() => setIsUploadModalOpen(true)}
data-modal-toggle="static-modal" className="block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 text-center"
class="block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
type="button" type="button"
> >
<FiPlus className="text-2xl" /> <FiPlus className="text-2xl" />
</button> </button>
</div> </div>
{error ? (
<p className="text-red-500">{error}</p>
) : (
<FileList files={files} />
)}
</div>
</div>
<FileList files={files}></FileList> <FileUploadModal
<section className="w-full flex justify-end items-center min-h-160"> show={isUploadModalOpen}
{/* <!-- Modal toggle --> */} onClose={() => setIsUploadModalOpen(false)}
</section> onUploadSuccess={() => {
</div> fetchFiles();
</div> setIsUploadModalOpen(false);
}}
/>
</> </>
); );
}; };
@@ -234,7 +234,7 @@ const DrivethruLandingPage = () => {
</svg> </svg>
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold text-black"> <h1 className="text-4xl md:text-5xl font-bold text-black">
Drive-thru Skycrate
</h1> </h1>
</div> </div>
@@ -268,7 +268,7 @@ const DrivethruLandingPage = () => {
<div className="relative p-4 bg-gradient-to-r from-blue-50 to-emerald-50 rounded-2xl max-w-xs sm:max-w-lg md:max-w-md lg:max-w-lvh"> <div className="relative p-4 bg-gradient-to-r from-blue-50 to-emerald-50 rounded-2xl max-w-xs sm:max-w-lg md:max-w-md lg:max-w-lvh">
<img <img
src="/Dashboard.png" src="/Dashboard.png"
alt="Drive-thru Dashboard Interface" alt="Skycrate Dashboard Interface"
className="w-full rounded-xl shadow-2xl transition-shadow duration-300" className="w-full rounded-xl shadow-2xl transition-shadow duration-300"
/> />
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/5 to-emerald-500/5 rounded-2xl pointer-events-none"></div> <div className="absolute inset-0 bg-gradient-to-r from-blue-500/5 to-emerald-500/5 rounded-2xl pointer-events-none"></div>
+18
View File
@@ -0,0 +1,18 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isUploading: false,
};
const uploadStatusSlice = createSlice({
name: "uploadStatus",
initialState,
reducers: {
setIsUploading: (state) => {
state.isUploading = !state.isUploading;
},
},
});
export const { setIsUploading } = uploadStatusSlice.actions;
export default uploadStatusSlice.reducer;
View File
+19
View File
@@ -0,0 +1,19 @@
// src/redux/pathSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
currentPath: "/",
};
const pathSlice = createSlice({
name: "path",
initialState,
reducers: {
setCurrentPath: (state, action) => {
state.currentPath = action.payload;
},
},
});
export const { setCurrentPath } = pathSlice.actions;
export default pathSlice.reducer;
+11
View File
@@ -0,0 +1,11 @@
// src/redux/store.js
import { configureStore } from "@reduxjs/toolkit";
import pathReducer from "./pathSlice";
import setIsUploadingReducer from "./UploadStatusSlice";
export const store = configureStore({
reducer: {
path: pathReducer,
upload: setIsUploadingReducer,
},
});
-34
View File
@@ -1,34 +0,0 @@
// utils/api.js
export const uploadFileToHDFS = async (
file,
hdfsPath,
uploadedFileName,
username
) => {
const formData = new FormData();
formData.append("file", file);
formData.append("hdfsPath", "/kalas");
formData.append("uploadedFileName", uploadedFileName);
formData.append("username", "kalas");
try {
const response = await fetch(
"http://192.168.29.61:8080/api/hdfs/uploadFile",
{
method: "POST",
body: formData,
}
);
if (response.ok) {
const data = await response.json(); // or response.json() if JSON is returned
console.log("Response:", data);
} else {
console.error("Upload failed with status:", response.status);
}
} catch (error) {
console.error("Error uploading file:", error);
alert("An error occurred while uploading the file.");
}
};
+2 -23
View File
@@ -1,26 +1,5 @@
# CC-MINI (2025) # Skycrate
--- ---
## Git config ⚠️⚠️ The `frontend` branch has been locked since all changes have been successfully merged into the `main` branch. Please create a new feature branch derived from the `main` branch and submit a pull request for your changes. ⚠️⚠️
Create a new directory for this project, and run these following commands for initalizing git:
```shell
git clone https://github.com/kshitij-ka/cc-mini.git
cd cc-mini
git config --local user.name "Your name"
git config --local user.email "your@ema.il"
git config --local core.autocrlf input # For Linux/MacOS users
git config --local core.autocrlf true # For Windows users
git checkout frontend # If you're working on frontend
git checkout backend # If you're working on backend
```
## Where to push?
- For frontend, please push to [Frontend](https://github.com/kshitij-ka/cc-mini/tree/frontend/Frontend) folder in the [frontend branch](https://github.com/kshitij-ka/cc-mini/tree/frontend).
- For backend, please push to [Backend](https://github.com/kshitij-ka/cc-mini/tree/backend/Backend) folder in the [backend branch](https://github.com/kshitij-ka/cc-mini/tree/backend/).
- I will be merging changes from both the branches in the main branch for deploying.
---
-6
View File
@@ -1,6 +0,0 @@
{
"name": "cc-mini",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}