updated login

This commit is contained in:
2025-04-13 13:49:15 +02:00
parent 2c06d64417
commit 1ea3e77fa8
26 changed files with 549 additions and 95 deletions

View File

@ -2,7 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/favicon/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body class="dark:bg-gray-900">

279
package-lock.json generated
View File

@ -17,6 +17,7 @@
"@react-jvectormap/core": "^1.0.4",
"@react-jvectormap/world": "^1.1.2",
"apexcharts": "^4.5.0",
"axios": "^1.8.4",
"clsx": "^2.1.1",
"flatpickr": "^4.6.13",
"react": "^19.0.0",
@ -2376,6 +2377,12 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/attr-accept": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
@ -2385,6 +2392,17 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2448,6 +2466,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -2541,6 +2572,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -2635,6 +2678,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@ -2666,6 +2718,20 @@
"tslib": "^2.0.3"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
@ -2707,6 +2773,51 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
@ -3095,6 +3206,41 @@
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -3109,6 +3255,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -3118,6 +3273,43 @@
"node": ">=6.9.0"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -3144,6 +3336,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -3168,6 +3372,45 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -3665,6 +3908,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3689,6 +3941,27 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -3945,6 +4218,12 @@
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -19,6 +19,7 @@
"@react-jvectormap/core": "^1.0.4",
"@react-jvectormap/world": "^1.1.2",
"apexcharts": "^4.5.0",
"axios": "^1.8.4",
"clsx": "^2.1.1",
"flatpickr": "^4.6.13",
"react": "^19.0.0",

45
public/api/login.php Normal file
View File

