Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
17046735da
|
|||
|
5cd80fe27e
|
|||
|
d2e9f80c30
|
|||
|
43d04c7f93
|
|||
|
1cbd74b6a5
|
|||
|
d88f1b6e0b
|
|||
|
73a1c521d5
|
|||
|
9050bbc5cf
|
|||
|
2cce8d89ca
|
|||
|
41435aa4fc
|
|||
|
7dc8a49a8d
|
|||
|
816d115fbc
|
|||
|
715f3a9d96
|
|||
|
3febc68b4e
|
|||
|
f06967708d
|
|||
|
f79435d64f
|
|||
|
04ac930900
|
|||
|
d8193f8174
|
|||
|
d08b0d6f90
|
|||
|
3b027e4a39
|
|||
|
49f57b5c10
|
|||
|
ac75a64ec8
|
|||
|
0e195ac079
|
|||
|
6358e7e72d
|
|||
|
97be5d1b93
|
|||
|
c42a9dacf0
|
|||
|
4b929bb272
|
|||
|
1189e7cb78
|
|||
|
2f6f0ba747
|
|||
|
96f9ddb1d8
|
|||
| 2c29597f1d | |||
| 1f6cbf4310 | |||
| afccce0be3 | |||
| 96fc18ab80 | |||
| febde7dffe | |||
| c426fecf43 | |||
| 0adc932e53 | |||
| 54dd5a1fcc | |||
| d59e8c789c | |||
| 920c793fa6 | |||
| 608435b758 | |||
| c55dd4b661 | |||
| 5c9e8fedbc | |||
| 6294066ea7 | |||
| 76f9b00624 | |||
| 5391410609 | |||
| a91d7fe8c7 | |||
| 65ca53b224 | |||
| 9d6387699e | |||
| f6371faf9a | |||
| 9632450d16 | |||
| e59784bfa9 | |||
| 6aae767aa6 | |||
| df4d3c1990 | |||
| 4ab49db6af | |||
| 00f6e28207 | |||
| 1f40b02346 | |||
|
8c67d9d4c6
|
|||
|
70f654179a
|
|||
| b42a53e99b | |||
| 2ae2002713 | |||
| 4457823342 | |||
| 1142ece2fd | |||
| 6916142deb | |||
| c798c53dcc | |||
|
596c6bf573
|
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Backend"]
|
||||||
|
path = Backend
|
||||||
|
url = https://git.kska.io/notkshitij/SkycrateBackend.git
|
||||||
@@ -5,10 +5,11 @@
|
|||||||
## Work distribution
|
## Work distribution
|
||||||
|
|
||||||
- Design: Kapil
|
- Design: Kapil
|
||||||
- Frontend: Shivani, Shriniwas, Ombase, Tejas, Sonali, Dinesh
|
- Frontend: Ombase, Shriniwas, Dinesh, Lalit, Shivani, Pracheta, Vaibhavi
|
||||||
- Backend: Vedang, Lalit
|
- Backend: Vedang, Sonali, Lalit
|
||||||
- DBMS: Lalit
|
- DBMS: Lalit
|
||||||
- HDFS: Sonali, Prajakta, Poonam
|
- HDFS: Sonali, Prajakta, Poonam
|
||||||
|
- Deployment: Kshitij, Sahil
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule Backend added at d3e1aff0fb
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_URL=http://localhost:8081
|
||||||
@@ -23,3 +23,7 @@ package-lock.json
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
#########
|
||||||
|
|
||||||
|
.vite/
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
## FRONTEND ##
|
||||||
|
|
||||||
|
# Base image
|
||||||
|
FROM node:22
|
||||||
|
|
||||||
|
# Metadata
|
||||||
|
LABEL maintainer="kshitijka"
|
||||||
|
LABEL version=1.0
|
||||||
|
LABEL description="Skycrate is a web based file management system that uses Hadoop as filesystem."
|
||||||
|
|
||||||
|
# Update & upgrade & rm
|
||||||
|
RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/* && npm install -g http-server
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN useradd -s /bin/bash skycrateFront
|
||||||
|
|
||||||
|
# Create work dir
|
||||||
|
RUN mkdir /app
|
||||||
|
RUN chown -R skycrateFront:skycrateFront /app
|
||||||
|
COPY ./dist/ /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Switch user
|
||||||
|
USER skycrateFront
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["http-server", "/app"]
|
||||||
+1
-1
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Generated
-4963
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -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,28 +10,28 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^2.6.0",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@tailwindcss/vite": "^4.0.9",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"i18next": "^25.2.1",
|
||||||
"lucide-react": "^0.476.0",
|
"lucide-react": "^0.476.0",
|
||||||
"react": "^19.0.0",
|
"prop-types": "^15.8.1",
|
||||||
"react-dom": "^19.0.0",
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"react-hot-toast": "^2.5.2",
|
||||||
|
"react-i18next": "^15.5.3",
|
||||||
"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.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"eslint": "^9.29.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint": "^9.21.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"postcss": "^8.5.3",
|
"vite": "^6.3.5"
|
||||||
"tailwindcss": "^4.0.9",
|
|
||||||
"vite": "^6.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
|
import LanguageSwitcher from './components/LanguageSwitcher'; // Language switcher dropdown menu
|
||||||
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 DrivethruLandingPage from "./pages/UserPages/DrivethruLandingPage";
|
||||||
@@ -9,6 +10,7 @@ import NotFoundPage from "./pages/UserPages/NotFoundPage";
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
|
<LanguageSwitcher />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<DrivethruLandingPage />} />
|
<Route path="/" element={<DrivethruLandingPage />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
@@ -8,15 +8,17 @@ import {
|
|||||||
Phone,
|
Phone,
|
||||||
MapPin,
|
MapPin,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
const { t } = useTranslation(); // for multilinguality
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|
||||||
//Currently storing user email in localstorage
|
//Currently storing user email in localstorage
|
||||||
const handleSubscribe = () => {
|
const handleSubscribe = () => {
|
||||||
if (email.trim() !== "") {
|
if (email.trim() !== "") {
|
||||||
localStorage.setItem("subscribedEmail", email);
|
localStorage.setItem("subscribedEmail", email);
|
||||||
alert("You have successfully subscribed!");
|
alert(t("subscribe_success"));
|
||||||
setEmail("");
|
setEmail("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -42,22 +44,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">{t("footer_brand")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/90">
|
<p className="text-white/90">
|
||||||
Your secure cloud storage solution for all your digital needs.
|
{t("footer_tagline")}
|
||||||
</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>
|
||||||
@@ -65,14 +83,14 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Quick Links */}
|
{/* Quick Links */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-white mb-4">Quick Links</h4>
|
<h4 className="font-semibold text-white mb-4">{t("footer_quick_links")}</h4>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#about"
|
href="#about"
|
||||||
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
>
|
>
|
||||||
About Us
|
{t("footer_about_us")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -80,7 +98,7 @@ const Footer = () => {
|
|||||||
href="#features"
|
href="#features"
|
||||||
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
>
|
>
|
||||||
Features
|
{t("footer_features")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -88,7 +106,7 @@ const Footer = () => {
|
|||||||
href="#howItWorks"
|
href="#howItWorks"
|
||||||
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
className="text-white/90 hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
>
|
>
|
||||||
How It Works
|
{t("footer_how_it_works")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -96,42 +114,42 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Contact Info */}
|
{/* Contact Info */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-white mb-4">Contact</h4>
|
<h4 className="font-semibold text-white mb-4">{t("footer_contact")}</h4>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
|
<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" />
|
<Mail className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
|
||||||
support@drivethru.com
|
{t("footer_email")}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
|
<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" />
|
<Phone className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
|
||||||
+91 3628206234
|
{t("footer_phone")}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center text-white/90 hover:text-white group transition-colors duration-200">
|
<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" />
|
<MapPin className="w-4 h-4 mr-2 group-hover:text-cyan-200" />
|
||||||
123 Cloud Street, Digital City
|
{t("footer_address")}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Newsletter */}
|
{/* Newsletter */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-white mb-4">Stay Updated</h4>
|
<h4 className="font-semibold text-white mb-4">{t("footer_newsletter_title")}</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.
|
{t("footer_newsletter_desc")}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="Enter your email"
|
placeholder={t("footer_newsletter_placeholder")}
|
||||||
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"
|
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
|
<button
|
||||||
onClick={handleSubscribe}
|
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"
|
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
|
{t("footer_newsletter_button")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,16 +159,25 @@ 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()} {t("footer_brand")}. {t("footer_rights")}</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
|
||||||
Privacy Policy
|
href="#"
|
||||||
|
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
|
>
|
||||||
|
{t("footer_privacy_policy")}
|
||||||
</a>
|
</a>
|
||||||
<a href="#" className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block">
|
<a
|
||||||
Terms of Service
|
href="#"
|
||||||
|
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
|
>
|
||||||
|
{t("footer_terms_of_service")}
|
||||||
</a>
|
</a>
|
||||||
<a href="#" className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block">
|
<a
|
||||||
Cookie Policy
|
href="#"
|
||||||
|
className="hover:text-white transition-all duration-200 hover:translate-x-1 inline-block"
|
||||||
|
>
|
||||||
|
{t("footer_cookie_policy")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{ code: 'en', label: 'English' },
|
||||||
|
{ code: 'hi', label: 'Hindi (हिंदी)' },
|
||||||
|
{ code: 'mr', label: 'Marathi (मराठी)' },
|
||||||
|
{ code: 'fr', label: 'French (Français)' },
|
||||||
|
// Add more languages as needed
|
||||||
|
];
|
||||||
|
|
||||||
|
function LanguageSwitcher() {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '1rem',
|
||||||
|
right: '1rem',
|
||||||
|
zIndex: 1000,
|
||||||
|
background: 'rgba(255,255,255,0.95)',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '0.25em 0.5em',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
value={i18n.language}
|
||||||
|
onChange={e => i18n.changeLanguage(e.target.value)}
|
||||||
|
style={{
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '0.25em 2em 0.25em 0.5em', // More right padding for arrow
|
||||||
|
minWidth: '100px',
|
||||||
|
appearance: 'auto', // Use browser default arrow
|
||||||
|
background: '#fff',
|
||||||
|
}}
|
||||||
|
|
||||||
|
aria-label="Select language"
|
||||||
|
>
|
||||||
|
{languages.map(lang => (
|
||||||
|
<option key={lang.code} value={lang.code}>
|
||||||
|
{lang.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LanguageSwitcher;
|
||||||
|
|
||||||
+107
-260
@@ -1,136 +1,122 @@
|
|||||||
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";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
|
const { t } = useTranslation(); // for multilinguality
|
||||||
|
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(t("sidebar_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: t("sidebar_logged_out"),
|
||||||
|
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">{t("sidebar_open_sidebar")}</span>
|
||||||
<svg
|
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
||||||
className="w-6 h-6"
|
<path
|
||||||
aria-hidden="true"
|
clipRule="evenodd"
|
||||||
fill="currentColor"
|
fillRule="evenodd"
|
||||||
viewBox="0 0 20 20"
|
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"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<Link to="/" className="flex ms-2 md:me-24">
|
|
||||||
<img
|
|
||||||
src="./image.png"
|
|
||||||
className="h-8 me-3"
|
|
||||||
alt="Drive-thru Logo"
|
|
||||||
/>
|
/>
|
||||||
<span className="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
|
</svg>
|
||||||
Drive-thru
|
</button>
|
||||||
</span>
|
<Link to="/" className="flex ms-2 md:me-24">
|
||||||
</Link>
|
<img src="./image.png" className="h-8 me-3" alt="Skycrate Logo" />
|
||||||
</div>
|
<span className="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap">
|
||||||
|
{t("sidebar_brand")}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center">
|
{/* Right Section - Search & User Menu */}
|
||||||
{" "}
|
<div className="flex items-center">
|
||||||
<div className="flex items-center justify-end mr-40 ">
|
{/* Search Bar */}
|
||||||
<input
|
<div className="flex items-center justify-end mr-40"></div>
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
{/* User Profile & Dropdown */}
|
||||||
className="w-full border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
<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">{t("sidebar_open_user_menu")}</span>
|
||||||
|
<img
|
||||||
|
className="w-8 h-8 rounded-full"
|
||||||
|
src="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
|
||||||
|
alt={t("sidebar_user_photo")}
|
||||||
/>
|
/>
|
||||||
<button
|
</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"
|
{userMenuOpen && (
|
||||||
>
|
|
||||||
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>
|
|
||||||
<img
|
|
||||||
className="w-8 h-8 rounded-full"
|
|
||||||
src="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
|
|
||||||
alt="user photo"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<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
|
{t("sidebar_logout")}
|
||||||
</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 +124,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">{t("sidebar_starred")}</span>
|
||||||
<span className="ms-3">Starred</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 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>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
{/* ...additional sidebar items... */}
|
||||||
</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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
import en from './locales/en.json';
|
||||||
|
import hi from './locales/hi.json';
|
||||||
|
import mr from './locales/mr.json';
|
||||||
|
import fr from './locales/fr.json';
|
||||||
|
// import more languages as needed
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
en: { translation: en },
|
||||||
|
hi: { translation: hi },
|
||||||
|
mr: { translation: mr },
|
||||||
|
fr: { translation: fr },
|
||||||
|
// add other languages here
|
||||||
|
};
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
resources,
|
||||||
|
lng: 'en', // default language
|
||||||
|
fallbackLng: 'en',
|
||||||
|
//interpolation: {
|
||||||
|
// escapeValue: false, // not needed for React
|
||||||
|
//},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"failed_to_load_files": "Failed to load files. Please try again later.",
|
||||||
|
|
||||||
|
"skycrate": "Skycrate",
|
||||||
|
"hero_subtitle": "Store, Access & Share Your Files — Anytime, Anywhere!",
|
||||||
|
"hero_desc": "A simple, secure, and fast cloud storage solution for all your files. Upload, organize, and access with ease.",
|
||||||
|
"get_started": "Get Started",
|
||||||
|
"login": "Login",
|
||||||
|
"key_features": "Key Features",
|
||||||
|
"feature_easy_upload_title": "Easy Upload & Access",
|
||||||
|
"feature_easy_upload_desc": "Drag & drop, instant access.",
|
||||||
|
"feature_secure_title": "Secure & Private",
|
||||||
|
"feature_secure_desc": "End-to-end encryption.",
|
||||||
|
"feature_sharing_title": "Seamless Sharing",
|
||||||
|
"feature_sharing_desc": "Share files with one click.",
|
||||||
|
"feature_access_anywhere_title": "Access Anywhere",
|
||||||
|
"feature_access_anywhere_desc": "Works on all devices.",
|
||||||
|
"how_it_works": "How It Works",
|
||||||
|
"how_create_account_title": "Create an account",
|
||||||
|
"how_create_account_desc": "Sign up in seconds.",
|
||||||
|
"how_upload_files_title": "Upload files",
|
||||||
|
"how_upload_files_desc": "Drag & drop or select from your device.",
|
||||||
|
"how_manage_files_title": "Manage files",
|
||||||
|
"how_manage_files_desc": "Rename, move, or delete easily.",
|
||||||
|
"how_access_anytime_title": "Access anytime",
|
||||||
|
"how_access_anytime_desc": "Open files from any device.",
|
||||||
|
|
||||||
|
"not_found_title": "Page Not Found",
|
||||||
|
"not_found_description": "Sorry, we couldn't find the page you were looking for. It may have been moved or deleted.",
|
||||||
|
"go_home": "Go Home",
|
||||||
|
|
||||||
|
"login_title": "Log in",
|
||||||
|
"email_placeholder": "Enter your email",
|
||||||
|
"password_placeholder": "Enter your password",
|
||||||
|
"forgot_password": "Forgot password?",
|
||||||
|
"logging_in": "Logging In...",
|
||||||
|
"login": "Login",
|
||||||
|
"dont_have_account": "Don’t have an account?",
|
||||||
|
"sign_up": "Sign up",
|
||||||
|
"login_successful": "Login successful!",
|
||||||
|
"login_failed": "Login failed.",
|
||||||
|
"an_error_occurred": "An error occurred. Please try again.",
|
||||||
|
"logging_in_toast": "Logging in...",
|
||||||
|
|
||||||
|
"signup_title": "Sign Up",
|
||||||
|
"first_name": "First Name",
|
||||||
|
"last_name": "Last Name",
|
||||||
|
"email_placeholder": "Enter your email",
|
||||||
|
"password_placeholder": "Enter your password",
|
||||||
|
"confirm_password_placeholder": "Confirm your password",
|
||||||
|
"signing_up": "Signing Up...",
|
||||||
|
"sign_up": "Sign Up",
|
||||||
|
"already_have_account": "Already have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"passwords_do_not_match": "Passwords do not match.",
|
||||||
|
"registering": "Registering...",
|
||||||
|
"signup_failed": "Signup failed.",
|
||||||
|
"folder_creation_failed": "Failed to create user folder.",
|
||||||
|
"signup_success": "Successfully registered and folder created!",
|
||||||
|
"an_error_occurred": "An error occurred. Please try again.",
|
||||||
|
|
||||||
|
"footer_brand": "Skycrate",
|
||||||
|
"footer_tagline": "Your secure cloud storage solution for all your digital needs.",
|
||||||
|
"footer_quick_links": "Quick Links",
|
||||||
|
"footer_about_us": "About Us",
|
||||||
|
"footer_features": "Features",
|
||||||
|
"footer_how_it_works": "How It Works",
|
||||||
|
"footer_contact": "Contact",
|
||||||
|
"footer_email": "support@drivethru.com",
|
||||||
|
"footer_phone": "+91 3628206234",
|
||||||
|
"footer_address": "123 Cloud Street, Digital City",
|
||||||
|
"footer_newsletter_title": "Stay Updated",
|
||||||
|
"footer_newsletter_desc": "Get exclusive tips, updates on new features, and special offers directly in your inbox.",
|
||||||
|
"footer_newsletter_placeholder": "Enter your email",
|
||||||
|
"footer_newsletter_button": "Subscribe to Newsletter",
|
||||||
|
"subscribe_success": "You have successfully subscribed!",
|
||||||
|
"footer_rights": "All rights reserved.",
|
||||||
|
"footer_privacy_policy": "Privacy Policy",
|
||||||
|
"footer_terms_of_service": "Terms of Service",
|
||||||
|
"footer_cookie_policy": "Cookie Policy",
|
||||||
|
|
||||||
|
"sidebar_logging_out": "Logging out...",
|
||||||
|
"sidebar_logged_out": "Logged out successfully!",
|
||||||
|
"sidebar_open_sidebar": "Open sidebar",
|
||||||
|
"sidebar_brand": "Skycrate",
|
||||||
|
"sidebar_open_user_menu": "Open user menu",
|
||||||
|
"sidebar_user_photo": "User Photo",
|
||||||
|
"sidebar_logout": "Log out",
|
||||||
|
"sidebar_starred": "Starred"
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "Tableau de bord",
|
||||||
|
"failed_to_load_files": "Échec du chargement des fichiers. Veuillez réessayer plus tard.",
|
||||||
|
|
||||||
|
"skycrate": "Skycrate",
|
||||||
|
"hero_subtitle": "Stockez, accédez et partagez vos fichiers — à tout moment, partout !",
|
||||||
|
"hero_desc": "Une solution de stockage cloud simple, sécurisée et rapide pour tous vos fichiers. Téléchargez, organisez et accédez facilement.",
|
||||||
|
"get_started": "Commencer",
|
||||||
|
"login": "Connexion",
|
||||||
|
"key_features": "Fonctionnalités clés",
|
||||||
|
"feature_easy_upload_title": "Téléversement et accès faciles",
|
||||||
|
"feature_easy_upload_desc": "Glissez-déposez, accès instantané.",
|
||||||
|
"feature_secure_title": "Sécurisé et privé",
|
||||||
|
"feature_secure_desc": "Chiffrement de bout en bout.",
|
||||||
|
"feature_sharing_title": "Partage sans effort",
|
||||||
|
"feature_sharing_desc": "Partagez des fichiers en un clic.",
|
||||||
|
"feature_access_anywhere_title": "Accès partout",
|
||||||
|
"feature_access_anywhere_desc": "Fonctionne sur tous les appareils.",
|
||||||
|
"how_it_works": "Comment ça marche",
|
||||||
|
"how_create_account_title": "Créer un compte",
|
||||||
|
"how_create_account_desc": "Inscrivez-vous en quelques secondes.",
|
||||||
|
"how_upload_files_title": "Téléverser des fichiers",
|
||||||
|
"how_upload_files_desc": "Glissez-déposez ou sélectionnez depuis votre appareil.",
|
||||||
|
"how_manage_files_title": "Gérer les fichiers",
|
||||||
|
"how_manage_files_desc": "Renommez, déplacez ou supprimez facilement.",
|
||||||
|
"how_access_anytime_title": "Accès à tout moment",
|
||||||
|
"how_access_anytime_desc": "Ouvrez des fichiers depuis n'importe quel appareil.",
|
||||||
|
|
||||||
|
"not_found_title": "Page non trouvée",
|
||||||
|
"not_found_description": "Désolé, nous n'avons pas pu trouver la page que vous cherchiez. Elle a peut-être été déplacée ou supprimée.",
|
||||||
|
"go_home": "Accueil",
|
||||||
|
|
||||||
|
"login_title": "Connexion",
|
||||||
|
"email_placeholder": "Entrez votre e-mail",
|
||||||
|
"password_placeholder": "Entrez votre mot de passe",
|
||||||
|
"forgot_password": "Mot de passe oublié ?",
|
||||||
|
"logging_in": "Connexion...",
|
||||||
|
"login": "Connexion",
|
||||||
|
"dont_have_account": "Vous n'avez pas de compte ?",
|
||||||
|
"sign_up": "S'inscrire",
|
||||||
|
"login_successful": "Connexion réussie !",
|
||||||
|
"login_failed": "Échec de la connexion.",
|
||||||
|
"an_error_occurred": "Une erreur s'est produite. Veuillez réessayer.",
|
||||||
|
"logging_in_toast": "Connexion en cours...",
|
||||||
|
|
||||||
|
"sign_up": "S'inscrire",
|
||||||
|
"first_name": "Prénom",
|
||||||
|
"last_name": "Nom de famille",
|
||||||
|
"email_placeholder": "Entrez votre e-mail",
|
||||||
|
"password_placeholder": "Entrez votre mot de passe",
|
||||||
|
"confirm_password_placeholder": "Confirmez votre mot de passe",
|
||||||
|
"already_have_account": "Vous avez déjà un compte ?",
|
||||||
|
"login": "Connexion",
|
||||||
|
"signing_up": "Inscription...",
|
||||||
|
"passwords_do_not_match": "Les mots de passe ne correspondent pas.",
|
||||||
|
"registering": "Enregistrement...",
|
||||||
|
"signup_failed": "Échec de l'inscription.",
|
||||||
|
"failed_create_folder": "Échec de la création du dossier utilisateur.",
|
||||||
|
"signup_success": "Inscription réussie et dossier créé !",
|
||||||
|
"an_error_occurred": "Une erreur s'est produite. Veuillez réessayer.",
|
||||||
|
|
||||||
|
"footer_brand": "Skycrate",
|
||||||
|
"footer_tagline": "Votre solution de stockage cloud sécurisée pour tous vos besoins numériques.",
|
||||||
|
"footer_quick_links": "Liens rapides",
|
||||||
|
"footer_about_us": "À propos",
|
||||||
|
"footer_features": "Fonctionnalités",
|
||||||
|
"footer_how_it_works": "Comment ça marche",
|
||||||
|
"footer_contact": "Contact",
|
||||||
|
"footer_email": "support@drivethru.com",
|
||||||
|
"footer_phone": "+91 3628206234",
|
||||||
|
"footer_address": "123 Rue du Cloud, Ville Digitale",
|
||||||
|
"footer_newsletter_title": "Restez informé",
|
||||||
|
"footer_newsletter_desc": "Recevez des conseils exclusifs, des mises à jour sur les nouvelles fonctionnalités et des offres spéciales directement dans votre boîte de réception.",
|
||||||
|
"footer_newsletter_placeholder": "Entrez votre e-mail",
|
||||||
|
"footer_newsletter_button": "S'abonner à la newsletter",
|
||||||
|
"subscribe_success": "Vous vous êtes abonné avec succès !",
|
||||||
|
"footer_rights": "Tous droits réservés.",
|
||||||
|
"footer_privacy_policy": "Politique de confidentialité",
|
||||||
|
"footer_terms_of_service": "Conditions d'utilisation",
|
||||||
|
"footer_cookie_policy": "Politique relative aux cookies",
|
||||||
|
|
||||||
|
"sidebar_logging_out": "Déconnexion...",
|
||||||
|
"sidebar_logged_out": "Déconnecté avec succès !",
|
||||||
|
"sidebar_open_sidebar": "Ouvrir la barre latérale",
|
||||||
|
"sidebar_brand": "Skycrate",
|
||||||
|
"sidebar_open_user_menu": "Ouvrir le menu utilisateur",
|
||||||
|
"sidebar_user_photo": "Photo de l'utilisateur",
|
||||||
|
"sidebar_logout": "Se déconnecter",
|
||||||
|
"sidebar_starred": "Favoris"
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "डैशबोर्ड",
|
||||||
|
"failed_to_load_files": "फ़ाइलें लोड करने में विफल। कृपया बाद में पुनः प्रयास करें।",
|
||||||
|
|
||||||
|
"skycrate": "Skycrate",
|
||||||
|
"hero_subtitle": "अपनी फ़ाइलें संग्रहित करें, एक्सेस करें और साझा करें — कभी भी, कहीं भी!",
|
||||||
|
"hero_desc": "आपकी सभी फ़ाइलों के लिए एक सरल, सुरक्षित और तेज़ क्लाउड स्टोरेज समाधान। अपलोड करें, व्यवस्थित करें और आसानी से एक्सेस करें।",
|
||||||
|
"get_started": "शुरू करें",
|
||||||
|
"login": "लॉगिन",
|
||||||
|
"key_features": "मुख्य विशेषताएं",
|
||||||
|
"feature_easy_upload_title": "सरल अपलोड और एक्सेस",
|
||||||
|
"feature_easy_upload_desc": "ड्रैग और ड्रॉप करें, त्वरित एक्सेस पाएं।",
|
||||||
|
"feature_secure_title": "सुरक्षित और निजी",
|
||||||
|
"feature_secure_desc": "एंड-टू-एंड एन्क्रिप्शन।",
|
||||||
|
"feature_sharing_title": "बिना रुकावट साझाकरण",
|
||||||
|
"feature_sharing_desc": "एक क्लिक में फ़ाइलें साझा करें।",
|
||||||
|
"feature_access_anywhere_title": "कहीं से भी एक्सेस करें",
|
||||||
|
"feature_access_anywhere_desc": "सभी डिवाइस पर कार्य करता है।",
|
||||||
|
|
||||||
|
"how_it_works": "यह कैसे कार्य करता है",
|
||||||
|
"how_create_account_title": "खाता बनाएं",
|
||||||
|
"how_create_account_desc": "कुछ ही सेकंड में साइन अप करें।",
|
||||||
|
"how_upload_files_title": "फ़ाइलें अपलोड करें",
|
||||||
|
"how_upload_files_desc": "ड्रैग और ड्रॉप करें या डिवाइस से चुनें।",
|
||||||
|
"how_manage_files_title": "फ़ाइलें प्रबंधित करें",
|
||||||
|
"how_manage_files_desc": "आसानी से नाम बदलें, स्थानांतरित करें या हटाएं।",
|
||||||
|
"how_access_anytime_title": "कभी भी एक्सेस करें",
|
||||||
|
"how_access_anytime_desc": "किसी भी डिवाइस से फ़ाइलें खोलें।",
|
||||||
|
|
||||||
|
"not_found_title": "पृष्ठ नहीं मिला",
|
||||||
|
"not_found_description": "क्षमा करें, हम वह पृष्ठ नहीं ढूंढ सके जिसे आप खोज रहे थे। यह हटाया गया हो सकता है या स्थानांतरित कर दिया गया हो।",
|
||||||
|
"go_home": "मुख्य पृष्ठ पर जाएं",
|
||||||
|
|
||||||
|
"login_title": "लॉग इन करें",
|
||||||
|
"email_placeholder": "अपना ईमेल दर्ज करें",
|
||||||
|
"password_placeholder": "अपना पासवर्ड दर्ज करें",
|
||||||
|
"forgot_password": "पासवर्ड भूल गए?",
|
||||||
|
"logging_in": "लॉग इन किया जा रहा है...",
|
||||||
|
"login": "लॉगिन",
|
||||||
|
"dont_have_account": "कोई खाता नहीं है?",
|
||||||
|
"sign_up": "साइन अप करें",
|
||||||
|
"login_successful": "सफलतापूर्वक लॉगिन हुआ!",
|
||||||
|
"login_failed": "लॉगिन विफल रहा।",
|
||||||
|
"an_error_occurred": "एक त्रुटि हुई। कृपया पुनः प्रयास करें।",
|
||||||
|
"logging_in_toast": "लॉग इन किया जा रहा है...",
|
||||||
|
|
||||||
|
"signup_title": "साइन अप करें",
|
||||||
|
"first_name": "पहला नाम",
|
||||||
|
"last_name": "अंतिम नाम",
|
||||||
|
"email_placeholder": "अपना ईमेल दर्ज करें",
|
||||||
|
"password_placeholder": "अपना पासवर्ड दर्ज करें",
|
||||||
|
"confirm_password_placeholder": "अपना पासवर्ड पुष्टि करें",
|
||||||
|
"signing_up": "साइन अप किया जा रहा है...",
|
||||||
|
"sign_up": "साइन अप करें",
|
||||||
|
"already_have_account": "पहले से ही खाता है?",
|
||||||
|
"login": "लॉगिन",
|
||||||
|
"passwords_do_not_match": "पासवर्ड मेल नहीं खा रहे हैं।",
|
||||||
|
"registering": "पंजीकरण किया जा रहा है...",
|
||||||
|
"signup_failed": "साइन अप विफल रहा।",
|
||||||
|
"folder_creation_failed": "यूज़र फ़ोल्डर बनाने में विफल।",
|
||||||
|
"signup_success": "सफलतापूर्वक पंजीकरण हुआ और फ़ोल्डर बनाया गया!",
|
||||||
|
"an_error_occurred": "एक त्रुटि हुई। कृपया पुनः प्रयास करें।",
|
||||||
|
|
||||||
|
"footer_brand": "Skycrate",
|
||||||
|
"footer_tagline": "आपकी सभी डिजिटल आवश्यकताओं के लिए सुरक्षित क्लाउड स्टोरेज समाधान।",
|
||||||
|
"footer_quick_links": "त्वरित लिंक",
|
||||||
|
"footer_about_us": "हमारे बारे में",
|
||||||
|
"footer_features": "विशेषताएं",
|
||||||
|
"footer_how_it_works": "यह कैसे कार्य करता है",
|
||||||
|
"footer_contact": "संपर्क करें",
|
||||||
|
"footer_email": "support@drivethru.com",
|
||||||
|
"footer_phone": "+91 3628206234",
|
||||||
|
"footer_address": "123 क्लाउड स्ट्रीट, डिजिटल सिटी",
|
||||||
|
"footer_newsletter_title": "अपडेट प्राप्त करें",
|
||||||
|
"footer_newsletter_desc": "विशेष सुझाव, नई सुविधाओं के अपडेट और ऑफ़र सीधे अपने इनबॉक्स में पाएं।",
|
||||||
|
"footer_newsletter_placeholder": "अपना ईमेल दर्ज करें",
|
||||||
|
"footer_newsletter_button": "न्यूज़लेटर की सदस्यता लें",
|
||||||
|
"subscribe_success": "आपने सफलतापूर्वक सदस्यता ली है!",
|
||||||
|
"footer_rights": "सभी अधिकार सुरक्षित।",
|
||||||
|
"footer_privacy_policy": "गोपनीयता नीति",
|
||||||
|
"footer_terms_of_service": "सेवा की शर्तें",
|
||||||
|
"footer_cookie_policy": "कुकी नीति",
|
||||||
|
|
||||||
|
"sidebar_logging_out": "लॉग आउट किया जा रहा है...",
|
||||||
|
"sidebar_logged_out": "सफलतापूर्वक लॉग आउट हुआ!",
|
||||||
|
"sidebar_open_sidebar": "साइडबार खोलें",
|
||||||
|
"sidebar_brand": "Skycrate",
|
||||||
|
"sidebar_open_user_menu": "उपयोगकर्ता मेनू खोलें",
|
||||||
|
"sidebar_user_photo": "उपयोगकर्ता फोटो",
|
||||||
|
"sidebar_logout": "लॉग आउट",
|
||||||
|
"sidebar_starred": "चिह्नित"
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "डॅशबोर्ड",
|
||||||
|
"failed_to_load_files": "फायली लोड करण्यात अयशस्वी. कृपया नंतर पुन्हा प्रयत्न करा.",
|
||||||
|
|
||||||
|
"skycrate": "Skycrate",
|
||||||
|
"hero_subtitle": "तुमच्या फायली साठवा, प्रवेश मिळवा आणि शेअर करा — कधीही, कुठेही!",
|
||||||
|
"hero_desc": "सर्व फायलींसाठी एक साधे, सुरक्षित आणि जलद क्लाऊड स्टोरेज सोल्यूशन. अपलोड करा, व्यवस्थापित करा आणि सहजपणे वापरा.",
|
||||||
|
"get_started": "सुरुवात करा",
|
||||||
|
"login": "लॉगिन",
|
||||||
|
"key_features": "मुख्य वैशिष्ट्ये",
|
||||||
|
"feature_easy_upload_title": "सोपे अपलोड आणि प्रवेश",
|
||||||
|
"feature_easy_upload_desc": "ड्रॅग आणि ड्रॉप, त्वरित प्रवेश.",
|
||||||
|
"feature_secure_title": "सुरक्षित आणि खाजगी",
|
||||||
|
"feature_secure_desc": "संपूर्ण एन्क्रिप्शन.",
|
||||||
|
"feature_sharing_title": "सुलभ शेअरिंग",
|
||||||
|
"feature_sharing_desc": "एक क्लिकमध्ये फायली शेअर करा.",
|
||||||
|
"feature_access_anywhere_title": "कोठूनही प्रवेश",
|
||||||
|
"feature_access_anywhere_desc": "सर्व डिव्हाइसेसवर कार्यरत.",
|
||||||
|
|
||||||
|
"how_it_works": "हे कसे कार्य करते",
|
||||||
|
"how_create_account_title": "खाते तयार करा",
|
||||||
|
"how_create_account_desc": "काही सेकंदांत साइन अप करा.",
|
||||||
|
"how_upload_files_title": "फायली अपलोड करा",
|
||||||
|
"how_upload_files_desc": "ड्रॅग आणि ड्रॉप करा किंवा तुमच्या डिव्हाइसमधून निवडा.",
|
||||||
|
"how_manage_files_title": "फायली व्यवस्थापित करा",
|
||||||
|
"how_manage_files_desc": "नाव बदला, हलवा किंवा हटवा.",
|
||||||
|
"how_access_anytime_title": "कधीही प्रवेश करा",
|
||||||
|
"how_access_anytime_desc": "कोणत्याही डिव्हाइसवरून फायली उघडा.",
|
||||||
|
|
||||||
|
"not_found_title": "पृष्ठ सापडले नाही",
|
||||||
|
"not_found_description": "क्षमस्व, तुम्ही शोधत असलेले पृष्ठ आम्हाला सापडले नाही. कदाचित ते हलवले गेले असेल किंवा हटवले गेले असेल.",
|
||||||
|
"go_home": "मुख्य पृष्ठावर जा",
|
||||||
|
|
||||||
|
"login_title": "लॉग इन करा",
|
||||||
|
"email_placeholder": "तुमचा ईमेल टाका",
|
||||||
|
"password_placeholder": "तुमचा पासवर्ड टाका",
|
||||||
|
"forgot_password": "पासवर्ड विसरलात?",
|
||||||
|
"logging_in": "लॉग इन करत आहे...",
|
||||||
|
"login": "लॉग इन",
|
||||||
|
"dont_have_account": "अजून खाते नाही?",
|
||||||
|
"sign_up": "साइन अप",
|
||||||
|
"login_successful": "यशस्वीरित्या लॉग इन झाले!",
|
||||||
|
"login_failed": "लॉग इन अयशस्वी.",
|
||||||
|
"an_error_occurred": "त्रुटी आली. कृपया पुन्हा प्रयत्न करा.",
|
||||||
|
"logging_in_toast": "लॉग इन होत आहे...",
|
||||||
|
|
||||||
|
"signup_title": "साइन अप करा",
|
||||||
|
"first_name": "पहिले नाव",
|
||||||
|
"last_name": "आडनाव",
|
||||||
|
"email_placeholder": "तुमचा ईमेल टाका",
|
||||||
|
"password_placeholder": "तुमचा पासवर्ड टाका",
|
||||||
|
"confirm_password_placeholder": "तुमचा पासवर्ड पुन्हा टाका",
|
||||||
|
"signing_up": "साइन अप करत आहे...",
|
||||||
|
"sign_up": "साइन अप",
|
||||||
|
"already_have_account": "आधीच खाते आहे?",
|
||||||
|
"login": "लॉग इन",
|
||||||
|
"passwords_do_not_match": "पासवर्ड जुळत नाहीत.",
|
||||||
|
"registering": "नोंदणी करत आहे...",
|
||||||
|
"signup_failed": "साइन अप अयशस्वी.",
|
||||||
|
"folder_creation_failed": "वापरकर्त्याची फोल्डर तयार करण्यात अयशस्वी.",
|
||||||
|
"signup_success": "यशस्वीरित्या नोंदणी झाली आणि फोल्डर तयार झाला!",
|
||||||
|
"an_error_occurred": "त्रुटी आली. कृपया पुन्हा प्रयत्न करा.",
|
||||||
|
|
||||||
|
"footer_brand": "Skycrate",
|
||||||
|
"footer_tagline": "तुमच्या सर्व डिजिटल गरजांसाठी सुरक्षित क्लाऊड स्टोरेज सोल्यूशन.",
|
||||||
|
"footer_quick_links": "त्वरित दुवे",
|
||||||
|
"footer_about_us": "आमच्याबद्दल",
|
||||||
|
"footer_features": "वैशिष्ट्ये",
|
||||||
|
"footer_how_it_works": "हे कसे कार्य करते",
|
||||||
|
"footer_contact": "संपर्क",
|
||||||
|
"footer_email": "support@drivethru.com",
|
||||||
|
"footer_phone": "+९१ ३६२८२०६२३४",
|
||||||
|
"footer_address": "१२३ क्लाऊड स्ट्रीट, डिजिटल सिटी",
|
||||||
|
"footer_newsletter_title": "अपडेट मिळवा",
|
||||||
|
"footer_newsletter_desc": "विशेष टिप्स, नवीन वैशिष्ट्यांवरील अपडेट्स आणि खास ऑफर्स तुमच्या इनबॉक्समध्ये मिळवा.",
|
||||||
|
"footer_newsletter_placeholder": "तुमचा ईमेल टाका",
|
||||||
|
"footer_newsletter_button": "न्यूजलेटरची सदस्यता घ्या",
|
||||||
|
"subscribe_success": "तुमची सदस्यता यशस्वीरित्या घेतली गेली आहे!",
|
||||||
|
"footer_rights": "सर्व हक्क राखीव.",
|
||||||
|
"footer_privacy_policy": "गोपनीयता धोरण",
|
||||||
|
"footer_terms_of_service": "सेवेच्या अटी",
|
||||||
|
"footer_cookie_policy": "कुकी धोरण",
|
||||||
|
|
||||||
|
"sidebar_logging_out": "लॉग आउट करत आहे...",
|
||||||
|
"sidebar_logged_out": "यशस्वीरित्या लॉग आउट झाले!",
|
||||||
|
"sidebar_open_sidebar": "साइडबार उघडा",
|
||||||
|
"sidebar_brand": "Skycrate",
|
||||||
|
"sidebar_open_user_menu": "वापरकर्ता मेनू उघडा",
|
||||||
|
"sidebar_user_photo": "वापरकर्त्याचा फोटो",
|
||||||
|
"sidebar_logout": "लॉग आउट",
|
||||||
|
"sidebar_starred": "आवडते"
|
||||||
|
}
|
||||||
+10
-2
@@ -1,10 +1,18 @@
|
|||||||
|
import './i18n'; // for multilingual functionality
|
||||||
|
|
||||||
import { StrictMode } from "react";
|
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>
|
||||||
<App />
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,67 +1,149 @@
|
|||||||
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
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL; // Using .env variable
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const [showPassword, setShowPassword] = React.useState(false);
|
const { t } = useTranslation(); // for multilinguality
|
||||||
|
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(t("logging_in_toast"));
|
||||||
|
|
||||||
|
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(t("login_successful"));
|
||||||
|
// Redirect to Dashboard
|
||||||
|
navigate("/dashboard");
|
||||||
|
} else {
|
||||||
|
// Show error toast if login fails
|
||||||
|
toast.error(data.message || t("login_failed"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Dismiss the loading toast and show error
|
||||||
|
toast.dismiss(toastId);
|
||||||
|
toast.error(t("an_error_occurred"));
|
||||||
|
} 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
|
{t("login_title")}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="mb-4">
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="flex items-center">
|
<div className="mb-4">
|
||||||
<input
|
<div className="flex items-center">
|
||||||
type="email"
|
<input
|
||||||
id="email"
|
type="email"
|
||||||
placeholder="Enter your email"
|
id="email"
|
||||||
className="w-full border border-gray-300 rounded-l-lg px-4 py-4 focus:outline-none focus:border-blue-500"
|
placeholder={t("email_placeholder")}
|
||||||
/>
|
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>
|
<div className="mb-1">
|
||||||
<div className="mb-1">
|
<div className="relative">
|
||||||
<div className="relative">
|
<input
|
||||||
<input
|
type={showPassword ? "text" : "password"}
|
||||||
type={showPassword ? "text" : "password"}
|
id="password"
|
||||||
id="password"
|
placeholder={t("password_placeholder")}
|
||||||
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)}
|
||||||
<button
|
required
|
||||||
type="button"
|
/>
|
||||||
onClick={togglePassword}
|
<button
|
||||||
className="absolute right-2 top-4 text-2xl text-gray-500 hover:text-gray-700"
|
type="button"
|
||||||
|
onClick={togglePassword}
|
||||||
|
className="absolute right-2 top-4 text-2xl text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
{showPassword ? <FiEyeOff /> : <FiEye />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 ">
|
||||||
|
<Link
|
||||||
|
to="#!"
|
||||||
|
className="text-sm text-blue-600 hover:underline inline-block"
|
||||||
>
|
>
|
||||||
{showPassword ? <FiEyeOff /> : <FiEye />}
|
{t("forgot_password")}
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button
|
||||||
<div className="mb-6 ">
|
type="submit"
|
||||||
<Link
|
disabled={loading}
|
||||||
to="#!"
|
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"
|
||||||
className="text-sm text-blue-600 hover:underline inline-block"
|
|
||||||
>
|
>
|
||||||
Forgot password?
|
{loading ? t("logging_in") : t("login")}
|
||||||
</Link>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<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">
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
<div className="text-center mt-6">
|
<div className="text-center mt-6">
|
||||||
<p className="text-gray-700">
|
<p className="text-gray-700">
|
||||||
Don’t have an account?{" "}
|
{t("dont_have_account")}{" "}
|
||||||
<Link
|
<Link
|
||||||
to="/signup"
|
to="/signup"
|
||||||
className="text-emerald-500 hover:underline font-medium"
|
className="text-emerald-500 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
Sign up
|
{t("sign_up")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
|
const { t } = useTranslation(); // for multilinguality
|
||||||
|
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(t("passwords_do_not_match"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const toastId = toast.loading(t("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 || t("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(t("failed_create_folder"), { id: toastId });
|
||||||
|
} else {
|
||||||
|
toast.success(t("signup_success"), { id: toastId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Redirect to login after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/login");
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(t("an_error_occurred"), { 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">{t("sign_up")}</h1>
|
||||||
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
{/* Form Fields */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="First Name"
|
name="firstname"
|
||||||
|
placeholder={t("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"
|
||||||
placeholder="Last Name"
|
name="lastname"
|
||||||
|
placeholder={t("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"
|
||||||
placeholder="Enter your email"
|
name="email"
|
||||||
|
placeholder={t("email_placeholder")}
|
||||||
|
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"}
|
||||||
placeholder="Enter your password"
|
name="password"
|
||||||
|
placeholder={t("password_placeholder")}
|
||||||
|
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,32 +143,44 @@ const SignUp = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type={showConfirmPassword ? "text" : "password"}
|
type={showConfirmPassword ? "text" : "password"}
|
||||||
placeholder="Confirm your password"
|
name="confirmPassword"
|
||||||
|
placeholder={t("confirm_password_placeholder")}
|
||||||
|
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"
|
||||||
</button>
|
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 ? t("signing_up") : t("sign_up")}
|
||||||
|
</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">
|
||||||
Already have an account?{" "}
|
{t("already_have_account")}{" "}
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to="/login"
|
||||||
className="text-blue-500 hover:underline font-medium"
|
className="text-blue-500 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
Login
|
{t("login")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -1,109 +1,88 @@
|
|||||||
import React from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
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 { t } = useTranslation(); // for multilinguality
|
||||||
|
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;
|
||||||
|
|
||||||
React.useEffect(() => {
|
const isUserLoggedIn = () => {
|
||||||
const fetchData = async () => {
|
const token = localStorage.getItem("token");
|
||||||
const response = await fetch(
|
const username = localStorage.getItem("username");
|
||||||
"http://192.168.29.61:8080/api/hdfs/listFiles?hdfsPath=/"
|
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();
|
const data = await response.json();
|
||||||
setFiles(data);
|
setFiles(data);
|
||||||
};
|
} catch (error) {
|
||||||
fetchData();
|
console.error("Failed to fetch files:", error);
|
||||||
}, []);
|
setError(t("failed_to_load_files"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUserLoggedIn()) {
|
||||||
|
navigate("/login");
|
||||||
|
} else {
|
||||||
|
fetchFiles();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [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">{t("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 ? (
|
||||||
<FileList files={files}></FileList>
|
<p className="text-red-500">{error}</p>
|
||||||
<section className="w-full flex justify-end items-center min-h-160">
|
) : (
|
||||||
{/* <!-- Modal toggle --> */}
|
<FileList files={files} />
|
||||||
</section>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FileUploadModal
|
||||||
|
show={isUploadModalOpen}
|
||||||
|
onClose={() => setIsUploadModalOpen(false)}
|
||||||
|
onUploadSuccess={() => {
|
||||||
|
fetchFiles();
|
||||||
|
setIsUploadModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import Footer from "../../components/Footer";
|
import Footer from "../../components/Footer";
|
||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
const DrivethruLandingPage = () => {
|
const DrivethruLandingPage = () => {
|
||||||
|
const { t } = useTranslation(); // for multilinguality
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
title: "Easy Upload & Access",
|
title: t("feature_easy_upload_title"),
|
||||||
description: "Drag & drop, instant access.",
|
description: t("feature_easy_upload_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -25,8 +27,8 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Secure & Private",
|
title: t("feature_secure_title"),
|
||||||
description: "End-to-end encryption.",
|
description: t("feature_secure_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -44,8 +46,8 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Seamless Sharing",
|
title: t("feature_sharing_title"),
|
||||||
description: "Share files with one click.",
|
description: t("feature_sharing_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -63,8 +65,8 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Access Anywhere",
|
title: t("feature_access_anywhere_title"),
|
||||||
description: "Works on all devices.",
|
description: t("feature_access_anywhere_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -85,8 +87,8 @@ const DrivethruLandingPage = () => {
|
|||||||
|
|
||||||
const howItWorks = [
|
const howItWorks = [
|
||||||
{
|
{
|
||||||
title: "Create an account",
|
title: t("how_create_account_title"),
|
||||||
description: "Sign up in seconds.",
|
description: t("how_create_account_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -104,8 +106,8 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Upload files",
|
title: t("how_upload_files_title"),
|
||||||
description: "Drag & drop or select from your device.",
|
description: t("how_upload_files_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -123,10 +125,10 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Manage files",
|
title: t("how_manage_files_title"),
|
||||||
description: "Rename, move, or delete easily.",
|
description: t("how_manage_files_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -148,8 +150,8 @@ const DrivethruLandingPage = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Access anytime",
|
title: t("how_access_anytime_title"),
|
||||||
description: "Open files from any device.",
|
description: t("how_access_anytime_desc"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -168,6 +170,7 @@ const DrivethruLandingPage = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// UseEffect and handle....click function to handle set and handle the animation of features..
|
// UseEffect and handle....click function to handle set and handle the animation of features..
|
||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
@@ -234,17 +237,16 @@ 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
|
{t("skycrate")}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-xl md:text-2xl font-bold mb-4 md:mb-6 text-black">
|
<h2 className="text-xl md:text-2xl font-bold mb-4 md:mb-6 text-black">
|
||||||
Store, Access & Share Your Files — Anytime, Anywhere!
|
{t("hero_subtitle")}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="text-gray-800 mb-6 md:mb-10 text-base md:text-lg">
|
<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
|
{t("hero_desc")}
|
||||||
files. Upload, organize, and access with ease.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Buttons */}
|
{/* Buttons */}
|
||||||
@@ -253,13 +255,13 @@ const DrivethruLandingPage = () => {
|
|||||||
to="/signup"
|
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"
|
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
|
{t("get_started")}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
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"
|
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
|
{t("login")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,7 +270,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>
|
||||||
@@ -283,7 +285,7 @@ const DrivethruLandingPage = () => {
|
|||||||
id="features"
|
id="features"
|
||||||
className="w-full max-w-5xl mx-auto p-6 sm:p-8 bg-gray-100 rounded-lg shadow-lg"
|
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>
|
<h2 className="text-3xl font-bold text-center mb-8">{t("key_features")}</h2>
|
||||||
<div className="flex flex-col-reverse md:flex-row items-center gap-8 lg:gap-12">
|
<div className="flex flex-col-reverse md:flex-row items-center gap-8 lg:gap-12">
|
||||||
{/* Left Side - Image */}
|
{/* Left Side - Image */}
|
||||||
<div className="w-full md:w-1/2 flex justify-center">
|
<div className="w-full md:w-1/2 flex justify-center">
|
||||||
@@ -328,7 +330,7 @@ const DrivethruLandingPage = () => {
|
|||||||
id="howItWorks"
|
id="howItWorks"
|
||||||
className="w-full max-w-5xl mx-auto p-6 sm:p-8 bg-gray-100 rounded-lg shadow-lg"
|
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>
|
<h2 className="text-3xl font-bold text-center mb-8">{t("how_it_works")}</h2>
|
||||||
<div className="flex flex-col md:flex-row items-center gap-8 lg:gap-12">
|
<div className="flex flex-col md:flex-row items-center gap-8 lg:gap-12">
|
||||||
{/* Left Side - Feature List */}
|
{/* Left Side - Feature List */}
|
||||||
<div className="w-full md:w-1/2">
|
<div className="w-full md:w-1/2">
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next"; // for multilinguality
|
||||||
|
|
||||||
const NotFoundPage = () => {
|
const NotFoundPage = () => {
|
||||||
|
const { t } = useTranslation(); // for multilinguality
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-screen bg-gray-100 p-4">
|
<div className="flex flex-col items-center justify-center h-screen bg-gray-100 p-4">
|
||||||
{/* Placeholder SVG - Replace this with your SVG */}
|
|
||||||
<img
|
<img
|
||||||
src="/404.png"
|
src="/404.png"
|
||||||
style={{ width: "30%", height: "auto" }}
|
style={{ width: "30%", height: "auto" }}
|
||||||
alt="404 Not Found"
|
alt="404 Not Found"
|
||||||
></img>
|
/>
|
||||||
{/* Page number and title */}
|
<h2 className="text-2xl font-bold mb-4 mt-4">{t("not_found_title")}</h2>
|
||||||
<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">
|
<p className="text-center text-gray-700 mb-6">
|
||||||
Sorry, we couldn't find the page you were looking for. It may have
|
{t("not_found_description")}
|
||||||
been moved or deleted.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Call-to-action button */}
|
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className="px-6 py-2 bg-[#1877F2] text-white rounded hover:bg-blue-600 transition duration-200"
|
className="px-6 py-2 bg-[#1877F2] text-white rounded hover:bg-blue-600 transition duration-200"
|
||||||
>
|
>
|
||||||
Go Home
|
{t("go_home")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,26 +1,54 @@
|
|||||||
# CC-MINI (2025)
|
# Skycrate
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This project is now multilingual. To contribute new languages, please read the [translation guide](./TRANSLATION.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Git config
|
## Versions
|
||||||
|
- Hadoop: 3.4.1
|
||||||
|
- Java: 17
|
||||||
|
- Node: 22.14.0
|
||||||
|
- NPM: 10.9.2
|
||||||
|
|
||||||
Create a new directory for this project, and run these following commands for initalizing git:
|
## How to run?
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> You must have [Docker](https://www.docker.com/products/docker-desktop/) and [Git](https://git-scm.com/) installed on your system.
|
||||||
|
|
||||||
|
1. Clone this repository:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/kshitij-ka/cc-mini.git
|
git clone https://git.kska.io/notkshitij/Skycrate.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?
|
2. Change into the directory:
|
||||||
|
|
||||||
- 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).
|
```shell
|
||||||
- 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/).
|
cd ./Skycrate
|
||||||
- I will be merging changes from both the branches in the main branch for deploying.
|
```
|
||||||
|
|
||||||
|
3. Create a `.env` file inside the directory containing the following variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
MYSQL_PASSWORD=<set-a-strong-password>
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Please choose a strong password, since it will be used for your MySQL database.
|
||||||
|
|
||||||
|
4. Execute the Docker Compose file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-compose -f docker-compose.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Use `-d` flag to run in detached mode.
|
||||||
|
|
||||||
|
5. Visit `localhost:8080` to enjoy using Skycrate!
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To stop and remove all the containers, run `docker compose down`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# TRANSLATION
|
||||||
|
|
||||||
|
This is a comprehensive guide for translation for those who wish to contribute in any language.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Add Your Language JSON File
|
||||||
|
|
||||||
|
- Go to the `Frontend/src/locales/` directory.
|
||||||
|
- Copy an existing language file (e.g., `en.json`) and rename it to your language code (e.g., `es.json` for Spanish, `de.json` for German).
|
||||||
|
- Translate all the key-value pairs in your new file.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cp Frontend/src/locales/en.json Frontend/src/locales/es.json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"skycrate": "Skycrate",
|
||||||
|
"hero_subtitle": "Store, Access & Share Your Files — Anytime, Anywhere!",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Register the Language in `Frontend/src/i18n.js`
|
||||||
|
|
||||||
|
- Open `Frontend/src/i18n.js`.
|
||||||
|
- Import your new JSON file:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import en from './locales/en.json';
|
||||||
|
import fr from './locales/fr.json';
|
||||||
|
// import more languages as needed
|
||||||
|
import es from './locales/es.json'; // <-- Add this line
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
en: { translation: en },
|
||||||
|
fr: { translation: fr },
|
||||||
|
// add other languages here
|
||||||
|
es: { translation: es }, // <-- Add this line
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Update the Language Switcher
|
||||||
|
|
||||||
|
- Open `Frontend/src/components/LanguageSwitcher.jsx`.
|
||||||
|
- Add your language to the `languages` array:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
const languages = [
|
||||||
|
{ code: 'en', label: 'English' },
|
||||||
|
{ code: 'fr', label: 'Français' },
|
||||||
|
// Add more languages as needed
|
||||||
|
{ code: 'es', label: 'Spanish' }, // <-- Add this line
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Test Your Translation
|
||||||
|
|
||||||
|
- Start the app.
|
||||||
|
- Use the language switcher to select your new language.
|
||||||
|
- Check all pages for missing or untranslated keys.
|
||||||
|
- If you see a key instead of a translation, add it to your JSON file.
|
||||||
|
|
||||||
|
## 5. Submit Your Contribution
|
||||||
|
|
||||||
|
- Double-check your translations for accuracy and completeness.
|
||||||
|
- Commit your changes to:
|
||||||
|
- `Frontend/src/locales/<your_language>.json`
|
||||||
|
- `Frontend/src/i18n.js`
|
||||||
|
- `Frontend/src/components/LanguageSwitcher.jsx`
|
||||||
|
- Open a pull request with a description of your contribution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Thank you for making Skycrate accessible to more people!
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
services:
|
||||||
|
namenode:
|
||||||
|
image: kshitijka/hadoop-namenode:3.4.1
|
||||||
|
container_name: skycrate-hadoop-namenode
|
||||||
|
restart: on-failure:5
|
||||||
|
ports:
|
||||||
|
- "9870:9870" # Web UI
|
||||||
|
#- "9000:9000" # Hadoop; No need to expose since backend will access internally
|
||||||
|
user: "hdoop:hdoop"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
volumes:
|
||||||
|
- skycrate-hadoop_namenode:/hadoop/dfs/name
|
||||||
|
environment:
|
||||||
|
- CLUSTER_NAME=skycreate
|
||||||
|
env_file:
|
||||||
|
- ./hadoop.env
|
||||||
|
|
||||||
|
datanode:
|
||||||
|
image: kshitijka/hadoop-datanode:3.4.1
|
||||||
|
container_name: skycrate-hadoop-datanode-1
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "hdoop:hdoop"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
volumes:
|
||||||
|
- skycrate-hadoop_datanode:/hadoop/dfs/data
|
||||||
|
environment:
|
||||||
|
SERVICE_PRECONDITION: "namenode:9870"
|
||||||
|
env_file:
|
||||||
|
- ./hadoop.env
|
||||||
|
# healthcheck:
|
||||||
|
# disable: true
|
||||||
|
|
||||||
|
resourcemanager:
|
||||||
|
image: kshitijka/hadoop-resourcemanager:3.4.1
|
||||||
|
container_name: skycrate-hadoop-resourcemanager
|
||||||
|
restart: on-failure:10
|
||||||
|
user: "hdoop:hdoop"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
environment:
|
||||||
|
SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode:9864"
|
||||||
|
env_file:
|
||||||
|
- ./hadoop.env
|
||||||
|
# healthcheck:
|
||||||
|
# disable: true
|
||||||
|
|
||||||
|
nodemanager:
|
||||||
|
image: kshitijka/hadoop-nodemanager:3.4.1
|
||||||
|
container_name: skycrate-hadoop-nodemanager
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "hdoop:hdoop"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
environment:
|
||||||
|
SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode:9864 resourcemanager:8088"
|
||||||
|
env_file:
|
||||||
|
- ./hadoop.env
|
||||||
|
# healthcheck:
|
||||||
|
# disable: true
|
||||||
|
|
||||||
|
historyserver:
|
||||||
|
image: kshitijka/hadoop-historyserver:3.4.1
|
||||||
|
container_name: skycrate-hadoop-historyserver
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "hdoop:hdoop"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
environment:
|
||||||
|
SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode:9864 resourcemanager:8088"
|
||||||
|
volumes:
|
||||||
|
- skycrate-hadoop_historyserver:/hadoop/yarn/timeline
|
||||||
|
env_file:
|
||||||
|
- ./hadoop.env
|
||||||
|
# healthcheck:
|
||||||
|
# disable: true
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mysql:8
|
||||||
|
container_name: skycrate-db
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "1000:1000"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
environment:
|
||||||
|
- MYSQL_DATABASE=skycrate
|
||||||
|
- MYSQL_USER=skycrateDB
|
||||||
|
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||||
|
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||||
|
volumes:
|
||||||
|
- skycrate-db:/var/lib/mysql
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: kshitijka/skycrate-frontend:1.0
|
||||||
|
container_name: skycrate-frontend
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "skycrateFront:skycrateFront"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
ports:
|
||||||
|
- "80:8080"
|
||||||
|
volumes:
|
||||||
|
- skycrate-frontend:/app
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
backend:
|
||||||
|
image: kshitijka/skycrate-backend:1.0
|
||||||
|
container_name: skycrate-backend
|
||||||
|
restart: on-failure:5
|
||||||
|
user: "skycrateBack:skycrateBack"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- skycrate-internal
|
||||||
|
ports:
|
||||||
|
- "8081:8081" # If you change, update in Frontend/.env file too and rebuild the image
|
||||||
|
environment:
|
||||||
|
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- skycrate-backend:/app
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
skycrate-hadoop_namenode:
|
||||||
|
skycrate-hadoop_datanode:
|
||||||
|
skycrate-hadoop_historyserver:
|
||||||
|
skycrate-db:
|
||||||
|
skycrate-frontend:
|
||||||
|
skycrate-backend:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
skycrate-internal:
|
||||||
|
external: false
|
||||||
|
driver: bridge
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
CORE_CONF_fs_defaultFS=hdfs://namenode:9000
|
||||||
|
CORE_CONF_hadoop_http_staticuser_user=root
|
||||||
|
CORE_CONF_hadoop_proxyuser_hue_hosts=*
|
||||||
|
CORE_CONF_hadoop_proxyuser_hue_groups=*
|
||||||
|
CORE_CONF_io_compression_codecs=org.apache.hadoop.io.compress.SnappyCodec
|
||||||
|
|
||||||
|
HDFS_CONF_dfs_webhdfs_enabled=true
|
||||||
|
HDFS_CONF_dfs_permissions_enabled=false
|
||||||
|
HDFS_CONF_dfs_namenode_datanode_registration_ip___hostname___check=false
|
||||||
|
|
||||||
|
YARN_CONF_yarn_log___aggregation___enable=true
|
||||||
|
YARN_CONF_yarn_log_server_url=http://historyserver:8188/applicationhistory/logs/
|
||||||
|
YARN_CONF_yarn_resourcemanager_recovery_enabled=true
|
||||||
|
YARN_CONF_yarn_resourcemanager_store_class=org.apache.hadoop.yarn.server.resourcemanager.recovery.FileSystemRMStateStore
|
||||||
|
YARN_CONF_yarn_resourcemanager_scheduler_class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler
|
||||||
|
YARN_CONF_yarn_scheduler_capacity_root_default_maximum___allocation___mb=8192
|
||||||
|
YARN_CONF_yarn_scheduler_capacity_root_default_maximum___allocation___vcores=4
|
||||||
|
YARN_CONF_yarn_resourcemanager_fs_state___store_uri=/rmstate
|
||||||
|
YARN_CONF_yarn_resourcemanager_system___metrics___publisher_enabled=true
|
||||||
|
YARN_CONF_yarn_resourcemanager_hostname=resourcemanager
|
||||||
|
YARN_CONF_yarn_resourcemanager_address=resourcemanager:8032
|
||||||
|
YARN_CONF_yarn_resourcemanager_scheduler_address=resourcemanager:8030
|
||||||
|
YARN_CONF_yarn_resourcemanager_resource__tracker_address=resourcemanager:8031
|
||||||
|
YARN_CONF_yarn_timeline___service_enabled=true
|
||||||
|
YARN_CONF_yarn_timeline___service_generic___application___history_enabled=true
|
||||||
|
YARN_CONF_yarn_timeline___service_hostname=historyserver
|
||||||
|
YARN_CONF_mapreduce_map_output_compress=true
|
||||||
|
YARN_CONF_mapred_map_output_compress_codec=org.apache.hadoop.io.compress.SnappyCodec
|
||||||
|
YARN_CONF_yarn_nodemanager_resource_memory___mb=16384
|
||||||
|
YARN_CONF_yarn_nodemanager_resource_cpu___vcores=8
|
||||||
|
YARN_CONF_yarn_nodemanager_disk___health___checker_max___disk___utilization___per___disk___percentage=98.5
|
||||||
|
YARN_CONF_yarn_nodemanager_remote___app___log___dir=/app-logs
|
||||||
|
YARN_CONF_yarn_nodemanager_aux___services=mapreduce_shuffle
|
||||||
|
|
||||||
|
MAPRED_CONF_mapreduce_framework_name=yarn
|
||||||
|
MAPRED_CONF_mapred_child_java_opts=-Xmx4096m
|
||||||
|
MAPRED_CONF_mapreduce_map_memory_mb=4096
|
||||||
|
MAPRED_CONF_mapreduce_reduce_memory_mb=8192
|
||||||
|
MAPRED_CONF_mapreduce_map_java_opts=-Xmx3072m
|
||||||
|
MAPRED_CONF_mapreduce_reduce_java_opts=-Xmx6144m
|
||||||
|
MAPRED_CONF_yarn_app_mapreduce_am_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.4.1/
|
||||||
|
MAPRED_CONF_mapreduce_map_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.4.1/
|
||||||
|
MAPRED_CONF_mapreduce_reduce_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.4.1/
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cc-mini",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user