466 lines
18 KiB
HTML
466 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<title>Ranchimall Certificate Verifier</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
|
rel="stylesheet">
|
|
<script id="floGlobals">
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
|
const floGlobals = {
|
|
|
|
//Required for all
|
|
blockchain: "FLO",
|
|
|
|
//Required for blockchain API operators
|
|
apiURL: {
|
|
FLO: ['https://flosight.duckdns.org/'],
|
|
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
|
|
},
|
|
//adminID: null,
|
|
RMincorporationID: "FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk",
|
|
RIBC_id: "FDaX363r1ooANA9A2erhehhigNTnidq3o4",
|
|
RM_CertificateIssuer_id: "FFCpiaZi31TpbYw5q5VNk8qJMeDiTLgsrE",
|
|
//sendAmt: 0.001,
|
|
//fee: 0.0005,
|
|
}
|
|
</script>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
padding: 0;
|
|
margin: 0;
|
|
font-family: 'Roboto', sans-serif;
|
|
}
|
|
|
|
:root {
|
|
font-size: clamp(16px, 1.2vmax, 40px);
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
|
|
#main {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
align-content: flex-start;
|
|
align-self: center;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
h4 {
|
|
font-size: 1.5rem;
|
|
line-height: 1.6;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
p {
|
|
max-width: 70ch;
|
|
color: rgba(0, 0, 0, 0.8);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
p,
|
|
strong,
|
|
.wrap-around {
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
word-break: break-word;
|
|
hyphens: auto;
|
|
}
|
|
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
.icon {
|
|
height: 1.2rem;
|
|
width: 1.2rem;
|
|
fill: rgba(0, 0, 0, 0.9);
|
|
}
|
|
|
|
.icon--big {
|
|
height: 4rem;
|
|
width: 4rem;
|
|
margin-bottom: 1rem;
|
|
align-self: center;
|
|
background-color: rgba(0, 0, 0, 0.06);
|
|
border-radius: 50%;
|
|
padding: 1rem;
|
|
}
|
|
|
|
#loading_page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
height: 100dvh;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.verification-steps {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
align-items: center;
|
|
justify-items: center;
|
|
margin-top: 5rem;
|
|
}
|
|
|
|
.step {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
sm-spinner {
|
|
--size: 1.2rem;
|
|
--accent-color: grey;
|
|
}
|
|
|
|
header h3 {
|
|
font-weight: 500;
|
|
}
|
|
|
|
#verification_status {
|
|
align-content: center;
|
|
display: grid;
|
|
text-align: center;
|
|
justify-items: center;
|
|
padding: 1.5rem 0;
|
|
}
|
|
|
|
#verification_status p {
|
|
color: rgba(0, 0, 0, 0.7);
|
|
}
|
|
|
|
.verified .icon {
|
|
fill: #1cad59;
|
|
}
|
|
|
|
.error .icon {
|
|
fill: rgb(255, 75, 75);
|
|
}
|
|
|
|
#result_box {
|
|
display: grid;
|
|
gap: 0.5rem;
|
|
align-content: center;
|
|
}
|
|
|
|
#result_box time {
|
|
font-size: 0.9rem;
|
|
color: rgba(0, 0, 0, 0.8);
|
|
}
|
|
|
|
#result_box p {
|
|
font-size: 1rem;
|
|
line-height: 1.7;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
#result_box h3 {
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.transaction-link {
|
|
align-self: flex-start;
|
|
text-decoration: none;
|
|
background-color: rgba(0, 0, 0, 0.06);
|
|
padding: 0.5rem 0.8rem;
|
|
font-size: 0.9rem;
|
|
border-radius: 0.3rem;
|
|
color: rgba(0, 0, 0, 0.7);
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.link {
|
|
display: inline;
|
|
background-color: transparent;
|
|
color: #3d5afe;
|
|
border: none;
|
|
font-size: inherit;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.hover-over {
|
|
position: relative;
|
|
}
|
|
|
|
.hover-over .verification-steps {
|
|
display: none;
|
|
position: absolute;
|
|
flex-direction: column;
|
|
text-align: left;
|
|
background-color: white;
|
|
width: 40vw;
|
|
left: 0;
|
|
right: 0;
|
|
margin-top: 0;
|
|
padding: max(1rem, 2vw);
|
|
box-shadow: 0 0.8rem 2rem rgba(0, 0, 0, 0.2), 0 0 0 100vmax rgba(0, 0, 0, 0.2);
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
.hover-over:hover .verification-steps {
|
|
display: flex;
|
|
}
|
|
|
|
@media screen and (max-width: 640px) {
|
|
#verification_status {
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.hover-over .verification-steps {
|
|
position: fixed;
|
|
width: 100vw;
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: 640px) {
|
|
#main.success {
|
|
align-self: center;
|
|
grid-template-columns: 18rem 1fr;
|
|
justify-content: center;
|
|
border-radius: 0.5rem;
|
|
border: solid 0.2rem dodgerblue;
|
|
padding: 5rem 3rem;
|
|
}
|
|
|
|
#main.success #verification_status {
|
|
padding-right: 1.5rem;
|
|
border-right: thin solid rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
#main.success #result_box {
|
|
grid-template-columns: 1fr auto;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
#main.success #result_box p {
|
|
grid-column: span 2;
|
|
align-self: flex-start;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body onload="onLoadStartUp()">
|
|
<div id="loading_page">
|
|
<div id="loading_page_content">
|
|
<h4>Verifying Certificate</h4>
|
|
<p>Please wait while we verify your certificate</p>
|
|
</div>
|
|
<div class="verification-steps">
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<sm-spinner></sm-spinner>
|
|
</div>
|
|
<p>
|
|
Verifying if this certificate was <strong>issued by Authorized Blockchain Issuer ID</strong>
|
|
FFCpiaZi31TpbYw5q5VNk8qJMeDiTLgsrE
|
|
</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<sm-spinner></sm-spinner>
|
|
</div>
|
|
<p>
|
|
Verifying if this certificate was <strong>correctly sent to approved Blockchain ID</strong>
|
|
FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk, or FDaX363r1ooANA9A2erhehhigNTnidq3o4
|
|
</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<sm-spinner></sm-spinner>
|
|
</div>
|
|
<p>
|
|
Verifying if <strong>blockchain data starts with CERTIFICATE OF INTERNSHIP</strong>, and
|
|
verification link has
|
|
those words
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="main" class="hidden">
|
|
<div id="verification_status"></div>
|
|
<div id="result_box">
|
|
<h3 id="cert_type"></h3>
|
|
<time id="issue_time"></time>
|
|
<p id="cert_text"></p>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const spinner = document.createElement("template"); spinner.innerHTML = '<style>*{padding: 0;margin: 0;-webkit-box-sizing: border-box;box-sizing: border-box;}.loader {display: flex;height: var(--size, 1.5rem);width: var(--size, 1.5rem);stroke-width: 8;overflow: visible;stroke: var(--accent-color, teal);fill: none;stroke-dashoffset: 180;stroke-dasharray: 180;animation: load 2s infinite, spin 1s linear infinite;}@keyframes load {50% {stroke-dashoffset: 0;}100%{stroke-dashoffset: -180;}}@keyframes spin {100% {transform: rotate(360deg);}}</style><svg viewBox="0 0 64 64" class="loader"><circle cx="32" cy="32" r="32" /></svg>'; class SpinnerLoader extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(spinner.content.cloneNode(!0)) } } window.customElements.define("sm-spinner", SpinnerLoader);
|
|
</script>
|
|
<script src="https://github.com/ranchimall/Standard_Operations/releases/latest/download/floCrypto.js"
|
|
defer></script>
|
|
<script src="https://github.com/ranchimall/Standard_Operations/releases/latest/download/floBlockchainAPI.js"
|
|
defer></script>
|
|
<script id="onLoadStartUp">
|
|
function onLoadStartUp() {
|
|
console.log("Ranchimall Certificate Verifier")
|
|
floBlockchainAPI.getBalance(floCrypto.generateNewID().floID).then(r => {
|
|
const [key, value] = window.location.search.substring(1).split('=')
|
|
console.log(key, value)
|
|
if (key === '' && !value) throw new Error("No verification ID found")
|
|
switch (key) {
|
|
case "internCertificate":
|
|
verifyCertificate(value, "RIBC certificate", "CERTIFICATE OF INTERNSHIP");
|
|
break;
|
|
case "employeeCertificate":
|
|
verifyCertificate(value, "Employee certificate", "CERTIFICATE OF EMPLOYMENT");
|
|
break;
|
|
case "volunteerCertificate":
|
|
verifyCertificate(value, "Volunteer certificate", "CERTIFICATE OF VOLUNTEERSHIP");
|
|
break;
|
|
case "participationCertificate":
|
|
verifyCertificate(value, "Participation certificate", "CERTIFICATE OF PARTICIPATION");
|
|
break;
|
|
|
|
}
|
|
}).catch(e => {
|
|
console.error(e)
|
|
showVerificationStatus('error', e.message)
|
|
})
|
|
}
|
|
|
|
const verificationStatus = document.getElementById('verification_status')
|
|
function showVerificationStatus(status, link) {
|
|
let statusComposition = ''
|
|
verificationStatus.className = status
|
|
switch (status) {
|
|
case 'verified':
|
|
statusComposition = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon--big" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> </svg>
|
|
<h4> Verified </h4>
|
|
<div class="hover-over">
|
|
<button class="link">See how verification works.</button>
|
|
<div class="verification-steps">
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
|
|
</div>
|
|
<p>
|
|
Verified that this certificate was <strong>issued by Authorized Blockchain Issuer ID</strong> FFCpiaZi31TpbYw5q5VNk8qJMeDiTLgsrE
|
|
</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
|
|
</div>
|
|
<p>
|
|
Verified that this certificate was <strong>correctly sent to approved Blockchain ID</strong> FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk, or FDaX363r1ooANA9A2erhehhigNTnidq3o4
|
|
</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
|
|
</div>
|
|
<p>
|
|
Verified that <strong>blockchain data starts with CERTIFICATE OF INTERNSHIP</strong>, and verification link has those words
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a href="${link}" target="_blank" class="transaction-link">View on blockchain</a>
|
|
`
|
|
document.getElementById('main').className = 'success'
|
|
break;
|
|
case 'error':
|
|
statusComposition = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon--big" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /> </svg>
|
|
<h4> Verification failed </h4>
|
|
<p>${link} </p>
|
|
`
|
|
break
|
|
}
|
|
verificationStatus.innerHTML = statusComposition
|
|
document.getElementById('loading_page').classList.add('hidden')
|
|
document.getElementById('main').classList.remove('hidden')
|
|
}
|
|
|
|
function wait(ms = 500) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
function processStatus(iVerify, oVerify, cVerify) {
|
|
return new Promise(async (resolve, reject) => {
|
|
const steps = document.querySelectorAll('.step')
|
|
await showProcessStatus(steps[0], iVerify)
|
|
if (!iVerify)
|
|
reject()
|
|
await showProcessStatus(steps[1], oVerify)
|
|
if (!oVerify)
|
|
reject()
|
|
await showProcessStatus(steps[2], cVerify)
|
|
if (!cVerify)
|
|
reject()
|
|
resolve()
|
|
})
|
|
}
|
|
async function showProcessStatus(elem, status) {
|
|
const check = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>`
|
|
const error = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>`
|
|
if (status) {
|
|
elem.classList.add('verified')
|
|
elem.firstElementChild.innerHTML = check
|
|
} else {
|
|
elem.classList.add('error')
|
|
elem.firstElementChild.innerHTML = error
|
|
}
|
|
await wait()
|
|
}
|
|
|
|
function verifyCertificate(id, certType, verifierContent) {
|
|
floBlockchainAPI.getTx(id).then(async tx => {
|
|
const { vin, vout, time, floData } = tx
|
|
const iVerify = vin.some(i => i.addr === floGlobals.RM_CertificateIssuer_id);
|
|
const oVerify = vout.some(o => [floGlobals.RMincorporationID, floGlobals.RIBC_id].includes(o.scriptPubKey.addresses[0]));
|
|
const cVerify = floData.startsWith(verifierContent);
|
|
processStatus(iVerify, oVerify, cVerify).then(() => {
|
|
console.log(`${certType} (${id}) verified`);
|
|
outputUI(verifierContent, time, floData);
|
|
let link = getBlockchainLink(`tx/${id}`)
|
|
showVerificationStatus('verified', link)
|
|
}).catch(() => {
|
|
showVerificationStatus('error')
|
|
})
|
|
}).catch(error => {
|
|
console.error(`${certType} (${id}) not verified`);
|
|
showVerificationStatus('error', `Incorrect Certificate ID\n${id}`)
|
|
})
|
|
}
|
|
|
|
function outputUI(verifierContent, time, floData) {
|
|
const content = floData.substring(verifierContent.length).trim();
|
|
const issueTime = `Issued: ${trimDate(time * 1000)}`;
|
|
document.getElementById('cert_type').textContent = verifierContent
|
|
document.getElementById('cert_text').innerHTML = content.replace(/([a-zA-Z0-9]){30,36}/, `<strong>$&</strong>`);
|
|
document.getElementById('issue_time').textContent = issueTime
|
|
}
|
|
|
|
function getBlockchainLink(path) {
|
|
let floSight = floBlockchainAPI.util.serverList[floBlockchainAPI.util.curPos]
|
|
return floSight + path
|
|
}
|
|
|
|
function trimDate(d) {
|
|
d = new Date(d).toString()
|
|
return `${d.substring(4, 10)}, ${d.substring(11, 15)}`
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |