Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions projects/qr-code-generator-scanner/index.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>QR Code Generator & Scanner</title>
<link rel="stylesheet" href="styles.css">

<!-- Styles -->
<link rel="stylesheet" href="styles.css" />

<!-- Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script src="https://unpkg.com/html5-qrcode"></script>
</head>

<body>
<h1>QR Code Generator & Scanner</h1>

<div id="qr-container">
<!-- TODO: Input for text/URL to generate QR code -->
<!-- TODO: Display generated QR code -->
<!-- TODO: Option to download QR code -->
<!-- TODO: Input for uploading/scanning QR code image -->
<!-- TODO: Display scanned QR code result -->
<!-- QR Generator Section -->
<section class="generator">
<h2>Generate a QR Code</h2>
<input
id="qr-input"
type="text"
placeholder="Enter text or URL..."
aria-label="QR code input"
/>
<button id="generate-btn">Generate</button>

<div id="qrcode"></div>

<button id="download-btn" class="hidden">Download QR</button>
</section>

<!-- QR Scanner Section -->
<section class="scanner">
<h2>Scan a QR Code</h2>
<div id="reader" style="width: 300px; margin: auto;"></div>
<p id="scan-result"></p>
</section>
</div>

<script src="main.js"></script>
</body>
</html>
</html>
126 changes: 116 additions & 10 deletions projects/qr-code-generator-scanner/main.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,120 @@
// TODO: Generate QR code from input text/URL
// TODO: Display generated QR code
// TODO: Download QR code image
// TODO: Scan uploaded QR code image
// TODO: Display scanned QR code result

function initQRCodeGeneratorScanner() {
// TODO: Generate QR code from input
// TODO: Scan QR code from image
// TODO: Display results
// TODO: Download QR code
const qrContainer = document.getElementById("qr-container");


qrContainer.innerHTML = `
<section class="generator">
<h2>Generate a QR Code</h2>
<input id="qr-input" type="text" placeholder="Enter text or URL..." />
<button id="generate-btn">Generate</button>
<div id="qrcode"></div>
<button id="download-btn" class="hidden">Download QR</button>
</section>

<section class="scanner">
<h2>Scan a QR Code (Upload Image)</h2>
<input id="file-input" type="file" accept="image/*" />
<p id="scan-result"></p>
</section>
`;

const qrInput = document.getElementById("qr-input");
const generateBtn = document.getElementById("generate-btn");
const qrContainerEl = document.getElementById("qrcode");
const downloadBtn = document.getElementById("download-btn");
const fileInput = document.getElementById("file-input");
const resultEl = document.getElementById("scan-result");

let qrCodeInstance = null;

generateBtn.addEventListener("click", () => {
const text = qrInput.value.trim();
if (!text) {
alert("Please enter some text or a URL!");
return;
}

qrContainerEl.innerHTML = "";

qrCodeInstance = new QRCode(qrContainerEl, {
text,
width: 200,
height: 200,
});

downloadBtn.classList.remove("hidden");
});

downloadBtn.addEventListener("click", () => {
const img = qrContainerEl.querySelector("img");
if (!img) {
alert("Please generate a QR code first!");
return;
}

const link = document.createElement("a");
link.href = img.src;
link.download = "qrcode.png";
link.click();
});


fileInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = async function () {
const imageDataUrl = reader.result;
resultEl.textContent = "Scanning...";

try {
const result = await scanQRCodeFromImage(imageDataUrl);
if (result) {
resultEl.textContent = `Scanned Result: ${result}`;
} else {
resultEl.textContent = "No QR code detected.";
}
} catch (err) {
console.error(err);
resultEl.textContent = "Error scanning QR code.";
}
};
reader.readAsDataURL(file);
});
}

async function scanQRCodeFromImage(imageDataUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);

const imageData = ctx.getImageData(0, 0, img.width, img.height);


if (typeof jsQR === "undefined") {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/jsqr/dist/jsQR.js";
script.onload = () => {
const code = jsQR(imageData.data, imageData.width, imageData.height);
resolve(code ? code.data : null);
};
script.onerror = () => reject("Failed to load jsQR library.");
document.body.appendChild(script);
} else {
const code = jsQR(imageData.data, imageData.width, imageData.height);
resolve(code ? code.data : null);
}
};
img.onerror = reject;
img.src = imageDataUrl;
});
}

window.addEventListener('DOMContentLoaded', initQRCodeGeneratorScanner);
window.addEventListener("DOMContentLoaded", initQRCodeGeneratorScanner);
118 changes: 117 additions & 1 deletion projects/qr-code-generator-scanner/styles.css
Original file line number Diff line number Diff line change
@@ -1 +1,117 @@
/* TODO: Style QR container, input, QR code display, download button, scan input, result display */


body {
font-family: system-ui, sans-serif;
text-align: center;
padding: 2rem 1rem;
background-color: var(--bg, #fafafa);
color: var(--text, #222);
}

h1 {
font-size: 1.8rem;
margin-bottom: 1.5rem;
}

section {
margin: 2rem auto;
max-width: 400px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

h2 {
font-size: 1.2rem;
margin-bottom: 1rem;
}

input[type="text"],
input[type="file"] {
width: 100%;
padding: 0.6rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 0.75rem;
box-sizing: border-box;
}

button {
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 0.6rem 1.2rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
}

button:hover {
background-color: #005fcc;
}

button.hidden {
display: none;
}

#qrcode {
margin: 1rem auto;
}

#scan-result {
margin-top: 0.75rem;
font-weight: 500;
color: #333;
word-break: break-word;
}

#file-input {
margin-top: 0.5rem;
}

#qr-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
}

body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}

body.dark-mode section {
background-color: #1e1e1e;
border-color: #333;
}

body.dark-mode input,
body.dark-mode button {
background-color: #2a2a2a;
color: #eee;
border-color: #444;
}

body.dark-mode button {
background-color: #4e8cff;
}

body.dark-mode button:hover {
background-color: #3c6fd1;
}

@media (max-width: 600px) {
body {
padding: 1rem;
}

section {
width: 100%;
padding: 1rem;
}
}