mirror of
https://gitlab.com/sarlink/kyc.git
synced 2025-04-20 06:36:59 +00:00
[feat] Form input page and validation
This commit is contained in:
parent
d656154c70
commit
dad311cb2d
6
.env.sample
Normal file
6
.env.sample
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
REGISTRATION_OPEN=true
|
||||||
|
BRANDING_TITLE=
|
||||||
|
BRANDING_LOGO=
|
||||||
|
BANK_NAME=
|
||||||
|
ACCOUNT_NAME=
|
||||||
|
ACCOUNT_NUMBER=
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -128,3 +128,5 @@ dist
|
|||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
sarlink.jpg
|
1511
package-lock.json
generated
1511
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,9 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@ -12,7 +14,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"dotenv": "^16.4.1",
|
"dotenv": "^16.4.1",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"multer": "^1.4.5-lts.1"
|
"multer": "^1.4.5-lts.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"daisyui": "^4.6.2",
|
||||||
|
"tailwindcss": "^3.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
server.js
47
server.js
@ -1,22 +1,61 @@
|
|||||||
require("dotenv").config()
|
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 express = require("express")
|
||||||
const app = 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.json())
|
||||||
app.use(express.urlencoded({ extended: true }))
|
app.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
// add multer
|
// add multer
|
||||||
const multer = require("multer")
|
const multer = require("multer")
|
||||||
const upload = multer()
|
const upload = multer()
|
||||||
app.use(upload.single("receipt"))
|
// app.use(upload.single("receipt"))
|
||||||
|
|
||||||
|
// routes
|
||||||
app.get("/", (req, res) => {
|
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;
|
const port = process.env.PORT || 4818;
|
||||||
app.listen(port, () => {
|
app.listen(port, '0.0.0.0', () => {
|
||||||
console.log(`Server is running on port ${port}`);
|
console.log(`Server is running on port ${port}`);
|
||||||
});
|
});
|
2209
src/public/styles.css
Normal file
2209
src/public/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
3
src/styles/input.css
Normal file
3
src/styles/input.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
20
src/views/closed.ejs
Normal file
20
src/views/closed.ejs
Normal 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
337
src/views/index.ejs
Normal 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
25
src/views/success.ejs
Normal 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
11
tailwind.config.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./src/**/*.{html,js,ejs}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require("daisyui"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user