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"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <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" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head> </head>
<body class="dark:bg-gray-900"> <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/core": "^1.0.4",
"@react-jvectormap/world": "^1.1.2", "@react-jvectormap/world": "^1.1.2",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.8.4",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"react": "^19.0.0", "react": "^19.0.0",
@ -2376,6 +2377,12 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0" "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": { "node_modules/attr-accept": {
"version": "2.2.5", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
@ -2385,6 +2392,17 @@
"node": ">=4" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "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": "^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": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -2541,6 +2572,18 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -2635,6 +2678,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/detect-libc": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@ -2666,6 +2718,20 @@
"tslib": "^2.0.3" "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": { "node_modules/electron-to-chromium": {
"version": "1.5.136", "version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
@ -2707,6 +2773,51 @@
"is-arrayish": "^0.2.1" "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": { "node_modules/esbuild": {
"version": "0.25.2", "version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
@ -3095,6 +3206,41 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "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": "^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": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -3118,6 +3273,43 @@
"node": ">=6.9.0" "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": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -3144,6 +3336,18 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -3168,6 +3372,45 @@
"node": ">=8" "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": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "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" "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": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3689,6 +3941,27 @@
"node": ">=8.6" "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": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -3945,6 +4218,12 @@
"react-is": "^16.13.1" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "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/core": "^1.0.4",
"@react-jvectormap/world": "^1.1.2", "@react-jvectormap/world": "^1.1.2",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.8.4",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"react": "^19.0.0", "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 React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { API_BASE_URL } from '../../config/apiConfig';
interface LoginProps { const Login: React.FC = () => {
onLogin: (token: string) => void; // Callback to handle successful login const [email, setEmail] = useState('');
}
const Login: React.FC<LoginProps> = ({ onLogin }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { login } = useAuth();
// Hardcoded credentials const handleSubmit = async (e: React.FormEvent) => {
const hardcodedUsername = 'admin';
const hardcodedPassword = 'password123';
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
setIsLoading(true);
// Validate hardcoded credentials try {
if (username === hardcodedUsername && password === hardcodedPassword) { // Send credentials as JSON
const token = 'hardcoded-token'; // Generate a mock token const response = await fetch(`${API_BASE_URL}/api/login.php`, {
onLogin(token); // Pass the token to the parent component 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 { } 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 ( return (
<div className="max-w-md mx-auto mt-10 p-6 bg-white rounded shadow"> <div className="min-h-screen flex items-center justify-center bg-gray-100 px-4">
<h2 className="text-2xl font-bold mb-4">Login</h2> <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>} {error && <p className="text-red-500 mb-4">{error}</p>}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="mb-4"> <div className="mb-4">
<label className="block text-gray-700">Username</label> <label className="block text-gray-700">Email</label>
<input <input
type="text" type="email"
value={username} value={email}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded" className="w-full px-3 py-2 border rounded"
required required
disabled={isLoading}
/> />
</div> </div>
<div className="mb-4"> <div className="mb-4">
@ -49,16 +80,19 @@ const Login: React.FC<LoginProps> = ({ onLogin }) => {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded" className="w-full px-3 py-2 border rounded"
required required
disabled={isLoading}
/> />
</div> </div>
<button <button
type="submit" 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> </button>
</form> </form>
</div> </div>
</div>
); );
}; };

View File

@ -24,7 +24,7 @@ export default function SignInForm() {
<div> <div>
<div className="mb-5 sm:mb-8"> <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"> <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> </h1>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Enter your email and password to sign in! Enter your email and password to sign in!

View File

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

View File

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

View File

@ -1,10 +1,11 @@
import { useState } from "react"; import { useState } from "react";
import { DropdownItem } from "../ui/dropdown/DropdownItem"; import { DropdownItem } from "../ui/dropdown/DropdownItem";
import { Dropdown } from "../ui/dropdown/Dropdown"; import { Dropdown } from "../ui/dropdown/Dropdown";
import { Link } from "react-router"; import { useAuth } from "../../context/AuthContext";
export default function UserDropdown() { export default function UserDropdown() {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { logout } = useAuth();
function toggleDropdown() { function toggleDropdown() {
setIsOpen(!isOpen); setIsOpen(!isOpen);
@ -13,6 +14,12 @@ export default function UserDropdown() {
function closeDropdown() { function closeDropdown() {
setIsOpen(false); setIsOpen(false);
} }
function handleSignOut() {
logout();
window.location.href = "/"; // Redirect to the login page
}
return ( return (
<div className="relative"> <div className="relative">
<button <button
@ -135,8 +142,8 @@ export default function UserDropdown() {
</DropdownItem> </DropdownItem>
</li> </li>
</ul> </ul>
<Link <button
to="/signin" 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" 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 <svg
@ -155,7 +162,7 @@ export default function UserDropdown() {
/> />
</svg> </svg>
Sign out Sign out
</Link> </button>
</Dropdown> </Dropdown>
</div> </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} onClick={handleToggle}
aria-label="Toggle Sidebar" aria-label="Toggle Sidebar"
> >
{isMobileOpen ? (
<svg <svg
className="dark:fill-gray-400"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -64,17 +64,34 @@ const AppHeader: React.FC = () => {
fill="currentColor" fill="currentColor"
/> />
</svg> </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> </button>
<Link to="/" className="lg:hidden"> <Link to="/" className="lg:hidden">
<img <img
className="dark:hidden" className="dark:hidden"
src="./images/logo/logo.svg" src="/images/logo.webp"
alt="Logo" alt="Logo"
/> />
<img <img
className="hidden dark:block" className="hidden dark:block"
src="./images/logo/logo-dark.svg" src="/images/logo.webp"
alt="Logo" alt="Logo"
/> />
</Link> </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" 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 <svg
className="dark:fill-gray-400"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@ -1,8 +1,10 @@
import { SidebarProvider, useSidebar } from "../context/SidebarContext"; import { SidebarProvider, useSidebar } from "../context/SidebarContext";
import { useAuth } from "../context/AuthContext";
import { Outlet } from "react-router"; import { Outlet } from "react-router";
import AppHeader from "./AppHeader"; import AppHeader from "./AppHeader";
import Backdrop from "./Backdrop"; import Backdrop from "./Backdrop";
import AppSidebar from "./AppSidebar"; import AppSidebar from "./AppSidebar";
import Login from "../components/auth/Login";
const LayoutContent: React.FC = () => { const LayoutContent: React.FC = () => {
const { isExpanded, isHovered, isMobileOpen } = useSidebar(); const { isExpanded, isHovered, isMobileOpen } = useSidebar();
@ -19,7 +21,7 @@ const LayoutContent: React.FC = () => {
} ${isMobileOpen ? "ml-0" : ""}`} } ${isMobileOpen ? "ml-0" : ""}`}
> >
<AppHeader /> <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 /> <Outlet />
</div> </div>
</div> </div>
@ -28,6 +30,12 @@ const LayoutContent: React.FC = () => {
}; };
const AppLayout: React.FC = () => { const AppLayout: React.FC = () => {
const { token } = useAuth();
if (!token) {
return <Login />;
}
return ( return (
<SidebarProvider> <SidebarProvider>
<LayoutContent /> <LayoutContent />

View File

@ -81,14 +81,6 @@ const othersItems: NavItem[] = [
{ name: "Videos", path: "/videos", pro: false }, { 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 = () => { const AppSidebar: React.FC = () => {
@ -307,14 +299,14 @@ const AppSidebar: React.FC = () => {
<> <>
<img <img
className="dark:hidden" className="dark:hidden"
src="/images/logo/logo.svg" src="/images/logo.webp"
alt="Logo" alt="RocketDeploy"
width={150} width={150}
height={40} height={40}
/> />
<img <img
className="hidden dark:block" className="hidden dark:block"
src="/images/logo/logo-dark.svg" src="/images/logo_dark.webp"
alt="Logo" alt="Logo"
width={150} width={150}
height={40} height={40}
@ -322,7 +314,7 @@ const AppSidebar: React.FC = () => {
</> </>
) : ( ) : (
<img <img
src="/images/logo/logo-icon.svg" src="/images/logo_icon.webp"
alt="Logo" alt="Logo"
width={32} width={32}
height={32} height={32}
@ -335,7 +327,7 @@ const AppSidebar: React.FC = () => {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div> <div>
<h2 <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 !isExpanded && !isHovered
? "lg:justify-center" ? "lg:justify-center"
: "justify-start" : "justify-start"
@ -351,7 +343,7 @@ const AppSidebar: React.FC = () => {
</div> </div>
<div className=""> <div className="">
<h2 <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 !isExpanded && !isHovered
? "lg:justify-center" ? "lg:justify-center"
: "justify-start" : "justify-start"

View File

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