80 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
Kshitij a5168a282b Merged Salvi's upload functionality and Ombase's list files functionality. 2025-04-15 11:04:40 +05:30
Atharva Ombase 4219570d80 Feat:Added file table on the Dashboard 2025-04-15 04:14:00 +05:30
Atharva Ombase 710a08c868 Feat:Added some dependancies 2025-04-15 04:12:02 +05:30
Atharva Ombase 2f6dcf13f7 Feat:Added some dependancies 2025-04-15 04:11:16 +05:30
Atharva Ombase 8dec47ff63 Feat: Added table to list and view HDFS files with dynamic navigation 2025-04-15 04:10:27 +05:30
KaranSalvi b42a53e99b feat:Added upload file functionality 2025-04-15 03:41:16 +05:30
Kshitij 9e68d73cf4 Merged Atharv Ombase's changes from frontend branch into the main branch. 2025-03-29 22:28:10 +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
Kshitij 65783c1ca0 Merged changes from frontend branch, i.e. commits made by Ombase, Dinesh and me in frontend. 2025-02-27 23:06:51 +05:30
Kshitij 8c070fd616 Added new dashboard in sample, designed by Dinesh! 2025-02-27 23:00:46 +05:30
Kshitij 95995d0a4e Merge branch 'dinesh' into frontend. 2025-02-27 22:52:42 +05:30
Kshitij 72c1968c67 Removed package-lock from Dinesh's commit. 2025-02-27 22:51:29 +05:30
Dinesh065 000ae78d1e Improved the landing page and footer 2025-02-27 22:42:14 +05:30
Kshitij f4ac9a4f15 Removed package-lock from repo. No longer tracking it. 2025-02-26 20:18:16 +05:30
Kshitij 7c7d8a6c3a Manually merging Ombase's changes to NotFoundPage.jsx since I rebased an older commit to add a more descriptive comment. 2025-02-26 20:13:58 +05:30
Kshitij 0fe8077f7e Fix: Changed 404 image in NotFoundPage.jsx 2025-02-26 20:09:52 +05:30
Kshitij a3ab6731c6 Added package-lock to gitignore and added server in vite.config.js 2025-02-26 20:09:37 +05:30
atharvaombase c7697a4b3c Fix:Fixed 404.png import path 2025-02-26 20:08:59 +05:30
Kshitij 7994f2768d Fix: Changed 404 image in NotFoundPage.jsx 2025-02-26 20:01:48 +05:30
Kshitij bb15fe9b7a Added package-lock to gitignore 2025-02-26 19:43:02 +05:30
Kshitij 785eb9e66a Added dashboard sample image & updated README. 2025-02-26 19:07:25 +05:30
Kshitij 7590e81f5c Merged Ombase's changes from frontend branch with main branch. Created sidebar, dashboard, and not found page. Minor changes to landing page. 2025-02-26 19:00:19 +05:30
atharvaombase 8ba3d6e093 Fix:Fixed height of first page where the height was dynamic 2025-02-26 18:04:06 +05:30
atharvaombase 88cd92a159 Fix:Added route for Custom NotFoundPage 2025-02-26 18:03:05 +05:30
atharvaombase 9c5a000555 Feat:Custom Page not found page 2025-02-26 18:02:16 +05:30
atharvaombase 130cada092 Fix:Kept the light theme and removed black theme 2025-02-26 18:01:36 +05:30
atharvaombase 7f6e377674 Feat:Added searchBar in navbar 2025-02-26 16:47:43 +05:30
atharvaombase fe06abadcf Feat:Added the icons and necessary buttons 2025-02-26 15:21:15 +05:30
atharvaombase 00eab70d71 Merge branch 'frontend' of https://github.com/kshitij-ka/cc-mini into frontend 2025-02-26 12:14:50 +05:30
atharvaombase 2e74a8b762 Moved DrivethruLandingPage.jsx to its new location 2025-02-26 12:12:37 +05:30
atharvaombase e3f2d4fd0a Feat:Custom css for the Overflow scrollbar in the sidebar 2025-02-26 12:10:28 +05:30
atharvaombase 6c0b39ddbf Fix:Moved user related pages to UserPages 2025-02-26 12:06:10 +05:30
Kshitij b14c132808 Fix: Moved design link above sample images. 2025-02-26 12:04:54 +05:30
atharvaombase a7b5c24d72 Feat:Created sidebar 2025-02-26 12:04:41 +05:30
Kshitij 2556843ef6 Misc: Added sample images for landing, login and signup page. Also, referenced in README. 2025-02-26 12:03:18 +05:30
atharvaombase 43f4a8f9ce Feat:Added routes 2025-02-26 12:00:49 +05:30
atharvaombase 9abd6c554a Fix:Removed files from Root folder to Frontend folder
Feat:Created landing page,Login page, Signup page
2025-02-26 10:46:33 +05:30
atharvaombase 1005852091 Initail Commit 2025-02-25 23:39:31 +05:30
Kshitij 61a36fc2bc Added link to design in README file. 2025-02-25 01:40:02 +05:30
Kshitij 714ece9637 Added a placeholder file to push Frontend folder. 2025-02-24 23:15:18 +05:30
Kshitij b2ba415373 Initial commit in frontend branch. 2025-02-24 23:04:49 +05:30
32 changed files with 1610 additions and 5294 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
View File
@@ -11,6 +11,7 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
package-lock.json
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
+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>
-4858
View File
File diff suppressed because it is too large Load Diff
+6 -2
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",
@@ -10,11 +10,15 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.6.0",
"@tailwindcss/vite": "^4.0.9", "@tailwindcss/vite": "^4.0.9",
"flowbite": "^3.1.2", "axios": "^1.8.4",
"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-router-dom": "^7.2.0" "react-router-dom": "^7.2.0"
}, },
"devDependencies": { "devDependencies": {
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+5 -4
View File
@@ -1,19 +1,20 @@
import "./App.css"; import "./App.css";
import DrivethruLandingPage from "./pages/DrivethruLandingPage";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./pages/Authentication/Login"; import Login from "./pages/Authentication/Login";
import SignUp from "./pages/Authentication/SignUp"; import SignUp from "./pages/Authentication/SignUp";
import DrivethruLandingPage from "./pages/UserPages/DrivethruLandingPage";
import Dashboard from "./pages/UserPages/Dashboard";
import NotFoundPage from "./pages/UserPages/NotFoundPage";
function App() { function App() {
return ( return (
<Router> <Router>
<Routes> <Routes>
{/* Landing Page at root ("/") */}
<Route path="/" element={<DrivethruLandingPage />} /> <Route path="/" element={<DrivethruLandingPage />} />
{/* Login Page at "/login" */}
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} /> <Route path="/signup" element={<SignUp />} />
<Route path="/Dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFoundPage />} />
</Routes> </Routes>
</Router> </Router>
); );
+251
View File
@@ -0,0 +1,251 @@
import { useState, useEffect } from "react";
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 }) => {
// Read username dynamically to avoid stale null on first load
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);
const getType = (entry) =>
entry.trim().startsWith("📁") ? "Folder" : "File";
const getName = (entry) => entry.trim().replace(/^📁\s*|^📄\s*/, "");
const isFile = (entry) => getType(entry) === "File";
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 () => {
try {
const res = await fetch(
`${API_URL}/api/hdfs/listFiles?hdfsPath=${encodeURIComponent(
currentPath
)}`
);
const data = await res.json();
const filtered = data.filter(
(entry) => entry.match(/^ */)[0].length === 0
);
setFiles(filtered);
} catch (err) {
console.error("Failed to fetch files:", err);
setFiles([]);
}
};
const deleteFileOrFolder = async (name, type, e) => {
e.stopPropagation();
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
)}`;
const resp = await fetch(endpoint, { method: "DELETE" });
if (!resp.ok) console.error("Deletion failed:", await resp.text());
fetchFiles();
} catch (err) {
console.error("Failed to delete:", err);
}
};
useEffect(() => {
dispatch(setCurrentPath(currentPath));
fetchFiles();
}, [currentPath, dispatch, isUploading]);
const handleOpenFolder = (folderName) => {
setCurrentPathState((prev) => `${prev}/${folderName}`);
};
const goBack = () => {
if (currentPath === userRoot) return;
const parts = currentPath.split("/").filter(Boolean);
parts.pop();
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 (
<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-semibold text-sm">
<span className="truncate max-w-[80%]">Path: {currentPath}</span>
{currentPath !== userRoot && (
<button
onClick={goBack}
className="flex items-center gap-1 text-blue-600 hover:underline text-sm"
>
<ArrowLeft className="w-4 h-4" />
Go Back
</button>
)}
</div>
<table className="w-full text-sm text-left text-black">
<thead className="text-xs uppercase bg-blue-50 text-blue-800 border-b border-blue-200">
<tr>
<th className="px-6 py-3">Name</th>
<th className="px-6 py-3">Actions</th>
</tr>
</thead>
<tbody>
{files.length === 0 ? (
<tr>
<td colSpan="2" className="px-6 py-4 text-gray-500 text-center">
No files found.
</td>
</tr>
) : (
files.map((entry, idx) => {
const name = getName(entry);
const type = getType(entry);
const hdfsPath = `${currentPath}/${name}`;
return (
<tr
key={idx}
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 flex items-center">
{getIcon(name, type)}
{name}
</td>
<td className="px-6 py-4 space-x-3">
{isFile(entry) && (
<button
onClick={(e) => handleFileDownload(hdfsPath, name, e)}
className="text-blue-600 hover:underline inline-flex items-center"
>
<Download className="w-4 h-4 mr-1" />
Download
</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>
</tr>
);
})
)}
</tbody>
</table>
</div>
);
};
FileTable.propTypes = {
initialPath: PropTypes.string,
};
export default FileTable;
+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;
+177 -8
View File
@@ -1,16 +1,185 @@
import { useState } from "react";
import {
Facebook,
Twitter,
Instagram,
Linkedin,
Mail,
Phone,
MapPin,
} from "lucide-react";
const Footer = () => { const Footer = () => {
const [email, setEmail] = useState("");
//Currently storing user email in localstorage
const handleSubscribe = () => {
if (email.trim() !== "") {
localStorage.setItem("subscribedEmail", email);
alert("You have successfully subscribed!");
setEmail("");
}
};
return ( return (
<footer className="bg-gradient-to-r from-[#689adc] via-[#6da1e6] h-[353px] w-full pt-16 pb-8"> <footer className="bg-gradient-to-r from-[#4a7cbd] via-[#5b4fd3] to-[#9377ff] w-full pt-16 pb-8">
<div className="container mx-auto px-4"> <div className="container mx-auto px-6">
<div className="flex items-center justify-center mb-4"> <div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-12">
<div className="text-cyan-400 mr-3"> <div className="space-y-4">
<img src="/image.png" alt="logo" className="h-auto w-16 drop-shadow-lg " /> <div className="flex items-center">
<div className="text-white mr-3">
<svg
className="w-10 h-10"
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> </div>
<h1 className="text-5xl font-bold text-black">Drive-thru</h1> <h3 className="text-2xl font-bold text-white">Skycrate</h3>
</div> </div>
<p className="text-black text-center"> <p className="text-white/90">
A mini project designed and engineering by Team 2025 <br />All rights reserved. Your secure cloud storage solution for all your digital needs.
</p> </p>
<div className="flex space-x-4">
<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" />
</a>
<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" />
</a>
<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" />
</a>
<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" />
</a>
</div>
</div>
{/* Quick Links */}
<div>
<h4 className="font-semibold text-white mb-4">Quick Links</h4>
<ul className="space-y-2">
<li>
<a
href="#about"
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
About Us
</a>
</li>
<li>
<a
href="#features"
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Features
</a>
</li>
<li>
<a
href="#howItWorks"
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
How It Works
</a>
</li>
</ul>
</div>
{/* Contact Info */}
<div>
<h4 className="font-semibold text-white mb-4">Contact</h4>
<ul className="space-y-2">
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
<Mail className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
support@drivethru.com
</li>
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
<Phone className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
+91 3628206234
</li>
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
<MapPin className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
123 Cloud Street, Digital City
</li>
</ul>
</div>
{/* Newsletter */}
<div>
<h4 className="font-semibold text-white mb-4">Stay Updated</h4>
<p className="text-white/90 mb-4">
Get exclusive tips, updates on new features, and special offers
directly in your inbox.
</p>
<div className="space-y-4">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="w-full px-4 py-2 rounded-md bg-white/10 border border-white/20 text-white placeholder:text-white/50 focus:bg-white/20 transition-all duration-200 outline-none focus:ring-2 focus:ring-white/30"
/>
<button
onClick={handleSubscribe}
className="w-full px-4 py-2 rounded-md bg-white text-blue-600 font-medium hover:bg-opacity-90 transition-all duration-200 transform hover:scale-105"
>
Subscribe to Newsletter
</button>
</div>
</div>
</div>
<div className="h-px w-full bg-white/20 my-8" />
{/* Bottom Section */}
<div className="flex flex-col md:flex-row justify-between items-center text-white/90 text-sm">
<p>© {new Date().getFullYear()} Skycrate. All rights reserved.</p>
<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"
>
Privacy Policy
</a>
<a
href="#"
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Terms of Service
</a>
<a
href="#"
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
>
Cookie Policy
</a>
</div>
</div>
</div> </div>
</footer> </footer>
); );
+153
View File
@@ -0,0 +1,153 @@
import { useState, useEffect, useRef } from "react";
import { Link, useNavigate } from "react-router-dom";
import { toast } from "react-hot-toast";
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 (
<>
<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 flex items-center justify-between">
{/* Left Section - Logo & Toggle */}
<div className="flex items-center">
<button
data-drawer-target="logo-sidebar"
data-drawer-toggle="logo-sidebar"
aria-controls="logo-sidebar"
type="button"
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>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path
clipRule="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"
/>
</svg>
</button>
<Link to="/" className="flex ms-2 md:me-24">
<img src="./image.png" className="h-8 me-3" alt="Skycrate Logo" />
<span className="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap">
Skycrate
</span>
</Link>
</div>
{/* Right Section - Search & User Menu */}
<div className="flex items-center">
{/* Search Bar */}
<div className="flex items-center justify-end mr-40"></div>
{/* User Profile & Dropdown */}
<div className="relative ms-3">
<button
type="button"
onClick={() => setUserMenuOpen((o) => !o)}
className="flex text-lg bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300"
>
<span className="sr-only">Open user menu</span>
<img
className="w-8 h-8 rounded-full"
src="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
alt="User Photo"
/>
</button>
{userMenuOpen && (
<div
ref={menuRef}
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">
<p className="text-lg text-white" role="none">
{localStorage.getItem("username")}
</p>
</div>
<ul className="py-1" role="none">
<li>
<button
onClick={handleLogout}
className="w-full text-left px-4 py-2 text-lg text-white hover:bg-[#37A0EA]"
role="menuitem"
>
Log out
</button>
</li>
</ul>
</div>
)}
</div>
</div>
</div>
</nav>
<aside
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"
aria-label="Sidebar"
>
<div className="h-full px-3 pb-4 overflow-y-auto bg-[#1877F2] custom-scrollbar">
<ul className="space-y-2 font-medium">
<li>
<Link
to="#"
className="flex items-center p-2 mt-5 pt-4 pb-4 text-white rounded-lg hover:bg-[#37A0EA] group"
>
<svg
className="w-5 h-5"
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" />
</svg>
<span className="ms-3">Starred</span>
</Link>
</li>
{/* ...additional sidebar items... */}
</ul>
</div>
</aside>
</>
);
};
export default Sidebar;
+27 -1
View File
@@ -1,2 +1,28 @@
@import "tailwindcss"; @import "tailwindcss";
@import "flowbite/src/themes/default";
/* For WebKit-based browsers */
.custom-scrollbar::-webkit-scrollbar {
width: 15px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
/* or a color of your choice */
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: #a0aec0;
/* Customize thumb color */
border-radius: 4px;
border: 2px solid transparent;
/* Optional: creates padding around thumb */
background-clip: content-box;
}
/* For Firefox */
.custom-scrollbar {
scrollbar-width: auto;
/* "auto" or "thin" */
scrollbar-color: #37A0EA transparent;
/* thumb and track colors */
}
+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>
); );
+88 -28
View File
@@ -1,34 +1,91 @@
import React from "react"; import { useState, useEffect } from "react";
import { FcGoogle } from "react-icons/fc";
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">
<h1 className="text-2xl font-bold mb-6 text-gray-900 text-center"> <h1 className="text-2xl font-bold mb-6 text-gray-900 text-center">
Log in Log in
</h1> </h1>
<button className="flex items-center justify-center w-full py-3 mb-4 border border-gray-300 rounded-lg hover:bg-gray-50">
<FcGoogle className="text-xl mr-2" /> <form onSubmit={handleSubmit}>
<span className="text-gray-700 font-medium">
Continue with Google
</span>
</button>
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-300" />
<span className="px-2 text-gray-500 text-sm">
Or login with email
</span>
<div className="flex-grow border-t border-gray-300" />
</div>
<div className="mb-4"> <div className="mb-4">
<div className="flex items-center"> <div className="flex items-center">
<input <input
@@ -36,16 +93,11 @@ 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 className="mb-6">
<Link
to="#!"
className="text-sm text-blue-600 hover:underline inline-block text-center"
>
Login via OTP
</Link>
</div>
</div> </div>
<div className="mb-1"> <div className="mb-1">
<div className="relative"> <div className="relative">
@@ -54,11 +106,14 @@ 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"
onClick={togglePassword} onClick={togglePassword}
className="absolute right-2 top-2 text-gray-500 hover:text-gray-700" className="absolute right-2 top-4 text-2xl text-gray-500 hover:text-gray-700"
> >
{showPassword ? <FiEyeOff /> : <FiEye />} {showPassword ? <FiEyeOff /> : <FiEye />}
</button> </button>
@@ -72,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?{" "}
+118 -13
View File
@@ -1,46 +1,139 @@
import React, { useState } from "react"; import { useState } from "react";
import { FcGoogle } from "react-icons/fc";
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-3 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 />}
</button> </button>
@@ -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-3 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">
-293
View File
@@ -1,293 +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="w-full bg-gray-100 shadow-md py-4 px-6 flex justify-between items-center mt-5">
<h1 className="text-xl font-bold">Drive-thru</h1>
<div className="flex space-x-6">
<a href="#features" className="text-gray-700 hover:text-black">Key Features</a>
<a href="#how-it-works" className="text-gray-700 hover:text-black">How It Works</a>
</div>
</div>
<div className="flex flex-col md:flex-row items-center justify-between w-full min-h-screen">
{/* <div className="hidden md:block md:w-1/2 lg:w-3/5"></div> */}
<div className="flex items-center justify-center">
<img src="data:image/png;base64,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" alt="cloud image" className="flex justify-center my-6 mx-7 w-full h-auto" />
</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">
<img src="/image.png" alt="logo" className="h-auto w-12" />
<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" id="features">
<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" id="how-it-works">
<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;
@@ -0,0 +1,86 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Sidebar from "../../components/Sidebar";
import FileList from "../../components/FileList";
import FileUploadModal from "../../components/FileUploadModal";
import { FiPlus } from "react-icons/fi";
const Dashboard = () => {
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");
if (!token || !username || !expiresIn) return false;
const expiryTime = new Date(expiresIn).getTime();
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();
setFiles(data);
} catch (error) {
console.error("Failed to fetch files:", error);
setError("Failed to load files. Please try again later.");
}
};
useEffect(() => {
if (!isUserLoggedIn()) {
navigate("/login");
} else {
fetchFiles();
}
}, [navigate]);
return (
<>
<Sidebar />
<div className="p-4 sm:ml-64">
<div className="p-4 border-2 border-gray-200 border-dashed rounded-lg mt-14">
<div className="w-full flex justify-between items-center">
<h1 className="text-2xl font-bold mb-4">Dashboard</h1>
<button
onClick={() => setIsUploadModalOpen(true)}
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"
type="button"
>
<FiPlus className="text-2xl" />
</button>
</div>
{error ? (
<p className="text-red-500">{error}</p>
) : (
<FileList files={files} />
)}
</div>
</div>
<FileUploadModal
show={isUploadModalOpen}
onClose={() => setIsUploadModalOpen(false)}
onUploadSuccess={() => {
fetchFiles();
setIsUploadModalOpen(false);
}}
/>
</>
);
};
export default Dashboard;
@@ -0,0 +1,376 @@
import Footer from "../../components/Footer";
import React from "react";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
const DrivethruLandingPage = () => {
const features = [
{
title: "Easy Upload & Access",
description: "Drag & drop, instant access.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
{
title: "Secure & Private",
description: "End-to-end encryption.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
{
title: "Seamless Sharing",
description: "Share files with one click.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
{
title: "Access Anywhere",
description: "Works on all devices.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
];
const howItWorks = [
{
title: "Create an account",
description: "Sign up in seconds.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
{
title: "Upload files",
description: "Drag & drop or select from your device.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
{
title: "Manage files",
description: "Rename, move, or delete easily.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
),
},
{
title: "Access anytime",
description: "Open files from any device.",
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
),
},
];
// UseEffect and handle....click function to handle set and handle the animation of features..
const [activeIndex, setActiveIndex] = useState(0);
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
if (!isPaused) {
const interval = setInterval(() => {
setActiveIndex((prevIndex) => (prevIndex + 1) % features.length);
}, 3000);
return () => clearInterval(interval);
}
}, [isPaused, features.length]);
// Handle user interaction
const handleFeatureClick = (index) => {
setActiveIndex(index);
setIsPaused(true);
setTimeout(() => setIsPaused(false), 1000);
};
const [activeIndex1, setActiveIndex1] = useState(0);
const [isPaused1, setIsPaused1] = useState(false);
useEffect(() => {
if (!isPaused1) {
const interval = setInterval(() => {
setActiveIndex1((prevIndex) => (prevIndex + 1) % howItWorks.length);
}, 3000);
return () => clearInterval(interval);
}
}, [isPaused1, howItWorks.length]);
const handleFeatureClick1 = (index) => {
setActiveIndex1(index);
setIsPaused1(true);
setTimeout(() => setIsPaused1(false), 1000);
};
return (
<div className="min-h-screen overflow-x-hidden bg-white">
{/* Hero Section */}
<div
id="about"
className="bg-gradient-to-r from-blue-50 to-white min-h-[90vh] flex items-center relative"
>
<div className="container mx-auto px-4 md:px-6 lg:px-8 relative z-10">
<div className="flex flex-col md:flex-row items-center gap-8 lg:gap-12">
{/* Left Side - Text Content */}
<div className="w-full md:w-1/2 text-center md:text-left order-1 md:order-1">
<div className="flex justify-center md:justify-start items-center mb-6 lg:mb-8">
<div className="text-cyan-400 mr-2 md:mr-3">
<svg
className="w-10 md:w-12 h-10 md: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-4xl md:text-5xl font-bold text-black">
Skycrate
</h1>
</div>
<h2 className="text-xl md:text-2xl font-bold mb-4 md:mb-6 text-black">
Store, Access & Share Your Files Anytime, Anywhere!
</h2>
<p className="text-gray-800 mb-6 md:mb-10 text-base md:text-lg">
A simple, secure, and fast cloud storage solution for all your
files. Upload, organize, and access with ease.
</p>
{/* Buttons */}
<div className="flex flex-col sm:flex-row justify-center md:justify-start space-y-4 sm:space-y-0 sm:space-x-4">
<Link
to="/signup"
className="bg-emerald-500 hover:bg-emerald-600 text-white font-medium rounded-full px-6 py-4 md:px-8 md:py-6 transform hover:scale-105 transition-all duration-300 shadow-lg hover:shadow-xl"
>
Get Started
</Link>
<Link
to="/login"
className="bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-full px-6 py-4 md:px-8 md:py-6 transform hover:scale-105 transition-all duration-300 shadow-lg hover:shadow-xl"
>
Login
</Link>
</div>
</div>
{/* Right Side - Image */}
<div className="w-full md:w-1/2 flex justify-center order-2 md:order-1">
<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
src="/Dashboard.png"
alt="Skycrate Dashboard Interface"
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>
</div>
</div>
</div>
</div>
{/* Features Section */}
<div
id="features"
className="w-full max-w-5xl mx-auto p-6 sm:p-8 bg-gray-100 rounded-lg shadow-lg"
>
<h2 className="text-3xl font-bold text-center mb-8">Key Features</h2>
<div className="flex flex-col-reverse md:flex-row items-center gap-8 lg:gap-12">
{/* Left Side - Image */}
<div className="w-full md:w-1/2 flex justify-center">
<img
src="/He.png"
alt="Feature Illustration"
className="w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg object-contain rounded-lg shadow-md"
/>
</div>
{/* Right Side - Feature List */}
<div className="w-full md:w-1/2">
<div className="space-y-6">
{features.map((feature, index) => (
<div
key={index}
className={`p-5 border-2 rounded-lg cursor-pointer transition-all duration-500 ${
index === activeIndex
? "border-blue-500 bg-white shadow-lg scale-105"
: "border-gray-300"
}`}
onClick={() => handleFeatureClick(index)}
>
<div className="flex items-center space-x-4">
{feature.icon}
<h3 className="text-lg font-semibold">{feature.title}</h3>
</div>
{index === activeIndex && (
<p className="text-gray-600 mt-3 transition-opacity duration-500 opacity-100">
{feature.description}
</p>
)}
</div>
))}
</div>
</div>
</div>
</div>
{/* How It Works Section */}
<div
id="howItWorks"
className="w-full max-w-5xl mx-auto p-6 sm:p-8 bg-gray-100 rounded-lg shadow-lg"
>
<h2 className="text-3xl font-bold text-center mb-8">How It Works</h2>
<div className="flex flex-col md:flex-row items-center gap-8 lg:gap-12">
{/* Left Side - Feature List */}
<div className="w-full md:w-1/2">
<div className="space-y-6">
{howItWorks.map((howItWork, index) => (
<div
key={index}
className={`p-5 border-2 rounded-lg cursor-pointer transition-all duration-500 ${
index === activeIndex1
? "border-blue-500 bg-white shadow-lg scale-105"
: "border-gray-300"
}`}
onClick={() => handleFeatureClick1(index)}
>
<div className="flex items-center space-x-4">
{howItWork.icon}
<h3 className="text-lg font-semibold">{howItWork.title}</h3>
</div>
{index === activeIndex1 && (
<p className="text-gray-600 mt-3 transition-opacity duration-500 opacity-100">
{howItWork.description}
</p>
)}
</div>
))}
</div>
</div>
{/* Right Side - Image */}
<div className="w-full md:w-1/2 flex justify-center">
<img
src="/She.png"
alt="Feature Illustration"
className="w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg object-contain rounded-lg shadow-md"
/>
</div>
</div>
</div>
<Footer />
</div>
);
};
export default DrivethruLandingPage;
@@ -0,0 +1,32 @@
import { Link } from "react-router-dom";
const NotFoundPage = () => {
return (
<div className="flex flex-col items-center justify-center h-screen bg-gray-100 p-4">
{/* Placeholder SVG - Replace this with your SVG */}
<img
src="/404.png"
style={{ width: "30%", height: "auto" }}
alt="404 Not Found"
></img>
{/* Page number and title */}
<h2 className="text-2xl font-bold mb-4 mt-4">Page Not Found</h2>
{/* Description text */}
<p className="text-center text-gray-700 mb-6">
Sorry, we couldn&apos;t find the page you were looking for. It may have
been moved or deleted.
</p>
{/* Call-to-action button */}
<Link
to="/"
className="px-6 py-2 bg-[#1877F2] text-white rounded hover:bg-blue-600 transition duration-200"
>
Go Home
</Link>
</div>
);
};
export default NotFoundPage;
+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;
+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,
},
});
+8 -1
View File
@@ -4,6 +4,13 @@ import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss(), plugins: [
react(),
tailwindcss(),
], ],
server: {
host: 'localhost',
port: 5173,
},
}) })
+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.
---