@ -0,0 +1,45 @@
<?php
// login.php
header('Content-Type: application/json');
// Enable CORS
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
// Simulated database of users
$users = [
'user1@example.com' => 'password123',
'user2@example.com' => 'securepassword',
];
// Read input data
$input = json_decode(file_get_contents('php://input'), true);
$email = $input['email'] ?? '';
$password = $input['password'] ?? '';
// Validate input
if (empty($email) || empty($password)) {
http_response_code(400);
echo json_encode(['error' => 'Email and password are required.']);
exit;
}
// Authenticate user
if (isset($users[$email]) && $users[$email] === $password) {
// Generate a token (for simplicity, using base64 encoding)
$token = base64_encode($email . ':' . time());
echo json_encode(['token' => $token]);
} else {
http_response_code(401);
echo json_encode(['error' => 'Invalid email or password.']);
}
?>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
public/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -0,0 +1,21 @@
{
"name": "MyWebSite",
"short_name": "MySite",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
public/images/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,44 +1,75 @@
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { API_BASE_URL } from '../../config/apiConfig';
interface LoginProps {
onLogin: (token: string) => void; // Callback to handle successful login
}
const Login: React.FC<LoginProps> = ({ onLogin }) => {
const [username, setUsername] = useState('');
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { login } = useAuth();
// Hardcoded credentials
const hardcodedUsername = 'admin';
const hardcodedPassword = 'password123';
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
// Validate hardcoded credentials
if (username === hardcodedUsername && password === hardcodedPassword) {
const token = 'hardcoded-token'; // Generate a mock token
onLogin(token); // Pass the token to the parent component
try {
// Send credentials as JSON
const response = await fetch(`${API_BASE_URL}/api/login.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (response.ok) {
// If response is successful, we should have a token
if (data.token) {
login(data.token);
} else {
setError('Invalid username or password');
setError('Invalid server response');
}
} else {
// Handle error responses
setError(data.error || 'Authentication failed');
}
} catch (err) {
setError('Failed to connect to the server. Please try again.');
console.error('Login error:', err);
} finally {
setIsLoading(false);
}
};
return (
<div className="max-w-md mx-auto mt-10 p-6 bg-white rounded shadow">
<h2 className="text-2xl font-bold mb-4">Login</h2>
<div className="min-h-screen flex items-center justify-center bg-gray-100 px-4">
<div className="max-w-md w-full mx-auto p-6 bg-white rounded shadow">
<div className="flex justify-center mb-6">
<img
src="/images/logo.webp"
alt="Logo"
className="h-16"
/>
</div>
<h2 className="text-2xl font-bold mb-4 text-center">Login</h2>
{error && <p className="text-red-500 mb-4">{error}</p>}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700">Username</label>
<label className="block text-gray-700">Email</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
disabled={isLoading}
/>
</div>
<div className="mb-4">
@ -49,16 +80,19 @@ const Login: React.FC<LoginProps> = ({ onLogin }) => {
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
disabled={isLoading}
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700"
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:bg-blue-400"
disabled={isLoading}
>
Login
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
</div>
</div>
);
};

View File

@ -24,7 +24,7 @@ export default function SignInForm() {
<div>
<div className="mb-5 sm:mb-8">
<h1 className="mb-2 font-semibold text-gray-800 text-title-sm dark:text-white/90 sm:text-title-md">
Sign In
Sign In2
</h1>
<p className="text-sm text-gray-500 dark:text-gray-400">
Enter your email and password to sign in!

View File

@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { API_BASE_URL } from '../../config/apiConfig';
interface DeployListProps {
setLog: React.Dispatch<React.SetStateAction<string>>;
@ -48,7 +49,7 @@ const DeployList: React.FC<DeployListProps> = ({ setLog, token }) => {
setStatus((prev) => ({ ...prev, [project]: 'Migrating...' }));
setLog(''); // Clear the log before starting
try {
const response = await fetch('https://deploy.projekti.info/api/migrate.php', {
const response = await fetch(`${API_BASE_URL}/api/migrate.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -78,7 +79,7 @@ const DeployList: React.FC<DeployListProps> = ({ setLog, token }) => {
setStatus((prev) => ({ ...prev, [project]: 'Deploying...' }));
setLog(''); // Clear the log before starting
try {
const response = await fetch('https://deploy.projekti.info/api/deploy.php', {
const response = await fetch(`${API_BASE_URL}/api/deploy.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -113,12 +114,13 @@ const DeployList: React.FC<DeployListProps> = ({ setLog, token }) => {
if (!window.confirm(`Are you sure you want to back up ${type} for ${project}?`)) {
return; // Exit if user cancels
}
setDropdownOpen(prev => ({ ...prev, [project]: false }));
setStatus(prev => ({ ...prev, [project]: `Backing up ${type}...` }));
setLog(''); // Clear the log before starting
try {
const response = await fetch('https://deploy.projekti.info/api/backup.php', {
const response = await fetch(`${API_BASE_URL}/api/backup.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -81,12 +81,12 @@ const Header: React.FC<HeaderProps> = ({ onClick, onToggle }) => {
<Link to="/" className="lg:hidden">
<img
className="dark:hidden"
src="./images/logo/logo.svg"
src="/images/logo.webp"
alt="Logo"
/>
<img
className="hidden dark:block"
src="./images/logo/logo-dark.svg"
src="/images/logo.webp"
alt="Logo"
/>
</Link>

View File

@ -1,10 +1,11 @@
import { useState } from "react";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
import { Dropdown } from "../ui/dropdown/Dropdown";
import { Link } from "react-router";
import { useAuth } from "../../context/AuthContext";
export default function UserDropdown() {
const [isOpen, setIsOpen] = useState(false);
const { logout } = useAuth();
function toggleDropdown() {
setIsOpen(!isOpen);
@ -13,6 +14,12 @@ export default function UserDropdown() {
function closeDropdown() {
setIsOpen(false);
}
function handleSignOut() {
logout();
window.location.href = "/"; // Redirect to the login page
}
return (
<div className="relative">
<button
@ -135,8 +142,8 @@ export default function UserDropdown() {
</DropdownItem>
</li>
</ul>
<Link
to="/signin"
<button
onClick={handleSignOut}
className="flex items-center gap-3 px-3 py-2 mt-3 font-medium text-gray-700 rounded-lg group text-theme-sm hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
<svg
@ -155,7 +162,7 @@ export default function UserDropdown() {
/>
</svg>
Sign out
</Link>
</button>
</Dropdown>
</div>
);

2
src/config/apiConfig.ts Normal file
View File

@ -0,0 +1,2 @@
// Base URL configuration for API endpoints
export const API_BASE_URL = 'https://deploy.projekti.info';

View File

@ -0,0 +1,37 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextType {
token: string | null;
login: (token: string) => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
const login = (newToken: string) => {
setToken(newToken);
localStorage.setItem('token', newToken);
};
const logout = () => {
setToken(null);
localStorage.removeItem('token');
};
return (
<AuthContext.Provider value={{ token, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@ -49,8 +49,8 @@ const AppHeader: React.FC = () => {
onClick={handleToggle}
aria-label="Toggle Sidebar"
>
{isMobileOpen ? (
<svg
className="dark:fill-gray-400"
width="24"
height="24"
viewBox="0 0 24 24"
@ -64,17 +64,34 @@ const AppHeader: React.FC = () => {
fill="currentColor"
/>
</svg>
) : (
<svg
width="16"
height="12"
viewBox="0 0 16 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.583252 1C0.583252 0.585788 0.919038 0.25 1.33325 0.25H14.6666C15.0808 0.25 15.4166 0.585786 15.4166 1C15.4166 1.41421 15.0808 1.75 14.6666 1.75L1.33325 1.75C0.919038 1.75 0.583252 1.41422 0.583252 1ZM0.583252 11C0.583252 10.5858 0.919038 10.25 1.33325 10.25L14.6666 10.25C15.0808 10.25 15.4166 10.5858 15.4166 11C15.4166 11.4142 15.0808 11.75 14.6666 11.75L1.33325 11.75C0.919038 11.75 0.583252 11.4142 0.583252 11ZM1.33325 5.25C0.919038 5.25 0.583252 5.58579 0.583252 6C0.583252 6.41421 0.919038 6.75 1.33325 6.75L7.99992 6.75C8.41413 6.75 8.74992 6.41421 8.74992 6C8.74992 5.58579 8.41413 5.25 7.99992 5.25L1.33325 5.25Z"
fill="currentColor"
/>
</svg>
)}
{/* Cross Icon */}
</button>
<Link to="/" className="lg:hidden">
<img
className="dark:hidden"
src="./images/logo/logo.svg"
src="/images/logo.webp"
alt="Logo"
/>
<img
className="hidden dark:block"
src="./images/logo/logo-dark.svg"
src="/images/logo.webp"
alt="Logo"
/>
</Link>
@ -84,7 +101,6 @@ const AppHeader: React.FC = () => {
className="flex items-center justify-center w-10 h-10 text-gray-700 rounded-lg z-99999 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 lg:hidden"
>
<svg
className="dark:fill-gray-400"
width="24"
height="24"
viewBox="0 0 24 24"

View File

@ -1,8 +1,10 @@
import { SidebarProvider, useSidebar } from "../context/SidebarContext";
import { useAuth } from "../context/AuthContext";
import { Outlet } from "react-router";
import AppHeader from "./AppHeader";
import Backdrop from "./Backdrop";
import AppSidebar from "./AppSidebar";
import Login from "../components/auth/Login";
const LayoutContent: React.FC = () => {
const { isExpanded, isHovered, isMobileOpen } = useSidebar();
@ -19,7 +21,7 @@ const LayoutContent: React.FC = () => {
} ${isMobileOpen ? "ml-0" : ""}`}
>
<AppHeader />
<div className="p-4 mx-auto max-w-(--breakpoint-2xl) md:p-6 dark:bg-gray-900 dark:text-white">
<div className="p-4 mx-auto max-w-(--breakpoint-2xl) md:p-6">
<Outlet />
</div>
</div>
@ -28,6 +30,12 @@ const LayoutContent: React.FC = () => {
};
const AppLayout: React.FC = () => {
const { token } = useAuth();
if (!token) {
return <Login />;
}
return (
<SidebarProvider>
<LayoutContent />

View File

@ -81,14 +81,6 @@ const othersItems: NavItem[] = [
{ name: "Videos", path: "/videos", pro: false },
],
},
{
icon: <PlugInIcon />,
name: "Authentication",
subItems: [
{ name: "Sign In", path: "/signin", pro: false },
{ name: "Sign Up", path: "/signup", pro: false },
],
},
];
const AppSidebar: React.FC = () => {
@ -307,14 +299,14 @@ const AppSidebar: React.FC = () => {
<>
<img
className="dark:hidden"
src="/images/logo/logo.svg"
alt="Logo"
src="/images/logo.webp"
alt="RocketDeploy"
width={150}
height={40}
/>
<img
className="hidden dark:block"
src="/images/logo/logo-dark.svg"
src="/images/logo_dark.webp"
alt="Logo"
width={150}
height={40}
@ -322,7 +314,7 @@ const AppSidebar: React.FC = () => {
</>
) : (
<img
src="/images/logo/logo-icon.svg"
src="/images/logo_icon.webp"
alt="Logo"
width={32}
height={32}
@ -335,7 +327,7 @@ const AppSidebar: React.FC = () => {
<div className="flex flex-col gap-4">
<div>
<h2
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 dark:text-gray-500 ${
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${
!isExpanded && !isHovered
? "lg:justify-center"
: "justify-start"
@ -351,7 +343,7 @@ const AppSidebar: React.FC = () => {
</div>
<div className="">
<h2
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 dark:text-gray-500 ${
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${
!isExpanded && !isHovered
? "lg:justify-center"
: "justify-start"

View File

@ -6,13 +6,16 @@ import "flatpickr/dist/flatpickr.css";
import App from "./App.tsx";
import { AppWrapper } from "./components/common/PageMeta.tsx";
import { ThemeProvider } from "./context/ThemeContext.tsx";
import { AuthProvider } from "./context/AuthContext";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<AuthProvider>
<ThemeProvider>
<AppWrapper>
<App />
</AppWrapper>
</ThemeProvider>
</AuthProvider>
</StrictMode>,
);