I cba with this commit message tbh
This commit is contained in:
@@ -3780,7 +3780,7 @@ form select.error:focus {
|
||||
|
||||
.btn-inverse-success {
|
||||
|
||||
color: #15ca20;
|
||||
color: #1aad24;
|
||||
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
|
||||
@@ -3792,7 +3792,7 @@ form select.error:focus {
|
||||
|
||||
.btn-inverse-success:hover {
|
||||
|
||||
color: #15ca20;
|
||||
color: #1aad24;
|
||||
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
|
||||
@@ -3802,7 +3802,7 @@ form select.error:focus {
|
||||
|
||||
.btn-inverse-success:focus {
|
||||
|
||||
color: #15ca20;
|
||||
color: #1aad24;
|
||||
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
|
||||
|
||||
50
assets/css/bootstrap-extended.css
vendored
50
assets/css/bootstrap-extended.css
vendored
@@ -23,33 +23,33 @@ a {
|
||||
}
|
||||
|
||||
.valid-feedback {
|
||||
color: #15ca20
|
||||
color: #23ac2c
|
||||
}
|
||||
|
||||
|
||||
.form-control.is-valid, .was-validated .form-control:valid {
|
||||
border-color: #15ca20;
|
||||
border-color: #23ac2c;
|
||||
}
|
||||
|
||||
.form-control.is-valid:focus, .was-validated .form-control:valid:focus {
|
||||
border-color: #15ca20;
|
||||
border-color: #23ac2c;
|
||||
}
|
||||
|
||||
.form-select.is-valid, .was-validated .form-select:valid {
|
||||
border-color: #15ca20;
|
||||
border-color: #23ac2c;
|
||||
}
|
||||
|
||||
.form-select.is-valid:focus, .was-validated .form-select:valid:focus {
|
||||
border-color: #15ca20;
|
||||
border-color: #23ac2c;
|
||||
}
|
||||
.form-check-input.is-valid, .was-validated .form-check-input:valid {
|
||||
border-color: #15ca20
|
||||
border-color: #23ac2c
|
||||
}
|
||||
.form-check-input.is-valid:checked, .was-validated .form-check-input:valid:checked {
|
||||
background-color: #15ca20
|
||||
background-color: #23ac2c
|
||||
}
|
||||
.form-check-input.is-valid~.form-check-label, .was-validated .form-check-input:valid~.form-check-label {
|
||||
color: #15ca20
|
||||
color: #23ac2c
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
@@ -120,8 +120,8 @@ a {
|
||||
|
||||
.btn-success {
|
||||
color: #fff;
|
||||
background-color: #15ca20;
|
||||
border-color: #15ca20
|
||||
background-color: #2375ac;
|
||||
border-color: #23ac2c
|
||||
}
|
||||
.btn-success:hover {
|
||||
color: #fff;
|
||||
@@ -207,27 +207,27 @@ a {
|
||||
|
||||
|
||||
.btn-outline-success {
|
||||
color: #15ca20;
|
||||
border-color: #15ca20
|
||||
color: #23ac2c;
|
||||
border-color: #23ac2c
|
||||
}
|
||||
.btn-outline-success:hover {
|
||||
color: #fff;
|
||||
background-color: #15ca20;
|
||||
border-color: #15ca20
|
||||
background-color: #23ac2c;
|
||||
border-color: #23ac2c
|
||||
}
|
||||
.btn-check:focus+.btn-outline-success, .btn-outline-success:focus {
|
||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
||||
}
|
||||
.btn-check:active+.btn-outline-success, .btn-check:checked+.btn-outline-success, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show, .btn-outline-success:active {
|
||||
color: #fff;
|
||||
background-color: #15ca20;
|
||||
border-color: #15ca20
|
||||
background-color: #23ac2c;
|
||||
border-color: #23ac2c
|
||||
}
|
||||
.btn-check:active+.btn-outline-success:focus, .btn-check:checked+.btn-outline-success:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus, .btn-outline-success:active:focus {
|
||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
||||
}
|
||||
.btn-outline-success.disabled, .btn-outline-success:disabled {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: transparent
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ a {
|
||||
}
|
||||
|
||||
.border-success {
|
||||
border-color: #15ca20!important
|
||||
border-color: #23ac2c!important
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
@@ -324,7 +324,7 @@ a {
|
||||
color: #6c757d!important
|
||||
}
|
||||
.text-success {
|
||||
color: #15ca20!important
|
||||
color: #23ac2c!important
|
||||
}
|
||||
.text-info {
|
||||
color: #0dcaf0!important
|
||||
@@ -364,7 +364,7 @@ a {
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: #15ca20 !important;
|
||||
background-color: #23ac2c !important;
|
||||
}
|
||||
|
||||
.bg-danger {
|
||||
@@ -373,19 +373,19 @@ a {
|
||||
|
||||
|
||||
.form-check-success .form-check-input:checked {
|
||||
background-color: #15ca20;
|
||||
border-color: #15ca20
|
||||
background-color: #23ac2c;
|
||||
border-color: #23ac2c
|
||||
}
|
||||
|
||||
.form-check-success .form-check-input[type=checkbox]:indeterminate {
|
||||
background-color: #15ca20;
|
||||
border-color: #15ca20;
|
||||
background-color: #23ac2c;
|
||||
border-color: #23ac2c;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")
|
||||
}
|
||||
|
||||
|
||||
.form-check-success .form-check-input:focus {
|
||||
border-color: #15ca20;
|
||||
border-color: #23ac2c;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 .25rem rgba(21, 202, 33, 0.25)
|
||||
}
|
||||
|
||||
@@ -72,11 +72,11 @@ $(function() {
|
||||
label: 'Facebook',
|
||||
data: [12, 30, 16, 23, 8, 14, 11],
|
||||
backgroundColor: [
|
||||
'#15ca20'
|
||||
'#23ac2c'
|
||||
],
|
||||
tension: 0,
|
||||
borderColor: [
|
||||
'#15ca20'
|
||||
'#23ac2c'
|
||||
],
|
||||
borderWidth: 3
|
||||
}]
|
||||
@@ -113,7 +113,7 @@ $(function() {
|
||||
'#6f42c1',
|
||||
'#d63384',
|
||||
'#fd7e14',
|
||||
'#15ca20',
|
||||
'#23ac2c',
|
||||
'#0dcaf0'
|
||||
],
|
||||
borderWidth: 1.5
|
||||
@@ -146,7 +146,7 @@ $(function() {
|
||||
'#6f42c1',
|
||||
'#d63384',
|
||||
'#fd7e14',
|
||||
'#15ca20',
|
||||
'#23ac2c',
|
||||
'#0dcaf0'
|
||||
],
|
||||
borderWidth: 1
|
||||
@@ -242,7 +242,7 @@ $(function() {
|
||||
'#6f42c1',
|
||||
'#d63384',
|
||||
'#fd7e14',
|
||||
'#15ca20',
|
||||
'#23ac2c',
|
||||
'#0dcaf0'
|
||||
],
|
||||
}]
|
||||
@@ -372,7 +372,7 @@ var myChart = new Chart(ctx, {
|
||||
label: 'Facebook',
|
||||
data: [5, 30, 16, 23, 8, 14, 2],
|
||||
backgroundColor: [
|
||||
'#15ca20'
|
||||
'#23ac2c'
|
||||
],
|
||||
fill: {
|
||||
target: 'origin',
|
||||
@@ -381,7 +381,7 @@ var myChart = new Chart(ctx, {
|
||||
},
|
||||
tension: 0.4,
|
||||
borderColor: [
|
||||
'#15ca20'
|
||||
'#23ac2c'
|
||||
],
|
||||
borderWidth: 4
|
||||
}]
|
||||
|
||||
@@ -41,12 +41,12 @@ Morris.Area({
|
||||
labels: ['iPhone', 'iPad'],
|
||||
pointSize: 3,
|
||||
fillOpacity: 0,
|
||||
pointStrokeColors:['#008cff', '#15ca20'],
|
||||
pointStrokeColors:['#008cff', '#23ac2c'],
|
||||
behaveLikeLine: true,
|
||||
gridLineColor: '#e0e0e0',
|
||||
lineWidth: 3,
|
||||
hideHover: 'auto',
|
||||
lineColors: ['#008cff', '#15ca20'],
|
||||
lineColors: ['#008cff', '#23ac2c'],
|
||||
resize: true
|
||||
|
||||
});
|
||||
@@ -67,7 +67,7 @@ Morris.Area({
|
||||
value: 20
|
||||
}],
|
||||
resize: true,
|
||||
colors:['#008cff', '#15ca20', '#fd3550']
|
||||
colors:['#008cff', '#23ac2c', '#fd3550']
|
||||
});
|
||||
|
||||
// Morris bar chart
|
||||
@@ -112,7 +112,7 @@ Morris.Area({
|
||||
xkey: 'y',
|
||||
ykeys: ['a', 'b', 'c'],
|
||||
labels: ['A', 'B', 'C'],
|
||||
barColors:['#008cff', '#15ca20', '#75808a'],
|
||||
barColors:['#008cff', '#23ac2c', '#75808a'],
|
||||
hideHover: 'auto',
|
||||
gridLineColor: '#eef0f2',
|
||||
resize: true
|
||||
@@ -153,7 +153,7 @@ Morris.Area({
|
||||
|
||||
|
||||
],
|
||||
lineColors: ['#008cff', '#15ca20'],
|
||||
lineColors: ['#008cff', '#23ac2c'],
|
||||
xkey: 'period',
|
||||
ykeys: ['iphone', 'ipad'],
|
||||
labels: ['Site A', 'Site B'],
|
||||
|
||||
@@ -2130,17 +2130,17 @@ form select.error:focus {
|
||||
}
|
||||
|
||||
.btn-inverse-success {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgb(212, 246, 214);
|
||||
}
|
||||
.btn-inverse-success:hover {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgba(21, 202, 32, 0.18);
|
||||
}
|
||||
.btn-inverse-success:focus {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgba(21, 202, 32, 0.18);
|
||||
box-shadow: 0 0 0 0.25rem rgba(23, 160, 14, 0.32);
|
||||
|
||||
@@ -2582,18 +2582,18 @@ form select.error:focus {
|
||||
}
|
||||
|
||||
.btn-inverse-success {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgb(212, 246, 214);
|
||||
|
||||
&:hover {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgba(21, 202, 32, 0.18);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: #15ca20;
|
||||
color: #23ac2c;
|
||||
background-color: rgba(21, 202, 32, 0.18);
|
||||
border-color: rgba(21, 202, 32, 0.18);
|
||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 32%);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
checkUserRole(['admin']);
|
||||
?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
checkUserRole(['admin']);
|
||||
?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -14,7 +14,10 @@
|
||||
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||
<!-- Theme Style CSS -->
|
||||
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||
<title>TOD - Admin Dashboard</title>
|
||||
</head>
|
||||
@@ -40,7 +43,7 @@
|
||||
</div>
|
||||
<form id="loginForm" class="row g-3">
|
||||
<div class="col-12">
|
||||
<label for="inputEmailAddress" class="form-label">Email</label>
|
||||
<label for="inputEmailAddress" class="form-label">Username</label>
|
||||
<input type="input" class="form-control" name="username" id="inputEmailAddress" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
265
public/shipping.php
Normal file
265
public/shipping.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!--favicon-->
|
||||
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||
<!--plugins-->
|
||||
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||
<!-- loader-->
|
||||
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||
<script src="../assets/js/pace.min.js"></script>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||
<!-- Theme Style CSS -->
|
||||
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||
|
||||
<title>TOD Dashboard</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--wrapper-->
|
||||
<div class="wrapper">
|
||||
<!--sidebar wrapper -->
|
||||
<?php include '../src/nav.php'; ?>
|
||||
<!--end sidebar wrapper -->
|
||||
<!--start header -->
|
||||
<?php include '../src/header.php'; ?>
|
||||
<!--end header -->
|
||||
<!--start page wrapper -->
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
|
||||
<!--start page content -->
|
||||
<div class="row g-4">
|
||||
<!-- Account Information Card -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card rounded-4 mb-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="widgets-icons bg-light-success text-success rounded-circle d-flex align-items-center justify-content-center p-3">
|
||||
<i class='bx bxs-user fs-3'></i>
|
||||
</div>
|
||||
<div id="accountInfo" class="ms-3">
|
||||
<h5 class="mb-0 fw-bold">Loading...</h5>
|
||||
<p class="text-muted mb-0">Account Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prepay Balance Card -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card rounded-4 mb-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="widgets-icons bg-light-info text-info rounded-circle d-flex align-items-center justify-content-center p-3">
|
||||
<i class='bx bx-wallet fs-3'></i>
|
||||
</div>
|
||||
<div id="prepayBalance" class="ms-3">
|
||||
<h5 class="mb-0 fw-bold">Loading...</h5>
|
||||
<p class="text-muted mb-0">Prepay Balance</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders Table -->
|
||||
<div class="col-12 col-xl-12">
|
||||
<div class="card rounded-4 mb-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title fw-bold mb-3">Orders</h5>
|
||||
<div id="ordersDetail" class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Courier</th>
|
||||
<th>Service</th>
|
||||
<th>Tracking Status</th>
|
||||
<th>Estimated Delivery</th>
|
||||
<th>Recipient</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">Loading orders...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end page content -->
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<!--end page wrapper -->
|
||||
<!--start overlay-->
|
||||
<div class="overlay mobile-toggle-icon"></div>
|
||||
<!--end overlay-->
|
||||
<!--Start Back To Top Button-->
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<!--End Back To Top Button-->
|
||||
</div>
|
||||
<!--end wrapper-->
|
||||
|
||||
|
||||
<!-- search modal -->
|
||||
<div class="modal" id="SearchModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header gap-2">
|
||||
<div class="position-relative popup-search w-100">
|
||||
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||
</div>
|
||||
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end search modal -->
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||
<!--plugins-->
|
||||
<script src="../assets/js/jquery.min.js"></script>
|
||||
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||
<!--app JS-->
|
||||
<script src="../assets/js/app.js"></script>
|
||||
|
||||
<script src="../assets/js/index.js"></script>
|
||||
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||
<script>
|
||||
$(".data-attributes span").peity("donut")
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// Fetch account information via AJAX
|
||||
function fetchAccountInfo() {
|
||||
$.ajax({
|
||||
url: '../src/shipping/get_account_info.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const data = response.data;
|
||||
let html = `
|
||||
<h5 class="mb-0 fw-bold">${data.Forename || 'N/A'} ${data.Surname || ''}</h5>
|
||||
<p class="text-muted mb-0">Email: ${data.Email || 'N/A'}</p>
|
||||
<p class="text-muted mb-0">Member Since: ${data.MemberSince || 'N/A'}</p>
|
||||
`;
|
||||
$('#accountInfo').html(html);
|
||||
} else {
|
||||
$('#accountInfo').html('<p class="text-danger">Error fetching account information.</p>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#accountInfo').html('<p class="text-danger">Error loading account information.</p>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch prepay balance via AJAX
|
||||
function fetchPrepayBalance() {
|
||||
$.ajax({
|
||||
url: '../src/shipping/get_prepay_balance.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const data = response.data;
|
||||
const balance = data.Balance || 0;
|
||||
$('#prepayBalance').html(`
|
||||
<h5 class="mb-0 fw-bold">£ ${balance}</h5>
|
||||
<p class="text-muted mb-0">Prepay Balance</p>
|
||||
`);
|
||||
} else {
|
||||
$('#prepayBalance').html('<p class="text-danger">Error fetching balance.</p>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#prepayBalance').html('<p class="text-danger">Error loading balance.</p>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch order details via AJAX
|
||||
function fetchOrdersDetail() {
|
||||
$.ajax({
|
||||
url: '../src/shipping/get_orders_detail.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const orders = response.data.Orders || [];
|
||||
let html = '';
|
||||
|
||||
if (orders.length === 0) {
|
||||
html = '<tr><td colspan="6" class="text-center text-muted">No orders found.</td></tr>';
|
||||
} else {
|
||||
orders.forEach(order => {
|
||||
const trackingStatus = order.Tracking?.Delivered ?
|
||||
'<span class="badge bg-success">Delivered</span>' :
|
||||
'<span class="badge bg-warning text-dark">In Transit</span>';
|
||||
html += `
|
||||
<tr>
|
||||
<td>${order.OrderLineId}</td>
|
||||
<td>${order.Courier}</td>
|
||||
<td>${order.Service}</td>
|
||||
<td>${trackingStatus}</td>
|
||||
<td>${order.EstimatedDeliveryDate ? new Date(order.EstimatedDeliveryDate).toLocaleString() : 'N/A'}</td>
|
||||
<td>${order.DeliveryAddress?.ContactName || 'N/A'}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
$('#ordersDetail tbody').html(html);
|
||||
} else {
|
||||
$('#ordersDetail tbody').html('<tr><td colspan="6" class="text-center text-danger">Error fetching orders.</td></tr>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#ordersDetail tbody').html('<tr><td colspan="6" class="text-center text-danger">Error loading orders.</td></tr>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger all fetches on page load
|
||||
fetchAccountInfo();
|
||||
fetchPrepayBalance();
|
||||
fetchOrdersDetail();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
319
public/userManagement.php
Normal file
319
public/userManagement.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php include '../src/session_check.php';
|
||||
checkUserRole(['admin']);
|
||||
?>
|
||||
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!--favicon-->
|
||||
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||
<!--plugins-->
|
||||
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||
<!-- loader-->
|
||||
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||
<script src="../assets/js/pace.min.js"></script>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||
<!-- Theme Style CSS -->
|
||||
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||
|
||||
<title>TOD Dashboard</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--wrapper-->
|
||||
<div class="wrapper">
|
||||
<!--sidebar wrapper -->
|
||||
<?php include '../src/nav.php'; ?>
|
||||
<!--end sidebar wrapper -->
|
||||
<!--start header -->
|
||||
<?php include '../src/header.php'; ?>
|
||||
<!--end header -->
|
||||
|
||||
<!--start page wrapper -->
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
<!--start page content -->
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container mt-4">
|
||||
<h2 class="text-center mb-4">User Management</h2>
|
||||
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||
<i class="fas fa-user-plus"></i> Create New User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-bordered align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="userTableBody">
|
||||
<!-- Rows dynamically populated -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Create User Modal -->
|
||||
<div class="modal fade" id="createUserModal" tabindex="-1" aria-labelledby="createUserModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="createUserModalLabel">Create New User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="createUserForm">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Role</label>
|
||||
<select id="role" class="form-select" required>
|
||||
<option value="">Select Role</option>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="user">User</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Create User</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Modal -->
|
||||
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="resetPasswordModalLabel">Reset Password</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="resetPasswordForm">
|
||||
<p>Enter a new password for <span id="resetUserName"></span>:</p>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">New Password</label>
|
||||
<input type="password" class="form-control" id="newPassword" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning w-100">Reset Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--end page content -->
|
||||
</div>
|
||||
</div>
|
||||
<!--end page wrapper -->
|
||||
|
||||
<!--start overlay-->
|
||||
<div class="overlay mobile-toggle-icon"></div>
|
||||
<!--end overlay-->
|
||||
<!--Start Back To Top Button-->
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<!--End Back To Top Button-->
|
||||
<footer class="page-footer">
|
||||
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
<!--end wrapper-->
|
||||
|
||||
|
||||
<!-- search modal -->
|
||||
<div class="modal" id="SearchModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header gap-2">
|
||||
<div class="position-relative popup-search w-100">
|
||||
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||
</div>
|
||||
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end search modal -->
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||
<!--plugins-->
|
||||
<script src="../assets/js/jquery.min.js"></script>
|
||||
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||
<!--app JS-->
|
||||
<script src="../assets/js/app.js"></script>
|
||||
|
||||
<script src="../assets/js/index.js"></script>
|
||||
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||
<script>
|
||||
$(".data-attributes span").peity("donut")
|
||||
</script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const apiUrl = '../src/userMananagementService.php';
|
||||
|
||||
// Load users on page load
|
||||
fetchUsers();
|
||||
|
||||
// Create new user form submission
|
||||
$('#createUserForm').submit(function (e) {
|
||||
e.preventDefault();
|
||||
const username = $('#username').val();
|
||||
const email = $('#email').val();
|
||||
const role = $('#role').val();
|
||||
const password = $('#password').val();
|
||||
|
||||
$.ajax({
|
||||
url: apiUrl,
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({ username, email, role, password }),
|
||||
contentType: 'application/json',
|
||||
success: function () {
|
||||
alert('User created successfully!');
|
||||
fetchUsers();
|
||||
$('#createUserModal').modal('hide');
|
||||
$('#createUserForm')[0].reset();
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert('Error creating user: ' + xhr.responseText);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Reset password form submission
|
||||
$('#resetPasswordForm').submit(function (e) {
|
||||
e.preventDefault();
|
||||
const userId = $('#resetPasswordModal').data('userId');
|
||||
const password = $('#newPassword').val();
|
||||
|
||||
$.ajax({
|
||||
url: `${apiUrl}?reset-password=${userId}`,
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({ password }),
|
||||
contentType: 'application/json',
|
||||
success: function () {
|
||||
alert('Password reset successfully!');
|
||||
$('#resetPasswordModal').modal('hide');
|
||||
$('#resetPasswordForm')[0].reset();
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert('Error resetting password: ' + xhr.responseText);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Fetch users from the backend
|
||||
function fetchUsers() {
|
||||
const apiUrl = '../src/userMananagementService.php';
|
||||
|
||||
$.ajax({
|
||||
url: apiUrl,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (users) {
|
||||
const tableBody = $('#userTableBody');
|
||||
tableBody.empty();
|
||||
|
||||
users.forEach(function (user) {
|
||||
const row = `
|
||||
<tr>
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>${user.role}</td>
|
||||
<td>
|
||||
<button class="btn btn-warning btn-sm" onclick="openResetPasswordModal(${user.id}, '${user.username}')">
|
||||
<i class="fas fa-key"></i> Reset Password
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteUser(${user.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
tableBody.append(row);
|
||||
});
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert('Error fetching users: ' + xhr.responseText);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Open the reset password modal
|
||||
function openResetPasswordModal(userId, username) {
|
||||
$('#resetPasswordModal').data('userId', userId);
|
||||
$('#resetUserName').text(username);
|
||||
$('#resetPasswordModal').modal('show');
|
||||
}
|
||||
|
||||
// Delete user
|
||||
function deleteUser(userId) {
|
||||
const apiUrl = '../src/userMananagementService.php';
|
||||
|
||||
if (confirm('Are you sure you want to delete this user?')) {
|
||||
$.ajax({
|
||||
url: `${apiUrl}?delete-user=${userId}`,
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
alert('User deleted successfully!');
|
||||
fetchUsers();
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert('Error deleting user: ' + xhr.responseText);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
@@ -139,16 +139,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
`;
|
||||
}
|
||||
|
||||
// Debounce function to reduce DOM updates
|
||||
function debounceRender(func, delay) {
|
||||
let inDebounce;
|
||||
return function() {
|
||||
const context = this, args = arguments;
|
||||
clearTimeout(inDebounce);
|
||||
inDebounce = setTimeout(() => func.apply(context, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
fetch('../src/filamentTracker/getFilamentPrices.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
@@ -159,36 +149,56 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
const prices = data.data[filament].prices.map(entry => entry.price);
|
||||
const timestamps = data.data[filament].prices.map(entry => entry.recordedAt);
|
||||
const latestPrice = prices[prices.length - 1] || 0;
|
||||
const previousPrice = prices[prices.length - 2] || latestPrice;
|
||||
const priceDifference = latestPrice - previousPrice;
|
||||
|
||||
// Find the last distinct price (where the price actually changed)
|
||||
let lastDistinctPrice = latestPrice;
|
||||
for (let i = prices.length - 2; i >= 0; i--) {
|
||||
if (prices[i] !== latestPrice) {
|
||||
lastDistinctPrice = prices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const priceDifference = latestPrice - lastDistinctPrice;
|
||||
|
||||
// Price Change Indicator
|
||||
let priceChangeIndicator;
|
||||
if (priceDifference > 0) {
|
||||
priceChangeIndicator = `<span class="text-danger ms-4">
|
||||
<i class="bx bx-up-arrow-alt"></i> +£${priceDifference.toFixed(2)}</span>`;
|
||||
} else if (priceDifference < 0) {
|
||||
priceChangeIndicator = `<span class="text-success ms-4">
|
||||
<i class="bx bx-down-arrow-alt"></i> £${Math.abs(priceDifference).toFixed(2)}</span>`;
|
||||
<i class="bx bx-down-arrow-alt"></i> -£${Math.abs(priceDifference).toFixed(2)}</span>`;
|
||||
} else {
|
||||
priceChangeIndicator = `<span class="text-muted ms-4">
|
||||
<i class="bx bx-minus"></i> £0.00</span>`;
|
||||
}
|
||||
|
||||
const amazonUrl = data.data[filament].amazonUrl || '#';
|
||||
const discount = data.data[filament].currentDiscount || 0; // Get discount percentage
|
||||
const chartId = `chart-${filament.replace(/\s+/g, '-')}`;
|
||||
const chartColor = getRandomColor();
|
||||
|
||||
// Create Card HTML with cart button
|
||||
// Create Card HTML
|
||||
const cardHTML = `
|
||||
<div class="col-lg-4 mb-1">
|
||||
<div class="card radius-10 overflow-hidden">
|
||||
<div class="card-body d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
|
||||
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
|
||||
<h5 class="mb-0">
|
||||
£${latestPrice} ${priceChangeIndicator}
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
${
|
||||
discount > 0
|
||||
? `<span class="text-success fw-bold" style="font-size: 1rem;">
|
||||
-${discount}%
|
||||
</span>`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div id="${chartId}"></div>
|
||||
</div>
|
||||
@@ -197,84 +207,38 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
container.insertAdjacentHTML('beforeend', cardHTML);
|
||||
|
||||
// Observer to Wait for DOM Insertion
|
||||
const observer = new MutationObserver(debounceRender(() => {
|
||||
const chartElement = document.getElementById(chartId);
|
||||
if (chartElement) {
|
||||
const chartOptions = {
|
||||
series: [{
|
||||
name: "Price",
|
||||
data: prices
|
||||
}],
|
||||
chart: {
|
||||
type: "area",
|
||||
height: 110,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: 3,
|
||||
left: 14,
|
||||
blur: 4,
|
||||
opacity: 0.12,
|
||||
color: chartColor
|
||||
},
|
||||
sparkline: {
|
||||
enabled: true
|
||||
}
|
||||
// Chart Rendering
|
||||
const chartOptions = {
|
||||
series: [{ name: "Price", data: prices }],
|
||||
chart: {
|
||||
type: "area",
|
||||
height: 110,
|
||||
toolbar: { show: false },
|
||||
zoom: { enabled: false },
|
||||
sparkline: { enabled: true },
|
||||
},
|
||||
markers: { size: 0 },
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { show: true, width: 2.4, curve: "smooth" },
|
||||
colors: [chartColor],
|
||||
xaxis: { categories: timestamps, labels: { show: false } },
|
||||
fill: { opacity: 1 },
|
||||
tooltip: {
|
||||
theme: "dark",
|
||||
y: {
|
||||
formatter: function (value, { dataPointIndex }) {
|
||||
const date = new Date(timestamps[dataPointIndex]);
|
||||
return `£${value} - ${date.toLocaleDateString()}`;
|
||||
},
|
||||
markers: {
|
||||
size: 0,
|
||||
colors: [chartColor],
|
||||
strokeColors: "#fff",
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
size: 7
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 2.4,
|
||||
curve: "smooth"
|
||||
},
|
||||
colors: [chartColor],
|
||||
xaxis: {
|
||||
categories: timestamps,
|
||||
labels: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
tooltip: {
|
||||
theme: "dark",
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
formatter: function (value, { dataPointIndex }) {
|
||||
const date = new Date(timestamps[dataPointIndex]);
|
||||
return `£${value} - ${date.toLocaleDateString()}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const chart = new ApexCharts(chartElement, chartOptions);
|
||||
chart.render();
|
||||
observer.disconnect();
|
||||
}
|
||||
}, 300));
|
||||
|
||||
observer.observe(container, { childList: true, subtree: true });
|
||||
const chartElement = document.getElementById(chartId);
|
||||
if (chartElement) {
|
||||
const chart = new ApexCharts(chartElement, chartOptions);
|
||||
chart.render();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
container.innerHTML = '<p class="text-center">No filament data available.</p>';
|
||||
@@ -288,6 +252,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
@@ -1,88 +1,73 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require '../../vendor/autoload.php';
|
||||
require '../db.php';
|
||||
require_once '../envLoader.php';
|
||||
require 'scraper.php';
|
||||
loadEnv(__DIR__ . '/../../.env');
|
||||
|
||||
include '../src/session_check.php';
|
||||
include '../session_check.php';
|
||||
checkUserRole(['admin']);
|
||||
|
||||
use Goutte\Client;
|
||||
|
||||
// Start session to get user ID
|
||||
session_start();
|
||||
if (!isset($_SESSION['userId'])) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check for POST data
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$filamentName = $_POST['productName'] ?? '';
|
||||
$amazonUrl = $_POST['productUrl'] ?? '';
|
||||
$userId = $_SESSION['userId'];
|
||||
|
||||
// Basic validation
|
||||
if (empty($filamentName) || empty($amazonUrl)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Filament name and URL are required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
// Scrape the Amazon page for initial price and other details
|
||||
$crawler = $client->request('GET', $amazonUrl);
|
||||
// Scrape data from Amazon
|
||||
$scrapedData = scrapeAmazonData($amazonUrl);
|
||||
if (!$scrapedData) {
|
||||
throw new Exception('Scraping failed for the provided URL.');
|
||||
}
|
||||
|
||||
// Scrape Price
|
||||
$whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0';
|
||||
$fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00';
|
||||
$price = $scrapedData['price'];
|
||||
$discount = $scrapedData['discount'];
|
||||
$details = $scrapedData['details'];
|
||||
|
||||
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||
$totalPrice = floatval($whole . '.' . $fraction);
|
||||
|
||||
// Scrape Filament Details from Table
|
||||
$details = [];
|
||||
$crawler->filter('table.a-normal tr')->each(function ($node) use (&$details) {
|
||||
$label = trim($node->filter('td.a-span3')->text());
|
||||
$value = trim($node->filter('td.a-span9')->text());
|
||||
$details[$label] = $value;
|
||||
});
|
||||
|
||||
|
||||
// Extract details or use default if not found
|
||||
// Extract details or use defaults
|
||||
$brand = $details['Brand'] ?? 'Unknown';
|
||||
$material = $details['Material'] ?? 'Unknown';
|
||||
$color = $details['Colour'] ?? 'Unknown';
|
||||
$filamentWeight = isset($details['Item weight']) ? preg_replace('/[^0-9.]/', '', $details['Item weight']) : 1;
|
||||
if (stripos($details['Item weight'], 'kilograms') !== false || stripos($details['Item weight'], 'kg') !== false) {
|
||||
$filamentWeight = $filamentWeight * 1000; // Convert KG to G
|
||||
$filamentWeight = $filamentWeight * 1000; // Convert KG to G
|
||||
}
|
||||
$itemDiameter = isset($details['Item diameter']) ? preg_replace('/[^0-9.]/', '', $details['Item diameter']) : 1.75;
|
||||
|
||||
// Start a transaction to ensure consistency
|
||||
// Database operations
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Check if filament already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM filamentTracker WHERE amazonUrl = :amazonUrl");
|
||||
$stmt->execute([':amazonUrl' => $amazonUrl]);
|
||||
$existingFilament = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existingFilament) {
|
||||
// Filament exists – just update the price history
|
||||
$filamentId = $existingFilament['id'];
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||
VALUES (:filamentId, :price)
|
||||
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||
VALUES (:filamentId, :price, :currentDiscount)
|
||||
");
|
||||
$stmt->execute([
|
||||
':filamentId' => $filamentId,
|
||||
':price' => $totalPrice
|
||||
':price' => $price,
|
||||
':currentDiscount' => $discount
|
||||
]);
|
||||
$message = 'Filament price updated successfully.';
|
||||
$message = 'Filament price and discount updated successfully.';
|
||||
} else {
|
||||
// Insert new filament into filamentTracker
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO filamentTracker (userId, filamentName, amazonUrl, filamentWeight, brand, material, color, itemDiameter)
|
||||
VALUES (:userId, :filamentName, :amazonUrl, :filamentWeight, :brand, :material, :color, :itemDiameter)
|
||||
@@ -98,29 +83,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
':itemDiameter' => $itemDiameter
|
||||
]);
|
||||
|
||||
// Get the last inserted filament ID
|
||||
$filamentId = $pdo->lastInsertId();
|
||||
|
||||
// Insert initial price into filamentPriceHistory
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||
VALUES (:filamentId, :price)
|
||||
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||
VALUES (:filamentId, :price, :currentDiscount)
|
||||
");
|
||||
$stmt->execute([
|
||||
':filamentId' => $filamentId,
|
||||
':price' => $totalPrice
|
||||
':price' => $price,
|
||||
':currentDiscount' => $discount
|
||||
]);
|
||||
|
||||
$message = $filamentName . ' Filament added successfully and price tracked.';
|
||||
$message = "$filamentName added successfully and price with discount tracked.";
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode(['status' => 'success', 'message' => $message]);
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Failed to scrape or insert data. Error: ' . $e->getMessage()]);
|
||||
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']);
|
||||
|
||||
@@ -9,7 +9,7 @@ include '../src/session_check.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
// Fetch all filament prices with a limit of 180 entries per filament using JOIN
|
||||
// Fetch all filament prices with the latest discount
|
||||
$stmt = $pdo->query("
|
||||
SELECT ft.filamentName,
|
||||
ft.brand,
|
||||
@@ -17,6 +17,7 @@ try {
|
||||
ft.color,
|
||||
ft.amazonUrl,
|
||||
fp.price,
|
||||
fp.currentDiscount,
|
||||
fp.recordedAt
|
||||
FROM filamentTracker ft
|
||||
JOIN filamentPriceHistory fp ON ft.id = fp.filamentId
|
||||
@@ -27,7 +28,7 @@ try {
|
||||
ROW_NUMBER() OVER (PARTITION BY filamentId ORDER BY recordedAt DESC) as rn
|
||||
FROM filamentPriceHistory
|
||||
) ranked
|
||||
WHERE rn <= 180
|
||||
WHERE rn <= 280
|
||||
) filtered ON fp.filamentId = filtered.filamentId AND fp.recordedAt = filtered.recordedAt
|
||||
ORDER BY ft.filamentName, fp.recordedAt ASC
|
||||
");
|
||||
@@ -46,7 +47,8 @@ try {
|
||||
'material' => $filament['material'],
|
||||
'color' => $filament['color'],
|
||||
'amazonUrl' => $filament['amazonUrl'],
|
||||
'prices' => []
|
||||
'prices' => [],
|
||||
'currentDiscount' => $filament['currentDiscount'] ?? 0 // Include the latest discount
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
46
src/filamentTracker/scraper.php
Normal file
46
src/filamentTracker/scraper.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||
|
||||
use Goutte\Client;
|
||||
|
||||
function scrapeAmazonData($url) {
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$crawler = $client->request('GET', $url);
|
||||
|
||||
// Scrape price
|
||||
$whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0';
|
||||
$fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00';
|
||||
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||
$totalPrice = floatval($whole . '.' . $fraction);
|
||||
|
||||
// Scrape discount
|
||||
$discount = $crawler->filter('.savingsPercentage')->count()
|
||||
? $crawler->filter('.savingsPercentage')->text()
|
||||
: null;
|
||||
if ($discount) {
|
||||
$discount = preg_replace('/[^0-9]/', '', $discount);
|
||||
} else {
|
||||
$discount = 0; // Default to 0 if no discount is found
|
||||
}
|
||||
|
||||
// Scrape additional details
|
||||
$details = [];
|
||||
$crawler->filter('table.a-normal tr')->each(function ($node) use (&$details) {
|
||||
$label = trim($node->filter('td.a-span3')->text());
|
||||
$value = trim($node->filter('td.a-span9')->text());
|
||||
$details[$label] = $value;
|
||||
});
|
||||
|
||||
return [
|
||||
'price' => $totalPrice,
|
||||
'discount' => $discount,
|
||||
'details' => $details
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return null; // Return null if scraping fails
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,9 @@
|
||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/db.php';
|
||||
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||
require 'scraper.php';
|
||||
loadEnv(__DIR__ . '/../../.env');
|
||||
|
||||
use Goutte\Client;
|
||||
|
||||
// Function to scrape price
|
||||
function scrapePrice($url) {
|
||||
$client = new Client(); // Reinitialize client for each request
|
||||
try {
|
||||
$crawler = $client->request('GET', $url);
|
||||
|
||||
$whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0';
|
||||
$fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00';
|
||||
|
||||
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||
|
||||
$totalPrice = floatval($whole . '.' . $fraction);
|
||||
|
||||
return $totalPrice;
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all filaments
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM filamentTracker");
|
||||
@@ -36,20 +14,21 @@ try {
|
||||
$amazonUrl = $filament['amazonUrl'];
|
||||
$filamentId = $filament['id'];
|
||||
|
||||
// Scrape price
|
||||
$totalPrice = scrapePrice($amazonUrl);
|
||||
// Use the scraper function to fetch price and discount
|
||||
$scrapedData = scrapeAmazonData($amazonUrl);
|
||||
|
||||
if ($totalPrice !== null && $totalPrice > 0) {
|
||||
if ($scrapedData && $scrapedData['price'] > 0) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO filamentPriceHistory (filamentId, price)
|
||||
VALUES (:filamentId, :price)
|
||||
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||
VALUES (:filamentId, :price, :currentDiscount)
|
||||
");
|
||||
$stmt->execute([
|
||||
':filamentId' => $filamentId,
|
||||
':price' => $totalPrice
|
||||
':price' => $scrapedData['price'],
|
||||
':currentDiscount' => $scrapedData['discount']
|
||||
]);
|
||||
|
||||
echo "Updated price for {$filament['filamentName']}: £{$totalPrice}\n";
|
||||
echo "Updated price for {$filament['filamentName']}: £{$scrapedData['price']}, Discount: {$scrapedData['discount']}%\n";
|
||||
} else {
|
||||
echo "Failed to update {$filament['filamentName']} (no price found or £0).\n";
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
</li>
|
||||
<li><a class="dropdown-item d-flex align-items-center" href="printerForm.php"><i class="bx bx-cog fs-5"></i><span>Add Printer</span></a>
|
||||
</li>
|
||||
<li><a class="dropdown-item d-flex align-items-center" href="userManagement.php"><i class="bx bx-cog fs-5"></i><span>user Management</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-divider mb-0"></div>
|
||||
</li>
|
||||
|
||||
10
src/nav.php
10
src/nav.php
@@ -21,6 +21,16 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Shipping Section -->
|
||||
<li class="menu-label">Shipping</li>
|
||||
<li>
|
||||
<a href="shipping.php">
|
||||
<div class="parent-icon"><i class='bx bx-code-alt'></i>
|
||||
</div>
|
||||
<div class="menu-title">Shipping Dashboard</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Printing Section -->
|
||||
<li class="menu-label">Printing Control</li>
|
||||
<li>
|
||||
|
||||
74
src/shipping/get_account_info.php
Normal file
74
src/shipping/get_account_info.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||
loadEnv(__DIR__ . '/../../.env');
|
||||
|
||||
// Get API credentials from the environment
|
||||
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||
$account_info_url = 'https://www.parcel2go.com/api/me/detail';
|
||||
|
||||
// Function to get the API token
|
||||
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||
$ch = curl_init($auth_url);
|
||||
|
||||
$data = http_build_query([
|
||||
'grant_type' => 'client_credentials',
|
||||
'scope' => 'public-api',
|
||||
'client_id' => $client_id,
|
||||
'client_secret' => $client_secret,
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: */*',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
return $result['access_token'] ?? null;
|
||||
}
|
||||
|
||||
// Function to fetch account information
|
||||
function getAccountInfo($token, $account_info_url) {
|
||||
$ch = curl_init($account_info_url);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer $token",
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
// Main logic
|
||||
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||
$account_info = getAccountInfo($token, $account_info_url);
|
||||
|
||||
if ($token) {
|
||||
$account_info = getAccountInfo($token, $account_info_url);
|
||||
|
||||
if ($account_info) {
|
||||
echo json_encode(['success' => true, 'data' => $account_info]);
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to fetch account information.']);
|
||||
}
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||
}
|
||||
?>
|
||||
73
src/shipping/get_orders_detail.php
Normal file
73
src/shipping/get_orders_detail.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||
loadEnv(__DIR__ . '/../../.env');
|
||||
|
||||
// Get API credentials from the environment
|
||||
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||
$orders_detail_url = 'https://www.parcel2go.com/api/me/orders/detail';
|
||||
|
||||
// Function to get the API token
|
||||
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||
$ch = curl_init($auth_url);
|
||||
|
||||
$data = http_build_query([
|
||||
'grant_type' => 'client_credentials',
|
||||
'scope' => 'public-api',
|
||||
'client_id' => $client_id,
|
||||
'client_secret' => $client_secret,
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: */*',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
return $result['access_token'] ?? null;
|
||||
}
|
||||
|
||||
// Function to fetch order details
|
||||
function getOrdersDetail($token, $orders_detail_url) {
|
||||
$ch = curl_init($orders_detail_url);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer $token",
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
// Main logic
|
||||
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||
|
||||
if ($token) {
|
||||
$orders_detail = getOrdersDetail($token, $orders_detail_url);
|
||||
|
||||
if ($orders_detail) {
|
||||
echo json_encode(['success' => true, 'data' => $orders_detail]);
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to fetch order details.']);
|
||||
}
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||
}
|
||||
?>
|
||||
73
src/shipping/get_prepay_balance.php
Normal file
73
src/shipping/get_prepay_balance.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||
loadEnv(__DIR__ . '/../../.env');
|
||||
|
||||
// Get API credentials from the environment
|
||||
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||
$prepay_balance_url = 'https://www.parcel2go.com/api/me/prepay/balance';
|
||||
|
||||
// Function to get the API token
|
||||
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||
$ch = curl_init($auth_url);
|
||||
|
||||
$data = http_build_query([
|
||||
'grant_type' => 'client_credentials',
|
||||
'scope' => 'public-api',
|
||||
'client_id' => $client_id,
|
||||
'client_secret' => $client_secret,
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: */*',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
return $result['access_token'] ?? null;
|
||||
}
|
||||
|
||||
// Function to fetch prepay balance
|
||||
function getPrepayBalance($token, $prepay_balance_url) {
|
||||
$ch = curl_init($prepay_balance_url);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer $token",
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
// Main logic
|
||||
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||
|
||||
if ($token) {
|
||||
$balance_info = getPrepayBalance($token, $prepay_balance_url);
|
||||
|
||||
if ($balance_info) {
|
||||
echo json_encode(['success' => true, 'data' => $balance_info]);
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to fetch prepay balance.']);
|
||||
}
|
||||
} else {
|
||||
http_response_code(402);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||
}
|
||||
?>
|
||||
111
src/userMananagementService.php
Normal file
111
src/userMananagementService.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once 'db.php';
|
||||
require_once 'session_check.php';
|
||||
checkUserRole(['admin']);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
die("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Handle requests
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
fetchUsers();
|
||||
} elseif ($method === 'POST') {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (isset($input['username']) && isset($input['email']) && isset($input['role']) && isset($input['password'])) {
|
||||
createUser($input);
|
||||
} elseif (isset($_GET['reset-password'])) {
|
||||
$userId = intval($_GET['reset-password']);
|
||||
resetPassword($userId, $input);
|
||||
} elseif (isset($_GET['delete-user'])) {
|
||||
$userId = intval($_GET['delete-user']);
|
||||
deleteUser($userId);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Invalid request"]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(405);
|
||||
echo json_encode(["error" => "Method not allowed"]);
|
||||
}
|
||||
|
||||
function fetchUsers()
|
||||
{
|
||||
global $pdo;
|
||||
$stmt = $pdo->prepare("SELECT id, username, email, role, disabled, created_at FROM users");
|
||||
$stmt->execute();
|
||||
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($users);
|
||||
}
|
||||
|
||||
function createUser($input)
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
$hashedPassword = password_hash($input['password'], PASSWORD_BCRYPT);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, email, role, password, disabled, created_at) VALUES (:username, :email, :role, :password, 0, NOW())");
|
||||
|
||||
if ($stmt->execute([
|
||||
':username' => $input['username'],
|
||||
':email' => $input['email'],
|
||||
':role' => $input['role'],
|
||||
':password' => $hashedPassword
|
||||
])) {
|
||||
http_response_code(201);
|
||||
echo json_encode(["success" => "User created successfully"]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to create user"]);
|
||||
}
|
||||
}
|
||||
|
||||
function resetPassword($userId, $input)
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
if (!isset($input['password'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Password not provided"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$newPassword = password_hash($input['password'], PASSWORD_BCRYPT);
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE users SET password = :password WHERE id = :id");
|
||||
|
||||
if ($stmt->execute([
|
||||
':password' => $newPassword,
|
||||
':id' => $userId
|
||||
])) {
|
||||
echo json_encode(["success" => "Password reset successfully"]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to reset password"]);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUser($userId)
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
|
||||
|
||||
if ($stmt->execute([':id' => $userId])) {
|
||||
echo json_encode(["success" => "User deleted successfully"]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to delete user"]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php include '../src/session_check.php'; ?>
|
||||
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
|
||||
Reference in New Issue
Block a user