Merge branch 'main' into Dev-branch
@ -4,3 +4,12 @@ How to get started with this project?
|
||||
|
||||
Security in GIT repo.
|
||||
i repeat DO NOT USE CREDS IN CODE! Please use .env files (https://www.npmjs.com/package/dotenv)
|
||||
|
||||
## Workload
|
||||
1) Ti Seng
|
||||
* Webserver Microservices
|
||||
* IoT sensor
|
||||
* All Database / Backend Functions
|
||||
* consumer website api and user function
|
||||
2) Sean
|
||||
* Admin Website Microservice
|
||||
|
@ -1,35 +1,50 @@
|
||||
// models/User.js
|
||||
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
const sequelize = new Sequelize(process.env.database, process.env.user, process.env.password, {
|
||||
host: process.env.host,
|
||||
dialect: 'mysql',
|
||||
timezone: 'Z', // Set the timezone to UTC
|
||||
});
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
const User = sequelize.define('User', {
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
jobTitle: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
});
|
||||
module.exports = (sequelize) => {
|
||||
const User = sequelize.define('users', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
jobTitle: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
reset_token: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
reset_token_expiry: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
}, {
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
user.password = await bcrypt.hash(user.password, 10);
|
||||
},
|
||||
},
|
||||
timestamps: false, // Disabling timestamps here
|
||||
});
|
||||
|
||||
module.exports = User;
|
||||
return User;
|
||||
};
|
||||
|
30
Sean/models/userLogs.js
Normal file
@ -0,0 +1,30 @@
|
||||
// userLogs.js
|
||||
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
const userLogs = sequelize.define('user_logs', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
activity: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
timestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
}, {
|
||||
timestamps: false, // Disabling timestamps
|
||||
});
|
||||
|
||||
return userLogs;
|
||||
};
|
@ -2,45 +2,47 @@ const mysql = require("mysql2");
|
||||
const path = require("path");
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
|
||||
const fs = require('fs');
|
||||
/*
|
||||
const mysqlConfig = {
|
||||
host: process.env.host,
|
||||
user: process.env.user,
|
||||
password: process.env.password,
|
||||
database: process.env.database,
|
||||
timezone: "Z", // Set the timezone to UTC
|
||||
};
|
||||
|
||||
const connection = mysql.createConnection(mysqlConfig);
|
||||
connection.connect((err) => {
|
||||
if (err) {
|
||||
console.error("Error connecting to MySQL:", err);
|
||||
return;
|
||||
}
|
||||
console.log("Connected to MySQL");
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
const connection = mysql.createConnection({
|
||||
host: process.env.host,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: "database",
|
||||
ssl: {
|
||||
ca: fs.readFileSync(path.resolve(__dirname, '../../cert/DigiCertGlobalRootCA.crt.pem')),
|
||||
}
|
||||
});
|
||||
*/
|
||||
const connection = mysql.createConnection({
|
||||
host: process.env.host,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: "adminusers",
|
||||
timezone: "Z", // Set the timezone to UTC
|
||||
});
|
||||
const UserModel = require('../models/User');// Adjust the path based on your project structure
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
|
||||
module.exports = { connection };
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
"adminusers",
|
||||
process.env.DB_USER,
|
||||
process.env.DB_PASS,
|
||||
{
|
||||
host: "mpsqldatabasean.mysql.database.azure.com",
|
||||
dialect: 'mysql',
|
||||
// attributeBehavior?: 'escape' | 'throw' | 'unsafe-legacy';
|
||||
attributeBehavior: 'escape',
|
||||
dialectOptions: {
|
||||
ssl: {
|
||||
ca: fs.readFileSync(path.resolve(__dirname, '../../cert/DigiCertGlobalRootCA.crt.pem')),
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
);
|
||||
|
||||
sequelize.authenticate().then(() => {
|
||||
console.log('Connection has been established successfully.');
|
||||
}).catch((error) => {
|
||||
console.error('Unable to connect to the database: ', error);
|
||||
});
|
||||
|
||||
|
||||
const User = UserModel(sequelize);
|
||||
|
||||
// Synchronize the models with the database
|
||||
sequelize.sync();
|
||||
|
||||
module.exports = {
|
||||
sequelize,
|
||||
User,
|
||||
};
|
||||
|
||||
|
||||
|
1146
Sean/server.js
@ -8,7 +8,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Home</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
.chart-container {
|
||||
width: 400px;
|
||||
height: 250px;
|
||||
margin: 20px;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Arial', sans-serif;
|
||||
@ -45,63 +53,22 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="navbar">
|
||||
<h1>Eco Saver</h1>
|
||||
<a href="/inusers">In-House Users</a>
|
||||
<a href="#">Users</a>
|
||||
<a href="#">Data Analysis</a>
|
||||
<a href="#">Logout</a>
|
||||
<a href="#">Sensors</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Welcome to the Home Page, <%= username %>!</h2>
|
||||
<h3>Last 10 Logins:</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Last Login Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% loginLogs.forEach(log => { %>
|
||||
<tr>
|
||||
<td><%= log.username %></td>
|
||||
<td><%= new Date(log.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
|
107
Sean/views/home.js
Normal file
@ -0,0 +1,107 @@
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
console.log("DOM Loaded");
|
||||
|
||||
// Extract data from sensorData
|
||||
const sensorData = JSON.parse('<%- JSON.stringify(sensorData) %>');
|
||||
console.log("Sensor Data:", sensorData);
|
||||
|
||||
// Fetch location names from the server
|
||||
const locationNames = await fetch('/api/locations') // Adjust the API endpoint
|
||||
.then(response => response.json())
|
||||
.then(data => data.map(location => ({ id: location.id, name: location.name })))
|
||||
.catch(error => console.error('Error fetching location names:', error));
|
||||
|
||||
|
||||
|
||||
// Group sensorData by locationid
|
||||
const groupedData = groupBy(sensorData, 'locationid');
|
||||
|
||||
// Get the content div
|
||||
const contentDiv = document.getElementById('content');
|
||||
|
||||
// Create a chart for each location
|
||||
Object.keys(groupedData).forEach(locationId => {
|
||||
const locationData = groupedData[locationId];
|
||||
|
||||
// Find the corresponding location name
|
||||
const locationName = locationNames.find(location => location.id === parseInt(locationId, 10))?.name || `Unknown Location ${locationId}`;
|
||||
|
||||
// Create a container for the chart
|
||||
const container = document.createElement('div');
|
||||
container.className = 'chart-container';
|
||||
|
||||
// Create a title for the container with location name
|
||||
const title = document.createElement('h4');
|
||||
title.textContent = `Location: ${locationName}`;
|
||||
container.appendChild(title);
|
||||
|
||||
// Get labels (Location IDs)
|
||||
const labels = locationData.map(data => new Date(data.createdAt).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }));
|
||||
|
||||
// Create datasets for each measurement
|
||||
const datasets = [
|
||||
{
|
||||
label: 'CO',
|
||||
data: locationData.map(data => data.measurement.co),
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.5)', // Red color
|
||||
},
|
||||
{
|
||||
label: 'O3',
|
||||
data: locationData.map(data => data.measurement.o3),
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.5)', // Blue color
|
||||
},
|
||||
{
|
||||
label: 'NO2',
|
||||
data: locationData.map(data => data.measurement.no2),
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.5)', // Yellow color
|
||||
},
|
||||
{
|
||||
label: 'SO2',
|
||||
data: locationData.map(data => data.measurement.so2),
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.5)', // Green color
|
||||
},
|
||||
];
|
||||
|
||||
// Create a canvas element for each location
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 400;
|
||||
canvas.height = 200;
|
||||
|
||||
// Append canvas to the container
|
||||
container.appendChild(canvas);
|
||||
|
||||
// Append container to the content div
|
||||
contentDiv.appendChild(container);
|
||||
|
||||
// Create a bar chart for each location
|
||||
const ctx = canvas.getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets,
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function to group data by a specified key
|
||||
function groupBy(arr, key) {
|
||||
return arr.reduce((acc, obj) => {
|
||||
const groupKey = obj[key];
|
||||
acc[groupKey] = acc[groupKey] || [];
|
||||
acc[groupKey].push(obj);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="/user-creation.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
|
||||
|
||||
@ -40,7 +41,6 @@
|
||||
<th>Name</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Last Login</th>
|
||||
<th>Job Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -51,7 +51,6 @@
|
||||
<td><%- user.name %></td>
|
||||
<td><%- user.username %></td>
|
||||
<td><%- user.email %></td>
|
||||
<td><%- new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %></td>
|
||||
<td><%- user.jobTitle %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
@ -92,10 +91,11 @@
|
||||
<span class="details">Job Title</span>
|
||||
<select name="jobTitle" id="jobTitle">
|
||||
<option value="admin">Admin</option>
|
||||
<option value="dataAnalyst">Data Analyst</option>
|
||||
<option value="user">User</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
|
||||
<div class="button">
|
||||
<input type="submit" value="Register">
|
||||
</div>
|
||||
@ -121,6 +121,7 @@
|
||||
<input type="password" name="confirmPassword" id="resetConfirmPassword" placeholder="Confirm new password" required>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
|
||||
<div class="button">
|
||||
<input type="submit" value="Reset Password">
|
||||
</div>
|
||||
@ -128,6 +129,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deleteUserContainer" style="display: none;">
|
||||
<h3>Delete User</h3>
|
||||
<div class="search-container">
|
||||
@ -136,15 +138,21 @@
|
||||
</div>
|
||||
<div id="searchResultsContainer" style="display: none;">
|
||||
<h4>Search Results</h4>
|
||||
<ul id="searchResultsList"></ul>
|
||||
<ul id="searchResultsList">
|
||||
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="logsContainer" style="display: none;">
|
||||
<!-- Content for logs will be added here -->
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const allUsers = <%- JSON.stringify(allUsers) %>;
|
||||
const currentUsername = '<%= currentUsername %>';
|
||||
|
||||
</script>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
@ -153,6 +161,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js"></script>
|
||||
<script src="inusers.js"></script>
|
||||
|
||||
</div>
|
||||
|
@ -1,6 +1,3 @@
|
||||
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#resetPasswordLink').on('click', function () {
|
||||
$('#resetPasswordFormContainer').show();
|
||||
@ -30,9 +27,10 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
$('#searchUserButton').on('click', function () {
|
||||
console.log('Search button clicked');
|
||||
|
||||
const searchUsername = $('#searchUserInput').val();
|
||||
// Call the function to search for the user
|
||||
|
||||
searchUser(searchUsername);
|
||||
});
|
||||
|
||||
@ -62,81 +60,7 @@ $('#logsLink').on('click', function () {
|
||||
|
||||
fetchLogs();
|
||||
});
|
||||
function fetchLogs() {
|
||||
// Make a fetch request to your server endpoint for logs
|
||||
fetch('/api/getLogs')
|
||||
.then(response => response.json())
|
||||
.then(logs => {
|
||||
// Process and display logs in the logs container
|
||||
displayLogs(logs);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching logs:', error);
|
||||
// Handle errors, e.g., display an alert
|
||||
});
|
||||
}
|
||||
|
||||
// Update the displayLogs function to generate a table
|
||||
function displayLogs(logs) {
|
||||
const logsContainer = $('#logsContainer');
|
||||
|
||||
// Clear previous logs
|
||||
logsContainer.empty();
|
||||
|
||||
if (logs && logs.length > 0) {
|
||||
// Create the table and header row
|
||||
const table = $('<table>').addClass('logs-table');
|
||||
const headerRow = '<tr><th>ID</th><th>Username</th><th>Activity</th><th>Timestamp</th></tr>';
|
||||
table.append(headerRow);
|
||||
|
||||
// Add each log as a row in the table
|
||||
logs.forEach(log => {
|
||||
const row = `<tr><td>${log.id}</td><td>${log.username}</td><td>${log.activity}</td><td>${log.timestamp}</td></tr>`;
|
||||
table.append(row);
|
||||
});
|
||||
|
||||
// Append the table to the logsContainer
|
||||
logsContainer.append(table);
|
||||
|
||||
// Add a download button at the top with the current date and time in the file name
|
||||
const currentDate = new Date();
|
||||
const formattedDate = currentDate.toISOString().split('T')[0];
|
||||
const formattedTime = currentDate.toTimeString().split(' ')[0].replace(/:/g, '-');
|
||||
const downloadButton = $('<button>').text('Download Log').on('click', function () {
|
||||
downloadLogs(logs, `log_${formattedDate}_${formattedTime}.csv`);
|
||||
});
|
||||
|
||||
// Prepend the download button to the logsContainer
|
||||
logsContainer.prepend(downloadButton);
|
||||
} else {
|
||||
// Display a message if no logs are available
|
||||
logsContainer.html('<p>No logs available.</p>');
|
||||
}
|
||||
}
|
||||
|
||||
function downloadLogs(logs, filename) {
|
||||
if (logs && logs.length > 0) {
|
||||
const csvContent = 'data:text/csv;charset=utf-8,';
|
||||
const header = 'ID,Username,Activity,Timestamp\n';
|
||||
const rows = logs.map(log => `${log.id},${log.username},${log.activity},"${log.timestamp}"`).join('\n');
|
||||
const data = header + rows;
|
||||
const encodedData = encodeURI(csvContent + data);
|
||||
|
||||
// Create a hidden anchor element to trigger the download
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', encodedData);
|
||||
link.setAttribute('download', 'logs.csv');
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Trigger the download
|
||||
link.click();
|
||||
|
||||
// Remove the link from the DOM
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
console.error('No logs available for download.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function searchUser(username) {
|
||||
@ -148,9 +72,9 @@ function searchUser(username) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(users => {
|
||||
.then(user => {
|
||||
// Display search results
|
||||
displaySearchResults(users);
|
||||
displaySearchResults(user);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Search error:', error);
|
||||
@ -160,31 +84,33 @@ function searchUser(username) {
|
||||
|
||||
// Function to display search results
|
||||
function displaySearchResults(users) {
|
||||
|
||||
const searchResultsList = $('#searchResultsList');
|
||||
|
||||
// Clear previous results
|
||||
searchResultsList.empty();
|
||||
|
||||
if (users && users.length > 0) {
|
||||
users.forEach(user => {
|
||||
const listItem = `<li>${user.username} - <button class="deleteUserButton" data-username="${user.username}">Delete</button></li>`;
|
||||
const listItem = `<li>${users.username} - <button class="deleteUserButton" data-username="${users.username}">Delete</button></li>`;
|
||||
searchResultsList.append(listItem);
|
||||
});
|
||||
|
||||
// Show the search results container
|
||||
$('#searchResultsContainer').show();
|
||||
} else {
|
||||
// Hide the search results container if no results
|
||||
$('#searchResultsContainer').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Event listener for delete user button in search results
|
||||
$('#searchResultsList').on('click', '.deleteUserButton', function () {
|
||||
const usernameToDelete = $(this).data('username');
|
||||
const csrfToken = $('[name="csrf_token"]').val(); // Access the CSRF token by name
|
||||
|
||||
console.log(csrfToken);
|
||||
console.log('Before fetch for user deletion');
|
||||
// Make a fetch request to delete the user
|
||||
|
||||
// Make a fetch request to delete the user with CSRF token in headers
|
||||
fetch(`/api/deleteUser/${usernameToDelete}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ csrfToken }), // Include CSRF token in the request body
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Inside fetch response handler');
|
||||
@ -278,8 +204,7 @@ function resetFormFields() {
|
||||
$('#confirmPassword').val('');
|
||||
$('#jobTitle').val('');
|
||||
}
|
||||
|
||||
|
||||
const csrf_token = $('#userForm input[name="csrf_token"]').val();
|
||||
$('#userForm').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@ -303,34 +228,36 @@ function resetFormFields() {
|
||||
fetch('/createUser', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
username: username,
|
||||
email: email,
|
||||
password: password,
|
||||
jobTitle: jobTitle,
|
||||
name: name,
|
||||
username: username,
|
||||
email: email,
|
||||
password: password,
|
||||
jobTitle: jobTitle,
|
||||
csrf_token: csrf_token, // Include the CSRF token in the body
|
||||
}),
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 201) {
|
||||
// Status 201 indicates successful creation
|
||||
return response.json();
|
||||
} else {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.error || `HTTP error! Status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
if (response.ok) {
|
||||
// Status 201 indicates successful creation
|
||||
return response.json();
|
||||
} else {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.error || `HTTP error! Status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
console.log('User registration success:', data);
|
||||
alert('User registered successfully!');
|
||||
resetFormFields();
|
||||
console.log('User registration success:', data);
|
||||
alert('User registered successfully!');
|
||||
resetFormFields();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('User registration error:', error);
|
||||
handleRegistrationError(error);
|
||||
console.error('User registration error:', error);
|
||||
handleRegistrationError(error);
|
||||
});
|
||||
});
|
||||
|
||||
@ -369,9 +296,8 @@ $('#resetPasswordForm').on('submit', function (e) {
|
||||
const username = $('#resetUsername').val();
|
||||
const password = $('#resetPassword').val();
|
||||
const confirmPassword = $('#resetConfirmPassword').val();
|
||||
const csrf_token = $('#userForm input[name="csrf_token"]').val();
|
||||
|
||||
console.log('Username:', username);
|
||||
console.log('New Password:', password);
|
||||
|
||||
// Validate passwords
|
||||
if (password !== confirmPassword) {
|
||||
@ -385,8 +311,7 @@ $('#resetPasswordForm').on('submit', function (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a fetch request
|
||||
fetch('/reset-password', {
|
||||
fetch('/reset-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -395,6 +320,7 @@ $('#resetPasswordForm').on('submit', function (e) {
|
||||
username: username,
|
||||
password: password,
|
||||
confirmPassword: confirmPassword,
|
||||
csrf_token: csrf_token
|
||||
}),
|
||||
})
|
||||
.then(response => {
|
||||
@ -429,5 +355,154 @@ $('#resetPasswordForm').on('submit', function (e) {
|
||||
});
|
||||
});
|
||||
|
||||
// Declare a variable to store fetched logs
|
||||
let logs = [];
|
||||
|
||||
// Function to fetch logs from the server
|
||||
function fetchLogs() {
|
||||
// Make a fetch request to your server endpoint for logs
|
||||
fetch('/api/getLogs')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Assign the logs to the variable
|
||||
logs = data;
|
||||
|
||||
// Process and display logs in the logs container
|
||||
displayLogs(logs);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching logs:', error);
|
||||
// Handle errors, e.g., display an alert
|
||||
});
|
||||
}
|
||||
|
||||
// Update the displayLogs function to generate a table
|
||||
function displayLogs(logs) {
|
||||
const logsContainer = $('#logsContainer');
|
||||
|
||||
// Clear previous logs and date filter elements
|
||||
logsContainer.empty();
|
||||
|
||||
if (logs && logs.length > 0) {
|
||||
// Add date filter elements
|
||||
logsContainer.append(`
|
||||
<label for="datePicker">Filter by Date:</label>
|
||||
<input type="text" id="datePicker">
|
||||
<button onclick="applyDateFilter()">Apply Filter</button>
|
||||
`);
|
||||
|
||||
// Create the table and header row
|
||||
const table = $('<table>').addClass('logs-table');
|
||||
const headerRow = '<tr><th>ID</th><th>Username</th><th>Activity</th><th>Timestamp</th></tr>';
|
||||
table.append(headerRow);
|
||||
|
||||
// Add each log as a row in the table
|
||||
logs.forEach(log => {
|
||||
const row = `<tr><td>${log.id}</td><td>${log.username}</td><td>${log.activity}</td><td>${log.timestamp}</td></tr>`;
|
||||
table.append(row);
|
||||
});
|
||||
|
||||
// Append the table to the logsContainer
|
||||
logsContainer.append(table);
|
||||
|
||||
// Add a download button at the top with the current date and time in the file name
|
||||
const currentDate = new Date();
|
||||
const formattedDate = currentDate.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
const formattedTime = currentDate.toTimeString().split(' ')[0].replace(/:/g, '-');
|
||||
const downloadButton = $('<button>').text('Download Log').on('click', function () {
|
||||
downloadLogs(logs, `log_${formattedDate}_${formattedTime}.csv`);
|
||||
});
|
||||
|
||||
// Prepend the download button to the logsContainer
|
||||
logsContainer.prepend(downloadButton);
|
||||
} else {
|
||||
// Display a message if no logs are available
|
||||
logsContainer.html('<p>No logs available.</p>');
|
||||
}
|
||||
|
||||
// Initialize Flatpickr for the date picker
|
||||
flatpickr("#datePicker", {
|
||||
dateFormat: "m/d/Y, h:i:S K", // Adjust the format to match your logs timestamp format
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply date filter
|
||||
function applyDateFilter() {
|
||||
const selectedDate = $("#datePicker").val();
|
||||
|
||||
const formattedSelectedDate = new Date(selectedDate).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
|
||||
const filteredLogs = logs.filter(log => {
|
||||
const formattedLogDate = new Date(log.timestamp).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
return formattedLogDate === formattedSelectedDate;
|
||||
});
|
||||
|
||||
|
||||
displayLogs(filteredLogs);
|
||||
}
|
||||
|
||||
function downloadLogs(logs, filename) {
|
||||
if (logs && logs.length > 0) {
|
||||
const csvContent = 'data:text/csv;charset=utf-8,';
|
||||
const header = 'ID,Username,Activity,Timestamp\n';
|
||||
const rows = logs.map(log => `${log.id},${log.username},${log.activity},"${log.timestamp}"`).join('\n');
|
||||
const data = header + rows;
|
||||
const encodedData = encodeURI(csvContent + data);
|
||||
|
||||
// Create a hidden anchor element to trigger the download
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', encodedData);
|
||||
link.setAttribute('download', 'logs.csv');
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Trigger the download
|
||||
link.click();
|
||||
|
||||
// Remove the link from the DOM
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
console.error('No logs available for download.');
|
||||
}
|
||||
}
|
||||
fetchLogs();
|
||||
|
||||
|
||||
|
||||
// Assuming EJS is properly configured to evaluate expressions
|
||||
|
||||
|
||||
// Assuming allUsers is an array containing user information
|
||||
const user = allUsers.find(user => user.username === currentUsername);
|
||||
const userRole = user?.jobTitle;
|
||||
|
||||
|
||||
// Function to enable/disable actions based on user role
|
||||
function handleUserRoleAccess() {
|
||||
// Disable user creation, deletion, and password reset for non-admin users
|
||||
if (userRole !== 'admin') {
|
||||
document.getElementById('addUserLink').style.display = 'none';
|
||||
document.getElementById('deleteUserLink').style.display = 'none';
|
||||
document.getElementById('resetPasswordLink').style.display = 'none';
|
||||
|
||||
}
|
||||
|
||||
// Allow admin users to view logs
|
||||
if (userRole === 'admin') {
|
||||
document.getElementById('logsLink').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function to handle user role access when the page loads
|
||||
handleUserRoleAccess();
|
||||
|
@ -86,7 +86,6 @@ button:hover {
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
@ -95,4 +94,5 @@ button:hover {
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -69,6 +69,7 @@
|
||||
<label for="otp">OTP:</label>
|
||||
<input type="text" id="otp" name="otp" required>
|
||||
<br>
|
||||
|
||||
<button type="submit">Submit OTP</button>
|
||||
</form>
|
||||
</body>
|
||||
|
5
api.MD
@ -171,10 +171,7 @@ http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&p
|
||||
|
||||
|
||||
|
||||
|
||||
curl localhost/api/v0/user/new -H "Content-Type: application/json" -X POST -d '{"username": "system", "password": "thisisthesystemuserpasswordnoob", "email": "system@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}'
|
||||
|
||||
curl localhost/api/v0/user/new -H "Content-Type: application/json" -X POST -d '{"username": "testuser", "password": "thisisthesystemuserpasswordnoob", "email": "testuser@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}'
|
||||
curl localhost/api/v0/user/register -H "Content-Type: application/json" -X POST -d '{"username": "testuser123", "password": "thisisthesystemuserpasswordnoob", "email": "testuser123@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}'
|
||||
|
||||
|
||||
curl localhost/api/v0/apikey/new -H "Content-Type: application/json" -X POST -d '{"userid": "2", "permission": "canRead"}'
|
||||
|
@ -1,22 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||
|
||||
<title>Hello, world!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.3/dist/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,37 +0,0 @@
|
||||
function validateForm() {
|
||||
var username = document.getElementById('email').value;
|
||||
var password = document.getElementById('password').value;
|
||||
|
||||
// Perform basic validation
|
||||
if (!email || !password) {
|
||||
alert('Please enter both email and password');
|
||||
return;
|
||||
}
|
||||
|
||||
// If validation passes, send data to the server
|
||||
sendDataToServer(email, password);
|
||||
}
|
||||
|
||||
function sendDataToServer(email, password) {
|
||||
// Use AJAX or fetch to send data to the server
|
||||
// Example using fetch:
|
||||
fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Handle the response from the server
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
// Redirect or perform other actions for successful login
|
||||
alert('Login successful');
|
||||
} else {
|
||||
alert('Login failed. Please check your credentials.');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const mysql = require('mysql');
|
||||
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
const db = mysql.createConnection({
|
||||
host: 'localhost',
|
||||
user: 'root',
|
||||
password: 'your_mysql_password',
|
||||
database: 'your_database_name',
|
||||
});
|
||||
|
||||
db.connect(err => {
|
||||
if (err) {
|
||||
console.error('Error connecting to MySQL:', err);
|
||||
} else {
|
||||
console.log('Connected to MySQL');
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/signup', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
// Perform server-side validation if needed
|
||||
|
||||
const sql = 'INSERT INTO users (username, password) VALUES (?, ?)';
|
||||
db.query(sql, [username, password], (err, result) => {
|
||||
if (err) {
|
||||
console.error('Error executing SQL query:', err);
|
||||
res.status(500).json({ success: false, message: 'Internal Server Error' });
|
||||
} else {
|
||||
console.log('User signed up successfully');
|
||||
res.json({ success: true, message: 'User signed up successfully' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
});
|
@ -4,15 +4,31 @@ const { userModel } = require("../database/model/userModel.js");
|
||||
const { Op, Sequelize } = require("sequelize");
|
||||
const { hashAPIKey } = require("../functions/bcrypt.js");
|
||||
const { generateUUID } = require("../functions/generateUUID.js");
|
||||
const { hashPassword } = require("../functions/bcrypt.js");
|
||||
|
||||
async function getUser() {
|
||||
const user = await userModel.findAll();
|
||||
return user;
|
||||
}
|
||||
//api/v0/user/register
|
||||
|
||||
/* Registering new user
|
||||
1) req.body is taken from html form or wtv
|
||||
2) bcrpyt and hash the password on the server side
|
||||
3) pass to db
|
||||
*/
|
||||
async function addUser(user) {
|
||||
//console.log(user);
|
||||
await userModel.create(user);
|
||||
console.log(user);
|
||||
//hash password
|
||||
let hash = await hashPassword(user.password);
|
||||
|
||||
await userModel.create({
|
||||
username: user.username,
|
||||
password: hash,
|
||||
email: user.email,
|
||||
address: user.address,
|
||||
phone: user.phone,
|
||||
});
|
||||
}
|
||||
|
||||
async function getAPIKey() {
|
||||
@ -46,7 +62,6 @@ async function addAPIKey(userId, permission) {
|
||||
|
||||
|
||||
//user token with -
|
||||
//"apikey": "1-9beba05f-1bf1-4d8a-9ee8-9f61e1428e20"
|
||||
return usertoken;
|
||||
}
|
||||
|
||||
|
@ -1,217 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<title>N & LW Lawn Care - Landscaping Bootstrap4 HTML5 Responsive Template </title>
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Fontawesome CSS -->
|
||||
<link href="css/all.css" rel="stylesheet">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<img src="images/logo.png" alt="logo" />
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="fas fa-bars"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">News</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="contact.html">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="profile.html">Profile</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- full Title -->
|
||||
<div class="full-title">
|
||||
<div class="container">
|
||||
<!-- Page Heading/Breadcrumbs -->
|
||||
<h1 class="mt-4 mb-3">Contact
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
<div class="breadcrumb-main">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="index.html">Home</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active">Contact</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- Content Row -->
|
||||
<div class="row">
|
||||
<!-- Map Column -->
|
||||
<div class="col-lg-8 mb-4">
|
||||
<!-- Embedded Google Map -->
|
||||
<iframe width="100%" height="300px" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/maps?hl=en&ie=UTF8&q=Singapore+410645&t=m&z=15&output=embed"></iframe>
|
||||
</div>
|
||||
<!-- Contact Details Column -->
|
||||
<div class="col-lg-4 mb-4 contact-right">
|
||||
<h3>Contact Details</h3>
|
||||
<p>
|
||||
Blk 645 Jalan Tenaga
|
||||
<br>S(410645)
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Phone">P</abbr>: (+65) 90064959
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Email">E</abbr>:
|
||||
<a href="mailto:name@example.com">leongdingxuan@gmail.com
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Hours">H</abbr>: Monday - Friday: 9:00 AM to 5:00 PM
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
<!-- Contact Form -->
|
||||
<!-- In order to set the email address and subject line for the contact form go to the bin/contact_me.php file. -->
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mb-4 contact-left">
|
||||
<h3>Send us a Message</h3>
|
||||
<form name="sentMessage" id="contactForm" novalidate>
|
||||
<div class="control-group form-group">
|
||||
<div class="controls">
|
||||
<label>Full Name:</label>
|
||||
<input type="text" class="form-control" id="name" required data-validation-required-message="Please enter your name.">
|
||||
<p class="help-block"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group form-group">
|
||||
<div class="controls">
|
||||
<label>Phone Number:</label>
|
||||
<input type="tel" class="form-control" id="phone" required data-validation-required-message="Please enter your phone number.">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group form-group">
|
||||
<div class="controls">
|
||||
<label>Email Address:</label>
|
||||
<input type="email" class="form-control" id="email" required data-validation-required-message="Please enter your email address.">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group form-group">
|
||||
<div class="controls">
|
||||
<label>Message:</label>
|
||||
<textarea rows="5" cols="100" class="form-control" id="message" required data-validation-required-message="Please enter your message" maxlength="999" style="resize:none"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="success"></div>
|
||||
<!-- For success/fail messages -->
|
||||
<button type="submit" class="btn btn-primary" id="sendMessageButton">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
|
||||
<!--footer starts from here-->
|
||||
<footer class="footer">
|
||||
<div class="container bottom_border">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Find us</h5>
|
||||
<p><i class="fa fa-location-arrow"></i> Blk 645 Jalan Tenaga</p>
|
||||
<p><i class="fa fa-phone"></i> +65 90064959</p>
|
||||
<p><i class="fa fa fa-envelope"></i> Leongdingxuan@gmail.com </p>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Follow us</h5>
|
||||
<!--headin5_amrc ends here-->
|
||||
<ul class="footer_ul2_amrc">
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-facebook-f fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.facebook.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-instagram fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.instagram.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-twitter fleft padding-right"></i> </a>
|
||||
<a href="#">https://twitter.com/</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<!--footer_ul2_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Quick links</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li><a href="#">Home</a></li>
|
||||
<li><a href="#">News</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
</ul>
|
||||
<!--footer_ul_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 ">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Recent posts</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>Singapore Government ...</p>
|
||||
<span>29 Sep 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>High risk of severe ...</p>
|
||||
<span>22 Jun 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container text-center">
|
||||
<br>
|
||||
<p>All Rights Reserved. © 2023 <a href="#">EcoSaver</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Contact form JavaScript -->
|
||||
<script src="js/jqBootstrapValidation.js"></script>
|
||||
<script src="js/contact_me.js"></script>
|
||||
</body>
|
||||
</html>
|
224
consumerWebsite/public/contactform.html
Normal file
@ -0,0 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Fontawesome CSS -->
|
||||
<link href="css/all.css" rel="stylesheet">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
<link href="css/contact.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<img src="images/logo.png" alt="logo" />
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
|
||||
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="fas fa-bars"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">News</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contactform.html">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="profile.html">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="signuplogin.html">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- full Title -->
|
||||
<div class="full-title">
|
||||
<div class="container">
|
||||
<!-- Page Heading/Breadcrumbs -->
|
||||
<h1 class="mt-4 mb-3">Contact
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
<div class="breadcrumb-main">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="index.html">Home</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active">Contact</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- Content Row -->
|
||||
<div class="row">
|
||||
<!-- Map Column -->
|
||||
<div class="col-lg-8 mb-4">
|
||||
<!-- Embedded Google Map -->
|
||||
<iframe width="100%" height="300px" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"
|
||||
src="http://maps.google.com/maps?hl=en&ie=UTF8&q=Singapore+408866&t=m&z=15&output=embed"></iframe>
|
||||
</div>
|
||||
<!-- Contact Details Column -->
|
||||
<div class="col-lg-4 mb-4 contact-right">
|
||||
<h3>Contact Details</h3>
|
||||
<p>
|
||||
50 Ubi Ave 3
|
||||
<br>S(408866)
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Phone">P</abbr>: (+65) 90064959
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Email">E</abbr>:
|
||||
<a href="mailto:name@example.com">leongdingxuan@gmail.com
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Hours">H</abbr>: Monday - Friday: 9:00 AM to 5:00 PM
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
<!-- Contact Form -->
|
||||
<!-- In order to set the email address and subject line for the contact form go to the bin/contact_me.php file. -->
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mb-4 contact-left">
|
||||
<h3>Send us a Message</h3>
|
||||
<form id="form">
|
||||
<input type="hidden" name="access_key" value="">
|
||||
<div class="mb-3">
|
||||
<label for="name">Full Name</label>
|
||||
<input type="text" name="name" id="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email">Email address</label>
|
||||
<input type="email" name="email" id="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message">Message</label>
|
||||
<textarea name="message" id="message" rows="3" required></textarea>
|
||||
</div>
|
||||
<button type="submit">Submit Form</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
|
||||
<!--footer starts from here-->
|
||||
<footer class="footer">
|
||||
<div class="container bottom_border">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Find us</h5>
|
||||
<p><i class="fa fa-location-arrow"></i> Blk 645 Jalan Tenaga</p>
|
||||
<p><i class="fa fa-phone"></i> +65 90064959</p>
|
||||
<p><i class="fa fa fa-envelope"></i> Leongdingxuan@gmail.com </p>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Follow us</h5>
|
||||
<!--headin5_amrc ends here-->
|
||||
<ul class="footer_ul2_amrc">
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-facebook-f fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.facebook.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-instagram fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.instagram.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-twitter fleft padding-right"></i> </a>
|
||||
<a href="#">https://twitter.com/</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<!--footer_ul2_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Quick links</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li><a href="#">Home</a></li>
|
||||
<li><a href="#">News</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
</ul>
|
||||
<!--footer_ul_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 ">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Recent posts</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>Singapore's air quality ...</p>
|
||||
<span>7 oct 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>Singapore Government ...</p>
|
||||
<span>29 Sep 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>High risk of severe ...</p>
|
||||
<span>22 Jun 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container text-center">
|
||||
<br>
|
||||
<p>All Rights Reserved. © 2023 <a href="#">EcoSaver</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Contact form JavaScript -->
|
||||
<script src="js/jqBootstrapValidation.js"></script>
|
||||
<script src="js/contact.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
34
consumerWebsite/public/css/contact.css
Normal file
@ -0,0 +1,34 @@
|
||||
.contact-left {
|
||||
width: 100%; /* Make the form take the whole width */
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
max-width: 150px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
83
consumerWebsite/public/css/forgot.css
Normal file
@ -0,0 +1,83 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap");
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Poppins",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0faff;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
max-width: 470px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
padding: 20px 30px 120px;
|
||||
background: #fff;
|
||||
box-shadow: 0 5px 10px rgba(0,
|
||||
0,
|
||||
0,
|
||||
0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form header {
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wrapper.active .form.login header {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wrapper.active .signup header {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.wrapper form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
form a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
form input {
|
||||
height: 60px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 0 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
|
||||
form input[type="submit"] {
|
||||
margin-top: 15px;
|
||||
padding: none;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
background: #4070f4;
|
||||
color: #fff;
|
||||
}
|
6
consumerWebsite/public/css/learnmore.css
Normal file
@ -0,0 +1,6 @@
|
||||
img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
80
consumerWebsite/public/css/reset.css
Normal file
@ -0,0 +1,80 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Lato:400,700&display=swap');
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Lato', sans-serif;
|
||||
background-color: #f9f9f9;
|
||||
color: #000000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
background-color: #4070f4;
|
||||
}
|
||||
|
||||
.main-header img {
|
||||
width: 10%;
|
||||
vertical-align: middle; /* Center the image vertically */
|
||||
}
|
||||
|
||||
.main-header h1 {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
background-color: #ffffff;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.content-section h2 {
|
||||
font-size: 18px;
|
||||
line-height: 25.2px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #1c103b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
max-width: 470px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
padding: 20px
|
||||
30px
|
||||
120px;
|
||||
background: #4070f4;
|
||||
box-shadow: 0
|
||||
5px
|
||||
10px
|
||||
rgba(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0.1
|
||||
);
|
||||
overflow: hidden;
|
||||
}
|
205
consumerWebsite/public/css/sp.css
Normal file
@ -0,0 +1,205 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Poppins",
|
||||
sans-serif;
|
||||
}
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0faff;
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
max-width: 470px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
padding: 20px
|
||||
30px
|
||||
120px;
|
||||
background: #4070f4;
|
||||
box-shadow: 0
|
||||
5px
|
||||
10px
|
||||
rgba(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0.1
|
||||
);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form.login {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -86%;
|
||||
transform: translateX(
|
||||
-50%
|
||||
);
|
||||
width: calc(
|
||||
100% +
|
||||
220px
|
||||
);
|
||||
padding: 35px
|
||||
140px;
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
transition: all
|
||||
0.6s
|
||||
ease;
|
||||
}
|
||||
.wrapper.active
|
||||
.form.login {
|
||||
bottom: -10%;
|
||||
border-radius: 35%;
|
||||
box-shadow: 0 -5px
|
||||
10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.form
|
||||
header {
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form.login
|
||||
header {
|
||||
color: #333;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.wrapper.active
|
||||
.form.login
|
||||
header {
|
||||
opacity: 1;
|
||||
}
|
||||
.wrapper.active
|
||||
.signup
|
||||
header {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.wrapper
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
form
|
||||
input {
|
||||
height: 60px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 0
|
||||
15px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.form.login
|
||||
input {
|
||||
border: 1px
|
||||
solid
|
||||
#aaa;
|
||||
}
|
||||
.form.login
|
||||
input:focus {
|
||||
box-shadow: 0
|
||||
1px 0
|
||||
#ddd;
|
||||
}
|
||||
form
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.checkbox
|
||||
input[type="checkbox"] {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
accent-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
form
|
||||
.checkbox
|
||||
label {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
}
|
||||
form a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
form
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
form
|
||||
input[type="submit"] {
|
||||
margin-top: 15px;
|
||||
padding: none;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form.login
|
||||
input[type="submit"] {
|
||||
background: #4070f4;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.form.login .forgot-password-section {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form.login .forgot-password-section p {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form.login .forgot-password-section input[type="text"],
|
||||
.form.login .forgot-password-section input[type="submit"] {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
outline: none;
|
||||
border: 1px solid #aaa;
|
||||
padding: 0 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form.login .forgot-password-section input[type="submit"] {
|
||||
background: #4070f4;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form.login .back-to-login {
|
||||
display: block;
|
||||
color: #4070f4;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form.login .back-to-login:hover {
|
||||
text-decoration: underline;
|
||||
}
|
@ -577,7 +577,6 @@ footer p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.pricing-box{
|
||||
padding: 30px 0px;
|
||||
}
|
||||
|
27
consumerWebsite/public/forgotpassword.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!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="stylesheet" href="css/forgot.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="wrapper">
|
||||
<div class="form">
|
||||
<header>Reset Password</header>
|
||||
<form action="resetpassword.html">
|
||||
<input type="text" id="email" placeholder="Email" required />
|
||||
<input type="password" id="password" placeholder="Password" required />
|
||||
<input type="password" id="confirmPassword" placeholder="Confirm Password" required />
|
||||
<input type="submit" onclick="validateReset()" value="Reset Password" />
|
||||
</form>
|
||||
<br>
|
||||
<a>Dont have an account?</a> <a href="signuplogin.html">Sign Up</a>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB |
BIN
consumerWebsite/public/images/map.png
Normal file
After Width: | Height: | Size: 495 KiB |
BIN
consumerWebsite/public/images/passwordreset.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,75 +0,0 @@
|
||||
$(function() {
|
||||
|
||||
$("#contactForm input,#contactForm textarea").jqBootstrapValidation({
|
||||
preventSubmit: true,
|
||||
submitError: function($form, event, errors) {
|
||||
// additional error messages or events
|
||||
},
|
||||
submitSuccess: function($form, event) {
|
||||
event.preventDefault(); // prevent default submit behaviour
|
||||
// get values from FORM
|
||||
var name = $("input#name").val();
|
||||
var email = $("input#email").val();
|
||||
var phone = $("input#phone").val();
|
||||
var message = $("textarea#message").val();
|
||||
var firstName = name; // For Success/Failure Message
|
||||
// Check for white space in name for Success/Fail message
|
||||
if (firstName.indexOf(' ') >= 0) {
|
||||
firstName = name.split(' ').slice(0, -1).join(' ');
|
||||
}
|
||||
$this = $("#sendMessageButton");
|
||||
$this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages
|
||||
$.ajax({
|
||||
url: "././mail/contact_me.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
name: name,
|
||||
phone: phone,
|
||||
email: email,
|
||||
message: message
|
||||
},
|
||||
cache: false,
|
||||
success: function() {
|
||||
// Success message
|
||||
$('#success').html("<div class='alert alert-success'>");
|
||||
$('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
|
||||
.append("</button>");
|
||||
$('#success > .alert-success')
|
||||
.append("<strong>Your message has been sent. </strong>");
|
||||
$('#success > .alert-success')
|
||||
.append('</div>');
|
||||
//clear all fields
|
||||
$('#contactForm').trigger("reset");
|
||||
},
|
||||
error: function() {
|
||||
// Fail message
|
||||
$('#success').html("<div class='alert alert-danger'>");
|
||||
$('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
|
||||
.append("</button>");
|
||||
$('#success > .alert-danger').append($("<strong>").text("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!"));
|
||||
$('#success > .alert-danger').append('</div>');
|
||||
//clear all fields
|
||||
$('#contactForm').trigger("reset");
|
||||
},
|
||||
complete: function() {
|
||||
setTimeout(function() {
|
||||
$this.prop("disabled", false); // Re-enable submit button when AJAX call is complete
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
filter: function() {
|
||||
return $(this).is(":visible");
|
||||
},
|
||||
});
|
||||
|
||||
$("a[data-toggle=\"tab\"]").click(function(e) {
|
||||
e.preventDefault();
|
||||
$(this).tab("show");
|
||||
});
|
||||
});
|
||||
|
||||
/*When clicking on Full hide fail/success boxes */
|
||||
$('#name').focus(function() {
|
||||
$('#success').html('');
|
||||
});
|
153
consumerWebsite/public/learnmore.html
Normal file
@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<title>EcoSaver</title>
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Fontawesome CSS -->
|
||||
<link href="css/all.css" rel="stylesheet">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
<link href="css/learnmore.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<img src="images/logo.png" alt="logo" />
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
|
||||
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="fas fa-bars"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="index.html">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">News</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contactform.html">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<!--<img src="images/profile-logo.png" alt="Profile Logo" class="profile-logo"> -->
|
||||
<a class="nav-link" href="profile.html">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="signuplogin.html">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<div class="map">
|
||||
<img src="images/map.png" alt="Map Image">
|
||||
</div>
|
||||
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<!--footer starts from here-->
|
||||
<footer class="footer">
|
||||
<div class="container bottom_border">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Find us</h5>
|
||||
<!--headin5_amrc-->
|
||||
<p><i class="fa fa-location-arrow"></i> Blk 645 Jalan Tenaga</p>
|
||||
<p><i class="fa fa-phone"></i> +65 90064959</p>
|
||||
<p><i class="fa fa fa-envelope"></i> Leongdingxuan@gmail.com </p>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 col">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Follow us</h5>
|
||||
<!--headin5_amrc ends here-->
|
||||
<ul class="footer_ul2_amrc">
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-facebook-f fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.facebook.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-instagram fleft padding-right"></i> </a>
|
||||
<a href="#">https://www.instagram.com/</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"><i class="fab fa-twitter fleft padding-right"></i> </a>
|
||||
<a href="#">https://twitter.com/</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<!--footer_ul2_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">Quick links</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li><a href="#">Home</a></li>
|
||||
<li><a href="#">News</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
</ul>
|
||||
<!--footer_ul_amrc ends here-->
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-sm-6 ">
|
||||
<h5 class="headin5_amrc col_white_amrc pt2">News</h5>
|
||||
<!--headin5_amrc-->
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>Singapore's air quality ...</p>
|
||||
<span>7 oct 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>Singapore Government ...</p>
|
||||
<span>29 Sep 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="footer_ul_amrc">
|
||||
<li class="media">
|
||||
<div class="media-left">
|
||||
<img class="img-fluid" src="images/post-img-01.jpg" alt="" />
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<p>High risk of severe ...</p>
|
||||
<span>22 Jun 2023</span>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container text-center">
|
||||
<br>
|
||||
<p>All Rights Reserved. © 2023 <a href="#">EcoSaver</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
// Check for empty fields
|
||||
if(empty($_POST['name']) ||
|
||||
empty($_POST['email']) ||
|
||||
empty($_POST['phone']) ||
|
||||
empty($_POST['message']) ||
|
||||
!filter_var($_POST['email'],FILTER_VALIDATE_EMAIL))
|
||||
{
|
||||
echo "No arguments Provided!";
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = strip_tags(htmlspecialchars($_POST['name']));
|
||||
$email_address = strip_tags(htmlspecialchars($_POST['email']));
|
||||
$phone = strip_tags(htmlspecialchars($_POST['phone']));
|
||||
$message = strip_tags(htmlspecialchars($_POST['message']));
|
||||
|
||||
// Create the email and send the message
|
||||
$to = 'yourname@yourdomain.com'; // Add your email address inbetween the '' replacing yourname@yourdomain.com - This is where the form will send a message to.
|
||||
$email_subject = "Website Contact Form: $name";
|
||||
$email_body = "You have received a new message from your website contact form.\n\n"."Here are the details:\n\nName: $name\n\nEmail: $email_address\n\nPhone: $phone\n\nMessage:\n$message";
|
||||
$headers = "From: noreply@yourdomain.com\n"; // This is the email address the generated message will be from. We recommend using something like noreply@yourdomain.com.
|
||||
$headers .= "Reply-To: $email_address";
|
||||
mail($to,$email_subject,$email_body,$headers);
|
||||
return true;
|
||||
?>
|
@ -36,11 +36,14 @@
|
||||
<a class="nav-link" href="news.html">News</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contact.html">Contact</a>
|
||||
<a class="nav-link" href="contactform.html">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="profile.html">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="signuplogin.html">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,7 +92,8 @@
|
||||
class="form-control" placeholder="re enter password" value=""></div>
|
||||
</div>
|
||||
<div class="mt-2 text-center">
|
||||
<button class="btn btn-sm btn-secondary change-password-button" type="button">Change Password</button>
|
||||
<button class="btn btn-sm btn-secondary change-password-button" type="button">Change
|
||||
Password</button>
|
||||
</div>
|
||||
<div class="mt-5 text-center"><button class="btn btn-primary profile-button" type="button">Save
|
||||
Profile</button></div>
|
||||
|
39
consumerWebsite/public/resetpassword.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Your password has been resetted</title>
|
||||
<link href="css/reset.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table class="main-header">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<img src="images/passwordreset.png" alt="Image">
|
||||
<h1>Your password has been resetted</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="content-section">
|
||||
<tr>
|
||||
<td>
|
||||
<p>Hello,</p>
|
||||
<p>Please check your email to reset your password.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="footer">
|
||||
<tr>
|
||||
<td>
|
||||
<p>© 2023 EcoSaver</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -13,7 +13,7 @@
|
||||
<section class="wrapper">
|
||||
<div class="form signup">
|
||||
<header>Signup</header>
|
||||
<form action="/api/v0/user/register" method="POST">
|
||||
<form action="routes/user.js/register" method="get">
|
||||
<input type="text" id="username" placeholder="Username" required />
|
||||
<input type="text" id="email" placeholder="Email" required />
|
||||
<input type="text" id="address" placeholder="Address" required />
|
||||
@ -24,7 +24,7 @@
|
||||
<input type="checkbox" id="signupCheck" />
|
||||
<label for="signupCheck">I accept all terms & conditions</label>
|
||||
</div>
|
||||
<input type="submit" value="Signup" />
|
||||
<input type="submit" onclick="validateFormSignup()" value="Signup" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
const { getUser, addUser } = require("../functions/apiDatabase.js");
|
||||
const { hashPassword } = require("../functions/bcrypt.js");
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
//get all users
|
||||
router.get("/", async (req, res, next) => {
|
||||
try {
|
||||
const location = await getUser();
|
||||
@ -14,12 +14,12 @@ router.get("/", async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// /user/register
|
||||
router.post("/register", async (req, res, next) => {
|
||||
try {
|
||||
console.log(req.body);
|
||||
//await addUser(req.body);
|
||||
//res.sendStatus(200);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
|
3152
package-lock.json
generated
10
package.json
@ -19,8 +19,13 @@
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csurf": "^1.11.0",
|
||||
"date-fns": "^3.2.0",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"ejs": "^3.1.9",
|
||||
"esm": "^3.2.25",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"express-session": "^1.17.3",
|
||||
@ -28,13 +33,16 @@
|
||||
"helmet": "^7.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"mqtt": "^5.3.3",
|
||||
"mysql2": "^3.6.5",
|
||||
"mysql2": "^3.7.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"otp-generator": "^4.0.1",
|
||||
"otplib": "^12.0.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"sanitize-html": "^2.11.0",
|
||||
"sequelize": "^6.35.2",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"sql": "^0.78.0",
|
||||
"validator": "^13.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -32,7 +32,7 @@ app.set("json spaces", 2);
|
||||
middleware logic ( called by next() )
|
||||
*/
|
||||
app.use("/api/seed/v0", [ apikeyCheck , APIlogger] ,require("../routes/seed_route.js"));
|
||||
app.use('/api/v0', [apikeyCheck, APIlogger] ,require("../routes/api_route.js")); //webserver\routes\api_route.js
|
||||
app.use('/api/v0', [apikeyCheck, APIlogger] ,require("../routes/api_route.js"));
|
||||
|
||||
|
||||
|
||||
|