Auto stash before merge of "main" and "origin/main"

This commit is contained in:
2024-02-09 21:11:19 -06:00
parent f5d267ea98
commit d21d91b80e
17 changed files with 844 additions and 7 deletions

9
.gitignore vendored
View File

@@ -1,8 +1,5 @@
# ---> Composer
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
composer.lock
/vendor/
.DS_Store
*.db

View File

@@ -13,5 +13,12 @@
"email": "hpz937+code@gmail.com"
}
],
"require": {}
"repositories": [{
"type": "composer",
"url": "https://git.hpz.pw/api/packages/hpz937/composer"
}
],
"require": {
"hpz937/restclient": "1.0.0"
}
}

41
db_setup.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Hpz937\BillReminder\Database\SQLiteAdapter;
// Initialize the SQLite database adapter
$dbAdapter = new SQLiteAdapter();
$db = $dbAdapter->connect();
// SQL for creating the 'users' table
$createUsersTable = <<<SQL
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
SQL;
// SQL for creating the 'bills' table
$createBillsTable = <<<SQL
CREATE TABLE IF NOT EXISTS bills (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
due_date DATE NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
description TEXT,
is_paid INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id)
);
SQL;
// Execute the SQL queries to create the tables
try {
$db->exec($createUsersTable);
$db->exec($createBillsTable);
echo "Database setup completed successfully.\n";
} catch (Exception $e) {
echo "Error setting up the database: " . $e->getMessage() . "\n";
}

79
public/assets/js/app.js Normal file
View File

@@ -0,0 +1,79 @@
// main.js
document.addEventListener('DOMContentLoaded', function() {
const addBillForm = document.getElementById('addBillForm');
if (addBillForm) {
addBillForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
axios.post('/api/add-bill', formData)
.then(function(response) {
// Handle success, e.g., close modal, refresh bill list
console.log('Bill added successfully');
loadBills(); // Reload the bills list
})
.catch(function(error) {
// Handle error, e.g., display error message
console.error('Error adding bill:', error);
});
document.querySelectorAll('.edit-bill-btn').forEach(button => {
button.addEventListener('click', function() {
const billId = this.getAttribute('data-bill-id');
// Fetch bill details and populate the form in the modal
// Then, handle the form submission similar to the add bill form
});
});
document.querySelectorAll('.delete-bill-btn').forEach(button => {
button.addEventListener('click', function() {
const billId = this.getAttribute('data-bill-id');
axios.post('/api/delete-bill', { id: billId })
.then(function(response) {
// Handle success
console.log('Bill deleted successfully');
loadBills(); // Reload the bills list
})
.catch(function(error) {
// Handle error
console.error('Error deleting bill:', error);
});
});
});
});
}
// Load bills if on the dashboard page
if (document.getElementById('billsTable')) {
loadBills();
}
});
function loadBills() {
const billsTableBody = document.querySelector('#billsTable tbody');
billsTableBody.innerHTML = '<tr><td colspan="4" class="text-center">Loading bills...</td></tr>'; // Loading indicator
axios.get('/api/bills')
.then(function(response) {
const bills = response.data;
billsTableBody.innerHTML = ''; // Clear loading indicator
bills.forEach(function(bill) {
const row = `
<tr>
<td>${bill.description}</td>
<td>$${parseFloat(bill.amount).toFixed(2)}</td>
<td>${bill.due_date}</td>
<td>
<!-- Add action buttons here -->
</td>
</tr>
`;
billsTableBody.innerHTML += row;
});
})
.catch(function(error) {
console.error('Error loading bills:', error);
billsTableBody.innerHTML = '<tr><td colspan="4" class="text-center">Error loading bills.</td></tr>'; // Error message
});
}

181
public/index.php Normal file
View File

