diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..50f6ff3e124a57a12b30c92a71c01d629999d9c2 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +DB_HOST='' +DB_PORT='' +DB_NAME='' +DB_USER='' +DB_PASSWORD='' + +POSTGRES_USER='' +POSTGRES_PASSWORD='' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2eea525d885d5148108f6f3a9a8613863f783d36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2e3ddd6d8b2132e3c4b53de7ba0e9ce61e60670a..36b899df10b0e4b44121cfa31f4e4d5cecc82e84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,11 @@ -FROM php:8.0-apache \ No newline at end of file +FROM php:8.0-apache + +# PHP extensions + +RUN apt-get update + +# Install Postgre PDO +RUN apt-get install -y libpq-dev \ + && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ + && docker-php-ext-install pdo pdo_mysql pdo_pgsql pgsql +RUN a2enmod rewrite \ No newline at end of file diff --git a/README.md b/README.md index 5614db31c485058c8277b93c72a82d02c6d04a21..3463d6491b38edd8bbd129aa8de42eda246eb59f 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,128 @@ -## Panduan Pengerjaan -Berikut adalah hal yang harus diperhatikan untuk pengumpulan tugas ini: -1. Buatlah grup pada Gitlab dengan format "IF3110-2023-01-XX", dengan XX adalah nomor kelompok (untuk K1 dan K2) atau kode kelompok (untuk K3). -2. Tambahkan anggota tim pada grup anda. -3. **Fork** pada repository ini dengan organisasi yang telah dibuat. -4. Ubah hak akses repository hasil Fork anda menjadi **private**. -5. Hal-hal yang harus diperhatikan. - * Silakan commit pada repository anda (hasil fork) - * Lakukan beberapa commit dengan pesan yang bermakna, contoh: “add register formâ€, “fix logout bugâ€, jangan seperti “finalâ€, “benerin dikitâ€, “fix bugâ€. - * Disarankan untuk tidak melakukan commit dengan perubahan yang besar karena akan mempengaruhi penilaian (contoh: hanya melakukan satu commit kemudian dikumpulkan). - * Commit dari setiap anggota tim akan mempengaruhi penilaian. - * Jadi, setiap anggota tim harus melakukan commit yang berpengaruh terhadap proses pembuatan aplikasi. - * Sebagai panduan bisa mengikuti [semantic commit](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716). -6. Buatlah file README yang berisi: - * Deskripsi aplikasi web - * Daftar requirement - * Cara instalasi - * Cara menjalankan server - * Screenshot tampilan aplikasi (tidak perlu semua kasus, minimal 1 per halaman), dan - * Penjelasan mengenai pembagian tugas masing-masing anggota (lihat formatnya pada bagian pembagian tugas). +# KBL E-Commerce +Tugas Besar 1 IF3110 Web Based Development +<br /> + +## Table of Contents +* [General Info](#general-information) +* [Tampilan Program](#tampilan-program) +* [How To Install](#how-to-install) +* [How To Run](#how-to-run) +* [Requirements](#requirements) +* [Tech Stack](#tech-stack) +* [Tampilan Google Lighthouse](#tampilan-google-lighthouse) +* [Pembagian Tugas](#pembagian-tugas) +* [Credits](#credits) + +## General Information +KBL adalah sebuah website e-commerce. Disini, pengguna dapat membeli produk-produk yang telah disediakan pada website. Untuk dapat membeli barang, pengguna perlu membuat akun dan melakukan login. Sebelum dapat membeli barang, pengguna perlu melakukan request top up untuk mengisi balance akun. Request top up ini hanya bisa disetujui atau ditolak oleh akun dengan role admin. + +## Tampilan Program +* Home +>  +* Signup +>  +* Login +>  +* User Home +>  +* Search Product +>  +* Product Filter +>  +* Topup +>  +* History +>  +* Admin Home +>  +* Edit Product +>  +* Handle Topup +>  +* Edit User +>  +* Account Settings +>  + +## How To Install +* Unduh Docker Desktop di situs resminya dan lakukan instalasi. +* Unduh PostgreSQL di situs resminya dan lakukan instalasi. + +## How To Run +In terminal, run: +```shell +docker-compose up -d +``` +After that, open http://localhost:8000/client/pages/home/ in your browser. + +## Requirements +* Docker Desktop +* PostgreSQL + +## Tech Stack +* HTML +* CSS +* JavaScript +* PHP + +## Tampilan Google Lighthouse +* Home +>  +* Signup +>  +* Login +>  +* User Home +>  +* Search Product +>  +* Product Filter +>  +* TopUp +>  +* History +>  +* Admin Home +>  +* Edit Product +>  +* Handle Topup +>  +* Edit User +>  +* Settings +>  + +## Pembagian Tugas +Server-side: +* Admin-Product-CRUD: 13521049 +* Admin-Top-Up-CRUD: 13521049 +* Admin-User-CRUD: 13521049 +* Register: 13521064 +* Login: 13521064 +* Settings: 13521064 +* History: 13521064 +* Core Database : 13521108 +* Docker : 13521108 +* Routing : 13521108 +* Home (Filter-sort-search-pagination) : 13521108 +* User-Product : 13521108 + +Client-side: +* Admin-Product-CRUD: 13521049 +* Admin-Top-Up-CRUD: 13521049 +* Admin-User-CRUD: 13521049 +* Register: 13521064 +* Login: 13521064 +* Settings: 13521064 +* History: 13521064 +* Docker : 13521108 +* Routing : 13521108 +* Home (Filter-sort-search-pagination) : 13521108 +* User-Product : 13521108 + +## Credits +This project is implemented by: +1. Brian Kheng (13521049) +2. Bill Clinton (13521064) +3. Michael Leon Putra Widhi (13521108) diff --git a/docker-compose.yml b/docker-compose.yml index 7c5e0aeeb93e6ef1a07efefaf9b94d87a8878aad..62fb587350c408594cee7d649b8730eadc6ecd4e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,22 @@ -version: '3.3' +version: '3.8' services: web: - image: tubes-1:latest + build: + context: ./ + dockerfile: Dockerfile + container_name: web + env_file: .env + volumes: + - ./scripts:/var/www/html/ ports: - - 8008:80 + - 8000:80 + depends_on: + - database + + database: + container_name: database + image: postgres:latest + ports: + - '5432:5432' + hostname: database + env_file: .env diff --git a/screenshots/edit-product.png b/screenshots/edit-product.png new file mode 100644 index 0000000000000000000000000000000000000000..d1df68361a4048b7882c3383ddc302661c96eb8c Binary files /dev/null and b/screenshots/edit-product.png differ diff --git a/screenshots/edit-user.png b/screenshots/edit-user.png new file mode 100644 index 0000000000000000000000000000000000000000..b5fe2fb17e0d39e225301f53c0fea7bec292de60 Binary files /dev/null and b/screenshots/edit-user.png differ diff --git a/screenshots/handle-topup.png b/screenshots/handle-topup.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd02b43183fe3e98ec50322a77558debd521311 Binary files /dev/null and b/screenshots/handle-topup.png differ diff --git a/screenshots/history.png b/screenshots/history.png new file mode 100644 index 0000000000000000000000000000000000000000..83ebae707dbeba6879687194f37d3e8ba0bd8083 Binary files /dev/null and b/screenshots/history.png differ diff --git a/screenshots/home-admin.png b/screenshots/home-admin.png new file mode 100644 index 0000000000000000000000000000000000000000..e325d042558ee780e5762bc0e7a01149711b944d Binary files /dev/null and b/screenshots/home-admin.png differ diff --git a/screenshots/home-user.png b/screenshots/home-user.png new file mode 100644 index 0000000000000000000000000000000000000000..d294ae7e559782a5184ce5c4499790349406ad44 Binary files /dev/null and b/screenshots/home-user.png differ diff --git a/screenshots/home.png b/screenshots/home.png new file mode 100644 index 0000000000000000000000000000000000000000..35353e7c83a8230af1d19b9f0fdebd28abd4e2a6 Binary files /dev/null and b/screenshots/home.png differ diff --git a/screenshots/lighthouse-edit-product.jpg b/screenshots/lighthouse-edit-product.jpg new file mode 100644 index 0000000000000000000000000000000000000000..047a72e89c8402b347ce6b5bb2e14584a0f69f64 Binary files /dev/null and b/screenshots/lighthouse-edit-product.jpg differ diff --git a/screenshots/lighthouse-edit-user.jpg b/screenshots/lighthouse-edit-user.jpg new file mode 100644 index 0000000000000000000000000000000000000000..214ccfa27ec893f86a090b6041d6e65e31f61bf1 Binary files /dev/null and b/screenshots/lighthouse-edit-user.jpg differ diff --git a/screenshots/lighthouse-handle-topup.jpg b/screenshots/lighthouse-handle-topup.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5fda614e6ca4949c9e51d77ad7247b3d87b616e Binary files /dev/null and b/screenshots/lighthouse-handle-topup.jpg differ diff --git a/screenshots/lighthouse-history.jpg b/screenshots/lighthouse-history.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05878361614e633ff474b292ba17865acc9b7841 Binary files /dev/null and b/screenshots/lighthouse-history.jpg differ diff --git a/screenshots/lighthouse-home-admin.jpg b/screenshots/lighthouse-home-admin.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bf1a397b134f4cc29d1bf17a965c78f7e62bbdb Binary files /dev/null and b/screenshots/lighthouse-home-admin.jpg differ diff --git a/screenshots/lighthouse-home-user.jpg b/screenshots/lighthouse-home-user.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c49009766a5fde68438c861f7245d3414c4a6db Binary files /dev/null and b/screenshots/lighthouse-home-user.jpg differ diff --git a/screenshots/lighthouse-home.jpg b/screenshots/lighthouse-home.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93f65a1c9321782ca8da27966b3836db215f1e3b Binary files /dev/null and b/screenshots/lighthouse-home.jpg differ diff --git a/screenshots/lighthouse-login.jpg b/screenshots/lighthouse-login.jpg new file mode 100644 index 0000000000000000000000000000000000000000..581174a57d5901cb6c86aeffbf294bbcec3a9536 Binary files /dev/null and b/screenshots/lighthouse-login.jpg differ diff --git a/screenshots/lighthouse-product-filter.jpg b/screenshots/lighthouse-product-filter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e5386e2faeb24254e61528b6ad078ced9e8eb12 Binary files /dev/null and b/screenshots/lighthouse-product-filter.jpg differ diff --git a/screenshots/lighthouse-search-product.jpg b/screenshots/lighthouse-search-product.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f67d0a36c7504f174fbfeda4894d1cd7550e1ec0 Binary files /dev/null and b/screenshots/lighthouse-search-product.jpg differ diff --git a/screenshots/lighthouse-settings.jpg b/screenshots/lighthouse-settings.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71ab460b516ba2f6f9677e466701bf3c20bf4fd0 Binary files /dev/null and b/screenshots/lighthouse-settings.jpg differ diff --git a/screenshots/lighthouse-signup.jpg b/screenshots/lighthouse-signup.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a6a327e57f0ad04dbdb44d7e17b789b3f973923 Binary files /dev/null and b/screenshots/lighthouse-signup.jpg differ diff --git a/screenshots/lighthouse-topup.jpg b/screenshots/lighthouse-topup.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5e1f328a9859083713fb28ed0f86029f1ef94821 Binary files /dev/null and b/screenshots/lighthouse-topup.jpg differ diff --git a/screenshots/login.png b/screenshots/login.png new file mode 100644 index 0000000000000000000000000000000000000000..212218a976431eed0fc9826688a4587db51845d7 Binary files /dev/null and b/screenshots/login.png differ diff --git a/screenshots/product-filter.png b/screenshots/product-filter.png new file mode 100644 index 0000000000000000000000000000000000000000..1ac9017ebcd990d8eaea11a52b89b1e66f8824e0 Binary files /dev/null and b/screenshots/product-filter.png differ diff --git a/screenshots/search-product.png b/screenshots/search-product.png new file mode 100644 index 0000000000000000000000000000000000000000..37c32739354a49fc0877e297ed5c54dee24be79c Binary files /dev/null and b/screenshots/search-product.png differ diff --git a/screenshots/settings.png b/screenshots/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..b8fbfeca8a4b4fdc5beb5c092c45ec771cd71a00 Binary files /dev/null and b/screenshots/settings.png differ diff --git a/screenshots/signup.png b/screenshots/signup.png new file mode 100644 index 0000000000000000000000000000000000000000..9a51e9e183a0c4ed8fdcabbf2560e98fbd5868db Binary files /dev/null and b/screenshots/signup.png differ diff --git a/screenshots/topup.png b/screenshots/topup.png new file mode 100644 index 0000000000000000000000000000000000000000..509215f9567f654ac4b3ace26bf9a822f25a21ec Binary files /dev/null and b/screenshots/topup.png differ diff --git a/scripts/.htaccess b/scripts/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..fd11ebfe155bb901305a389c3156f9c67f6b9945 --- /dev/null +++ b/scripts/.htaccess @@ -0,0 +1,12 @@ +Options -Multiviews + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^$ /client/ [L] +RewriteRule ^pages/(.*)$ /client/pages/$1 [L,NC] +RewriteRule ^public/(.*)$ /client/public/$1 [L,NC] +RewriteRule ^api/(.*)$ /server/api/$1 [L,NC] + +php_value upload_max_filesize 300M +php_value post_max_size 300M \ No newline at end of file diff --git a/scripts/build-image.sh b/scripts/build-image.sh deleted file mode 100644 index ce096bac1ad2e7c6eec731197a2b126c245fa237..0000000000000000000000000000000000000000 --- a/scripts/build-image.sh +++ /dev/null @@ -1 +0,0 @@ -docker build -t tubes-1:latest . \ No newline at end of file diff --git a/scripts/client/pages/admin-product-create/index.php b/scripts/client/pages/admin-product-create/index.php new file mode 100644 index 0000000000000000000000000000000000000000..6fdf8b306838ac7b79bfdcb89f1d268b952e2f8b --- /dev/null +++ b/scripts/client/pages/admin-product-create/index.php @@ -0,0 +1,44 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-product-create.css'; ?></style> + +<section id="create-product"> + <div class="create-product-container"> + <div class="create-product-header"> + <div class="create-product-title">Create Product</div> + <svg onclick="window.location.href = '/pages/admin-product'" height="25px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="25px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/></svg> + </div> + <div class="create-product-form-container"> + <form id="product-form" enctype="multipart/form-data" onsubmit="createProduct(event)"> + <label for="name">Product Name <span class="required-field">*</span></label> + <input type="text" name="name" required> + + <label for="description">Description <span class="required-field">*</span></label> + <textarea name="description" required></textarea> + + <label for="price">Price <span class="required-field">*</span></label> + <input type="number" name="price" required min="0"> + + <label for="stock">Stock <span class="required-field">*</span></label> + <input type="number" name="stock" required min="0"> + + <!-- Dropdown Category --> + <label for="idCategory">Category <span class="required-field">*</span></label> + <select name="idCategory" id="category-dropdown" required></select> + + <!-- Image/ Video --> + <label for="image">Image/ Video</label> + <input type="file" name="image" accept="image/*, video/mp4"> + + <!-- Error Message --> + <div id="error-message"></div> + + <button type="submit">Submit</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-product-create.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/admin-product-edit/index.php b/scripts/client/pages/admin-product-edit/index.php new file mode 100644 index 0000000000000000000000000000000000000000..39407dd1ba6cf11f645ca4025b1853f281fb39ac --- /dev/null +++ b/scripts/client/pages/admin-product-edit/index.php @@ -0,0 +1,44 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-product-edit.css'; ?></style> + +<section id="edit-product"> + <div class="edit-product-container"> + <div class="edit-product-header"> + <div class="edit-product-title">Edit Product</div> + <svg onclick="window.location.href = '/pages/admin-product'" height="25px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="25px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/></svg> + </div> + <div class="edit-product-form-container"> + <form id="product-form" enctype="multipart/form-data" onsubmit="editProduct(event)"> + <label for="name">Product Name <span class="required-field">*</span></label> + <input type="text" name="name" required> + + <label for="description">Description <span class="required-field">*</span></label> + <textarea name="description" required></textarea> + + <label for="price">Price <span class="required-field">*</span></label> + <input type="number" name="price" required min="0"> + + <label for="stock">Stock <span class="required-field">*</span></label> + <input type="number" name="stock" required min="0"> + + <!-- Dropdown Category --> + <label for="idCategory">Category <span class="required-field">*</span></label> + <select name="idCategory" id="category-dropdown" required></select> + + <!-- Image/ Video --> + <label for="image">Image/ Video</label> + <input type="file" name="image" accept="image/*, video/mp4"> + + <!-- Error Message --> + <div id="error-message"></div> + + <button type="submit">Submit</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-product-edit.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/admin-product/index.php b/scripts/client/pages/admin-product/index.php new file mode 100644 index 0000000000000000000000000000000000000000..20f9f30e79d5a827e63cc02a7a891c4ff8f1266c --- /dev/null +++ b/scripts/client/pages/admin-product/index.php @@ -0,0 +1,14 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-product.css'; ?></style> + +<div id="product-header"> + <h1 id="product-title">Admin's Product</h1> + <button id="product-create-btn" onclick="redirectToCreateProduct()">Create Product</button> +</div> +<div id="product-container"></div> +<div id="pagination-container"></div> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-product.js"></script> diff --git a/scripts/client/pages/admin-top-up-create/index.php b/scripts/client/pages/admin-top-up-create/index.php new file mode 100644 index 0000000000000000000000000000000000000000..ea27fdc1c224ee0032b5255a89bb7c65661ce1dd --- /dev/null +++ b/scripts/client/pages/admin-top-up-create/index.php @@ -0,0 +1,34 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-top-up-create.css'; ?></style> + +<section id="create-top-up"> + <div class="create-top-up-container"> + <div class="create-top-up-header"> + <div class="create-top-up-title">Create Top Up</div> + <svg onclick="window.location.href = '/pages/admin-top-up'" height="25px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="25px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/></svg> + </div> + <div class="create-top-up-form-container"> + <form id="top-up-form" enctype="multipart/form-data" onsubmit="createTopUp(event)"> + <label for="username">Username <span class="required-field">*</span></label> + <input type="text" name="username" required> + + <label for="amount">Amount <span class="required-field">*</span></label> + <input type="number" name="amount" min="1" required> + + <!-- Dropdown Status --> + <label for="status">Status <span class="required-field">*</span></label> + <select name="status" id="status-dropdown" required></select> + + <!-- Error Message --> + <div id="error-message"></div> + + <button type="submit">Submit</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-top-up-create.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/admin-top-up/index.php b/scripts/client/pages/admin-top-up/index.php new file mode 100644 index 0000000000000000000000000000000000000000..4751e10b8acf49440c38e65b15927c197a08eb4a --- /dev/null +++ b/scripts/client/pages/admin-top-up/index.php @@ -0,0 +1,14 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-top-up.css'; ?></style> + +<div id="top-up-header"> + <h1 id="top-up-title">Admin's Top Up</h1> + <button id="top-up-create-btn" onClick="redirectToCreateTopUp()">Create Top Up</button> +</div> +<div id="top-up-container"></div> +<div id="pagination-container"></div> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-top-up.js"></script> diff --git a/scripts/client/pages/admin-user-create/index.php b/scripts/client/pages/admin-user-create/index.php new file mode 100644 index 0000000000000000000000000000000000000000..1f4070be8ef11b2ddcc4b092c86d0c191eb3a4dd --- /dev/null +++ b/scripts/client/pages/admin-user-create/index.php @@ -0,0 +1,36 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-user-create.css'; ?></style> + +<section id="create-user"> + <div class="create-user-container"> + <div class="create-user-header"> + <div class="create-user-title">Create User</div> + <svg onclick="window.location.href = '/pages/admin-user'" height="25px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="25px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/></svg> + </div> + <div class="create-user-form-container"> + <form id="user-form" enctype="multipart/form-data" onsubmit="createUser(event)"> + <label for="username">Username <span class="required-field">*</span></label> + <input type="text" name="username" required> + + <label for="password">Password <span class="required-field">*</span></label> + <input type="password" name="password" required></input> + + <label for="name">Name <span class="required-field">*</span></label> + <input type="text" name="name" required> + + <label for="balance">Balance <span class="required-field">*</span></label> + <input type="number" name="balance" required min="0"> + + <!-- Error Message --> + <div id="error-message"></div> + + <button type="submit">Submit</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-user-create.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/admin-user-edit/index.php b/scripts/client/pages/admin-user-edit/index.php new file mode 100644 index 0000000000000000000000000000000000000000..a2f555cd8e6c9e77f4dc418f926364b421a1529c --- /dev/null +++ b/scripts/client/pages/admin-user-edit/index.php @@ -0,0 +1,33 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-user-edit.css'; ?></style> + +<section id="edit-user"> + <div class="edit-user-container"> + <div class="edit-user-header"> + <div class="edit-user-title">Edit User</div> + <svg onclick="window.location.href = '/pages/admin-user'" height="25px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="25px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/></svg> + </div> + <div class="edit-user-form-container"> + <form id="user-form" enctype="multipart/form-data" onsubmit="editUser(event)"> + <label for="username">Username <span class="required-field">*</span></label> + <input type="text" name="username" required> + + <label for="name">Name <span class="required-field">*</span></label> + <input type="text" name="name" required> + + <label for="balance">Balance <span class="required-field">*</span></label> + <input type="number" name="balance" required min="0"> + + <!-- Error Message --> + <div id="error-message"></div> + + <button type="submit">Submit</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-user-edit.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/admin-user/index.php b/scripts/client/pages/admin-user/index.php new file mode 100644 index 0000000000000000000000000000000000000000..c638c648653c59bec00a4c4095317f98e75c258e --- /dev/null +++ b/scripts/client/pages/admin-user/index.php @@ -0,0 +1,14 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> +<style><?php include '../../public/css/pages/admin-user.css'; ?></style> + +<div id="user-header"> + <h1 id="user-title">Admin's User</h1> + <button id="user-create-btn" onClick="redirectToCreateUser()">Create User</button> +</div> +<div id="user-container"></div> +<div id="pagination-container"></div> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script type="text/javascript" src="/../../public/js/admin-user.js"></script> diff --git a/scripts/client/pages/detailProduct/index.php b/scripts/client/pages/detailProduct/index.php new file mode 100644 index 0000000000000000000000000000000000000000..e997507ede05d28560826124553ad3e99bcd62d9 --- /dev/null +++ b/scripts/client/pages/detailProduct/index.php @@ -0,0 +1,40 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<section id="detailProduct"> + <div class="detailContainer"> + <div class="detailImage"> + <img id="productImage"></img> + </div> + <div class="productInfo"> + <div id="productName"></div> + <div class="productStockCategory"> + <div class="categorySec"> + <img src="/public/images/category.png" class="detailIcon"> + <div id="productCategory"></div> + </div> + <div class="stockSec"> + <img src="/public/images/quantity.png" class="detailIcon"> + <div id="productStock"></div> + </div> + </div> + <div id="productPrice"></div> + <div id="productDesc"></div> + <div class="buySection"> + <div class="amountsec"> + <div type="button" class="buttonOp" onclick="subsAmount()">-</div> + <input type="text" id="numberamount" name="Amount" aria-required="true" readonly> + <div type="button" class="buttonOp" onclick="addAmount()">+</div> + </div> + <div type="button" class="buttonSec" onclick="buyProduct()">Buy</div> + <div> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + /* required scripts */ + <?php include '../../public/js/detailProduct.js'; ?> +</script> \ No newline at end of file diff --git a/scripts/client/pages/history/index.php b/scripts/client/pages/history/index.php new file mode 100644 index 0000000000000000000000000000000000000000..bd707fc23da41e0f74f095fd135e147d3e402634 --- /dev/null +++ b/scripts/client/pages/history/index.php @@ -0,0 +1,34 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<style> + <?php include '../../public/css/pages/history.css'; ?> +</style> + +<section id="history"> + <div class="history-ctn"> + <div class="history-title">History</div> + <hr class="separator"> + <div class="buy-history"> + <div class="buy-history-title">Buy History</div> + <div id="buy-history-grid"> + <div class="col-name">Date</div> + <div class="col-name">Total</div> + </div> + </div> + <hr class="table-separator"> + <div class="topup-history"> + <div class="topup-history-title">Top Up</div> + <div id="topup-history-grid"> + <div class="col-name">Date</div> + <div class="col-name">Amount</div> + </div> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + <?php include '../../public/js/history.js'; ?> +</script> diff --git a/scripts/client/pages/home/index.php b/scripts/client/pages/home/index.php new file mode 100644 index 0000000000000000000000000000000000000000..7218e2ab0305a674ad5e4c72d01cd05818e5d936 --- /dev/null +++ b/scripts/client/pages/home/index.php @@ -0,0 +1,90 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<style> + /* page css part */ + <?php include '../../public/css/pages/product.css'; ?> +</style> + +<section id="product"> + <div class="queryCt"> + <div class="productMenu"> + <div class="pageTitle">List of Products</div> + <div class="queryMenu"> + <div class="dropdown1"> + <input class="filter-category" id="catlast" type="text" placeholder="Select filter category" readonly> + <div class="options" id="categoryFilter"> + <!-- List of category --> + </div> + </div> + <div class="dropdown2"> + <input class="filter-price" id="prilast" type="text" placeholder="Select filter price" readonly> + <div class="options"> + <div onclick="showFilterPrice('< 5K')">< 5K</div> + <div onclick="showFilterPrice('5K - 30K')">5K - 30K</div> + <div onclick="showFilterPrice('30K - 100K')">30K - 100K</div> + <div onclick="showFilterPrice('> 100K')">> 100K</div> + </div> + </div> + <div class="dropdown3"> + <input class="sort-type" id="sortip" type="text" placeholder="Select sort method" readonly> + <div class="options"> + <div onclick="showSort('Name (A to Z)')">Name (A to Z)</div> + <div onclick="showSort('Name (Z to A)')">Name (Z to A)</div> + <div onclick="showSort('Price (Lowest First)')">Price (Lowest First)</div> + <div onclick="showSort('Price (Highest First)')">Price (Highest First)</div> + </div> + </div> + <div class="filterCollapse"> + <div class="filterParent" id="startquery" type="button">Apply Filter</div> + </div> + </div> + </div> + <div class="listTitle">Showing <input class="numsie" type="text" id="numsofObj" value="" placeholder="0" readonly> items...</div> + <div id="queryResultProduct" class="queryResultProduct"></div> + <div class="pagination" id="pagenumProduct"></div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + /* required scripts */ + <?php include '../../public/js/product.js'; ?> +</script> + +<script> + // Function to populate the category dropdown + function populateCategoryDropdown() { + $.ajax({ + url: 'http://localhost:8000/api/CategoryController/showAllcategories', + method: 'GET', + dataType: 'json', + success: function (data) { + // Assuming the response data is an array of category names + var categoryDropdown = $('#categoryFilter'); + + // Clear existing divs + categoryDropdown.empty(); + + // Populate the dropdown with category names + $.each(data.data, function (index, category) { + categoryDropdown.append($('<div>', { + text: category.name, + click: function () { + showFilterCategory(category.name); + } + })); + }); + }, + error: function (error) { + console.error('Error fetching categories: ' + error.statusText); + } + }); + } + + // Call the function to populate the category dropdown when the page loads + $(document).ready(function () { + populateCategoryDropdown(); + }); +</script> \ No newline at end of file diff --git a/scripts/client/pages/login/index.php b/scripts/client/pages/login/index.php new file mode 100644 index 0000000000000000000000000000000000000000..949ef7ab45900f34477bfbd620e45e0f53df0e1a --- /dev/null +++ b/scripts/client/pages/login/index.php @@ -0,0 +1,36 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<style> + <?php include '../../public/css/pages/login.css'; ?> +</style> + +<section id="login"> + <div class="lgn-ctn"> + <div class="lgn-title">Welcome Back!</div> + <div class="lgn-sub-title">We're excited to see you again!</div> + <div class="lgn-form-ctn"> + <form id="login-form" method="post" action="index.php" role="form"> + <label for="username">USERNAME <span class="required-field">*</span></label> + <input type="text" id="username" name="username" aria-required="true"> + + <label for="password">PASSWORD <span class="required-field">*</span></label> + <input type="password" id="password" name="password" aria-required="true"> + + <div class="space"></div> + + <button type="submit">Login</button> + </form> + </div> + + <div class="reg-link-ctn"> + <p>Need an account? <a href="../signup/index.php" class="reg-link">Signup</a></p> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + <?php include '../../public/js/login.js'; ?> +</script> \ No newline at end of file diff --git a/scripts/client/pages/settings/index.php b/scripts/client/pages/settings/index.php new file mode 100644 index 0000000000000000000000000000000000000000..6da137a339bd50b9c8bfb4939710765bd78e8339 --- /dev/null +++ b/scripts/client/pages/settings/index.php @@ -0,0 +1,39 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<style> + <?php include '../../public/css/pages/settings.css'; ?> +</style> + +<section id="settings"> + <div class="acct-ctn"> + <div class="acct-title">Account</div> + <hr class="separator"> + <div class="acct-form-ctn"> + <form id="settings-form" method="post" action="index.php" class="grid-container" role="form"> + <label for="username">Username</label> + <input type="text" id="username" name="username" readonly disabled> + + <label for="name">Name</label> + <input type="text" id="name" name="name" aria-required="true"> + + <label for="password">Password</label> + <input type="password" id="password" name="password" aria-required="true"> + + <label for="confirm-password">Confirm Password</label> + <input type="password" id="confirm-password" name="confirm-password" aria-required="true"> + + <label for="balance">Balance</label> + <input type="text" id="balance" name="balance" readonly disabled> + + <button type="submit">Save</button> + </form> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + <?php include '../../public/js/settings.js'; ?> +</script> diff --git a/scripts/client/pages/signup/index.php b/scripts/client/pages/signup/index.php new file mode 100644 index 0000000000000000000000000000000000000000..ae32756aaa80e31d02a17a80adf77cd5fa7e95f3 --- /dev/null +++ b/scripts/client/pages/signup/index.php @@ -0,0 +1,39 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<style> + <?php include '../../public/css/pages/signup.css'; ?> +</style> + +<section id="signup"> + <div class="reg-ctn"> + <h1 class="reg-title">Create an Account</h1> + <div class="reg-form-ctn"> + <form id="register-form" method="post" action="index.php" role="form"> + <label for="username">USERNAME <span class="required-field">*</span></label> + <input type="text" id="username" name="username" aria-required="true"> + + <label for="password">PASSWORD <span class="required-field">*</span></label> + <input type="password" id="password" name="password" aria-required="true"> + + <label for="name">DISPLAY NAME <span class="required-field">*</span ></label> + <input type="text" id="name" name="name" aria-required="true"> + + <!-- Error Message --> + <div id="error-message" role="alert"></div> + + <button type="submit">Sign Up</button> + </form> + </div> + + <div class="lgn-link-ctn"> + <p>Have an account? <a href="../login/index.php" class="lgn-link">Login</a></p> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + <?php include '../../public/js/signup.js'; ?> +</script> diff --git a/scripts/client/pages/template/footer.php b/scripts/client/pages/template/footer.php new file mode 100644 index 0000000000000000000000000000000000000000..6d1ea5d48f0b33e03e77cfef183b7eddf7597b3d --- /dev/null +++ b/scripts/client/pages/template/footer.php @@ -0,0 +1,6 @@ +<script> + /* required scripts */ + <?php include '../../public/js/navbar.js'; ?> + <?php include '../../public/js/navigation.js'; ?> +</script> +<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> \ No newline at end of file diff --git a/scripts/client/pages/template/header.php b/scripts/client/pages/template/header.php new file mode 100644 index 0000000000000000000000000000000000000000..03a45410d0ac10cbb40fc584dc1c0187969cd9e0 --- /dev/null +++ b/scripts/client/pages/template/header.php @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="icon" type="image/png" href="/../public/images/logo.png"> + + <link href='https://fonts.googleapis.com/css?family=Poppins' rel='stylesheet'> + <style> + /* global css part */ + <?php include '../../public/css/globals.css'; ?> + <?php include '../../public/css/colors.css'; ?> + <?php include '../../public/css/typography.css'; ?> + <?php include '../../public/css/animations.css'; ?> + + /* components css part */ + <?php include '../../public/css/components/navbar.css'; ?> + + /* page css part */ + <?php include '../../public/css/pages/product.css'; ?> + <?php include '../../public/css/pages/detailProduct.css'; ?> + <?php include '../../public/css/pages/topup.css'; ?> + </style> + + <title><?= $data['title'] ?? "KBL" ?></title> +</head> + +<body> \ No newline at end of file diff --git a/scripts/client/pages/template/layout.php b/scripts/client/pages/template/layout.php new file mode 100644 index 0000000000000000000000000000000000000000..5049743cad9272448ce3359f59916e2e5e39b88a --- /dev/null +++ b/scripts/client/pages/template/layout.php @@ -0,0 +1,12 @@ +<?php require_once __DIR__ . "/header.php" ?> +<?php require_once __DIR__ . "/navbar.php" ?> + +<div class="layout-container"> + <div class="content-container"> + <div class="content"> + <?= $this->fetch($data) ?> + </div> + </div> +</div> + +<?php require_once __DIR__ . "/footer.php" ?> \ No newline at end of file diff --git a/scripts/client/pages/template/navbar.php b/scripts/client/pages/template/navbar.php new file mode 100644 index 0000000000000000000000000000000000000000..be776899fe8091eac9702008261949d67d4bace9 --- /dev/null +++ b/scripts/client/pages/template/navbar.php @@ -0,0 +1,18 @@ +<body onload="initPage()"> + <nav class="navbar"> + <div class="navCt"> + <div class="navLeft"> + <div class="navLogoSide" onclick="redirectToHome()"> + <img class="navLogo" src="../../public/images/logo.png" alt="logo"/> + <h1 class="navTitle">KBL</h1> + </div> + <div class="navSearch"> + <img class="navSearchIcon" id="productqueryimg" src="../../public/images/search-black.png" alt="search"/> + <input class="navSearchInput" type="text" id="queryproduct" placeholder=" Search for products or categories" > + </div> + </div> + <div class="navRight"> + <!-- Content will be injected here using JavaScript --> + </div> + </div> + </nav> \ No newline at end of file diff --git a/scripts/client/pages/topup/index.php b/scripts/client/pages/topup/index.php new file mode 100644 index 0000000000000000000000000000000000000000..1050ce4df6f4774ac0296db4311d71e612f74d06 --- /dev/null +++ b/scripts/client/pages/topup/index.php @@ -0,0 +1,46 @@ +<?php require_once __DIR__ . "/../template/header.php" ?> +<?php require_once __DIR__ . "/../template/navbar.php" ?> + +<section id="topup"> + <div class="topupContainer"> + <div class="historyTopupSec"> + <div class="toupUpReq"> + <div class="topuptitle">Pending</div> + <div class="col-name1"> + <div>Date</div> + <div>Amount</div> + </div> + <div class="topupgrid1" id="pending-grid"> + </div> + </div> + <div class="toupUpAcc"> + <div class="topuptitle">History</div> + <div class="col-name2"> + <div>Date</div> + <div>Status</div> + <div>Amount</div> + </div> + <div class="topupgrid2" id="historytopup-grid"> + </div> + </div> + </div> + <div class="mainTopupSec"> + <div class="topuplogo"> + <img class="mainlogo" src="/../public/images/topup.png" alt="topuplogo"></img> + </div> + <div class="topupSec"> + <div class="topupSectitle">Top up Page</div> + <label for="amount" class="amountlabel">Insert the amount</label> + <input type="number" id="amount" name="amount" min="1" value="1" oninput="validity.valid||(value='');"> + <div type="button" class="buttonTopup" onclick="requestTopup()">Request Topup</div> + </div> + </div> + </div> +</section> + +<?php require_once __DIR__ . "/../template/footer.php" ?> + +<script> + /* required scripts */ + <?php include '../../public/js/topup.js'; ?> +</script> \ No newline at end of file diff --git a/scripts/client/public/assets/images/default.jpg b/scripts/client/public/assets/images/default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e813b47c1b1f91088af5c308624b69dd954b62df Binary files /dev/null and b/scripts/client/public/assets/images/default.jpg differ diff --git a/scripts/client/public/assets/images/user.png b/scripts/client/public/assets/images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..24aa8e7db8a09d6a3a5354b2605e122c2efcbb7b Binary files /dev/null and b/scripts/client/public/assets/images/user.png differ diff --git a/scripts/client/public/assets/videos/sample-5s.mp4 b/scripts/client/public/assets/videos/sample-5s.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7936dc09092f5a02a4e228f4c6cd9a7d7240696d Binary files /dev/null and b/scripts/client/public/assets/videos/sample-5s.mp4 differ diff --git a/scripts/client/public/css/animations.css b/scripts/client/public/css/animations.css new file mode 100644 index 0000000000000000000000000000000000000000..c45451459a8f3f0618b6b4ac3a840661d75056a1 --- /dev/null +++ b/scripts/client/public/css/animations.css @@ -0,0 +1,29 @@ +@keyframes fadein { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes slideIn { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(0); + } +} + +@keyframes slideOut { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(-100%); + } +} \ No newline at end of file diff --git a/scripts/client/public/css/colors.css b/scripts/client/public/css/colors.css new file mode 100644 index 0000000000000000000000000000000000000000..a63964c43d859641af6ef3d666531c0ff7470fd7 --- /dev/null +++ b/scripts/client/public/css/colors.css @@ -0,0 +1,20 @@ +/* use var() to use variables */ + +:root { + --bg-primary: #6e6e71; + --bg-primary-white: #FFFFFF; + + --text-primary: #FFFFFF; + --text-primary-black: #2A2B30; + --text-secondary: #E1E1E2; + --text-advanced: #8A8C8F; + --text-disabled: rgba(138, 140, 143, 0.48); + + --red: #FF646C; + --yellow: #FFDD3C; + --blue: #0C79F8; + --grey: #3F4044; + --black: #1E1F22; + --green: #16e957; + --green-hover: #128d35; +} \ No newline at end of file diff --git a/scripts/client/public/css/components/navbar.css b/scripts/client/public/css/components/navbar.css new file mode 100644 index 0000000000000000000000000000000000000000..ca40e1883573af8e1c94a0f1127cd0f08c2dd92c --- /dev/null +++ b/scripts/client/public/css/components/navbar.css @@ -0,0 +1,300 @@ +.navCt { + background-color: #33383a; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + padding: 10px 20px; + justify-content: space-between; + overflow: hidden; + display: flex; +} + +.navbar { + position: sticky; + z-index: 5; + top: 0; +} + +.navLeft { + float: left; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + column-gap: 10vw; +} + +.navLogoSide { + float: left; + display: flex; + align-items: center; + flex-direction: row; + color: white; +} + +.navLogo { + width: 40px; + height: 40px; +} + +h1.navTitle { + font-size: 1.5em; + margin-block-start: 0.2em; + margin-block-end: 0.2em; + font-weight: 700; +} + +.navLogoSide:hover { + cursor: pointer; +} + +.navSearch { + background-color: white; + border: 1px solid #6c6c6c; + border-radius: 20px; + width: 340px; + height: 40px; + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +.navSearch input { + border: none; + font-size: 15px; + font-family: "Arial"; + text-align: left; + color: black; + width: 85%; +} + +.navSearch input::placeholder { + border: none; + font-size: 15px; + font-family: "Arial"; + text-align: left; + color: #828282; + width: 85%; +} + +.navSearch input:focus { + outline: none; +} + +.navSearchIcon { + width: 30px; + height: 30px; + margin-left: 10px; +} + +.navRight { + float: right; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + color: #B3B3B3; + column-gap: 5px; +} + +.navItems { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-start; + column-gap: 5px; + border-radius: 20px; + padding: 15px; + padding-left: 20px; + padding-right: 20px; +} + +.navItems:hover { + background-color: #44494c; + cursor: pointer; +} + +.navIcon { + width: 20px; + height: 20px; + margin-left: 5px; +} + +.navText { + font-family: "Arial"; + font-size: 18px; +} + +.navCollapse { + float: right; + overflow: hidden; +} + +.navCollapse, .navItems { + font-size: 14px; + border: none; + outline: none; + color: white; + background-color: inherit; + font-family: inherit; + margin: 0; +} + +.navDrop { + display: none; + position: absolute; + background-color: black; + outline: 5px solid black; + min-width: 100px; + box-shadow: 0px 8px 16px 0px rgba(255, 255, 255,0.2); + z-index: 5; + border-radius: 20px; + color: white; + padding: 5px; + text-decoration: none; + text-align: left; + margin-left: 0; + margin-right: auto; +} + +.navDrop:hover { + border-radius: 20px; + display: block; + cursor: pointer; +} + +.navCollapse:hover .navDrop { + display: block; +} + +.navChild { + border-radius: 20px; + padding: 1rem; + padding-left: 2rem; + padding-right: 2rem; + margin-left: 0; + margin-right: auto; +} + +.navChild:hover { + background-color: #282828; + cursor: pointer; +} + +.homeCt { + color: white; + padding: 20px +} + +@media only screen and (max-width: 1200px) { + h1.navTitle { + font-size: 1.5em; + } + .navItems { + width: auto; + background-color: #535353; + border-radius: 20px; + padding: 5px; + } + .navIcon { + margin-left: 0; + } + .navText { + display: none; + } +} + +@media only screen and (max-width: 1024px) { + h1.navTitle { + font-size: 1.7em; + } + .navRight { + column-gap: 10px; + } +} + +@media only screen and (max-width: 800px) { + .navRight { + column-gap: 5px; + } + .navItems { + column-gap: 5px; + } +} + +@media screen and (max-width: 768px) { + h1.navTitle { + font-size: 1.5em; + } + .navLeft { + column-gap: 5vw; + } + .navSearch { + width: 50vw; + max-width: 35vw; + height: 35px; + } + .navSearch input { + width: 80%; + } + .navSearch input::placeholder { + width: 80%; + } + .navSearchIcon { + margin-left: 7px; + } + .navLogo { + width: 30px; + height: 30px; + } +} + +@media only screen and (max-width: 600px) { + h1.navTitle { + font-size: 1em; + } + .navLeft { + column-gap: 2.5vw; + } + .navLogo { + width: 25px; + height: 25px; + } + .navRight { + column-gap: 2px; + } + .navSearch input { + font-size: 10px; + } + .navSearch input::placeholder { + font-size: 10px; + } + .navSearch { + border-radius: 20px; + height: 28px; + } + .navSearch img { + width: 15px; + height: 15px; + } +} + +@media only screen and (max-width: 480px) { + .navSearch { + border-radius: 20px; + height: 22px; + } + .navItems { + padding: 3px; + } + .navIcon { + width: 15px; + height: 15px; + margin-left: 2px; + } +} + +.navSearchIcon:hover{ + cursor: pointer; +} \ No newline at end of file diff --git a/scripts/client/public/css/globals.css b/scripts/client/public/css/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..b2900c358f427fa537e4ac3c7b523d8862baedfb --- /dev/null +++ b/scripts/client/public/css/globals.css @@ -0,0 +1,97 @@ +/* Basic Structure */ +html, body { + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif; + font-weight: normal; +} + +body { + -ms-overflow-style: none; + scrollbar-width: none; + overflow-y: scroll; + background-color: var(--bg-primary); + color: var(--text-primary); + background-attachment: fixed; +} + +/* Links */ +a { + color: var(--green); + text-decoration: none; + cursor: pointer; +} + +a:hover { + color: var(--green-hover); + text-decoration: none; +} + +/* Navbar */ +.indexCt { + background-color: #33383a; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + margin: auto; +} + +.bigText img { + width: 15vw; +} + +/* Redirection */ +.redirect { + text-align: center; + color: white; + font-size: 1rem; +} + +.main-title-spl { + text-align: center; + padding-bottom: 1rem; + color: white; + font-size: 4rem; +} + +/* Responsive design */ +.mobile-only { + display: block; +} + +.desktop-only { + display: none; +} + +@media only screen and (min-width: 768px) { + .mobile-only { + display: none; + } + + .desktop-only { + display: block; + } +} + +/* Main as section */ +section { + padding-right: 1.75rem; + padding-left: 1.75rem; + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +/* Scrollbar */ +::-webkit-scrollbar { + background-color: transparent; + width: 1px; +} + +::-webkit-scrollbar-thumb { + background-color: var(--text-disabled); + border-radius: 5px; +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/admin-product-create.css b/scripts/client/public/css/pages/admin-product-create.css new file mode 100644 index 0000000000000000000000000000000000000000..6d2b60859eb043c2a99d1acd22162db5d6ab8a73 --- /dev/null +++ b/scripts/client/public/css/pages/admin-product-create.css @@ -0,0 +1,138 @@ +#create-product { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.create-product-container { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 20%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); +} + +.create-product-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2%; +} + +.create-product-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.create-product-form-container { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#product-form input[type=text], input[type=number], input[type=file]{ + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +textarea { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; + resize: none; + height: 100px; +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +#category-dropdown { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.category-option { + background-color: #1F1F1F; + color: #d1d1d1; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .create-product-container { + width: 30%; + } +} + +@media (max-width: 896px) { + .create-product-container { + width: 50%; + } +} + +@media (max-width: 576px) { + .create-product-container { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-product-edit.css b/scripts/client/public/css/pages/admin-product-edit.css new file mode 100644 index 0000000000000000000000000000000000000000..484cbd086571725f3964e8983b33b99f53898657 --- /dev/null +++ b/scripts/client/public/css/pages/admin-product-edit.css @@ -0,0 +1,138 @@ +#edit-product { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.edit-product-container { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 20%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); +} + +.edit-product-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2%; +} + +.edit-product-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.edit-product-form-container { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#product-form input[type=text], input[type=number], input[type=file]{ + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +textarea { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; + resize: none; + height: 100px; +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +#category-dropdown { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.category-option { + background-color: #1F1F1F; + color: #d1d1d1; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .edit-product-container { + width: 30%; + } +} + +@media (max-width: 896px) { + .edit-product-container { + width: 50%; + } +} + +@media (max-width: 576px) { + .edit-product-container { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-product.css b/scripts/client/public/css/pages/admin-product.css new file mode 100644 index 0000000000000000000000000000000000000000..9a9d9932792d90d4753a5bbe1bbdb641081d74ed --- /dev/null +++ b/scripts/client/public/css/pages/admin-product.css @@ -0,0 +1,198 @@ +p, h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; +} + +#product-header { + width: 90%; + margin: 50px auto; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +#product-title { + font-size: 2.5rem; + font-weight: bold; +} + +#product-create-btn { + width: 15%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + color: #16E957; + background-color: #000; + border-radius: 10px; + padding: 10px; + cursor: pointer; +} + +#create-product-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.create-product-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.btn { + margin: 20px; + display: inline-block; + padding: 10px 20px; + border-radius: 5px; + text-decoration: none; + color: #fff; + background-color: #121212; + border: none; + outline: none; + cursor: pointer; + font-size: 1rem; + font-weight: bold; + transition: all 0.3s ease-in-out; +} + +.btn:hover { + transform: scale(1.1); +} + +#product-container { + display: flex; + flex-wrap: wrap; + justify-content: center; + width: 100%; +} + +.product-card { + background-color: #121212; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + width: calc(25% - 100px); + border-radius: 10px; + padding: 20px; + margin: 20px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); +} + +.product-image { + width: 100%; + height: 300px; + margin-bottom: 20px; + border-radius: 5px; + overflow: hidden; +} + +.product-image img, +.product-image video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.product-info { + display: flex; + flex-direction: column; + width: 100%; +} + +.product-category-stock { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.product-category, +.product-stock { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-size: 0.9rem; +} + +.category-icon, +.stock-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.product-name { + font-size: 1.6rem; + margin: 5px 0; + font-weight: bold; +} + +.product-price { + font-size: 1rem; + font-weight: bold; + margin-bottom: 50px; +} + +.product-action { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; +} + +#pagination-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + margin: 20px 0; +} + +.pagination-list { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + list-style: none; + margin: 0; + padding: 0; +} + +.pagination-item { + margin: 0 5px; +} + +.active { + color: #000; + font-weight: bold; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .product-card { + width: calc(34% - 100px); + } +} + +@media (max-width: 896px) { + .product-card { + width: calc(50% - 100px); + } +} + +@media (max-width: 576px) { + .product-card { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-top-up-create.css b/scripts/client/public/css/pages/admin-top-up-create.css new file mode 100644 index 0000000000000000000000000000000000000000..110b659178fb6d98e5eccacd3ea1314cc6e8b606 --- /dev/null +++ b/scripts/client/public/css/pages/admin-top-up-create.css @@ -0,0 +1,124 @@ +#create-top-up { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.create-top-up-container { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 20%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); +} + +.create-top-up-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2%; +} + +.create-top-up-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.create-top-up-form-container { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#top-up-form input[type=text], input[type=number] { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +#status-dropdown { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.status-option { + background-color: #1F1F1F; + color: #d1d1d1; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .create-top-up-container { + width: 30%; + } +} + +@media (max-width: 896px) { + .create-top-up-container { + width: 50%; + } +} + +@media (max-width: 576px) { + .create-top-up-container { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-top-up.css b/scripts/client/public/css/pages/admin-top-up.css new file mode 100644 index 0000000000000000000000000000000000000000..aa3c16790f8565f61199c190fbf70890002d9e7d --- /dev/null +++ b/scripts/client/public/css/pages/admin-top-up.css @@ -0,0 +1,168 @@ +p, h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; +} + +#top-up-header { + width: 90%; + margin: 50px auto; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +#top-up-title { + font-size: 2.5rem; + font-weight: bold; +} + +#top-up-create-btn { + width: 15%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + color: #16E957; + background-color: #000; + border-radius: 10px; + padding: 10px; + cursor: pointer; +} + +#top-up-container { + width: 90%; + margin: 50px auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + column-gap: 4%; + background-color: #fff; + border-radius: 10px; + padding: 20px 0; +} + +.top-up-card { + width: 90%; + background-color: #000; + border-radius: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 20px; + margin: 20px 0; +} + +.top-up-info { + width: 70%; + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; +} + +.top-up-user { + width: 10%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.user-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.top-up-date { + width: 25%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.date-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.top-up-amount { + width: 20%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.amount-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} +.top-up-status { + width: 15%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.status-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.top-up-action { + width: 30%; + display: flex; + flex-direction: row; + align-items: space-between; + margin-left: 15%; +} + +.top-up-action-item { + width: 10%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin: auto; +} + +#pagination-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + margin: 20px 0; +} + +.pagination-list { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + list-style: none; + margin: 0; + padding: 0; +} + +.pagination-item { + margin: 0 5px; +} + +.active { + color: #000; + font-weight: bold; +} diff --git a/scripts/client/public/css/pages/admin-user-create.css b/scripts/client/public/css/pages/admin-user-create.css new file mode 100644 index 0000000000000000000000000000000000000000..ffdf0e41b3c55c9a449bdd9b9b40ed39355a1384 --- /dev/null +++ b/scripts/client/public/css/pages/admin-user-create.css @@ -0,0 +1,107 @@ +#create-user { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.create-user-container { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 20%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); +} + +.create-user-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2%; +} + +.create-user-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.create-user-form-container { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#user-form input[type=text], input[type=number], input[type=password]{ + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .create-user-container { + width: 30%; + } +} + +@media (max-width: 896px) { + .create-user-container { + width: 50%; + } +} + +@media (max-width: 576px) { + .create-user-container { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-user-edit.css b/scripts/client/public/css/pages/admin-user-edit.css new file mode 100644 index 0000000000000000000000000000000000000000..ceeda081b1062c94c23ba6c13c40b16cd2b04660 --- /dev/null +++ b/scripts/client/public/css/pages/admin-user-edit.css @@ -0,0 +1,107 @@ +#edit-user { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.edit-user-container { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 20%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); +} + +.edit-user-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2%; +} + +.edit-user-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.edit-user-form-container { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#user-form input[type=text], input[type=number] { + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +/* Responsive Layout */ +@media (max-width: 1366px) { + .edit-user-container { + width: 30%; + } +} + +@media (max-width: 896px) { + .edit-user-container { + width: 50%; + } +} + +@media (max-width: 576px) { + .edit-user-container { + width: 100%; + } +} diff --git a/scripts/client/public/css/pages/admin-user.css b/scripts/client/public/css/pages/admin-user.css new file mode 100644 index 0000000000000000000000000000000000000000..e3916f7848ee6f659aa0e1f9f12178c3142d946c --- /dev/null +++ b/scripts/client/public/css/pages/admin-user.css @@ -0,0 +1,139 @@ +p, h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; +} + +#user-header { + width: 90%; + margin: 50px auto; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +#user-title { + font-size: 2.5rem; + font-weight: bold; +} + +#user-create-btn { + width: 15%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + color: #16E957; + background-color: #000; + border-radius: 10px; + padding: 10px; + cursor: pointer; +} + +#user-container { + width: 90%; + margin: 50px auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + column-gap: 4%; + background-color: #fff; + border-radius: 10px; + padding: 20px 0; +} + +.user-card { + width: 90%; + background-color: #000; + border-radius: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 20px; + margin: 20px 0; +} + +.user-info { + width: 70%; + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; +} + +.user-user { + width: 35%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.user-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.user-balance { + width: 35%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.balance-icon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.user-action { + width: 30%; + display: flex; + flex-direction: row; + align-items: space-between; + margin-left: 15%; +} + +.user-action-item { + width: 10%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin: auto; +} + +#pagination-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + margin: 20px 0; +} + +.pagination-list { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + list-style: none; + margin: 0; + padding: 0; +} + +.pagination-item { + margin: 0 5px; +} + +.active { + color: #000; + font-weight: bold; +} diff --git a/scripts/client/public/css/pages/detailProduct.css b/scripts/client/public/css/pages/detailProduct.css new file mode 100644 index 0000000000000000000000000000000000000000..b9009470761d25f7738c72ec0663b6d313f814d1 --- /dev/null +++ b/scripts/client/public/css/pages/detailProduct.css @@ -0,0 +1,165 @@ +#detailProduct { + padding-top: 3rem; + padding-left: 2.5rem; + padding-right: 2.5rem; +} + +.detailContainer { + display: flex; + width: 100%; + flex-direction: row; + column-gap: 4%; +} + +@media only screen and (max-width: 920px) { + .detailContainer { + display: flex; + width: 100%; + flex-direction: column; + column-gap: 4%; + } + + .detailImage { + width: 90% !important; + margin-bottom: 2rem; + } + + #productImage { + width: 100% !important; + } + + .productInfo { + width: 100% !important; + margin-bottom: 2rem; + } +} + +@media only screen and (max-width: 480px) { + #productName { + font-size: 3rem !important; + margin-bottom: 0.1rem; + } +} + +@media only screen and (max-width: 400px) { + #productName { + font-size: 2.5rem !important; + margin-bottom: 0.1rem; + } + + #productPrice { + font-size: 1.25rem !important; + } +} + +.detailImage { + width: 38%; + border-radius: 2rem; + padding: 1rem; + background-color: black; +} + +#productImage { + border-radius: 1.8rem; + width: 100%; + height: auto; +} + +.productInfo { + width: 58%; +} + +#productName { + font-size: 4.5rem; + margin-bottom: 0.1rem; +} + +.productStockCategory { + display: flex; + flex-direction: row; + column-gap: 2rem; +} + +.detailIcon { + width: 2rem; + height: 2rem; + margin-right: 1rem; +} + +.categorySec, .stockSec { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 1rem; +} + +#productCategory, #productStock { + font-size: 1.2rem; +} + +#productPrice { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 1rem; +} + +#productDesc { + font-size: 1rem; +} + +.buttonSec { + background-color: black; + margin-top: 1rem; + border-radius: 20px; + font-size: 16px; + color: white; + padding: 15px 6px; + width: 100px; + text-align: center; + justify-content: center; + align-items: center; + display: flex; +} + +.buttonOp { + background-color: black; + margin-top: 1rem; + border-radius: 20px; + font-size: 20px; + color: white; + padding: 15px 15px; + align-items: center; + display: flex; + text-align: center; +} + +#numberamount { + background-color: inherit; + margin-top: 1rem; + border: none; + font-size: 20px; + color: white; + width: 1.5rem; + padding: 15px 15px; + align-items: center; + display: flex; + text-align: center; +} + +.buttonSec:hover, .buttonOp:hover { + background-color: #44494c; + cursor: pointer; +} + +.buySection { + display: flex; + flex-direction: row; + column-gap: 1.5rem; + align-items: center; +} + +.amountsec { + display: flex; + flex-direction: row; + column-gap: 7px; +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/history.css b/scripts/client/public/css/pages/history.css new file mode 100644 index 0000000000000000000000000000000000000000..0e78532e14e249bb9fde47225caa99f7b786026c --- /dev/null +++ b/scripts/client/public/css/pages/history.css @@ -0,0 +1,95 @@ +#history { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + height: 100vh; + color: black; + font-family: 'Poppins', Helvetica, sans-serif; +} + +.history-ctn { + background-color: #f1f1f1; + padding: 2% 2.5% 2%; + border-radius: 1.5%; + width: 50vw; + height: 75vh; + box-shadow: 0px 0px 30px rgb(69, 63, 63); + +} + +@media screen and (max-width: 900px) { + .history-ctn { + width: 70%; + margin: 3% 0; + padding: 2% 3% 7%; + } +} + +@media screen and (min-width: 900px) and (max-width: 1280px) { + .history-ctn { + width: 65%; + margin: 3% 0; + padding: 2% 3% 7%; + } +} + + +.history-title { + font-size: x-large; + font-weight: 700; +} + +.separator { + border-top: 2px solid #414142; + margin-bottom: 2%; +} + +.table-separator { + border-top: 2px solid #414142; + margin: 5% 0; +} + +.topup-history { + height: 35%; +} + +.buy-history { + height: 35%; +} + +.topup-history-title { + padding: 2% 3%; + background-color: rgb(74, 254, 74); + border-radius: 50px; +} + +.buy-history-title { + padding: 2% 3%; + background-color: rgb(255, 114, 114); + border-radius: 50px; +} + +#buy-history-grid, #topup-history-grid { + padding: 1% 3%; + margin-top: 1%; + display: grid; + grid-template-columns: auto 45%; + font-size: small; + font-weight: 400; + gap: 2vh; + + height: 65%; + overflow-y: auto; +} + +@media screen and (max-width : 900px) { + .history-title { + font-size: large; + } + .topup-history-title, + .buy-history-title { + font-size : medium; + } +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/login.css b/scripts/client/public/css/pages/login.css new file mode 100644 index 0000000000000000000000000000000000000000..cee398931794804d74281a51acaa561b3cf247b5 --- /dev/null +++ b/scripts/client/public/css/pages/login.css @@ -0,0 +1,155 @@ +#login { + display: flex; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.lgn-ctn { + margin-top: 2%; + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 55vh; + box-shadow: 0px 0px 30px rgb(69, 63, 63); + + padding: 4% 2.5% 3%; + border-radius: 1.5%; +} + +@media screen and (max-width: 900px) { + .lgn-ctn { + width: 75%; + margin: 3% 0; + padding: 5% 5%; + } +} + +@media screen and (min-width: 900px) { + .lgn-ctn { + width: 50%; + } +} + +@media screen and (min-width: 1200px) { + .lgn-ctn { + width: 25%; + } +} + +.lgn-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 2.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.lgn-sub-title { + display: flex; + font-weight: 500; + margin-bottom: 0.5%; + justify-content: center; + font-size: small; + color: #f1f1f1; +} + +.lgn-form-ctn { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 8%; + color: #d1d1d1; + font-size: small; +} + +#login-form input[type=text], input[type=password], input[type=name]{ + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: small; + background-color: #1F1F1F; + color: #d1d1d1; + font-family: 'Poppins', Helvetica, sans-serif; +} + +@media screen and (min-width: 1200px) { + .lgn-title, label { + font-size: large; + } + + #login-form input[type=text], input[type=password], input[type=name]{ + font-size: large; + } +} + +@media screen and (min-width: 900px) { + .lgn-title, label { + font-size: medium; + } + + #login-form input[type=text], input[type=password], input[type=name]{ + font-size: medium; + } +} + +@media screen and (min-width: 600px) { + .lgn-title, label { + font-size: small; + } + + #login-form input[type=text], input[type=password], input[type=name]{ + font-size: small; + } +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: small; + color: #FFFCF9; +} + +.space { + margin-top: 5%; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +.reg-link-ctn { + display: flex; + font-size: large; + justify-content: center; + margin-top: 7%; + margin-bottom: 5%; +} + +.reg-link { + text-decoration: none; + color: #FFFCF9; +} + +.reg-link-ctn p { + font-size: x-small; + color: #A9A9A9; +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/product.css b/scripts/client/public/css/pages/product.css new file mode 100644 index 0000000000000000000000000000000000000000..b8f37e9c47574aff6e145394b60ea330da33e4ca --- /dev/null +++ b/scripts/client/public/css/pages/product.css @@ -0,0 +1,391 @@ +.queryCt { + font-size: 16px; + padding: 0px 0.75rem; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + row-gap: 0.25vh; +} + +#numsofObj { + background-color: inherit; + font-size: 1.25rem; + width: 1.5rem; + color: white; + border: none; + justify-items: center; + text-align: center; +} + +#numsofObj::placeholder { + background-color: inherit; + font-size: 1.25rem; + width: 1.5rem; + color: white; + border: none; + justify-items: center; + text-align: center; +} + +.productMenu { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +@media only screen and (max-width: 1315px) { + .productMenu { + flex-direction: column; + } + + .queryMenu { + flex-direction: column; + overflow-x: auto; + } +} + +.pageTitle { + float: left; + font-size: 2.5rem; + font-weight: bold; + flex-wrap: nowrap; + display: flex; + align-items: center; +} + +.listTitle { + font-size: 1.25rem; + align-items: center; + margin-bottom: 1.5rem; + display: inline; + white-space: nowrap; +} + +@media only screen and (max-width: 768px) { + .pageTitle { + font-size: 1.7rem; + } + + .listTitle { + font-size: 1.2rem; + } + + #numsofObj { + font-size: 1.2rem; + } + + #numsofObj::placeholder { + font-size: 1.2rem; + } + + .dropdown1, .dropdown2, .dropdown3 { + width: 150px; + height: 35px; + } + + .dropdown1 input, .dropdown2 input, .dropdown3 input { + width: 140px; + height: 35px; + font-size: 12px; + padding: 7px; + } +} + +@media only screen and (max-width: 440px) { + .pageTitle { + font-size: 1.5rem; + } + + #numsofObj::placeholder { + font-size: 1rem; + } + + .listTitle { + font-size: 1rem; + } +} + +.queryMenu { + float: right; + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: nowrap; + margin: 2vh 0; + column-gap: 2vw; +} + +.queryMenu:hover { + cursor: pointer; +} + +.filterChild { + display: flex; + position: relative; + z-index: 3; + float: left; + font-size: 16px; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +.filterCollapse { + display: flex; + z-index: 3; + width: 200px; + height: 46px; +} + +.filterCollapse:hover .filterParent { + border: 1px solid white; + color: white; +} + +.filterParent { + background-color: inherit; + margin: 0; + border: 1px solid white; + border-radius: 20px; + font-size: 16px; + color: white; + padding: 9px; + text-align: center; + width: 180px; + height: 25px; +} + +.filterDrop { + display: none; + flex-direction: column; + background-color: black; + outline: 5px solid black; + box-shadow: 0px 8px 16px 0px rgba(255, 255, 255,0.2); + z-index: 3; + + color: white; + text-decoration: none; + text-align: left; +} + +.filterChild:hover { + background-color: var(--green); +} + +.filterCollapse:hover .filterDrop { + display: flex; + flex-direction: column; + z-index: 3; +} + +.dropdown1, .dropdown2, .dropdown3 { + width: 200px; + height: 46px; +} + +.dropdown1 input, .dropdown2 input, .dropdown3 input { + display: flex; + width: 180px; + height: 30px; + margin: 0; + cursor: pointer; + background-color: inherit; + color: white; + font-size: 16px; + border: 1px solid white; + border-radius: 20px; + padding: 7px; + border-radius: 20px; + text-align: center; +} + +.dropdown1 input::placeholder, .dropdown2 input::placeholder, .dropdown3 input::placeholder { + color: white; +} + +.dropdown1 .options, .dropdown2 .options, .dropdown3 .options { + width: 200px; + position: relative; + z-index: 200; + color: white; + background-color: black; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); + border-radius: 10px; + overflow: hidden; + display: none; +} + +.dropdown1.active .options, .dropdown2.active .options, .dropdown3.active .options { + display: block; + z-index: 300; +} + +.dropdown1 .options div, .dropdown2 .options div, .dropdown3 .options div { + padding: 12px 20px; + cursor: pointer; +} + +.dropdown1 .options div:hover, .dropdown2 .options div:hover, .dropdown3.options div:hover { + background: #62baea; + color: #fff; +} + +.queryResult { + color: white; + padding: 20px +} + +@media only screen and (max-width: 1050px) { + .queryResultProduct { + width: 100%; + } +} + +@media screen and (max-width: 1260px) { + .card { + width: calc(34% - 70px) !important; + } +} + +@media screen and (max-width: 920px) { + .card { + width: calc(50% - 60px) !important; + } +} + +@media screen and (max-width: 570px) { + .card { + width: 100% !important; + } +} + +#queryResultProduct { + display: flex; + flex-wrap: wrap; + justify-content: center; + width: 100%; +} + +.card { + background-color: #121212; + border-radius: 17px; + padding: 20px; + margin: 10px; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + width: calc(25% - 60px); + font-size: 10px; + cursor: pointer; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); +} + +.card:hover { + background-color: #1a1a1a; + transition: ease-out; + transition-duration: 0.5s; + scale: 1.05; +} + +.card:active { + background-color: #000; +} + +.cardImage { + width: 100%; + height: 300px; + margin-bottom: 20px; + border-radius: 5px; + overflow: hidden; +} + +.cardImage img, .cardImage video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.productdesc { + display: flex; + flex-direction: column; + width: 100%; +} + +.productTitle { + font-size: 1.6rem; + margin-top: 5px; + margin-bottom: 5px; + font-weight: bold; +} + +.price { + font-size: 1rem; + font-weight: bold; + margin-bottom: 2.5px; +} + +.stockCategory { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.category, .stock { + display: flex; + flex-direction: row; + font-size: 0.9rem; + justify-content: center; + align-items: center; +} + +.cardIcon { + width: 2vw; + height: 2vh; + object-fit: cover; + margin-right: 0.5rem; +} + +.pagination { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + column-gap: 10px; + margin: 5vh 0; +} + +.page { + font-size: 1rem; + color: white; + padding: 5px; + width: 2.5rem; + text-align: center; + align-items: center; + border: 1px solid white; + border-radius: 3rem; +} + +.pageCurr { + font-size: 1rem; + color: white; + background-color: #282828; + padding: 5px; + width: 2.5rem; + text-align: center; + align-items: center; + border: 1px solid #282828; + border-radius: 3rem; +} + +.page:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/settings.css b/scripts/client/public/css/pages/settings.css new file mode 100644 index 0000000000000000000000000000000000000000..0a7e3d04b6433fc18b9f2dac5790d597754fb7d9 --- /dev/null +++ b/scripts/client/public/css/pages/settings.css @@ -0,0 +1,141 @@ +#settings { + display: flex; + background-color: #6e6e71; + justify-content: center; + align-items: center; + height: 100vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.acct-ctn { + background-color: #3d4143; + padding: 2% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); + +} + +@media screen and (max-width: 900px) { + .acct-ctn { + width: fit-content; + height: fit-content; + margin: 3% 0; + padding: 5% 10% 25%; + } +} + +@media screen and (min-width: 900px) and (max-width: 1280px) { + .acct-ctn { + width: 80%; + margin: 3% 0; + } +} + + +.acct-title { + display: flex; + font-weight: 700; + margin-bottom: 3%; + justify-content: start; + font-size: x-large; + color: #f1f1f1; +} + +.separator { + border-top: 2px solid #f1f1f1; + margin-bottom: 8%; +} + +label { + color: #d1d1d1; +} + +.grid-container { + display: grid; + grid-template-columns: auto auto; + gap: 5vh; + width: 65vh; + margin: 0 auto; + align-items: center; +} + +.grid-container > label { + grid-column: 1; + align-self: center; + font-size: medium; +} + +.grid-container > input, +.grid-container > button { + font-size: medium; + align-self: center; +} + +#username, #balance { + border: 0.5px solid #414142; + padding: 2% 1%; + color: #d1d1d1; + background-color: #414142; +} + +#name, #password, #confirm-password { + border: 0.5px solid #1F1F1F; + border-radius: 1%; + padding: 2% 3%; + background-color: #1F1F1F; + color: #d1d1d1; +} + +button[type=submit] { + margin-top: 0%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; + grid-column: span 2; +} + +button[type=submit]:hover { + background-color: #16702E; + +} + +@media screen and (max-width: 900px) { + .acct-title { + font-size: medium; + } + + .grid-container { + grid-template-columns : auto; + width : auto; + margin: 3% 0; + gap: 2%; + font-size: small; + } + + #balance { + margin-bottom: 5%; + } + + button[type=submit] { + grid-column : span 1; + } +} + +@media screen and (min-width: 900px) and (max-width: 1280px) { + .grid-container { + grid-template-columns : auto; + width : auto; + margin: 3% 0; + gap: 4%; + font-size: small; + } + + #balance { + margin-bottom: 8%; + } +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/signup.css b/scripts/client/public/css/pages/signup.css new file mode 100644 index 0000000000000000000000000000000000000000..fe5ea0d0ab94841c9fb7d7623619a85849b1aeaf --- /dev/null +++ b/scripts/client/public/css/pages/signup.css @@ -0,0 +1,150 @@ +#signup { + display: flex; + flex-direction: column; + background-color: #6e6e71; + justify-content: center; + align-items: center; + min-height: 85vh; + + font-family: 'Poppins', Helvetica, sans-serif; +} + +.reg-ctn { + display: flex; + flex-direction: column; + background-color: #3d4143; + width: 25%; + padding: 4% 2.5% 2%; + border-radius: 1.5%; + box-shadow: 0px 0px 30px rgb(69, 63, 63); + +} + +@media screen and (max-width: 900px) { + .reg-ctn { + width: 75%; + margin: 3% 0; + padding: 7% 5%; + } +} + +@media screen and (min-width: 900px) { + .reg-ctn { + width: 50%; + } +} + +@media screen and (min-width: 1200px) { + .reg-ctn { + width: 25%; + } +} + +.reg-title { + display: flex; + font-weight: 700; + margin-top: -2%; + margin-bottom: 0.5%; + justify-content: center; + font-size: larger; + color: #f1f1f1; +} + +.reg-form-ctn { + justify-content: center; + align-items: center; +} + +label { + display: block; + margin-top: 10%; + color: #d1d1d1; + font-size: medium; +} + +#register-form input[type=text], input[type=password], input[type=name]{ + width: 100%; + box-sizing: border-box; + border: 0.5px solid #1F1F1F; + border-radius: 2px; + padding: 2% 3%; + margin-top: 5px; + font-size: medium; + background-color: #1F1F1F; + color: #d1d1d1; +} + +@media screen and (min-width: 1200px) { + .reg-title, label { + font-size: large; + } + + #register-form input[type=text], input[type=password], input[type=name]{ + font-size: large; + } +} + +@media screen and (min-width: 900px) { + .reg-title, label { + font-size: medium; + } + + #register-form input[type=text], input[type=password], input[type=name]{ + font-size: medium; + } +} + +@media screen and (min-width: 600px) { + .reg-title, label { + font-size: small; + } + + #register-form input[type=text], input[type=password], input[type=name]{ + font-size: small; + } +} + +.required-field { + color: red; +} + +button[type=submit] { + width: 100%; + margin-top: 4%; + margin-bottom: 5%; + padding: 3% 0; + background-color: #1c893f; + border: none; + border-radius: 5px; + font-size: medium; + color: #FFFCF9; +} + +button[type=submit]:hover { + background-color: #16702E; +} + +#error-message { + color: red; + margin-top: 3%; + font-size: smaller; + margin-bottom: 3%; +} + +.lgn-link-ctn { + display: flex; + font-size: large; + justify-content: center; + margin-top: 3%; + margin-bottom: 5%; +} + +.lgn-link { + text-decoration: none; + color: #FFFCF9; +} + +.lgn-link-ctn p { + font-size: x-small; + color: #A9A9A9; +} \ No newline at end of file diff --git a/scripts/client/public/css/pages/topup.css b/scripts/client/public/css/pages/topup.css new file mode 100644 index 0000000000000000000000000000000000000000..6765cfe42df610c4f54e427d030ec8eb45fb74b5 --- /dev/null +++ b/scripts/client/public/css/pages/topup.css @@ -0,0 +1,218 @@ +#topup { + padding-left: 2.5rem; + padding-right: 2.5rem; + margin-top: 1rem; + height: 80vh; + overflow-y: auto; +} + +.topupContainer { + display: flex; + width: 100%; + height: 100%; + flex-direction: row; + column-gap: 2%; +} + +@media only screen and (max-width: 1035px) { + #topup { + height: 100% !important; + } + + .topupContainer { + display: flex; + flex-direction: column-reverse; + } + + .mainTopupSec { + width: 100% !important; + height: 100%; + margin-top: 4rem; + margin-bottom: 2rem; + display: flex; + } + + .historyTopupSec { + width: 100% !important; + height: 100%; + margin-top: 2rem; + margin-bottom: 2rem; + display: flex; + } + + .toupUpReq { + margin-bottom: 2rem; + } + + .topuplogo { + width: 20% !important; + } + + .topupSec { + margin-top: 1rem !important; + } +} + +@media only screen and (max-width: 655px) { + .topuplogo { + width: 20% !important; + } +} + +@media only screen and (max-width: 475px) { + .topuplogo { + width: 40% !important; + } + + #amount { + width: 15rem !important; + height: 1rem !important; + padding: 1rem; + border-radius: 25px; + font-size: 1rem; + } +} + +.historyTopupSec { + width: 50%; + border-radius: 2rem; + display: flex; + flex-direction: column; + row-gap: 8%; +} + +.toupUpReq, .toupUpAcc { + height: 46%; +} + +.mainTopupSec { + width: 46%; + height: 100%; + margin-top: 2rem; + display: flex; + flex-direction: column; + justify-items: center; + align-items: center; +} + +.topuptitle { + font-size: 1.25rem; + text-align: center; + padding-top: 10px; + padding-bottom: 10px; + width: 100%; + background-color: #282828; + border-radius: 1.5rem 1.5rem 0 0; +} + +.col-name1 { + position: sticky; + background-color: #282828; + z-index: 5; + top: 0; + padding: 1% 3%; + display: grid; + grid-template-columns: auto auto; + font-weight: 700; +} + +.col-name2 { + position: sticky; + background-color: #282828; + z-index: 5; + top: 0; + padding: 1% 3%; + display: grid; + grid-template-columns: auto auto auto; + font-weight: 700; +} + +.topupgrid1 { + padding: 1% 3%; + display: grid; + background-color: rgb(23, 23, 23); + border-radius: 0 0 1.5rem 1.5rem; + grid-template-columns: auto auto; + font-size: 1rem; + font-weight: 400; + align-items: center; + gap: 2vh; + max-height: 25vh; + overflow-y: auto; +} + +.topupgrid2 { + padding: 1% 3%; + display: grid; + background-color: rgb(23, 23, 23); + border-radius: 0 0 1.5rem 1.5rem; + grid-template-columns: auto auto auto; + font-size: 1rem; + font-weight: 400; + align-items: center; + gap: 2vh; + max-height: 25vh; + overflow-y: auto; +} + +.topuplogo { + width: 35%; + display: flex; + justify-items: center; + align-items: center; +} + +.mainlogo { + width: 100%; +} + +.topupSec { + margin-top: 2rem; + width: 100%; + display: flex; + flex-direction: column; + justify-items: center; + align-items: center; +} + +.topupSectitle { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.amountlabel { + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +#amount { + width: 20rem; + height: 2rem; + padding: 1rem; + background-color: #282828; + border-radius: 25px; + color: white; + font-size: 1.5rem; + display: flex; + justify-items: center; + text-align: center; +} + +.buttonTopup { + background-color: black; + margin-top: 1rem; + border-radius: 20px; + font-size: 16px; + color: white; + padding: 15px 6px; + width: 10rem; + text-align: center; + justify-content: center; + align-items: center; + display: flex; +} + +.buttonTopup:hover { + background-color: #44494c; + cursor: pointer; +} \ No newline at end of file diff --git a/scripts/client/public/css/typography.css b/scripts/client/public/css/typography.css new file mode 100644 index 0000000000000000000000000000000000000000..fabe2015c347dadf963f36fc873fe59acd9b9e71 --- /dev/null +++ b/scripts/client/public/css/typography.css @@ -0,0 +1,34 @@ +h1, h2, h3, h4, h5, h6 { + font-weight: normal; + margin: 0.5em; + margin-top: 0.75em; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.4em; +} + +h3 { + font-size: 1.25em; +} + +h4 { + font-size: 1.2em; +} + +h5 { + font-size: 1.1em; +} + +h6 { + font-size: 1em; +} + +p { + font-size: 1em; + margin: 0.5em; +} \ No newline at end of file diff --git a/scripts/client/public/images/amount.png b/scripts/client/public/images/amount.png new file mode 100644 index 0000000000000000000000000000000000000000..95a68c0c48b65e109e37ce2704f73f6b25b3622d Binary files /dev/null and b/scripts/client/public/images/amount.png differ diff --git a/scripts/client/public/images/category.png b/scripts/client/public/images/category.png new file mode 100644 index 0000000000000000000000000000000000000000..c966258e96673a781f0281286d8041f9ca7e91a0 Binary files /dev/null and b/scripts/client/public/images/category.png differ diff --git a/scripts/client/public/images/date.png b/scripts/client/public/images/date.png new file mode 100644 index 0000000000000000000000000000000000000000..5f9ffa07216699e2cad3f16a36048b842dc69a78 Binary files /dev/null and b/scripts/client/public/images/date.png differ diff --git a/scripts/client/public/images/edit-product.png b/scripts/client/public/images/edit-product.png new file mode 100644 index 0000000000000000000000000000000000000000..df013cd2d5f36a2563148f6d26ec0eb776658b8c Binary files /dev/null and b/scripts/client/public/images/edit-product.png differ diff --git a/scripts/client/public/images/history.png b/scripts/client/public/images/history.png new file mode 100644 index 0000000000000000000000000000000000000000..e46c0a1a60747a1bdff32d144b53c32dce948f05 Binary files /dev/null and b/scripts/client/public/images/history.png differ diff --git a/scripts/client/public/images/logo.png b/scripts/client/public/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..52d87065d185f36a9ae3da6d24efc23fbd14d0b9 Binary files /dev/null and b/scripts/client/public/images/logo.png differ diff --git a/scripts/client/public/images/product.png b/scripts/client/public/images/product.png new file mode 100644 index 0000000000000000000000000000000000000000..0bcdc274e77bf3407186ca61ac88ac5d974cdcf1 Binary files /dev/null and b/scripts/client/public/images/product.png differ diff --git a/scripts/client/public/images/quantity.png b/scripts/client/public/images/quantity.png new file mode 100644 index 0000000000000000000000000000000000000000..adde2eddca2d039c0de1071322e4708c1bf4e711 Binary files /dev/null and b/scripts/client/public/images/quantity.png differ diff --git a/scripts/client/public/images/search-black.png b/scripts/client/public/images/search-black.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd2be57f0cfce9cc870145af7f9739060b78abe Binary files /dev/null and b/scripts/client/public/images/search-black.png differ diff --git a/scripts/client/public/images/search-white.png b/scripts/client/public/images/search-white.png new file mode 100644 index 0000000000000000000000000000000000000000..96a843d0866356a6cb655daf8c607ccdec40f97e Binary files /dev/null and b/scripts/client/public/images/search-white.png differ diff --git a/scripts/client/public/images/status.png b/scripts/client/public/images/status.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d9dfb434b2de045a35db746f15343548cd53af Binary files /dev/null and b/scripts/client/public/images/status.png differ diff --git a/scripts/client/public/images/topup.png b/scripts/client/public/images/topup.png new file mode 100644 index 0000000000000000000000000000000000000000..01d09765344afa4711c5d465f47132d560aaf25a Binary files /dev/null and b/scripts/client/public/images/topup.png differ diff --git a/scripts/client/public/images/user-manage.png b/scripts/client/public/images/user-manage.png new file mode 100644 index 0000000000000000000000000000000000000000..4466094abab7aa82abff755afc4f6f6a11049b07 Binary files /dev/null and b/scripts/client/public/images/user-manage.png differ diff --git a/scripts/client/public/images/user.png b/scripts/client/public/images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..24aa8e7db8a09d6a3a5354b2605e122c2efcbb7b Binary files /dev/null and b/scripts/client/public/images/user.png differ diff --git a/scripts/client/public/js/admin-product-create.js b/scripts/client/public/js/admin-product-create.js new file mode 100644 index 0000000000000000000000000000000000000000..432c71c51348f962f4c0b8f8e4a1b5d4c0ee2f81 --- /dev/null +++ b/scripts/client/public/js/admin-product-create.js @@ -0,0 +1,107 @@ +window.onload = function () { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + setDropdownCategory(); +} + +let setDropdownCategory = async () => { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + let categories = res["data"]; + let categoryDropdown = document.getElementById("category-dropdown"); + let option = document.createElement("option"); + option.value = ""; + option.innerHTML = "--Please select a category--"; + option.className = "category-option"; + categoryDropdown.appendChild(option); + + for (let i = 0; i < categories.length; i++) { + let category = categories[i]; + let option = document.createElement("option"); + option.value = category.id; + option.innerHTML = category.name; + option.className = "category-option"; + categoryDropdown.appendChild(option); + } + } else { + alert("Failed to get categories!"); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open( + "GET", + "/api/CategoryController/getAllCategories", + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +} + +let createProduct = async (event) => { + event.preventDefault(); + + let form = document.getElementById("product-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-product"; + } else { + let errorMessage = document.getElementById("error-message"); + errorMessage.textContent = res["data"]; + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open( + "POST", + "/api/ProductController/createProduct", + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +}; diff --git a/scripts/client/public/js/admin-product-edit.js b/scripts/client/public/js/admin-product-edit.js new file mode 100644 index 0000000000000000000000000000000000000000..8befdbe3ef1795058db66de4d1081550d00e1666 --- /dev/null +++ b/scripts/client/public/js/admin-product-edit.js @@ -0,0 +1,158 @@ +let urlParams = new URLSearchParams(window.location.search); +let id = urlParams.get("id"); + +window.onload = async () => { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + getProductById(id); +}; + +let getProductById = async (id) => { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + console.log(1); + if (this.status == 200) { + let res = JSON.parse(this.responseText); + console.log(res); + + if (res["status"]) { + let product = res["data"]; + let form = document.getElementById("product-form"); + + form["name"].value = product.product_name; + form["description"].value = product.description; + form["price"].value = product.price; + form["stock"].value = product.stock; + + setDropdownCategory(product.category_id); + } else { + alert("Failed to get product!"); + } + } else { + console.log(3); + } + } else { + console.log(2); + } + }; + + xhr.open( + "GET", + `/api/ProductController/getProductById/${id}`, + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +} + + +let setDropdownCategory = async (activeCategoryId) => { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + let categories = res["data"]; + let categoryDropdown = document.getElementById("category-dropdown"); + let option = document.createElement("option"); + option.value = ""; + option.innerHTML = "--Please select a category--"; + option.className = "category-option"; + categoryDropdown.appendChild(option); + + for (let i = 0; i < categories.length; i++) { + let category = categories[i]; + let option = document.createElement("option"); + option.value = category.id; + option.innerHTML = category.name; + option.className = "category-option"; + if (category.id == activeCategoryId) { + option.selected = true; + } + categoryDropdown.appendChild(option); + } + } else { + alert("Failed to get categories!"); + } + } + } + }; + + xhr.open( + "GET", + "/api/CategoryController/getAllCategories", + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +} + + +let editProduct = async (event) => { + event.preventDefault(); + + let confirmation = confirm("Are you sure you want to edit this product?"); + if (!confirmation) { + return; + } + + let form = document.getElementById("product-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-product"; + } else { + let errorMessage = document.getElementById("error-message"); + errorMessage.textContent = res["data"]; + } + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open( + "POST", + `/api/ProductController/editProduct/${id}`, + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +} + diff --git a/scripts/client/public/js/admin-product.js b/scripts/client/public/js/admin-product.js new file mode 100644 index 0000000000000000000000000000000000000000..e4ad6e7dbf2ef1a98e65a4c4f44fe4206c297b7d --- /dev/null +++ b/scripts/client/public/js/admin-product.js @@ -0,0 +1,160 @@ +const ROWS_PER_PAGE = 8; +const INITIAL_PAGE = 1; + +window.onload = function() { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + getProductsByPage(INITIAL_PAGE); + setPagination(INITIAL_PAGE); +} + +let getProductsByPage = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let products = res['data']; + let productContainer = document.getElementById('product-container'); + productContainer.innerHTML = ''; + for (let i = 0; i < products.length; i++) { + let product = products[i]; + let productCard = document.createElement('div'); + productCard.className = 'product-card'; + productCard.innerHTML = ` + <div class="product-image"> + ${product.image.includes('.mp4') ? + `<video src="/public/assets/videos/${product.image}" alt="Product Image" autoplay loop muted></video>` : + `<img src="/public/assets/images/${product.image}" alt="Product Image">` + } + </div> + <div class="product-info"> + <div class="product-category-stock"> + <div class="product-category"> + <img src="/public/images/category.png" alt="Category Icon" class="category-icon"> + <p>${product.category_name}</p> + </div> + <div class="product-stock"> + <img src="/public/images/quantity.png" alt="Stock Icon" class="stock-icon"> + <p>${product.stock}</p> + </div> + </div> + <div class="product-name"> + <h3>${product.product_name}</h3> + </div> + <div class="product-price"> + <h3>${product.price.toLocaleString('id-ID', { style: 'currency', currency: 'IDR' })}</h3> + </div> + <div class="product-action"> + <a href="/pages/admin-product-edit?id=${product.product_id}">Edit</a> + <a href="#" onclick="deleteProduct(${product.product_id})">Delete</a> + </div> + </div> + `; + productContainer.appendChild(productCard); + } + } else { + alert('Failed to get products!'); + } + } + } + } + + xhr.open('GET', `/api/ProductController/getProductsByPage/${page}`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let setPagination = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let paginationContainer = document.getElementById('pagination-container'); + paginationContainer.innerHTML = ''; + let totalPage = Math.ceil(res['data'].length / ROWS_PER_PAGE); + let paginationList = document.createElement('ul'); + paginationList.className = 'pagination-list'; + for (let i = 0; i < totalPage; i++) { + let pageItem = document.createElement('li'); + pageItem.className = 'pagination-item'; + if (i + 1 == page) { + pageItem.className += ' active'; + } + pageItem.innerHTML = `<a href="#" onclick="handlePaginationClick(${i + 1})">${i + 1}</a>`; + paginationList.appendChild(pageItem); + } + paginationContainer.appendChild(paginationList); + } else { + alert('Failed to get pagination!'); + } + } + } + } + + xhr.open('GET', `/api/ProductController/getAllProducts`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let handlePaginationClick = async (page) => { + getProductsByPage(page); + setPagination(page); +} + +let deleteProduct = async (id) => { + let confirmation = confirm("Are you sure you want to delete this product?"); + if (!confirmation) { + return; + } + + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-product"; + } else { + alert("Failed to delete product!"); + } + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open("DELETE", `/api/ProductController/deleteProduct/${id}`, true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +}; diff --git a/scripts/client/public/js/admin-top-up-create.js b/scripts/client/public/js/admin-top-up-create.js new file mode 100644 index 0000000000000000000000000000000000000000..dbcaac96d2c0507fa265ca3870a6da3ebde2f7f2 --- /dev/null +++ b/scripts/client/public/js/admin-top-up-create.js @@ -0,0 +1,76 @@ +const TOP_UP_STATUS = ["Pending", "Approved", "Rejected"]; + +window.onload = async function () { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + setDropdownStatus(); +}; + +let setDropdownStatus = async () => { + let statusDropdown = document.getElementById("status-dropdown"); + let option = document.createElement("option"); + option.value = ""; + option.innerHTML = "--Please select a status--"; + option.className = "status-option"; + statusDropdown.appendChild(option); + + for (let i = 0; i < TOP_UP_STATUS.length; i++) { + let option = document.createElement("option"); + option.value = i; + option.innerHTML = TOP_UP_STATUS[i]; + option.className = "status-option"; + statusDropdown.appendChild(option); + } +}; + +let createTopUp = async (event) => { + event.preventDefault(); + + let form = document.getElementById("top-up-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-top-up"; + } else { + let errorMessage = document.getElementById("error-message"); + errorMessage.textContent = res["data"]; + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open("POST", "/api/TopUpController/createTopUp", true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +}; diff --git a/scripts/client/public/js/admin-top-up.js b/scripts/client/public/js/admin-top-up.js new file mode 100644 index 0000000000000000000000000000000000000000..603a4c5e1cc5c95e56418d5da7bbdb3df5bd50eb --- /dev/null +++ b/scripts/client/public/js/admin-top-up.js @@ -0,0 +1,198 @@ +const ROWS_PER_PAGE = 8; +const INITIAL_PAGE = 1; +const TOP_UP_STATUS = { + 0: 'Pending', + 1: 'Approved', + 2: 'Rejected' +} + +window.onload = function() { + infoNavbarAdded(); + getTopUpsByPage(INITIAL_PAGE); + setPagination(INITIAL_PAGE); +} + +let getTopUpsByPage = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let topUps = res['data']; + let topUpContainer = document.getElementById('top-up-container'); + topUpContainer.innerHTML = ''; + for (let i = 0; i < topUps.length; i++) { + let topUp = topUps[i]; + topUp.status = TOP_UP_STATUS[topUp.status]; + + let topUpCard = document.createElement('div'); + topUpCard.className = 'top-up-card'; + topUpCard.innerHTML = ` + <div class="top-up-info"> + <div class="top-up-user"> + <img src="/public/images/user.png" alt="User Icon" class="user-icon"> + <p>${topUp.username}</p> + </div> + <div class="top-up-date"> + <img src="/public/images/date.png" alt="Date Icon" class="date-icon"> + <p>${topUp.date}</p> + </div> + <div class="top-up-amount"> + <img src="/public/images/amount.png" alt="Amount Icon" class="amount-icon"> + <p>${topUp.amount.toLocaleString('id-ID', { style: 'currency', currency: 'IDR' })}</p> + </div> + <div class="top-up-status"> + <img src="/public/images/status.png" alt="Status Icon" class="status-icon"> + <p>${topUp.status}</p> + </div> + </div> + <div class="top-up-action"> + ${topUp.status == 'Pending' ? + `<a href="#" onclick="handleApproveClick(${topUp.top_up_id})" class="top-up-action-item">Approve</a> + <a href="#" onclick="handleRejectClick(${topUp.top_up_id})" class="top-up-action-item">Reject</a>` : ''} + <a href="#" onclick="deleteTopUp(${topUp.top_up_id})" class="top-up-action-item">Delete</a> + </div> + `; + topUpContainer.appendChild(topUpCard); + } + } else { + alert('Failed to get Top Ups!'); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + } + + xhr.open('GET', `/api/TopUpController/getTopUpsByPage/${page}`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let setPagination = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let paginationContainer = document.getElementById('pagination-container'); + paginationContainer.innerHTML = ''; + let totalPage = Math.ceil(res['data'].length / ROWS_PER_PAGE); + let paginationList = document.createElement('ul'); + paginationList.className = 'pagination-list'; + for (let i = 0; i < totalPage; i++) { + let pageItem = document.createElement('li'); + pageItem.className = 'pagination-item'; + if (i + 1 == page) { + pageItem.className += ' active'; + } + pageItem.innerHTML = `<a href="#" onclick="handlePaginationClick(${i + 1})">${i + 1}</a>`; + paginationList.appendChild(pageItem); + } + paginationContainer.appendChild(paginationList); + } else { + alert('Failed to get pagination!'); + } + } + } + } + + xhr.open('GET', `/api/TopUpController/getAllTopUps`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let handlePaginationClick = async (page) => { + getTopUpsByPage(page); + setPagination(page); +} + +let handleApproveClick = async (id) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res['status']) { + window.location.href = '/pages/admin-top-up'; + } else { + alert('Failed to approve top up!'); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + } + + xhr.open('PUT', `/api/TopUpController/approveTopUp/${id}`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let handleRejectClick = async (id) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res['status']) { + window.location.href = '/pages/admin-top-up'; + } else { + alert('Failed to reject top up!'); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + } + + xhr.open('PUT', `/api/TopUpController/rejectTopUp/${id}`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let deleteTopUp = async (id) => { + let confirmation = confirm("Are you sure you want to delete this Top Up?"); + if (!confirmation) { + return; + } + + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-top-up"; + } else { + alert("Failed to delete top up!"); + } + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + }; + + xhr.open("DELETE", `/api/TopUpController/deleteTopUp/${id}`, true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +}; diff --git a/scripts/client/public/js/admin-user-create.js b/scripts/client/public/js/admin-user-create.js new file mode 100644 index 0000000000000000000000000000000000000000..3c28d280344d7ed176c017321d53099d405f605b --- /dev/null +++ b/scripts/client/public/js/admin-user-create.js @@ -0,0 +1,51 @@ +window.onload = function () { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); +}; + +let createUser = async (event) => { + event.preventDefault(); + + let form = document.getElementById("user-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-user"; + } else { + let errorMessage = document.getElementById("error-message"); + errorMessage.textContent = res["data"]; + } + } + } + }; + + xhr.open("POST", "/api/UserController/createUser", true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +}; diff --git a/scripts/client/public/js/admin-user-edit.js b/scripts/client/public/js/admin-user-edit.js new file mode 100644 index 0000000000000000000000000000000000000000..d152a5802067ea94cc67d651142343ccf0cd1ff0 --- /dev/null +++ b/scripts/client/public/js/admin-user-edit.js @@ -0,0 +1,97 @@ +let urlParams = new URLSearchParams(window.location.search); +let id = urlParams.get("id"); + +window.onload = function () { + infoNavbarAdded(); + + // Check role + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhttp.open("GET","http://localhost:8000/api/Auth/isAdmin",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + getUserById(id); +}; + +let getUserById = async (id) => { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + let user = res["data"]; + let form = document.getElementById("user-form"); + + form["username"].value = user.username; + form["name"].value = user.name; + form["balance"].value = user.balance; + } else { + alert("Failed to get user!"); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open("GET", `/api/UserController/getUserById/${id}`, true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +}; + +let editUser = async (event) => { + event.preventDefault(); + + let confirmation = confirm("Are you sure you want to edit this user?"); + if (!confirmation) { + return; + } + + let form = document.getElementById("user-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-user"; + } else { + let errorMessage = document.getElementById("error-message"); + errorMessage.textContent = res["data"]; + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open("POST", `/api/UserController/editUser/${id}`, true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +}; diff --git a/scripts/client/public/js/admin-user.js b/scripts/client/public/js/admin-user.js new file mode 100644 index 0000000000000000000000000000000000000000..3302a10757e4f184297233b00b5a2ae8a01cd53c --- /dev/null +++ b/scripts/client/public/js/admin-user.js @@ -0,0 +1,128 @@ +const ROWS_PER_PAGE = 8; +const INITIAL_PAGE = 1; + +window.onload = function() { + infoNavbarAdded(); + getUsersByPage(INITIAL_PAGE); + setPagination(INITIAL_PAGE); +} + +let getUsersByPage = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let users = res['data']; + let userContainer = document.getElementById('user-container'); + userContainer.innerHTML = ''; + for (let i = 0; i < users.length; i++) { + let user = users[i]; + + let userCard = document.createElement('div'); + userCard.className = 'user-card'; + userCard.innerHTML = ` + <div class="user-info"> + <div class="user-user"> + <img src="/public/images/user.png" alt="User Icon" class="user-icon"> + <p>${user.username}</p> + </div> + <div class="user-balance"> + <img src="/public/images/amount.png" alt="Balance Icon" class="balance-icon"> + <p>${user.balance.toLocaleString('id-ID', { style: 'currency', currency: 'IDR' })}</p> + </div> + </div> + <div class="user-action"> + <a href="/pages/admin-user-edit?id=${user.id}" class="user-action-item">Edit</a> + <a href="#" onclick="deleteUser(${user.id})" class="user-action-item">Delete</a> + </div> + `; + userContainer.appendChild(userCard); + } + } else { + alert('Failed to get Users!'); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + } + + xhr.open('GET', `/api/UserController/getUsersByPage/${page}`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let setPagination = async (page) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + let paginationContainer = document.getElementById('pagination-container'); + paginationContainer.innerHTML = ''; + let totalPage = Math.ceil(res['data'].length / ROWS_PER_PAGE); + let paginationList = document.createElement('ul'); + paginationList.className = 'pagination-list'; + for (let i = 0; i < totalPage; i++) { + let pageItem = document.createElement('li'); + pageItem.className = 'pagination-item'; + if (i + 1 == page) { + pageItem.className += ' active'; + } + pageItem.innerHTML = `<a href="#" onclick="handlePaginationClick(${i + 1})">${i + 1}</a>`; + paginationList.appendChild(pageItem); + } + paginationContainer.appendChild(paginationList); + } else { + alert('Failed to get pagination!'); + } + } + } + + xhr.open('GET', `/api/UserController/getAllUsers`, true); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.withCredentials = true; + xhr.send(); +} + +let handlePaginationClick = async (page) => { + getUsersByPage(page); + setPagination(page); +} + +let deleteUser = async (id) => { + let confirmation = confirm("Are you sure you want to delete this User?"); + if (!confirmation) { + return; + } + + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status == 200) { + let res = JSON.parse(this.responseText); + + if (res["status"]) { + window.location.href = "/pages/admin-user"; + } else { + alert("Failed to delete user!"); + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + xhr.open("DELETE", `/api/UserController/deleteUser/${id}`, true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(); +}; diff --git a/scripts/client/public/js/detailProduct.js b/scripts/client/public/js/detailProduct.js new file mode 100644 index 0000000000000000000000000000000000000000..368771fa5ffa09958f18d6d8ae3ae282aee9f8b9 --- /dev/null +++ b/scripts/client/public/js/detailProduct.js @@ -0,0 +1,104 @@ +const queryString = window.location.search; +const urlParams = new URLSearchParams(queryString); +var product_id = urlParams.get('product_id'); +var product = null; +let stock = 0; +let nums = 1; +let price = 0; + +window.onload = function() { + infoNavbarAdded(); + getProduct(); +} + +function getProduct() { + document.getElementById("numberamount").value = 1; + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4 && this.status == 200) { + let products = JSON.parse(this.responseText); + if (products["status"]) { + product = products["data"]; + console.log(product); + appendData(products["data"]); + stock = products["data"].stock; + price = products["data"].price; + } + } + }; + xhttp.open("GET", "http://localhost:8000/api/ProductController/getProduct?product_id="+product_id, true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); +} + +function appendData(productDetail) { + var div1 = document.getElementById("productImage"); + div1.src = "/../public/assets/images/" + productDetail.image; + div1.style.objectFit = "cover"; + + var div2 = document.getElementById("productName"); + div2.innerHTML += productDetail.product_name; + + var div3 = document.getElementById("productCategory"); + div3.innerHTML += productDetail.category_name; + + var div4 = document.getElementById("productStock"); + div4.innerHTML += productDetail.stock; + + var div6 = document.getElementById("productPrice"); + div6.innerHTML += productDetail.price.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + }); + + var div7 = document.getElementById("productDesc"); + div7.innerHTML += productDetail.description; +} + +function subsAmount() { + if (parseInt(document.getElementById("numberamount").value) > 1) { + document.getElementById("numberamount").value = parseInt(document.getElementById("numberamount").value) - 1; + } +} + +function addAmount() { + if (parseInt(document.getElementById("numberamount").value) < stock) { + document.getElementById("numberamount").value = parseInt(document.getElementById("numberamount").value) + 1; + } +} + +function buyProduct() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + let res = JSON.parse(this.responseText); + if (res['status']) { + alert("Item successfully purchased!"); + window.location.href = "http://localhost:8000/pages/home"; + } else { + alert(res['data']); + } + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + let nums = parseInt(document.getElementById("numberamount").value); + + let data = { + "product_id": product_id, + "amount": nums, + "total": price + }; + xhttp.open("POST","http://localhost:8000/api/ProductController/buyProduct",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(JSON.stringify(data)); +} \ No newline at end of file diff --git a/scripts/client/public/js/history.js b/scripts/client/public/js/history.js new file mode 100644 index 0000000000000000000000000000000000000000..1d379676c7c80114e807a1cd360aee56d94bf896 --- /dev/null +++ b/scripts/client/public/js/history.js @@ -0,0 +1,62 @@ +window.onload = function() { + infoNavbarAdded(); + + // Get Buy History + var xhr1 = new XMLHttpRequest(); + xhr1.open('GET', 'http://localhost:8000/api/HistoryController/getBuyHistory', true); + xhr1.onreadystatechange = function () { + if (xhr1.readyState == 4) { + if (xhr1.status == 200) { + var response = JSON.parse(xhr1.responseText); + var data = response.data; + var grid = document.getElementById('buy-history-grid'); + data.forEach(item => { + var divDate = document.createElement('div'); + divDate.textContent = item.buydate; + divDate.className = 'grid-value'; + grid.appendChild(divDate); + + var divTotalPrice = document.createElement('div'); + divTotalPrice.textContent = item.totalprice.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + });; + divTotalPrice.className = 'grid-value'; + grid.appendChild(divTotalPrice); + }); + } else { + var errorData = JSON.parse(xhr1.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + xhr1.send(); + + // Get Top Up History + var xhr2 = new XMLHttpRequest(); + xhr2.open('GET', 'http://localhost:8000/api/historycontroller/getTopUpHistory', true); + xhr2.onreadystatechange = function () { + if (xhr2.readyState == 4 && xhr2.status == 200) { + console.log(this.responseText); + var response = JSON.parse(xhr2.responseText); + var data = response.data; + var grid = document.getElementById('topup-history-grid'); + data.forEach(item => { + var divDate = document.createElement('div'); + divDate.textContent = item.date; + divDate.className = 'grid-value'; + grid.appendChild(divDate); + + var divNominal = document.createElement('div'); + divNominal.textContent = item.amount.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + });; + divNominal.className = 'grid-value'; + grid.appendChild(divNominal); + }); + } + }; + xhr2.send(); +}; diff --git a/scripts/client/public/js/login.js b/scripts/client/public/js/login.js new file mode 100644 index 0000000000000000000000000000000000000000..689a8bbccfaac8978a5e31320d5cecbe941abcca --- /dev/null +++ b/scripts/client/public/js/login.js @@ -0,0 +1,29 @@ +document.getElementById('login-form').addEventListener('submit', function(event) { + event.preventDefault(); + + let form = document.getElementById("login-form"); + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + console.log(this.responseText); + if (this.responseText == "Login successful.") { + alert("Login success!"); + window.location.href = "http://localhost:8000/pages/home"; + } else { + alert("Invalid username or password. Please try again."); + window.location.href = "http://localhost:8000/pages/login"; + } + } + }; + + xhr.open( + "POST", + "http://localhost:8000/api/auth/login", + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); +}); \ No newline at end of file diff --git a/scripts/client/public/js/navbar.js b/scripts/client/public/js/navbar.js new file mode 100644 index 0000000000000000000000000000000000000000..66d1cfd8519a5d8c2e85afa1bd2b9eaa4c463499 --- /dev/null +++ b/scripts/client/public/js/navbar.js @@ -0,0 +1,98 @@ +function initPage() { + infoNavbarAdded(); +} + +function infoNavbarAdded() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4 && this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + // Role-based navbar + if (res['data'].role == 'admin') { + putNavbar("admin"); + } else { + putNavbar("user"); + } + document.getElementById("unameuser").innerHTML = res['data'].name; + } else { + putNavbar("guest"); + document.getElementById("unameuser").innerHTML = "Guest"; + document.getElementById("logout").innerHTML = "Login"; + document.getElementById("set").style.display = "none"; + } + } + }; + xhttp.open("GET","http://localhost:8000/api/auth/info",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); +} + +function logout() { + if (document.getElementById("logout").innerHTML == "Login") { + window.location.href = "http://localhost:8000/pages/login"; + } else { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4 && this.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + window.location.href = "http://localhost:8000/pages/home"; + } + } + }; + xhttp.open("POST","http://localhost:8000/api/auth/logout",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + } +} + +function searchProducts() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if(this.readyState == 4 && this.status == 200){ + products = JSON.parse(this.responseText); + appendData(products['data']); + } + }; + xhttp.open("GET","http://localhost:8000/api/productcontroller/showAllproducts",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); +} + +function searchProducts() { + query = document.getElementById("queryproduct").value; + window.location.href = "http://localhost:8000/pages/home?query=" + query; +} + +// debounce +function debounce(func, delay) { + let timeoutId; + + return function() { + clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + func.apply(this, arguments); + }, delay); + }; +} + +// Create a debounced version of searchProducts +const debouncedSearchProducts = debounce(searchProducts, 300); +// Call debouncedSearchProducts when the search icon is clicked +document.getElementById('productqueryimg').addEventListener('click', debouncedSearchProducts); + +document.getElementById("queryproduct") + .addEventListener("keyup", function(event) { + // console.log("searching"); + event.preventDefault(); + // If the user presses the "Enter" key on the keyboard + if (event.keyCode == 13) { + // Trigger the button element with a click + document.getElementById("productqueryimg").click(); + } +}); \ No newline at end of file diff --git a/scripts/client/public/js/navigation.js b/scripts/client/public/js/navigation.js new file mode 100644 index 0000000000000000000000000000000000000000..7af390b4fae157e605ab660c7f1c470c2fda1d66 --- /dev/null +++ b/scripts/client/public/js/navigation.js @@ -0,0 +1,72 @@ +function putNavbar(userRole) { + // Create an object to store navigation data for different roles + const navData = { + user: [ + { text: "History", icon: "history.png", alt: "history", onClick: "http://localhost:8000/pages/history" }, + { text: "Topup", icon: "topup.png", alt: "topup", onClick: "http://localhost:8000/pages/topup" } + ], + admin: [ + { text: "Edit Product", icon: "edit-product.png", alt: "editpro", onClick: "http://localhost:8000/pages/admin-product" }, + { text: "Handle Topup", icon: "topup.png", alt: "topup", onClick: "http://localhost:8000/pages/admin-top-up" }, + { text: "Users", icon: "user-manage.png", alt: "user", onClick: "http://localhost:8000/pages/admin-user" } + ], + guest : [], + }; + + // Queryselector + const navRight = document.querySelector('.navRight'); + + if (userRole != "guest") { + const roleNavData = navData[userRole]; + function createNavItem(item) { + navRight.innerHTML += ` + <div class="navItems" onclick="window.location.href='${item.onClick}'"> + <img class="navIcon" src="../../public/images/${item.icon}" alt="${item.alt}"/> + <span class="navText">${item.text}</span> + </div>`; + } + + roleNavData.forEach(createNavItem); + } + + navRight.innerHTML += + '<div class="navCollapse"> \ + <div class="navItems"> \ + <img class="navIcon" src="../../public/images/user.png" alt="profile"/> \ + <span class="navText" id="unameuser"></span> \ + <i class="fa fa-caret-down"></i> \ + </div> \ + <div class="navDrop"> \ + <div class="navChild" onclick="redirectToSettings()" id="set">Settings</div> \ + <div class="navChild" onclick="logout()" id="logout">Log out</div> \ + </div> \ + </div>'; +} + +function redirectToHome() { + window.location.href = "http://localhost:8000/pages/home"; +} + +function redirectToSettings() { + window.location.href = "http://localhost:8000/pages/settings"; +} + +function redirectToCreateProduct() { + window.location.href = "http://localhost:8000/pages/admin-product-create"; +} + +function redirectToEditProduct(id) { + window.location.href = "http://localhost:8000/pages/admin-product-edit?id=" + id; +} + +function redirectToCreateTopUp() { + window.location.href = "http://localhost:8000/pages/admin-top-up-create"; +} + +function redirectToCreateUser() { + window.location.href = "http://localhost:8000/pages/admin-user-create"; +} + +function redirectToEditUser(id) { + window.location.href = "http://localhost:8000/pages/admin-user-edit?id=" + id; +} \ No newline at end of file diff --git a/scripts/client/public/js/product.js b/scripts/client/public/js/product.js new file mode 100644 index 0000000000000000000000000000000000000000..2c87565826892d3bf90d68ee126fe83c6e0cfd34 --- /dev/null +++ b/scripts/client/public/js/product.js @@ -0,0 +1,274 @@ +const queryString = window.location.search; +const urlParams = new URLSearchParams(queryString); +var query = urlParams.get('query'); +var order_by_name = urlParams.get('order_by_name'); +var order_by_price = urlParams.get('order_by_price'); +var filter_category = null; +var filter_price = null; +var totalPageProduct = null; +var currentPageProduct = null; + +window.onload = function() { + // Setup the query + document.getElementById("queryproduct").value = query; + + // Filter mechanism + if (urlParams.get('filter_category') == null) { + setCategory('None'); + } else { + setCategory(urlParams.get('filter_category')); + document.querySelector("#catlast").value = urlParams.get('filter_category'); + } + + if (urlParams.get('filter_price') == null) { + setPrice('None'); + } else { + setPrice(urlParams.get('filter_price')); + document.querySelector("#prilast").value = urlParams.get('filter_price'); + } + + // Order mechanism + if (order_by_name != null) { + setOrder('nama', order_by_name); + if (order_by_name == 'ASC') { + document.querySelector("#sortip").value = 'Name (A to Z)'; + } else { + document.querySelector("#sortip").value = 'Name (Z to A)'; + } + } else if (order_by_price != null){ + setOrder('harga', order_by_price); + if (order_by_price == 'ASC') { + document.querySelector("#sortip").value = 'Price (Lowest First)'; + } else { + document.querySelector("#sortip").value = 'Price (Highest First)'; + } + } else { + setOrder("nama", "ASC"); + } + infoNavbarAdded(); + selectProduct(1); +} + +function queryProduct() { + param = ''; + query = document.getElementById("queryproduct").value; + if (query != null && query != '') { + param += 'query='+query+'&'; + } + + if (order_by_name != null && order_by_name != '' ){ + param += 'order_by_name='+order_by_name+'&'; + } + if (order_by_price != null && order_by_price != '') { + param += 'order_by_price='+order_by_price+'&'; + } + if (filter_category != null && filter_category != 'None') { + param += 'filter_category='+filter_category+'&'; + } + if (filter_price != null && filter_price != 'None') { + param += 'filter_price='+filter_price+'&'; + } + window.location.href = "http://localhost:8000/pages/home?" + param; +} + +const debouncedSearchQuery = debounce(queryProduct, 300); +document.getElementById('startquery').addEventListener('click', debouncedSearchQuery); + +function selectProduct(numPage) { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4 && this.status == 200) { + let res = JSON.parse(this.responseText); + currentPageProduct = numPage; + if (res['status']) { + console.log(res['data']['total']); + products = res['data']; + document.querySelector("#numsofObj").value = String(res['data']['total']); + totalPageProduct = res['data']['pages']; + clearProduct(); + appendData(products['products'], "queryResultProduct"); + } else { + clearProduct(); + }; + } + }; + + let data = { + "query" : query, + "order_by_price": order_by_price, + "filter_category": filter_category, + "order_by_name": order_by_name, + "filter_price": filter_price + }; + xhttp.open("POST","http://localhost:8000/api/ProductController/queryProduct/"+numPage+"/8/",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(JSON.stringify(data)); +} + +function clearProduct() { + let qAchild = document.getElementById("queryResultProduct"); + while (qAchild.firstChild) { + qAchild.removeChild(qAchild.firstChild); + } + document.getElementById("queryResultProduct").innerHTML = ""; + var pageProductContainer = document.getElementById("pagenumProduct"); + while (pageProductContainer.firstChild) { + pageProductContainer.removeChild(pageProductContainer.firstChild); + } + document.getElementById("pagenumProduct").innerHTML = ""; +} + +function showFilterCategory(value) { + document.querySelector(".filter-category").value = value; + setCategory(value); +} + +function showFilterPrice(value) { + document.querySelector(".filter-price").value = value; + setPrice(value); +} + +function showSort(value) { + document.querySelector(".sort-type").value = value; + if (value === 'Name (A to Z)') { + setOrder('nama', 'ASC'); + } else if (value === 'Name (Z to A)') { + setOrder('nama', 'DESC'); + } else if (value === 'Price (Lowest First)') { + setOrder('harga', 'ASC'); + } else { + setOrder('harga', 'DESC'); + } +} + +let dropdown1 = document.querySelector(".dropdown1") +dropdown1.onclick = function() { + dropdown1.classList.toggle("active") +} + +let dropdown2 = document.querySelector(".dropdown2") +dropdown2.onclick = function() { + dropdown2.classList.toggle("active") +} + +let dropdown3 = document.querySelector(".dropdown3") +dropdown3.onclick = function() { + dropdown3.classList.toggle("active") +} + +function appendData(data, target) { + var mainContainer = document.getElementById(target); + for (var i = 0; i < data.length; i++) { + if (data[i].product_name == null) { + data[i].product_name = "-"; + } + if (data[i].category_name == null) { + data[i].category_name = "-"; + } + if (data[i].image == null) { + data[i].image = ""; + } + if (target=='queryResultProduct') { + mainContainer.innerHTML += + `<div class="card" onclick="rerouteproduct(${data[i].product_id})"> \ + <div class="cardImage"> \ + ${data[i].image.includes('.mp4') ? + `<video src="/public/assets/videos/${data[i].image}" alt="Product Image" autoplay loop muted></video>` : + `<img src="/public/assets/images/${data[i].image}" alt="Product Image">` + } + </div> \ + <div class="productdesc"> \ + <div class="stockCategory"> \ + <div class="category"> \ + <img src="/public/images/category.png" class="cardIcon" alt="category"> + ${data[i].category_name} + </div> \ + <div class="stock"> \ + <img src="/public/images/quantity.png" class="cardIcon" alt="stock"> + ${data[i].stock} + </div> \ + </div> \ + <div class="productTitle"> ${data[i].product_name} </div> \ + <div class="price"> ${data[i].price.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + })}</div> \ + </div> \ + </div>`; + } + } + mainContainer.style.display = "flex"; + mainContainer.style.flexDirection = "row"; + mainContainer.style.flexWrap = "wrap"; + mainContainer.style.justifyContent = "center"; + mainContainer.style.alignItems = "flex-start"; + mainContainer.style.color = "white"; + + paginationProduct(); +}; + +function rerouteproduct(id){ + window.location.href = "http://localhost:8000/pages/detailProduct?product_id="+id; +} + +function paginationProduct() { + var pageProductContainer = document.getElementById("pagenumProduct"); + for (i = 0; i < totalPageProduct; i++) { + if (i == currentPageProduct-1) { + pageProductContainer.innerHTML += '<div class="pageCurr">' + (i+1) + '</div>'; + } + else { + pageProductContainer.innerHTML += '<div class="page" onclick="selectProduct(' + (i+1) + ')">' + (i + 1) + '</div>'; + } + } +} + +function setCategory(inputCategory) { + if (inputCategory == filter_category) { + filter_category = null; + } else { + filter_category = inputCategory; + } +} + +function setPrice(inputPrice) { + if (inputPrice == filter_price) { + filter_price = null; + } else { + filter_price = inputPrice; + } +} + +// Add an event listener to the select element +function setOrder(type, order) { + if (order_by_price != null && order_by_price != '') { + if (type == "nama") { + order_by_price = null; + order_by_name = order; + } else { + order_by_price = order; + } + } else if (order_by_name != null && order_by_name != '') { + if (type=="nama") { + order_by_name = order; + } else { + order_by_price = order; + order_by_name = null; + } + } else { + order_by_name = "ASC"; + } +} + +// Enter key +document.getElementById("queryproduct") + .addEventListener("keyup", function(event) { + event.preventDefault(); + // Enter + if (event.keyCode == 13) { + // Click + document.getElementById("productqueryimg").click(); + } +}); \ No newline at end of file diff --git a/scripts/client/public/js/settings.js b/scripts/client/public/js/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..f2a50a5147adc73902b839113cd41a8ee94d5e2e --- /dev/null +++ b/scripts/client/public/js/settings.js @@ -0,0 +1,63 @@ +window.onload = function() { + infoNavbarAdded(); + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://localhost:8000/api/auth/getInfo', true); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var data = JSON.parse(xhr.responseText); + document.getElementById('username').value = data.username; + document.getElementById('name').value = data.name; + document.getElementById('balance').value = data.balance; + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + xhr.send(); +}; + +document.getElementById('settings-form').addEventListener('submit', function(event) { + event.preventDefault(); + + var name = document.getElementById('name').value; + var password = document.getElementById('password').value; + var confirmPassword = document.getElementById('confirm-password').value; + + if (name.length === 0) { + alert('Name cannot be empty.'); + return; + } + + if (password.length < 6 || !/[A-Za-z]/.test(password) || !/[0-9]/.test(password)) { + alert('Password must be at least 6 characters long and contain both letters and numbers.'); + return; + } + + if (password !== confirmPassword) { + alert('Passwords do not match.'); + return; + } + + var formData = new FormData(event.target); + + var xhr = new XMLHttpRequest(); + xhr.open('POST', 'http://localhost:8000/api/auth/changeAccSettings', true); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var responseText = xhr.responseText; + if (responseText.trim() === '') { + window.location.href = 'http://localhost:8000/client/pages/login'; + } + } else { + var errorData = JSON.parse(xhr.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + xhr.send(formData); +}); diff --git a/scripts/client/public/js/signup.js b/scripts/client/public/js/signup.js new file mode 100644 index 0000000000000000000000000000000000000000..eb0d007cfccf27ae6e9683ac751b598076651eb7 --- /dev/null +++ b/scripts/client/public/js/signup.js @@ -0,0 +1,74 @@ +document.getElementById('register-form').addEventListener('submit', function(event) { + event.preventDefault(); + + var username = document.getElementById('username').value; + var password = document.getElementById('password').value; + var name = document.getElementById('name').value; + + var errorMessage = document.getElementById('error-message'); + var submitButton = document.querySelector('#register-form button[type=submit]'); + + // Clear previous messages + errorMessage.textContent = ''; + + // Check if the username is at least 6 characters long + if (username.length < 6) { + errorMessage.textContent = 'Username must be at least 6 characters.'; + event.preventDefault(); + return; + } + + // Check if the password is at least 6 characters long and contains both letters and numbers + if (password.length < 6 || !/[A-Za-z]/.test(password) || !/[0-9]/.test(password)) { + errorMessage.textContent = 'Password must be at least 6 characters long and contain both letters and numbers.'; + event.preventDefault(); + return; + } + + // Check if the name is empty + if (name.length === 0) { + errorMessage.textContent = 'Name cannot be empty.'; + event.preventDefault(); + return; + } + + // Show loading text + submitButton.textContent = 'Loading...'; + + + // Determine role based on username + var role = username.startsWith('adm-') ? 'admin' : 'user'; + + let form = document.getElementById("register-form"); + let formData = new FormData(form); + + formData.append('role', role); + + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + let res = JSON.parse(this.responseText); + console.log(res); + if (res["status"]) { + alert("Account created!"); + window.location.href = "http://localhost:8000/pages/login"; + } else { + alert("Username already exists! Failed to create account!"); + window.location.href = "http://localhost:8000/pages/signup" + } + } + }; + + xhr.open( + "POST", + "http://localhost:8000/api/auth/signup", + true + ); + xhr.setRequestHeader("Accept", "application/json"); + xhr.withCredentials = true; + xhr.send(formData); + + // Reset the form + event.target.reset(); +}); diff --git a/scripts/client/public/js/topup.js b/scripts/client/public/js/topup.js new file mode 100644 index 0000000000000000000000000000000000000000..e3c5848e1059c45c890d7db1bb7b14862936359d --- /dev/null +++ b/scripts/client/public/js/topup.js @@ -0,0 +1,110 @@ +window.onload = function() { + infoNavbarAdded(); + loadTopupdata(); +} + +function loadTopupdata() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (xhttp.readyState == 4) { + if (xhttp.status == 200) { + let res = JSON.parse(this.responseText); + data = res['data']; + var grid = document.getElementById('pending-grid'); + data.forEach(topup => { + var divDate = document.createElement('div'); + divDate.textContent = topup.date; + divDate.className = 'grid-value'; + grid.appendChild(divDate); + + var divTotalPrice = document.createElement('div'); + divTotalPrice.textContent = topup.amount.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + }); + divTotalPrice.className = 'grid-value'; + grid.appendChild(divTotalPrice); + }); + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + xhttp.open("GET", "http://localhost:8000/api/topupcontroller/getTopUpRequested", true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.withCredentials = true; + xhttp.send(); + + var xhttp1 = new XMLHttpRequest(); + xhttp1.onreadystatechange = function(){ + if (xhttp1.readyState == 4 && xhttp1.status == 200) { + let res = JSON.parse(this.responseText); + if (res['status']) { + data = res['data']; + var grid = document.getElementById('historytopup-grid'); + data.forEach(topup => { + var divDate = document.createElement('div'); + divDate.textContent = topup.date; + divDate.className = 'grid-value'; + grid.appendChild(divDate); + + var divStatus = document.createElement('div'); + if (topup.status == 1) { + divStatus.textContent = 'Success'; + } else { + divStatus.textContent = 'Failed'; + } + divStatus.className = 'grid-value'; + grid.appendChild(divStatus); + + var divTotalPrice = document.createElement('div'); + divTotalPrice.textContent = topup.amount.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + }); + divTotalPrice.className = 'grid-value'; + grid.appendChild(divTotalPrice); + }); + }; + } + }; + xhttp1.open("GET", "http://localhost:8000/api/topupcontroller/getTopUpHistory", true); + xhttp1.setRequestHeader("Accept", "application/json"); + xhttp1.withCredentials = true; + xhttp1.send(); +} + +function requestTopup() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function(){ + if (this.readyState == 4) { + if (this.status == 200) { + console.log(this.responseText); + let res = JSON.parse(this.responseText); + if (res['status']) { + alert("Topup successfully requested!"); + window.location.href = "http://localhost:8000/pages/topup"; + } else { + alert(res['data']); + } + } else { + var errorData = JSON.parse(xhttp.responseText); + alert(errorData.message); + window.location.href = errorData.location; + } + } + }; + + let nums = parseInt(document.getElementById("amount").value); + + let data = { + "amount": nums + }; + xhttp.open("POST", "http://localhost:8000/api/topupcontroller/createTopUpRequest",true); + xhttp.setRequestHeader("Accept", "application/json"); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.withCredentials = true; + xhttp.send(JSON.stringify(data)); +} \ No newline at end of file diff --git a/scripts/index.html b/scripts/index.html new file mode 100644 index 0000000000000000000000000000000000000000..51686ca81fff816569ee9c3416800602432b4d04 --- /dev/null +++ b/scripts/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML5> + <head> + <meta charset="utf-8" /> + <link rel="icon" type="image/png" href="http://localhost:8000/public/images/logo.png"> + <meta http-equiv="refresh" content="3;url=http://localhost:8000/pages/home" /> + <link rel="stylesheet" href="http://localhost:8000/public/css/globals.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> + <title>KBL</title> + </head> + <body class="indexCt"> + <a href="http://localhost:8000/pages/home" class="bigText"> + <img src="http://localhost:8000/public/images/logo.png" /> + </a> + <div class="main-title-spl">- KBL -</div> + <div class="redirect">Redirecting you to Home Page...</div> + </body> +</html> \ No newline at end of file diff --git a/scripts/server/api/.htaccess b/scripts/server/api/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..6aa02622c32a35283f19340fe6293074782b7136 --- /dev/null +++ b/scripts/server/api/.htaccess @@ -0,0 +1,6 @@ +Options -Multiviews + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php?url=$1 [QSA] \ No newline at end of file diff --git a/scripts/server/api/index.php b/scripts/server/api/index.php new file mode 100644 index 0000000000000000000000000000000000000000..d4a1234de9338a9fb3cb15dd5ff8568edf84bfc3 --- /dev/null +++ b/scripts/server/api/index.php @@ -0,0 +1,4 @@ +<?php +require_once '../app/init.php'; + +$app = new App; \ No newline at end of file diff --git a/scripts/server/app/.htaccess b/scripts/server/app/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..45552cb63ea3062d1d3dca0417dda3e3a433351e --- /dev/null +++ b/scripts/server/app/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/scripts/server/app/applications/response.php b/scripts/server/app/applications/response.php new file mode 100644 index 0000000000000000000000000000000000000000..b7596558d2c10a947aab64d2bc3e402b085464dd --- /dev/null +++ b/scripts/server/app/applications/response.php @@ -0,0 +1,9 @@ +<?php + +function json_response_success($data) { + echo(json_encode(array('status' => true, 'data' => $data))); +} + +function json_response_fail($msg) { + echo(json_encode(array('status' => false, 'data' => $msg))); +} \ No newline at end of file diff --git a/scripts/server/app/constants/base.php b/scripts/server/app/constants/base.php new file mode 100644 index 0000000000000000000000000000000000000000..5eb653db32bb966d4754c1bddf1ffdc9cff137cb --- /dev/null +++ b/scripts/server/app/constants/base.php @@ -0,0 +1,10 @@ +<?php + +// DATABASE +define('DB_HOST', $_ENV['DB_HOST']); +define('DB_PORT', $_ENV['DB_PORT']); +define('DB_NAME', $_ENV['DB_NAME']); +define('DB_USER', $_ENV['DB_USER']); +define('DB_PASSWORD', $_ENV['DB_PASSWORD']); + +define('ROWS_PER_PAGE', 8); \ No newline at end of file diff --git a/scripts/server/app/constants/response.php b/scripts/server/app/constants/response.php new file mode 100644 index 0000000000000000000000000000000000000000..c51c1faccfa245b6d9da7f7aa0a3f7ecf44bf27c --- /dev/null +++ b/scripts/server/app/constants/response.php @@ -0,0 +1,28 @@ +<?php + +// GENERAL API RESPONSE +define('API_NOT_FOUND', 'api_not_found'); +define('METHOD_NOT_ALLOWED', 'method_not_allowed'); +define('WRONG_API_CALL', 'wrong_api_call'); +define('INVALID_PARAMETER','invalid_parameter'); +define('INVALID_REQUEST_METHOD', 'invalid_request_method'); + +// LOGIN API RESPONSE +define('ALREADY_LOGIN', 'already_login'); +define('ACCOUNT_NOT_FOUND', 'account_not_found'); +define('NOT_LOGGED_IN', 'not_logged_in'); + +// REGISTER API RESPONSE +define('EMAIL_REGISTERED', 'email_registered'); +define('USERNAME_REGISTERED','username_registered'); + +// PRODUCT API RESPONSE +define('PRODUCT_NOT_FOUND','product_not_found'); + +// CATEGORY API RESPONSE +define('CATEGORY_NOT_FOUND','category_not_found'); + +// TOPUP API RESPONSE +define('TOPUP_REQ_NOT_FOUND','topup_req_not_found'); +define('TOPUP_HIST_NOT_FOUND','topup_hist_not_found'); +define('TOPUP_CREATION_FAILED', 'topup_creation_failed'); \ No newline at end of file diff --git a/scripts/server/app/controllers/Auth.php b/scripts/server/app/controllers/Auth.php new file mode 100644 index 0000000000000000000000000000000000000000..c6f616074fcce0d8f8df9553af4e6e7a1a958a1e --- /dev/null +++ b/scripts/server/app/controllers/Auth.php @@ -0,0 +1,96 @@ +<?php + +class Auth extends Controller { + public function signup() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = $_POST['username']; + $password = $_POST['password']; + + // Hashing + $password = password_hash($password, PASSWORD_DEFAULT); + $name = $_POST['name']; + $role = $_POST['role']; + + $data['username'] = $_POST['username']; + $data['password'] = $password; + $data['name'] = $_POST['name']; + $data['role'] = $_POST['role']; + + if ($this->model('UserModel')->signup($data)) { + json_response_success("success"); + } else { + json_response_fail("fail"); + } + } + } + + public function login() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = $_POST['username']; + $password = $_POST['password']; + + $user = $this->model('UserModel')->getPassword($username); + if ($user === false) { + echo "Username not found.\n"; + return; + } + $true_password = $user['password']; + if (password_verify($password, $true_password)) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['role'] = $user['role']; + echo "Login successful."; + + } else { + echo "Invalid username or password.\n"; + } + + } + } + + public function logout() { + session_destroy(); + json_response_success("success"); + } + + public function info() { + if ($_SERVER['REQUEST_METHOD'] == 'GET' && isset($_SESSION["user_id"])) { + $user_data = $this->model('UserModel')->getUserById($_SESSION["user_id"]); + json_response_success($user_data); + } else { + json_response_fail(NOT_LOGGED_IN); + } + } + + public function getInfo() { + $user = $this->model('UserModel')->getUserInfo($_SESSION['user_id']); + echo json_encode($user); + } + + public function changeAccSettings() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name']; + $password = password_hash($_POST['password'], PASSWORD_DEFAULT); + $id = $_SESSION['user_id']; + + $data = [ + 'name' => $name, + 'password' => $password, + 'id' => $id + ]; + + $user = $this->model('UserModel')->changeAccountSettings($data); + } + } + + public function isAdmin() { + if (isset($_SESSION["role"])) { + if ($_SESSION["role"] == 'admin') { + json_response_success("success"); + } else { + json_response_fail("not"); + } + } else { + json_response_fail("not"); + } + } +} \ No newline at end of file diff --git a/scripts/server/app/controllers/CategoryController.php b/scripts/server/app/controllers/CategoryController.php new file mode 100644 index 0000000000000000000000000000000000000000..5fbc4f59d4b2191de3e53b0a4672d489fb63f4df --- /dev/null +++ b/scripts/server/app/controllers/CategoryController.php @@ -0,0 +1,26 @@ +<?php + +class CategoryController extends Controller { + public function __construct() { + parent::__construct(); + } + + public function getAllCategories() { + $categories = $this->model('CategoryModel')->getAllCategories(); + + return json_response_success($categories); + } + + public function showAllcategories() { + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $res = $this->model('CategoryModel')->getAllCategories(); + if ($res) { + json_response_success($res); + } else { + json_response_fail(CATEGORY_NOT_FOUND); + } + } +} \ No newline at end of file diff --git a/scripts/server/app/controllers/HistoryController.php b/scripts/server/app/controllers/HistoryController.php new file mode 100644 index 0000000000000000000000000000000000000000..e7d5e637e37f93b29dc14b11b3d55a5bf0780618 --- /dev/null +++ b/scripts/server/app/controllers/HistoryController.php @@ -0,0 +1,18 @@ +<?php + +class HistoryController extends Controller { + + public function getTopUpHistory() { + $data = $_SESSION['user_id']; + $topUpHistory = $this->model('HistoryModel')->getAllTopUpHistory($data); + + json_response_success($topUpHistory); + } + + public function getBuyHistory() { + $data = $_SESSION['user_id']; + $buyHistory = $this->model('HistoryModel')->getAllBuyHistory($data); + + json_response_success($buyHistory); + } +} diff --git a/scripts/server/app/controllers/ProductController.php b/scripts/server/app/controllers/ProductController.php new file mode 100644 index 0000000000000000000000000000000000000000..0998d726d7067a3c9595490d5bb113ec2afe3cab --- /dev/null +++ b/scripts/server/app/controllers/ProductController.php @@ -0,0 +1,245 @@ +<?php + +class ProductController extends Controller { + public function __construct() { + parent::__construct(); + } + + public function getAllProducts() { + $products = $this->model('ProductModel')->getAllProducts(); + + json_response_success($products); + } + + public function showAllproducts($page = 1, $limit_page = 10) { + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $res = $this->model('ProductModel')->showAllproducts(); + $total = count($res); + $res = array_slice($res, $page * $limit_page, $limit_page); + + // Pagination + if ($res) { + json_response_success(array("products" => $res, "pages" => ceil($total/$limit_page))); + } else { + json_response_fail(PRODUCT_NOT_FOUND); + } + } + + public function getProductById($id) { + $product = $this->model('ProductModel')->getProductById($id); + + json_response_success($product); + } + + public function getProduct() { + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $res = $this->model('ProductModel')->getProduct($_GET['product_id']); + if ($res) { + json_response_success($res); + } else { + json_response_fail(PRODUCT_NOT_FOUND); + } + } + + public function getProductsByPage($page) { + $products = $this->model('ProductModel')->getProductsByPage($page); + + json_response_success($products); + } + + public function createProduct() { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(INVALID_REQUEST_METHOD); + } + + $data['name'] = $_POST['name']; + $data['description'] = $_POST['description']; + $data['price'] = $_POST['price']; + $data['stock'] = $_POST['stock']; + $data['idCategory'] = $_POST['idCategory']; + $data['image'] = $_FILES['image']['name']; + + // Check if product name already exists + if ($this->model('ProductModel')->getProductByName($data['name'])) { + return json_response_fail("Product name already exists!"); + } + + // Move uploaded image and video to assets folder + if ($data['image'] != '') { + // Get image type + $imageType = ''; + for ($i = strlen($data['image']) - 1; $i >= 0; $i--) { + if ($data['image'][$i] == '.') { + $imageType = substr($data['image'], $i + 1); + break; + } + } + + if (strtolower($imageType) == 'mp4') { + $videoPath = $_SERVER['DOCUMENT_ROOT'] . '/client/public/assets/videos/' . $data['image']; + move_uploaded_file($_FILES['image']['tmp_name'], $videoPath); + } else { + $imagePath = $_SERVER['DOCUMENT_ROOT'] . '/client/public/assets/images/' . $data['image']; + move_uploaded_file($_FILES['image']['tmp_name'], $imagePath); + } + } else { + $data['image'] = 'default.jpg'; + } + + if ($this->model('ProductModel')->createProduct($data)) { + return json_response_success("Product created successfully!"); + } else { + return json_response_fail("Failed to create product!"); + } + } + + public function editProduct($id) { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + json_response_fail(INVALID_REQUEST_METHOD); + } + + $data['id'] = $id; + $data['name'] = $_POST['name']; + $data['image'] = $_FILES['image']['name']; + $data['description'] = $_POST['description']; + $data['idCategory'] = $_POST['idCategory']; + $data['price'] = $_POST['price']; + $data['stock'] = $_POST['stock']; + + // Move uploaded image and video to assets folder + if ($data['image'] != '') { + // Get image type + $imageType = ''; + for ($i = strlen($data['image']) - 1; $i >= 0; $i--) { + if ($data['image'][$i] == '.') { + $imageType = substr($data['image'], $i + 1); + break; + } + } + + if (strtolower($imageType) == 'mp4') { + $videoPath = $_SERVER['DOCUMENT_ROOT'] . '/client/public/assets/videos/' . $data['image']; + move_uploaded_file($_FILES['image']['tmp_name'], $videoPath); + } else { + $imagePath = $_SERVER['DOCUMENT_ROOT'] . '/client/public/assets/images/' . $data['image']; + move_uploaded_file($_FILES['image']['tmp_name'], $imagePath); + } + } else { + $data['image'] = $this->model('ProductModel')->getProductById($id)['image']; + } + + if ($this->model('ProductModel')->editProduct($data)) { + json_response_success("Product edited successfully!"); + } else { + json_response_fail("Failed to edit product!"); + } + } + + public function deleteProduct($id) { + if ($_SERVER['REQUEST_METHOD'] != 'DELETE') { + json_response_fail(INVALID_REQUEST_METHOD); + } + + if ($this->model('ProductModel')->deleteProduct($id)) { + json_response_success("Product deleted successfully!"); + } else { + json_response_fail("Failed to delete product!"); + } + } + + public function queryProduct($page = 1, $limit_page = 10) { + // Blocking other method + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $query = NULL; + $order_by_price = NULL; + $filter_category = NULL; + $order_by_name = 'ASC'; + $filter_price = NULL; + $page = (int) $page - 1; + + if (isset($_POST['order_by_price'])) { + $order_by_price = $_POST['order_by_price']; + } + + if (isset($_POST['filter_price'])) { + if($_POST['filter_price'] != 'None') { + $filter_price = $_POST['filter_price']; + } + } + + if (isset($_POST['filter_category'])) { + if($_POST['filter_category'] != 'None') { + $filter_category = $_POST['filter_category']; + } + } + + if (isset($_POST['query'])) { + $query = $_POST['query']; + } + + if (isset($_POST['order_by_name'])) { + $order_by_name = $_POST['order_by_name']; + } + + $res = $this->model('ProductModel')->queryProduct($query, $order_by_price, $order_by_name, $filter_category, $filter_price); + $total = count($res); + $res = array_slice($res, $page * $limit_page, $limit_page); + + // Pagination + if ($res) { + json_response_success(array("products" => $res, "pages" => ceil($total/$limit_page), "total" => $total)); + } else { + json_response_fail(PRODUCT_NOT_FOUND); + } + } + + public function buyProduct() { + // Blocking other method + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $user_id = $_SESSION['user_id']; + $product_id = $_POST['product_id']; + $amount = $_POST['amount']; + $total = $_POST['total']; + + try { + // Mengurangi jumlah duit + $res1 = $this->model('UserModel')->updateCash($user_id, $total * $amount); + if (!$res1) { + // If updating user's cash fails, rollback the transaction + throw new Exception("Insufficient amount of money"); + } + + // Mengurangi stok produk + $res2 = $this->model('ProductModel')->updateStock($product_id, $amount); + if (!$res2) { + // If updating stock fails, rollback the transaction + throw new Exception("Insufficient stock quantity, please enter a value below the stock limit"); + } + + // Nambah history beli + $res3 = $this->model('HistoryModel')->addHistoryBuy($user_id, $product_id, $amount, $total); + if (!$res3) { + // If adding purchase history fails, rollback the transaction + throw new Exception("History update failed"); + } + + json_response_success("Item successfully purchased"); + + } catch (Exception $e) { + // Handle any exceptions that may occur during the transaction + json_response_fail($e->getMessage()); + } + } +} diff --git a/scripts/server/app/controllers/TopUpController.php b/scripts/server/app/controllers/TopUpController.php new file mode 100644 index 0000000000000000000000000000000000000000..cae95a8d45a107da64d4603e28c3008edb74fce0 --- /dev/null +++ b/scripts/server/app/controllers/TopUpController.php @@ -0,0 +1,160 @@ +<?php + +class TopUpController extends Controller { + public function __construct() { + parent::__construct(); + } + + public function getAllTopUps() { + $topUps = $this->model('TopUpModel')->getAllTopUps(); + + json_response_success($topUps); + } + + public function getTopUpById($id) { + $topUp = $this->model('TopUpModel')->getTopUpById($id); + + json_response_success($topUp); + } + + public function getTopUpsByPage($page) { + $topUps = $this->model('TopUpModel')->getTopUpsByPage($page); + + json_response_success($topUps); + } + + public function createTopUp() { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $data['username'] = $_POST['username']; + $data['amount'] = $_POST['amount']; + $data['status'] = $_POST['status']; + + $user = $this->model('UserModel')->getUserByUsername($data['username']); + + // Check if user exists + if (!$user) { + return json_response_fail("User not found!"); + } + $data['idUser'] = $user['id']; + + // Add amount to user's balance if status is 1 + if ($data['status'] == 1) { + $this->model('UserModel')->addUserBalance($user['id'], $data['amount']); + } + + // Get current date + $data['date'] = date('Y-m-d'); + + if ($this->model('TopUpModel')->createTopUp($data)) { + json_response_success("Top up created successfully!"); + } else { + json_response_fail("Failed to create top up!"); + } + } + + public function getTopUpRequested() { + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + if (!isset($_SESSION['user_id'])) { + return json_response_fail(NOT_LOGGED_IN); + } + + $data = $_SESSION['user_id']; + $res = $this->model('TopUpModel')->getTopUpRequested($data); + + if ($res) { + json_response_success($res); + } else { + json_response_fail(TOPUP_REQ_NOT_FOUND); + } + } + + public function getTopUpHistory() { + if ($_SERVER['REQUEST_METHOD'] != 'GET') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + if (!isset($_SESSION['user_id'])) { + return json_response_fail(NOT_LOGGED_IN); + } + + $data = $_SESSION['user_id']; + $res = $this->model('TopUpModel')->getTopUpHistory($data); + + if ($res) { + json_response_success($res); + } else { + json_response_fail(TOPUP_HIST_NOT_FOUND); + } + } + + public function createTopUpRequest() { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + if (!$_SESSION['user_id']) { + return json_response_fail(NOT_LOGGED_IN); + } + + $data = $_SESSION['user_id']; + $res = $this->model('TopUpModel')->createTopUpRequest($data, $_POST['amount']); + + if ($res) { + json_response_success($res); + } else { + json_response_fail(TOPUP_CREATION_FAILED); + } + } + + public function approveTopUp($id) { + if ($_SERVER['REQUEST_METHOD'] != 'PUT') { + return json_response_fail(INVALID_REQUEST_METHOD); + } + + $data['id'] = $id; + $data['status'] = 1; + + // Add amount to user's balance + $topUp = $this->model('TopUpModel')->getTopUpById($id); + $this->model('UserModel')->addUserBalance($topUp['user_id'], $topUp['amount']); + + if ($this->model('TopUpModel')->editTopUp($data)) { + json_response_success("Top up approved successfully!"); + } else { + json_response_fail("Failed to approve top up!"); + } + } + + public function rejectTopUp($id) { + if ($_SERVER['REQUEST_METHOD'] != 'PUT') { + return json_response_fail(INVALID_REQUEST_METHOD); + } + + $data['id'] = $id; + $data['status'] = 2; + + if ($this->model('TopUpModel')->editTopUp($data)) { + json_response_success("Top up rejected successfully!"); + } else { + json_response_fail("Failed to reject top up!"); + } + } + + public function deleteTopUp($id) { + if ($_SERVER['REQUEST_METHOD'] != 'DELETE') { + return json_response_fail(INVALID_REQUEST_METHOD); + } + + if ($this->model('TopUpModel')->deleteTopUp($id)) { + json_response_success("Top up deleted successfully!"); + } else { + json_response_fail("Failed to delete top up!"); + } + } +} \ No newline at end of file diff --git a/scripts/server/app/controllers/UserController.php b/scripts/server/app/controllers/UserController.php new file mode 100644 index 0000000000000000000000000000000000000000..25aba168507bb61cb6807500dda7e4aa6163a527 --- /dev/null +++ b/scripts/server/app/controllers/UserController.php @@ -0,0 +1,81 @@ +<?php + +class UserController extends Controller { + public function __construct() { + parent::__construct(); + } + + public function getAllUsers() { + $users = $this->model('UserModel')->getAllUsers(); + + json_response_success($users); + } + + public function getUserById($id) { + $user = $this->model('UserModel')->getUserById($id); + + json_response_success($user); + } + + public function getUsersByPage($page) { + $users = $this->model('UserModel')->getUsersByPage($page); + + json_response_success($users); + } + + public function createUser() { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $data['username'] = $_POST['username']; + $data['password'] = $_POST['password']; + $data['name'] = $_POST['name']; + $data['role'] = "user"; + $data['balance'] = $_POST['balance']; + + // Hashing password + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + + if ($this->model('UserModel')->createUser($data)) { + json_response_success("User created successfully!"); + } else { + json_response_fail("Failed to create user!"); + } + } + + public function editUser($id) { + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + $data['id'] = $id; + $data['username'] = $_POST['username']; + $data['name'] = $_POST['name']; + $data['balance'] = $_POST['balance']; + + // Check if username exists + $user = $this->model('UserModel')->getUserByUsername($data['username']); + if ($user && $user['id'] != $data['id']) { + return json_response_fail("Username already exists!"); + } + + if ($this->model('UserModel')->editUser($data)) { + json_response_success("User updated successfully!"); + } else { + json_response_fail("Failed to edit user!"); + } + } + + public function deleteUser($id) { + if ($_SERVER['REQUEST_METHOD'] != 'DELETE') { + return json_response_fail(METHOD_NOT_ALLOWED); + } + + if ($this->model('UserModel')->deleteUser($id)) { + json_response_success("User deleted successfully!"); + } else { + json_response_fail("Failed to delete user!"); + } + } +} \ No newline at end of file diff --git a/scripts/server/app/core/App.php b/scripts/server/app/core/App.php new file mode 100644 index 0000000000000000000000000000000000000000..1fef8b21e08ab9444b8c909c5562c0bb1201dafc --- /dev/null +++ b/scripts/server/app/core/App.php @@ -0,0 +1,147 @@ +<?php + +class App { + protected $params = []; + public function __construct() { + if (isset($_SERVER['HTTP_ORIGIN'])) { + header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}"); + } else { + header('Access-Control-Allow-Origin: *'); + } + + header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); + header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); + header('Access-Control-Allow-Credentials: true'); + header('Access-Control-Max-Age: 86400'); + session_start(); + + $url = $this->parseUrl(); + if (isset($url[0]) && file_exists(__DIR__ . '/../controllers/' . $url[0] . '.php')) { + $this->controller = $url[0]; + } else { + echo json_encode(Array('status' => false, 'message' => "api_not_found")); + return; + } + + require_once __DIR__ . '/../controllers/' . $this->controller . '.php'; + $this->controller = new $this->controller; + + if (isset($url[1])) { + if (method_exists($this->controller, $url[1])) { + $this->method = $url[1]; + unset($url[1]); + } + } else { + $this->method = $url[0]; + } + unset($url[0]); + + if (!empty($url)) { + $this->params = array_values($url); + } + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if ($_POST == null) { + $_POST = json_decode(file_get_contents('php://input'), true); + } + } + + if (isset($_SESSION['user_id'])) { + $this->user = $_SESSION['user_id']; + $this->role = $_SESSION['role']; + } + + // Implement logic to check user already login or not + if (!$this->checkLogin()) { + http_response_code(403); // Forbidden status code + echo json_encode(['status' => false, 'message' => 'You need to login first', 'location' => '/pages/login']); + return; + } + + // Implement logic for role-based access controller + if (!$this->checkAccess()) { + http_response_code(403); // Forbidden status code + echo json_encode(['status' => false, 'message' => 'You are not allowed to access this page', 'location' => '/pages/home']); + return; + } + + call_user_func_array([$this->controller, $this->method], $this->params); + } + + protected function checkLogin() { + $accessControl1 = [ + // Define the allowed ednpoint for unloggedin user + 'ProductController' => [ + 'showAllProducts', 'queryProduct', 'getProduct' + ], + 'CategoryController' => [ + 'showAllcategories' + ], + 'Auth' => [ + 'info', 'login', 'signup', 'isAdmin' + ] + ]; + + $controllerName1 = get_class($this->controller); + + if (isset($accessControl1[$controllerName1]) && in_array($this->method, $accessControl1[$controllerName1])) { + if (!isset($_SESSION['user_id'])) { + return true; + } else { + return true; + } + } else { + // If the method is not explicitly restricted, restrict + if (isset($_SESSION['user_id'])) { + return true; + } else { + return false; + } + } + } + + protected function checkAccess() { + $accessControl = [ + // Define the only admin method controller + 'ProductController' => [ + 'getAllProducts', 'getProductsByPage', 'deleteProduct', 'createProduct', 'editProduct' + ], + 'TopUpController' => [ + 'getAllTopUps', 'getTopUpsByPage', 'createTopUp', 'approveTopUp', 'rejectTopUp', 'deleteTopUp' + ], + 'UserController' => [ + 'getAllUsers', 'getUsersByPage', 'createUser', 'deleteUser' + ], + 'CategoryController' => [ + 'getAllCategories' + ], + 'Auth' => [ + 'isAdmin' + ] + ]; + + $controllerName = get_class($this->controller); + + if (isset($accessControl[$controllerName]) && in_array($this->method, $accessControl[$controllerName])) { + if (isset($_SESSION['role']) && $_SESSION['role'] == 'admin') { + // Admin has access to the method + return true; + } else { + // User doesn't have access to the method + return false; + } + } + + // If the method is not explicitly restricted, allow access + return true; + } + + public function parseURL() { + if (isset($_GET['url'])) { + $url = rtrim($_GET['url'], '/'); + $url = filter_var($url, FILTER_SANITIZE_URL); + $url = explode('/', $url); + return $url; + } + } +} \ No newline at end of file diff --git a/scripts/server/app/core/Controller.php b/scripts/server/app/core/Controller.php new file mode 100644 index 0000000000000000000000000000000000000000..a06c0932da5c519c1148d7de0fce49ded6c9780d --- /dev/null +++ b/scripts/server/app/core/Controller.php @@ -0,0 +1,13 @@ +<?php + +class Controller { + public function __construct() { + require_once __DIR__ . '/../applications/response.php'; + require_once __DIR__ . '/../constants/response.php'; + } + + public function model($model) { + require_once __DIR__ . '/../models/' . $model . '.php'; + return new $model(); + } +} \ No newline at end of file diff --git a/scripts/server/app/core/Database.php b/scripts/server/app/core/Database.php new file mode 100644 index 0000000000000000000000000000000000000000..5d3249a7c200d26dc169aea954ee19d9ba7fa13d --- /dev/null +++ b/scripts/server/app/core/Database.php @@ -0,0 +1,88 @@ +<?php + +class Database { + private $dbh; + private $stmt; + + public function __construct() { + require_once __DIR__ . '/../constants/base.php'; + $option = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]; + + try { + $dsn = 'pgsql:host=' . DB_HOST . ';port=' . DB_PORT . ';dbname=' . DB_NAME; + $this->dbh = new PDO($dsn, DB_USER, DB_PASSWORD, $option); + } catch (PDOException $e) { + die($e->getMessage()); + } + } + + public function startTransaction() { + try { + $this->dbh->beginTransaction(); + } catch (PDOException $e) { + return false; + } + } + + public function commit() { + try { + $this->dbh->beginTransaction(); + } catch (PDOException $e) { + return $this->dbh->commit(); + } + } + + public function rollback() { + try { + $this->dbh->beginTransaction(); + } catch (PDOException $e) { + return $this->dbh->rollBack(); + } + } + + public function query($query) { + $this->stmt = $this->dbh->prepare($query); + } + + public function bind($param, $value, $type = null) { + if (is_null($type)) { + switch (true) { + case is_int($value): + $type = PDO::PARAM_INT; + break; + case is_bool($value): + $type = PDO::PARAM_BOOL; + break; + case is_null($value): + $type = PDO::PARAM_NULL; + break; + default: + $type = PDO::PARAM_STR; + } + } + + $this->stmt->bindValue($param, $value, $type); + } + + public function execute() { + try { + return $this->stmt->execute(); + } catch (\Throwable $th) { + return false; + } + } + + public function resultSet() { + $this->execute(); + return $this->stmt->fetchAll(PDO::FETCH_ASSOC); + } + + public function single() { + try { + $this->execute(); + return $this->stmt->fetch(PDO::FETCH_ASSOC); + } catch (\Throwable $th) { + return false; + } + } +} diff --git a/scripts/server/app/database/create_databases.down.sql b/scripts/server/app/database/create_databases.down.sql new file mode 100644 index 0000000000000000000000000000000000000000..0f4521ba34587a3942a6ae3494359f2acaca3d24 --- /dev/null +++ b/scripts/server/app/database/create_databases.down.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS topUp; +DROP TABLE IF EXISTS buyHistory; +DROP TABLE IF EXISTS product; +DROP TABLE IF EXISTS category; +DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/scripts/server/app/database/create_databases.up.sql b/scripts/server/app/database/create_databases.up.sql new file mode 100644 index 0000000000000000000000000000000000000000..c5d5fa4940037a073899aaed4293bc63ef9f887b --- /dev/null +++ b/scripts/server/app/database/create_databases.up.sql @@ -0,0 +1,50 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY NOT NULL, + username VARCHAR(50) NOT NULL, + password VARCHAR(255) NOT NULL, + name VARCHAR(50) NOT NULL, + role VARCHAR(50) NOT NULL, + balance INTEGER NOT NULL DEFAULT 0, + CONSTRAINT UC_IDUser UNIQUE (id), + CONSTRAINT UC_Username UNIQUE (username) +); + +CREATE TABLE category ( + id SERIAL PRIMARY KEY NOT NULL, + name VARCHAR(100) NOT NULL, + CONSTRAINT UC_IDCategory UNIQUE (id) +); + +CREATE TABLE product ( + id SERIAL PRIMARY KEY NOT NULL, + name VARCHAR(500) NOT NULL, + image VARCHAR(255), + description VARCHAR(255) NOT NULL, + idCategory INTEGER NOT NULL, + price INTEGER NOT NULL, + stock INTEGER NOT NULL, + FOREIGN KEY (idCategory) REFERENCES category(id) ON DELETE CASCADE, + CONSTRAINT UC_IDProduct UNIQUE (id) +); + +CREATE TABLE buyHistory ( + id SERIAL PRIMARY KEY NOT NULL, + idUser INTEGER NOT NULL, + idProduct INTEGER NOT NULL, + quantity INTEGER NOT NULL, + totalPrice INTEGER NOT NULL, + buyDate DATE NOT NULL, + FOREIGN KEY (idUser) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (idProduct) REFERENCES product(id) ON DELETE CASCADE, + CONSTRAINT UC_IDBuyHistory UNIQUE (id) +); + +CREATE TABLE topUp ( + id SERIAL PRIMARY KEY NOT NULL, + idUser INTEGER NOT NULL, + amount INTEGER NOT NULL, + date DATE NOT NULL, + status INTEGER NOT NULL, + FOREIGN KEY (idUser) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT UC_IDTopUp UNIQUE (id) +); \ No newline at end of file diff --git a/scripts/server/app/database/seeding_database.sql b/scripts/server/app/database/seeding_database.sql new file mode 100644 index 0000000000000000000000000000000000000000..bf36177e15da3b9515e2fd9ad27c898fd939aa0d --- /dev/null +++ b/scripts/server/app/database/seeding_database.sql @@ -0,0 +1,19 @@ +INSERT INTO category VALUES (1, 'produkIseng'); +INSERT INTO category VALUES (2, 'isengLagi'); +INSERT INTO category VALUES (3, 'masihIseng'); + +INSERT INTO product VALUES (1, 'produk1', 'default.jpg', 'ini adalah produk1', 1, 20000, 5); +INSERT INTO product VALUES (2, 'ini', 'default.jpg', 'ini adalah produk2', 1, 15000, 4); +INSERT INTO product VALUES (3, 'namanya', 'default.jpg', 'ini adalah produk3', 1, 30000, 5); +INSERT INTO product VALUES (4, 'yah', 'default.jpg', 'ini adalah produk4', 1, 5000, 5); +INSERT INTO product VALUES (5, 'juga', 'default.jpg', 'ini adalah produk5', 1, 20000000, 5); +INSERT INTO product VALUES (6, 'nyoba', 'default.jpg', 'ini adalah produk6', 1, 2000, 5); +INSERT INTO product VALUES (7, 'produk2', 'default.jpg', 'ini adalah produk7', 1, 8000, 5); +INSERT INTO product VALUES (8, 'produk3', 'default.jpg', 'ini adalah produk8', 1, 900000, 5); +INSERT INTO product VALUES (9, 'namanya2', 'default.jpg', 'ini adalah produk9', 2, 30000, 5); +INSERT INTO product VALUES (10, 'yah2', 'default.jpg', 'ini adalah produk10', 2, 50000, 5); +INSERT INTO product VALUES (11, 'juga2', 'default.jpg', 'ini adalah produk11', 2, 2989000, 5); +INSERT INTO product VALUES (12, 'nyoba2', 'default.jpg', 'ini adalah produk12', 2, 200830, 5); +INSERT INTO product VALUES (13, 'produk4', 'default.jpg', 'ini adalah produk13', 3, 80040, 5); +INSERT INTO product VALUES (14, 'produk5', 'default.jpg', 'ini adalah produk14', 3, 90063000, 5); +INSERT INTO product VALUES (15, 'Lorem Ipsum', 'default.jpg', 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s', 3, 9999999, 200); \ No newline at end of file diff --git a/scripts/server/app/init.php b/scripts/server/app/init.php new file mode 100644 index 0000000000000000000000000000000000000000..96dfb13632f74ef39b662872ab7054eaea033280 --- /dev/null +++ b/scripts/server/app/init.php @@ -0,0 +1,5 @@ +<?php + +require_once 'core/App.php'; +require_once 'core/Controller.php'; +require_once 'core/Database.php'; \ No newline at end of file diff --git a/scripts/server/app/models/CategoryModel.php b/scripts/server/app/models/CategoryModel.php new file mode 100644 index 0000000000000000000000000000000000000000..6662acf5543f4c53dad545d34b6da2b562b2c4bb --- /dev/null +++ b/scripts/server/app/models/CategoryModel.php @@ -0,0 +1,21 @@ +<?php + +class CategoryModel { + private $db; + + public function __construct() { + $this->db = new Database(); + } + + public function getAllCategories() { + $this->db->query('SELECT * FROM category ORDER BY id'); + + try { + $this->db->execute(); + // Fetch and return results. + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } +} \ No newline at end of file diff --git a/scripts/server/app/models/HistoryModel.php b/scripts/server/app/models/HistoryModel.php new file mode 100644 index 0000000000000000000000000000000000000000..9427b3b94576375349161d51887958cff6c20106 --- /dev/null +++ b/scripts/server/app/models/HistoryModel.php @@ -0,0 +1,41 @@ +<?php + +class HistoryModel { + private $db; + + public function __construct() { + $this->db = new Database; + } + + public function getAllTopUpHistory($data) { + $this->db->query('SELECT date, amount FROM topUp where idUser = :idUser and status = 1'); + $this->db->bind(':idUser', $data); + + try { + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function getAllBuyHistory($data) { + $this->db->query('SELECT buyDate, totalPrice FROM buyHistory where idUser = :idUser'); + $this->db->bind(':idUser', $data); + + try { + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function addHistoryBuy($user_id, $product_id, $amount, $total) { + $this->db->query('INSERT INTO buyHistory (idUser, idProduct, quantity, totalPrice, buyDate) VALUES (:idUser, :idProduct, :quantity, :totalPrice, CURRENT_DATE)'); + $this->db->bind(':idUser', (int) $user_id); + $this->db->bind(':idProduct', (int) $product_id); + $this->db->bind(':quantity', (int) $amount); + $this->db->bind(':totalPrice', (int) $amount * $total); + + return $this->db->execute(); + } +} \ No newline at end of file diff --git a/scripts/server/app/models/ProductModel.php b/scripts/server/app/models/ProductModel.php new file mode 100644 index 0000000000000000000000000000000000000000..820d05b5dd97cdd976ad5d6fb8b9f92700420c7e --- /dev/null +++ b/scripts/server/app/models/ProductModel.php @@ -0,0 +1,317 @@ +<?php + +class ProductModel { + private $db; + + public function __construct() { + $this->db = new Database(); + } + + public function showAllproducts() { + // SQL query with explicit JOIN and ordering by name. + $sql = 'SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + price, + stock + FROM product + INNER JOIN category ON product.idCategory = category.id WHERE stock > 0'; + + try { + // Prepare and execute the query. + $this->db->query($sql); + $this->db->execute(); + + // Fetch and return results. + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function getAllProducts() { + $this->db->query('SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + category.id AS category_id, + product.price, + product.stock + FROM product + INNER JOIN category ON product.idCategory = category.id + ORDER BY product.name'); + + return $this->db->resultSet(); + } + + public function getProduct($id) { + $this->db->query('SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + price, + stock + FROM product + INNER JOIN category ON product.idCategory = category.id + WHERE product.id = :id + ORDER BY product.name'); + $this->db->bind(':id', (int) $id); + + try { + $this->db->execute(); + return $this->db->single(); + } catch (PDOException $e) { + return false; + } + } + + public function getProductById($id) { + $this->db->query('SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + category.id AS category_id, + product.price, + product.stock + FROM product + INNER JOIN category ON product.idCategory = category.id + WHERE product.id = :id + ORDER BY product.name'); + $this->db->bind(':id', (int) $id); + + return $this->db->single(); + } + + public function getProductsByPage($page) { + $this->db->query('SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + category.id AS category_id, + product.price, + product.stock + FROM product + INNER JOIN category ON product.idCategory = category.id + ORDER BY product.name + LIMIT :limit + OFFSET :offset'); + $this->db->bind(':offset', (int) ($page - 1) * ROWS_PER_PAGE); + $this->db->bind(':limit', (int) ROWS_PER_PAGE); + + return $this->db->resultSet(); + } + + public function getProductByName($name) { + $this->db->query('SELECT * FROM product WHERE name = :name'); + $this->db->bind(':name', $name); + + return $this->db->single(); + } + + public function createProduct($data) { + $this->db->query('INSERT INTO product (name, image, description, idCategory, price, stock) VALUES (:name, :image, :description, :idCategory, :price, :stock)'); + $this->db->bind(':name', $data['name']); + $this->db->bind(':image', $data['image']); + $this->db->bind(':description', $data['description']); + $this->db->bind(':idCategory', (int) $data['idCategory']); + $this->db->bind(':price', (int) $data['price']); + $this->db->bind(':stock', (int) $data['stock']); + + return $this->db->execute(); + } + + public function editProduct($data) { + $this->db->query('UPDATE product SET name = :name, image = :image, description = :description, idCategory = :idCategory, price = :price, stock = :stock WHERE id = :id'); + $this->db->bind(':id', (int) $data['id']); + $this->db->bind(':name', $data['name']); + $this->db->bind(':image', $data['image']); + $this->db->bind(':description', $data['description']); + $this->db->bind(':idCategory', (int) $data['idCategory']); + $this->db->bind(':price', (int) $data['price']); + $this->db->bind(':stock', (int) $data['stock']); + + return $this->db->execute(); + } + + public function deleteProduct($id) { + $this->db->query('DELETE FROM product WHERE id = :id'); + $this->db->bind(':id', (int) $id); + + return $this->db->execute(); + } + + public function queryProduct($query, $orderByPrice, $orderByName, $filterByCategory, $filterByPrice) { + $likeQuery = '%' . $query . '%'; + $filterCategory = '%' . $filterByCategory . '%'; + $pricequery = NULL; + if ($filterByPrice == '< 5K') { + $pricequery = 'price < 5000'; + } else if ($filterByPrice == '5K - 30K') { + $pricequery = 'price BETWEEN 5000 AND 30000'; + } else if ($filterByPrice == '30K - 100K') { + $pricequery = 'price BETWEEN 30000 AND 100000'; + } else if ($filterByPrice == '> 100K') { + $pricequery = 'price > 100000'; + } + + $initSelection = 'SELECT + product.id AS product_id, + product.name AS product_name, + product.image, + product.description, + category.name AS category_name, + price, + stock + FROM product + INNER JOIN category ON product.idCategory = category.id'; + if (isset($query)) { + if (is_numeric($query)) { + if (isset($filterByCategory)) { + if (isset($filterByPrice)) { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR price = :query) AND category.name LIKE :filterCategory AND ' . $pricequery . ' AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR price = :query) AND category.name LIKE :filterCategory AND ' . $pricequery . ' AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR price = :query) AND category.name LIKE :filterCategory AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR price = :query) AND category.name LIKE :filterCategory AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } + } + } else { + if (isset($filterByPrice)) { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery OR price = :query) AND ' . $pricequery . ' AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + } else { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery OR price = :query) AND ' . $pricequery . ' AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery OR price = :query) AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + } else{ + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery OR price = :query) AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':query', $query); + $this->db->bind(':likeQuery', $likeQuery); + } + } + } + } else { + if (isset($filterByCategory)) { + if (isset($filterByPrice)) { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE product.name LIKE :likeQuery AND category.name LIKE :filterCategory AND ' . $pricequery . ' AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE product.name LIKE :likeQuery AND category.name LIKE :filterCategory AND '. $pricequery . ' AND stock > 0 ORDER BY product.name '. $orderByName); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE product.name LIKE :likeQuery AND category.name LIKE :filterCategory AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE product.name LIKE :likeQuery AND category.name LIKE :filterCategory AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':likeQuery', $likeQuery); + $this->db->bind(':filterCategory', $filterCategory); + } + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery) AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':likeQuery', $likeQuery); + } else { + $this->db->query($initSelection . ' WHERE (product.name LIKE :likeQuery OR category.name LIKE :likeQuery) AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':likeQuery', $likeQuery); + } + } + } + } else { + if (isset($filterByCategory)) { + if (isset($filterByPrice)) { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE category.name LIKE :filterCategory AND ' . $pricequery . ' AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE category.name LIKE :filterCategory AND ' . $pricequery . ' AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':filterCategory', $filterCategory); + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE category.name LIKE :filterCategory AND stock > 0 ORDER BY price ' . $orderByPrice); + $this->db->bind(':filterCategory', $filterCategory); + } else { + $this->db->query($initSelection . ' WHERE category.name LIKE :filterCategory AND stock > 0 ORDER BY product.name ' . $orderByName); + $this->db->bind(':filterCategory', $filterCategory); + } + } + } else { + if (isset($filterByPrice)) { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE ' . $pricequery . ' AND stock > 0 ORDER BY price ' . $orderByPrice); + } else { + $this->db->query($initSelection . ' WHERE ' . $pricequery . ' AND stock > 0 ORDER BY product.name ' . $orderByName); + } + } else { + if (isset($orderByPrice)) { + $this->db->query($initSelection . ' WHERE stock > 0 ORDER BY price ' . $orderByPrice); + } else { + $this->db->query($initSelection . ' WHERE stock > 0 ORDER BY product.name ' . $orderByName); + } + } + } + } + + try { + $this->db->execute(); + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function updateStock($product_id, $amount) { + $this->db->query('UPDATE product SET stock = stock - :amount WHERE id = :id'); + $this->db->bind(':amount', $amount); + $this->db->bind(':id', $product_id); + + try { + return $this->db->execute(); + } catch (PDOException $e) { + return false; + } + } +} \ No newline at end of file diff --git a/scripts/server/app/models/TopUpModel.php b/scripts/server/app/models/TopUpModel.php new file mode 100644 index 0000000000000000000000000000000000000000..279941b2e0570973306ee5fbf5b74462ca575ba9 --- /dev/null +++ b/scripts/server/app/models/TopUpModel.php @@ -0,0 +1,118 @@ +<?php + +class TopUpModel { + private $db; + + public function __construct() { + $this->db = new Database(); + } + + public function getAllTopUps() { + $this->db->query('SELECT + topUp.id AS top_up_id, + topUp.amount, + topUp.date, + topUp.status, + users.id AS user_id, + username + FROM topUp + INNER JOIN users ON topUp.idUser = users.id + ORDER BY topUp.status, topUp.date DESC, topUp.id DESC'); + + return $this->db->resultSet(); + } + + public function getTopUpById($id) { + $this->db->query('SELECT + topUp.id AS top_up_id, + topUp.amount, + topUp.date, + topUp.status, + users.id AS user_id, + username + FROM topUp + INNER JOIN users ON topUp.idUser = users.id + WHERE topUp.id = :id'); + $this->db->bind(':id', $id); + + return $this->db->single(); + } + + public function getTopUpsByPage($page) { + $this->db->query('SELECT + topUp.id AS top_up_id, + topUp.amount, + topUp.date, + topUp.status, + users.id AS user_id, + username + FROM topUp + INNER JOIN users ON topUp.idUser = users.id + ORDER BY topUp.status, topUp.date DESC, topUp.id DESC + LIMIT :limit + OFFSET :offset'); + $this->db->bind(':limit', (int) ROWS_PER_PAGE); + $this->db->bind(':offset', ($page - 1) * ROWS_PER_PAGE); + + return $this->db->resultSet(); + } + + public function createTopUp($data) { + $this->db->query('INSERT INTO topUp (idUser, amount, date, status) VALUES (:idUser, :amount, :date, :status)'); + $this->db->bind(':idUser', $data['idUser']); + $this->db->bind(':amount', $data['amount']); + $this->db->bind(':date', $data['date']); + $this->db->bind(':status', $data['status']); + + return $this->db->execute(); + } + + public function getTopUpRequested($id) { + $this->db->query('SELECT * FROM topUp WHERE idUser = :id AND status = 0 ORDER BY id'); + $this->db->bind(':id', $id); + + try { + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function getTopUpHistory($id) { + $this->db->query('SELECT * FROM topUp WHERE idUser = :id AND (status = 1 OR status = 2) ORDER BY id'); + $this->db->bind(':id', $id); + + try { + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function createTopUpRequest($id, $amount) { + $this->db->query('INSERT INTO topUp (idUser, amount, date, status) VALUES (:idUser, :amount, CURRENT_DATE, 0)'); + $this->db->bind(':idUser', $id); + $this->db->bind(':amount', $amount); + + try { + return $this->db->execute(); + } catch (PDOException $e) { + return false; + } + } + + public function editTopUp($data) { + $this->db->query('UPDATE topUp SET status = :status WHERE id = :id'); + $this->db->bind(':id', $data['id']); + $this->db->bind(':status', $data['status']); + + return $this->db->execute(); + } + + public function deleteTopUp($id) { + $this->db->query('DELETE FROM topUp WHERE id = :id'); + $this->db->bind(':id', $id); + + return $this->db->execute(); + } +} \ No newline at end of file diff --git a/scripts/server/app/models/UserModel.php b/scripts/server/app/models/UserModel.php new file mode 100644 index 0000000000000000000000000000000000000000..a21ee05da18cc4aa3e8a4d077d2b2abdf1808f44 --- /dev/null +++ b/scripts/server/app/models/UserModel.php @@ -0,0 +1,144 @@ +<?php + +class UserModel { + private $table = 'users'; + private $db; + + public function __construct() { + $this->db = new Database(); + } + + public function getAllUsers() { + $this->db->query('SELECT * FROM users WHERE role = :role ORDER BY id'); + $this->db->bind(':role', 'user'); + + return $this->db->resultSet(); + } + + public function showAllUsers() { + $this->db->query('SELECT * FROM ' . $this-> table . ' ORDER BY id'); + + try { + return $this->db->resultSet(); + } catch (PDOException $e) { + return false; + } + } + + public function getUserById($id) { + $this->db->query('SELECT * FROM users WHERE id = :id'); + $this->db->bind(':id', $id); + + return $this->db->single(); + } + + public function getUserInfo($data) { + $this->db->query('SELECT username, name, balance FROM users WHERE id = :id'); + $this->db->bind(':id', $data); + + return $this->db->single(); + } + + public function getUserByUsername($username) { + $this->db->query('SELECT * FROM users WHERE username = :username'); + $this->db->bind(':username', $username); + + return $this->db->single(); + } + + public function getPassword($data) { + $this->db->query('SELECT id, password, role FROM users WHERE username = :username'); + $this->db->bind(':username', $data); + + return $this->db->single(); + } + + public function getUsersByPage($page) { + $this->db->query('SELECT * FROM users WHERE role = :role ORDER BY id LIMIT :limit OFFSET :offset'); + $this->db->bind(':role', 'user'); + $this->db->bind(':limit', (int) ROWS_PER_PAGE); + $this->db->bind(':offset', ($page - 1) * ROWS_PER_PAGE); + + return $this->db->resultSet(); + } + + public function addUserBalance($id, $amount) { + $this->db->query('UPDATE users SET balance = balance + :amount WHERE id = :id'); + $this->db->bind(':amount', $amount); + $this->db->bind(':id', $id); + + return $this->db->execute(); + } + + public function signup($data) { + $this->db->query('INSERT INTO users (username, password, name, role) VALUES (:username, :password, :name, :role)'); + $this->db->bind(':username', $data['username']); + $this->db->bind(':password', $data['password']); + $this->db->bind(':name', $data['name']); + $this->db->bind(':role', $data['role']); + + return $this->db->execute(); + } + + public function changeAccountSettings($data) { + $this->db->query('UPDATE users SET name = :name, password = :password WHERE id = :id'); + $this->db->bind(':name', $data['name']); + $this->db->bind(':password', $data['password']); + $this->db->bind(':id', $data['id']); + + return $this->db->execute(); + } + + public function updateCash($user_id, $amount) { + // Check if the user has sufficient balance + $this->db->query('SELECT balance FROM users WHERE id = :id'); + $this->db->bind(':id', $user_id); + + try { + $result = $this->db->single(); // Fetch the user's current balance + $currentBalance = $result['balance']; + + if ($currentBalance >= $amount) { + // If the balance is sufficient, update it + $this->db->query('UPDATE users SET balance = balance - :amount WHERE id = :id'); + $this->db->bind(':amount', $amount); + $this->db->bind(':id', $user_id); + + return $this->db->execute(); + } else { + // If the balance is insufficient, return false + return false; + } + } catch (PDOException $e) { + return false; + } + } + + public function createUser($data) { + $this->db->query('INSERT INTO users (username, password, name, role, balance) VALUES (:username, :password, :name, :role, :balance)'); + $this->db->bind(':username', $data['username']); + $this->db->bind(':password', $data['password']); + $this->db->bind(':name', $data['name']); + $this->db->bind(':role', $data['role']); + $this->db->bind(':balance', $data['balance']); + + return $this->db->execute(); + } + + public function editUser($data) { + $this->db->query('UPDATE users SET username = :username, name = :name, balance = :balance WHERE id = :id'); + $this->db->bind(':id', $data['id']); + $this->db->bind(':username', $data['username']); + $this->db->bind(':name', $data['name']); + $this->db->bind(':balance', $data['balance']); + + return $this->db->execute(); + } + + public function deleteUser($id) { + $this->db->query('DELETE FROM users WHERE id = :id'); + $this->db->bind(':id', $id); + + return $this->db->execute(); + } +} \ No newline at end of file