[feat] Form input page and validation

This commit is contained in:
WovenCoast 2024-02-10 22:46:51 +05:00
parent d656154c70
commit dad311cb2d
11 changed files with 4175 additions and 5 deletions

6
.env.sample Normal file
View File

@ -0,0 +1,6 @@
REGISTRATION_OPEN=true
BRANDING_TITLE=
BRANDING_LOGO=
BANK_NAME=
ACCOUNT_NAME=
ACCOUNT_NUMBER=

2
.gitignore vendored
View File

@ -128,3 +128,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
sarlink.jpg

1511
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "tailwindcss -o ./src/public/styles.css",
"build:watch": "tailwindcss -o ./src/public/styles.css --watch",
"start": "npm run build && node server.js"
},
"keywords": [],
"author": "",
@ -12,7 +14,12 @@
"dependencies": {
"axios": "^1.6.7",
"dotenv": "^16.4.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"daisyui": "^4.6.2",
"tailwindcss": "^3.4.1"
}
}

View File

@ -1,22 +1,61 @@
require("dotenv").config()
const registrationOpen = process.env.REGISTRATION_OPEN === "true"
let registrationOpen, branding;
function loadEnv() {
registrationOpen = process.env.REGISTRATION_OPEN === "true"
branding = {
title: process.env.BRANDING_TITLE || "SAR Link",
logo: process.env.BRANDING_LOGO || "/public/logo.png",
bankDetails: {
name: process.env.BANK_NAME || "Bank Name",
accountNumber: process.env.ACCOUNT_NUMBER || "1234567890",
accountName: process.env.ACCOUNT_NAME || "Account Name",
},
}
}
loadEnv()
// watch for changes to .env file
const path = require("path")
const fs = require("fs")
fs.watchFile(path.join(__dirname, ".env"), (curr, prev) => {
delete require.cache[require.resolve("dotenv")]
require("dotenv").config({ override: true })
loadEnv()
console.log(`Registration is now ${registrationOpen ? "open" : "closed"}`)
})
// initialize express
const express = require("express")
const app = express()
app.set("view engine", "ejs")
app.set("views", "./src/views")
app.use("/public", express.static("./src/public"))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// add multer
const multer = require("multer")
const upload = multer()
app.use(upload.single("receipt"))
// app.use(upload.single("receipt"))
// routes
app.get("/", (req, res) => {
res.render(registrationOpen ? "index" : "closed")
res.render(registrationOpen ? "index" : "closed", { branding })
});
app.post("/register", upload.single("receipt"), (req, res) => {
if (!registrationOpen) {
res.render("closed")
return
}
console.log(req.body)
console.log(req.file)
res.render("success", { branding })
})
const port = process.env.PORT || 4818;
app.listen(port, () => {
app.listen(port, '0.0.0.0', () => {
console.log(`Server is running on port ${port}`);
});

2209
src/public/styles.css Normal file

File diff suppressed because it is too large Load Diff

3
src/styles/input.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

20
src/views/closed.ejs Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%- branding.title %> - KYC</title>
<link rel="shortcut icon" href="<%- branding.logo %>" type="image/x-icon" />
<link rel="stylesheet" href="/public/styles.css" />
</head>
<body>
<main class="flex h-[100vh] w-[100vw] items-center justify-center">
<div class="card w-96 bg-base-100 shadow-xl image-full">
<div class="card-body">
<h2 class="card-title">Registration Closed</h2>
<p>Adding new devices is currently disabled.</p>
</div>
</div>
</main>
</body>
</html>

337
src/views/index.ejs Normal file
View File

@ -0,0 +1,337 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%- branding.title %> - KYC</title>
<link rel="shortcut icon" href="<%- branding.logo %>" type="image/x-icon" />
<link rel="stylesheet" href="/public/styles.css" />
<script>
const blacklistedChars = /\s/g;
function fillDeviceName() {
let deviceType = "";
const macAddress = document.getElementById("mac_address").value;
// now we find out which format it is
// 35:df:61 = customerfirstname-android
// 3A-DF-61 = customerfirstname-laptop
// 3A:DF:61 = customerfirstname-iphone
const macAddressRegex = new RegExp(
/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/
);
if (macAddress === macAddress.toLowerCase()) {
deviceType = "android";
} else if (macAddress === macAddress.replace(/:/g, "-")) {
deviceType = "laptop";
} else if (macAddress === macAddress.toUpperCase()) {
deviceType = "iphone";
} else {
deviceType = "phone";
}
const fullName = document.getElementById("customer_name").value;
const firstName = fullName.split(" ")[0];
const deviceName = `${firstName}-${deviceType}`.toLowerCase();
const deviceNameInput = document.getElementById("device_name");
if (!deviceNameInput.value) deviceNameInput.value = deviceName;
}
function validateMACAddress() {
var macAddress = document.getElementById("mac_address");
var macAddressRegex = new RegExp(
/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/
);
const macAddressValue = macAddress.value;
macAddress.setCustomValidity("");
macAddress.classList.remove("input-error");
macAddress.classList.remove("input-success");
if (!macAddressRegex.test(macAddressValue)) {
macAddress.classList.add("input-error");
macAddress.setCustomValidity("Invalid MAC Address");
macAddress.reportValidity();
return false;
}
macAddress.classList.add("input-success");
macAddress.reportValidity();
fillDeviceName();
return true;
}
function validateForm() {
const macAddressValid = validateMACAddress();
const customerName = document.getElementById("customer_name");
customerName.classList.remove("input-error");
// customerName.setCustomValidity("");
const deviceName = document.getElementById("device_name");
deviceName.classList.remove("input-error");
// deviceName.setCustomValidity("");
const transferReceipt = document.getElementById("transfer_receipt");
transferReceipt.classList.remove("input-error");
// transferReceipt.setCustomValidity("");
if (customerName.value === "") {
customerName.classList.add("input-error");
// customerName.setCustomValidity("Customer Name is required");
// customerName.reportValidity();
}
if (deviceName.value === "") {
deviceName.classList.add("input-error");
// deviceName.setCustomValidity("Device Name is required");
// deviceName.reportValidity();
}
if (transferReceipt.files.length === 0) {
transferReceipt.classList.add("input-error");
// transferReceipt.setCustomValidity("Transfer Receipt is required");
// transferReceipt.reportValidity();
}
return (
macAddressValid &&
customerName.value !== "" &&
deviceName.value !== "" &&
transferReceipt.files.length > 0
);
}
</script>
</head>
<body>
<header>
<div class="navbar bg-base-300">
<button class="btn btn-ghost text-xl">
<img class="w-10 rounded" src="<%- branding.logo %>" />
<%- branding.title %>
</button>
</div>
</header>
<main>
<form class="flex justify-center" action="/register" method="post">
<div class="w-[80vw]">
<div class="join-horizontal mt-8">
<div class="join-vertical justify-center">
<h1 class="text-3xl font-bold">KYC</h1>
<p class="text-base">
Please fill in the form to complete your KYC
</p>
</div>
<br />
<div class="join-vertical">
<div class="form-control">
<label class="label">
<span class="label-text font-bold text-lg text-base-content"
>Customer Name</span
>
</label>
<input
type="text"
id="customer_name"
name="customer_name"
placeholder="John Doe"
class="input input-bordered"
onchange="validateForm()"
/>
</div>
</div>
<br />
<div class="join-vertical">
<div class="form-control">
<label class="label">
<span class="label-text font-bold text-lg text-base-content"
>MAC Address</span
>
</label>
<input
type="text"
name="mac_address"
id="mac_address"
placeholder="00-00-00-00-00-00"
class="input input-bordered"
onchange="validateForm()"
oninput="validateMACAddress()"
/>
</div>
</div>
<br />
<div class="collapse bg-base-200">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">
How do I find my MAC Address?
</div>
<div class="collapse-content overflow-x-auto">
<p>
A MAC (Media Access Control) address is a unique identifier
assigned to a device's network. It is used to identify the
device on a network, helping to differentiate devices on a
network.
</p>
<div class="collapse collapse-arrow bg-base-200">
<input
type="checkbox"
name="my-accordion-1"
checked="checked"
/>
<div class="collapse-title text-xl font-medium">iPhone</div>
<div class="collapse-content overflow-x-auto">
<div class="overflow-x-auto text-sm breadcrumbs">
<ul>
<li>Settings</li>
<li>General</li>
<li>About</li>
<li>Wi-Fi Address</li>
</ul>
</div>
</div>
</div>
<div class="collapse collapse-arrow bg-base-200">
<input type="checkbox" name="my-accordion-1" />
<div class="collapse-title text-xl font-medium">Redmi</div>
<div class="collapse-content overflow-x-auto">
<div class="overflow-x-auto text-sm breadcrumbs">
<ul>
<li>Settings</li>
<li>About</li>
<li>Wi-Fi MAC Address</li>
</ul>
</div>
</div>
</div>
<div class="collapse collapse-arrow bg-base-200">
<input type="checkbox" name="my-accordion-1" />
<div class="collapse-title text-xl font-medium">Samsung</div>
<div class="collapse-content overflow-x-auto">
<div class="overflow-x-auto text-sm breadcrumbs">
<ul>
<li>Settings</li>
<li>About phone</li>
<li>Status Information</li>
<li>Wi-Fi MAC Address</li>
</ul>
</div>
</div>
</div>
<div class="collapse collapse-arrow bg-base-200">
<input type="checkbox" name="my-accordion-1" />
<div class="collapse-title text-xl font-medium">
Windows Laptop
</div>
<div class="collapse-content overflow-x-auto">
<div class="overflow-x-auto text-sm breadcrumbs">
<ul>
<li>Settings</li>
<li>Network and Internet</li>
<li>Wi-Fi</li>
<li>Hardware Properties</li>
<li>Physical address (MAC):</li>
</ul>
</div>
</div>
</div>
<div class="collapse collapse-arrow bg-base-200">
<input type="checkbox" name="my-accordion-1" />
<div class="collapse-title text-xl font-medium">
Other Device
</div>
<div class="collapse-content overflow-x-auto">
Please contact <%- branding.title %> for assistance.
</div>
</div>
</div>
</div>
<br />
<div class="join-vertical">
<div class="form-control">
<label class="label">
<span class="label-text font-bold text-lg text-base-content"
>Device Name</span
>
</label>
<input
type="text"
id="device_name"
name="device_name"
placeholder="iphone-12"
class="input input-bordered"
onchange="validateForm()"
oninput="this.value =
this.value.toLowerCase().replace(blacklistedChars, '-');"
/>
</div>
</div>
<br />
<br />
<div class="collapse bg-base-200">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">
Transfer Details
</div>
<div class="collapse-content overflow-x-auto">
<p>
Please transfer MVR 100 to the following account and upload
the receipt below.
</p>
<br />
<div class="overflow-x-auto text-sm">
<ul>
<li>Bank: <%- branding.bankDetails.name %></li>
<li>
Account Number: <%- branding.bankDetails.accountNumber %>
</li>
<li>
Account Name: <%- branding.bankDetails.accountName %>
</li>
</ul>
<br />
<button
type="button"
class="btn btn-block btn-info"
onclick="navigator.clipboard.writeText('<%- branding.bankDetails.accountNumber %>')"
>
Copy Account Number
</button>
</div>
</div>
</div>
<br />
<div class="collapse bg-base-200">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">
How to transfer from BML to MIB?
</div>
<div class="collapse-content"></div>
</div>
<br />
<div class="join-vertical">
<div class="form-control">
<label class="label">
<span class="label-text font-bold text-lg text-base-content"
>Transfer Receipt</span
>
</label>
<input
type="file"
id="transfer_receipt"
name="transfer_receipt"
class="file-input file-input-bordered w-full max-w-xs"
accept="image/*"
onchange="validateForm()"
/>
</div>
</div>
<br />
<button
class="btn btn-info btn-block"
onclick="if (!validateForm()) event.preventDefault()"
>
Submit
</button>
</div>
</div>
</form>
<footer>
<div style="height: 10em"></div>
</footer>
</main>
</body>
</html>

25
src/views/success.ejs Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%- branding.title %> - KYC</title>
<link rel="shortcut icon" href="<%- branding.logo %>" type="image/x-icon" />
<link rel="stylesheet" href="/public/styles.css" />
</head>
<body>
<main class="flex h-[100vh] w-[100vw] items-center justify-center">
<div
class="card w-96 bg-base-100 shadow-xl bg-primary text-primary-content"
>
<div class="card-body">
<h2 class="card-title">Success</h2>
<p>
Your request has been sent to the owner! Please wait until access is
granted.
</p>
</div>
</div>
</main>
</body>
</html>

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,ejs}"],
theme: {
extend: {},
},
plugins: [
require("daisyui"),
],
}