@@ -0,0 +1,181 @@
<?php
use Hpz937\BillReminder\Bill\Bill;
use Hpz937\BillReminder\Database\SQLiteAdapter;
require_once __DIR__ . '/../vendor/autoload.php';
session_start();
$request = $_SERVER['REQUEST_URI'];
// Initialize database connection
// $dbConfig = require __DIR__ . '/../config/database.php'; // Assuming you have a config file
$db = new \Hpz937\BillReminder\Database\SQLiteAdapter(); // Or whichever adapter you're using
switch ($request) {
case '/':
require __DIR__ . '/../views/home.php';
break;
case '/login':
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($username) || empty($password)) {
$error = 'Username and password are required.';
require __DIR__ . '/../views/auth/login.php';
break;
}
$user = new \Hpz937\BillReminder\User\User($db);
if ($user->login($username, $password)) {
header('Location: /dashboard');
exit;
} else {
$error = 'Login failed. Please check your credentials.';
require __DIR__ . '/../views/auth/login.php';
}
} else {
require __DIR__ . '/../views/auth/login.php';
}
break;
case '/register':
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Extract form data
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
// Perform validation (basic example)
if (empty($username) || empty($password) || empty($email)) {
// Handle validation error
$error = 'All fields are required.';
require __DIR__ . '/../views/auth/register.php';
break;
}
// Instantiate User class and call register method
$user = new \Hpz937\BillReminder\User\User($db); // Assume $db is your DatabaseInterface instance
if ($user->register($username, $password, $email)) {
// Redirect to login on success
header('Location: /');
exit;
} else {
// Handle registration error
$error = 'Registration failed. Please try again.';
require __DIR__ . '/../views/auth/register.php';
}
} else {
require __DIR__ . '/../views/auth/register.php';
}
break;
case '/dashboard':
if (!isset($_SESSION['user_id'])) {
header('Location: /login');
exit;
}
require __DIR__ . '/../views/dashboard.php';
break;
case '/settings':
if (!isset($_SESSION['user_id'])) {
header('Location: /login');
exit;
}
require __DIR__ . '/../views/settings.php';
break;
case '/api/bills':
if (!isset($_SESSION['user_id'])) {
// Return an error response if the user is not logged in
echo json_encode(['error' => 'Unauthorized']);
http_response_code(401);
exit;
}
// Initialize your database and Bill class instance
$db = new SQLiteAdapter();
$billManager = new Bill($db);
$userId = $_SESSION['user_id'];
$bills = $billManager->getBillsByUserId($userId); // Assuming $billManager is your Bill class instance
// Return the bills as JSON
header('Content-Type: application/json');
echo json_encode($bills);
break;
case '/api/add-bill':
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// Extract bill details from POST data
$userId = $_SESSION['user_id']; // Assuming you store user ID in session upon login
$description = $_POST['description'] ?? '';
$amount = $_POST['amount'] ?? '';
$dueDate = $_POST['due_date'] ?? '';
// Validate the inputs...
// Insert the bill into the database
$result = $billManager->addBill($userId, $dueDate, $amount, $description);
if ($result) {
echo json_encode(['success' => 'Bill added successfully']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to add bill']);
}
exit;
}
break;
case '/api/edit-bill':
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// Extract bill details and ID from POST data
$billId = $_POST['id'] ?? '';
$description = $_POST['description'] ?? '';
$amount = $_POST['amount'] ?? '';
$dueDate = $_POST['due_date'] ?? '';
// Validate the inputs and ensure the bill belongs to the logged-in user...
// Update the bill in the database
$result = $billManager->editBill($billId, $dueDate, $amount, $description);
if ($result) {
echo json_encode(['success' => 'Bill updated successfully']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to update bill']);
}
exit;
}
break;
case '/api/delete-bill':
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// Extract bill ID from POST data
$billId = $_POST['id'] ?? '';
// Validate the ID and ensure the bill belongs to the logged-in user...
// Delete the bill from the database
$result = $billManager->deleteBill($billId);
if ($result) {
echo json_encode(['success' => 'Bill deleted successfully']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to delete bill']);
}
exit;
}
break;
default:
http_response_code(404);
echo 'Page not found';
break;
}

80
src/Bill/Bill.php Normal file
View File

@@ -0,0 +1,80 @@
<?php
namespace Hpz937\BillReminder\Bill;
use Hpz937\BillReminder\Database\DatabaseInterface;
use Exception;
class Bill
{
private $db;
public function __construct(DatabaseInterface $db)
{
$this->db = $db;
}
public function addBill($userId, $dueDate, $amount, $description)
{
$sql = "INSERT INTO bills (user_id, due_date, amount, description, is_paid) VALUES (:user_id, :due_date, :amount, :description, 0)";
$params = [
':user_id' => $userId,
':due_date' => $dueDate,
':amount' => $amount,
':description' => $description
];
try {
$this->db->query($sql, $params);
return true;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
public function editBill($billId, $dueDate, $amount, $description)
{
$sql = "UPDATE bills SET due_date = :due_date, amount = :amount, description = :description WHERE id = :id";
$params = [
':due_date' => $dueDate,
':amount' => $amount,
':description' => $description,
':id' => $billId
];
try {
$this->db->query($sql, $params);
return true;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
public function markAsPaid($billId)
{
$sql = "UPDATE bills SET is_paid = 1 WHERE id = :id";
$params = [':id' => $billId];
try {
$this->db->query($sql, $params);
return true;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
public function getBillsByUserId($userId) {
$sql = "SELECT * FROM bills WHERE user_id = :user_id ORDER BY due_date ASC";
$params = [':user_id' => $userId];
try {
return $this->db->query($sql, $params);
} catch (Exception $e) {
// Handle or log the error appropriately
return [];
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Hpz937\BillReminder\Database;
interface DatabaseInterface
{
public function connect();
public function query(string $query, array $params = []);
public function close();
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Hpz937\BillReminder\Database;
use PDO;
use PDOException;
class SQLiteAdapter implements DatabaseInterface
{
private $connection;
public function connect()
{
if ($this->connection === null) {
try {
$this->connection = new PDO('sqlite:' . __DIR__ . '/../../database.db');
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
// In a real application, you might want to use a more sophisticated error handling approach
die("Connection error: " . $e->getMessage());
}
}
return $this->connection;
}
public function query(string $query, array $params = [])
{
$stmt = $this->connect()->prepare($query);
if (!$stmt->execute($params)) {
// Again, consider a more sophisticated error handling in a real application
die("Query error: " . implode(", ", $stmt->errorInfo()));
}
if (preg_match('/^(SELECT|SHOW|PRAGMA)/i', $query)) {
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
return null;
}
public function close()
{
$this->connection = null;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Hpz937\BillReminder\Notification;
use Hpz937\Restclient\RestClient;
use Exception;
class NftyNotification implements NotificationInterface
{
private $restClient;
private $apiKey;
public function __construct(RestClient $restClient, string $apiKey)
{
$this->restClient = $restClient;
$this->apiKey = $apiKey;
// Set the base URL for the nfty.sh API if it's consistent for all requests
$this->restClient->setBaseUrl('https://api.nfty.sh');
// Set the headers required for authentication and content type, if not already set in RestClient
$this->restClient->setHeaders([
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json' // Assuming JSON content type, adjust if necessary
]);
}
public function send(string $to, string $message): bool
{
$endpoint = '/send'; // Adjust the endpoint if necessary
$data = [
'to' => $to,
'message' => $message,
];
try {
$this->restClient->post($endpoint, $data);
$response = $this->restClient->getResponse();
// Assuming the API returns a JSON response indicating success, adjust as necessary
$decodedResponse = $this->restClient->decodeJson();
// Check the decoded response for success indication, adjust the condition based on actual API response structure
return isset($decodedResponse['success']) && $decodedResponse['success'];
} catch (Exception $e) {
// Log or handle the error as needed
return false;
}
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Hpz937\BillReminder\Notification;
interface NotificationInterface
{
public function send(string $to, string $message): bool;
}

69
src/Settings/Settings.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
namespace Hpz937\BillReminder\Settings;
use Hpz937\BillReminder\Database\DatabaseInterface;
use Exception;
class Settings
{
private $db;
public function __construct(DatabaseInterface $db)
{
$this->db = $db;
}
public function saveSettings($userId, $notificationPeriod)
{
// Check if settings already exist for the user
if ($this->settingsExist($userId)) {
// Update existing settings
$sql = "UPDATE settings SET notification_period = :notification_period WHERE user_id = :user_id";
} else {
// Insert new settings
$sql = "INSERT INTO settings (user_id, notification_period) VALUES (:user_id, :notification_period)";
}
$params = [
':user_id' => $userId,
':notification_period' => $notificationPeriod,
];
try {
$this->db->query($sql, $params);
return true;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
public function getSettings($userId)
{
$sql = "SELECT * FROM settings WHERE user_id = :user_id";
$params = [':user_id' => $userId];
try {
$settings = $this->db->query($sql, $params);
return $settings ? $settings[0] : null;
} catch (Exception $e) {
// Handle or log the error appropriately
return null;
}
}
private function settingsExist($userId)
{
$sql = "SELECT id FROM settings WHERE user_id = :user_id";
$params = [':user_id' => $userId];
try {
$result = $this->db->query($sql, $params);
return !empty($result);
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
}

76
src/User/User.php Normal file
View File

@@ -0,0 +1,76 @@
<?php
namespace Hpz937\BillReminder\User;
use Hpz937\BillReminder\Database\DatabaseInterface;
use Exception;
class User
{
private $db;
public function __construct(DatabaseInterface $db)
{
$this->db = $db;
}
public function register($username, $password, $email)
{
// Check if the username or email already exists
if ($this->userExists($username, $email)) {
throw new Exception("Username or email already exists.");
}
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (username, password, email) VALUES (:username, :password, :email)";
$params = [
':username' => $username,
':password' => $hashedPassword,
':email' => $email
];
try {
$this->db->query($sql, $params);
return true;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
public function login($username, $password)
{
$sql = "SELECT * FROM users WHERE username = :username";
$params = [':username' => $username];
try {
$user = $this->db->query($sql, $params);
if ($user && password_verify($password, $user[0]['password'])) {
// Set session or token here as per your session management strategy
$_SESSION['user_id'] = $user[0]['id'];
return true;
}
return false;
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
private function userExists($username, $email)
{
$sql = "SELECT id FROM users WHERE username = :username OR email = :email";
$params = [
':username' => $username,
':email' => $email
];
try {
$result = $this->db->query($sql, $params);
return !empty($result);
} catch (Exception $e) {
// Handle or log the error appropriately
return false;
}
}
}

14
views/auth/login.php Normal file
View File

@@ -0,0 +1,14 @@
<?php $content = function() { ?>
<h2>Login</h2>
<form action="/login" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<?php }; include __DIR__ . '/../layouts/app.php'; ?>

42
views/auth/register.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
// Check if the user is already logged in
if (isset($_SESSION['user_id'])) {
// Redirect to the dashboard if the user is already logged in
header('Location: /dashboard');
exit;
}
// Include error handling
$error = $_SESSION['error'] ?? '';
unset($_SESSION['error']); // Clear the error after displaying
$content = function() use ($error) { ?>
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="mt-5">Register</h2>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form action="/register" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
<p class="mt-3">Already have an account? <a href="/">Login here</a>.</p>
</div>
</div>
<?php };
include __DIR__ . '/../layouts/app.php';

74
views/dashboard.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
use Hpz937\BillReminder\Database\SqLiteAdapter;
use Hpz937\BillReminder\Bill\Bill;
// Assuming session_start() is called in the front controller or app.php
// Redirect to login page if the user is not logged in
if (!isset($_SESSION['user_id'])) {
header('Location: /');
exit;
}
$content = function() { /* use ($bills) if fetching bills from the database */
?>
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mt-5">Dashboard</h2>
<button type="button" class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#addBillModal">Add New Bill</button>
<!-- Bills Table -->
<table class="table" id="billsTable">
<thead>
<tr>
<th scope="col">Description</th>
<th scope="col">Amount</th>
<th scope="col">Due Date</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<!-- Bills will be loaded here by Axios -->
</tbody>
</table>
<!-- Add Bill Modal -->
<div class="modal fade" id="addBillModal" tabindex="-1" role="dialog" aria-labelledby="addBillModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addBillModalLabel">Add New Bill</h5>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form action="/add-bill" method="post">
<div class="modal-body">
<div class="form-group">
<label for="description">Description</label>
<input type="text" class="form-control" id="description" name="description" required>
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input type="number" class="form-control" id="amount" name="amount" step="0.01" required>
</div>
<div class="form-group">
<label for="due_date">Due Date</label>
<input type="date" class="form-control" id="due_date" name="due_date" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Add Bill</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<?php };
include __DIR__ . '/layouts/app.php';

30
views/home.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
// Assuming session_start() is called in the front controller or app.php
$content = function() { ?>
<div class="jumbotron">
<h1 class="display-4">Welcome to Bill Reminder!</h1>
<p class="lead">Never miss a payment again with our easy-to-use bill management system.</p>
<hr class="my-4">
<p>Get started by logging in or registering a new account.</p>
<a class="btn btn-primary btn-lg" href="/login" role="button">Login</a>
<a class="btn btn-secondary btn-lg" href="/register" role="button">Register</a>
</div>
<div class="row">
<div class="col-md-4">
<h2>Track Your Bills</h2>
<p>Keep all your bills in one place and get reminders so you can pay them on time, every time.</p>
</div>
<div class="col-md-4">
<h2>Easy to Use</h2>
<p>Our intuitive interface makes managing your bills simple, whether you're at home or on the go.</p>
</div>
<div class="col-md-4">
<h2>Secure</h2>
<p>Your security is our top priority. We use state-of-the-art security measures to protect your information.</p>
</div>
</div>
<?php };
include __DIR__ . '/layouts/app.php';

32
views/layouts/app.php Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bill Reminder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Bill Reminder</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" href="/dashboard">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="/settings">Settings</a></li>
</ul>
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
</ul>
</div>
</nav>
<div class="container">
<?php if (isset($content) && is_callable($content)) $content(); ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="/assets/js/app.js"></script>
</body>
</html>