')
def qr_code(code):
- fg_hex = request.args.get('fg', '000000')
- bg_hex = request.args.get('bg', 'ffffff')
- size = min(int(request.args.get('size', 300)), 1000)
- style = request.args.get('style', 'square')
with get_db() as conn:
link = conn.execute('SELECT 1 FROM links WHERE code=? AND is_active=1', (code,)).fetchone()
if not link:
return jsonify({'error': 'Not found'}), 404
- png = generate_qr_png(f"{BASE_URL}/{code}", size=size,
- fg=hex_to_rgb(fg_hex), bg=hex_to_rgb(bg_hex), style=style)
- return Response(png, mimetype='image/png', headers={'Cache-Control': 'public, max-age=3600'})
+ return qr_response(f"{BASE_URL}/{code}", request.args,
+ filename=f'qr-{code}', cache=True)
@app.route('/api/qr/custom', methods=['GET'])
@@ -1392,12 +1501,7 @@ def qr_custom():
url = request.args.get('url', '').strip()
if not url or not validate_url(url):
return jsonify({'error': 'Valid URL required'}), 400
- fg_hex = request.args.get('fg', '000000')
- bg_hex = request.args.get('bg', 'ffffff')
- size = min(int(request.args.get('size', 300)), 1000)
- style = request.args.get('style', 'square')
- png = generate_qr_png(url, size=size, fg=hex_to_rgb(fg_hex), bg=hex_to_rgb(bg_hex), style=style)
- return Response(png, mimetype='image/png')
+ return qr_response(url, request.args, filename='qrcode')
@app.route('/api/qr/custom', methods=['POST'])
@@ -1406,10 +1510,6 @@ def qr_custom_post():
url = (data.get('url') or '').strip()
if not url or not validate_url(url):
return jsonify({'error': 'Valid URL required'}), 400
- fg_hex = (data.get('fg') or '000000').lstrip('#')
- bg_hex = (data.get('bg') or 'ffffff').lstrip('#')
- size = min(int(data.get('size', 300)), 1000)
- style = data.get('style', 'square')
logo_bytes = None
logo_b64 = data.get('logo', '')
if logo_b64:
@@ -1417,9 +1517,7 @@ def qr_custom_post():
logo_bytes = base64.b64decode(logo_b64)
except Exception:
return jsonify({'error': 'Invalid logo data'}), 400
- png = generate_qr_png(url, size=size, fg=hex_to_rgb(fg_hex), bg=hex_to_rgb(bg_hex),
- style=style, logo_bytes=logo_bytes)
- return Response(png, mimetype='image/png')
+ return qr_response(url, data, logo_bytes=logo_bytes, filename='qrcode')
# ─────────────────────────────────────────────
diff --git a/index.html b/index.html
index 7a0d517..7582eae 100644
--- a/index.html
+++ b/index.html
@@ -697,6 +697,26 @@ input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:16px;
+
+
+
+
+ Dot styles apply to PNG & PDF only
+
+
+
+
+
+
@@ -713,7 +733,7 @@ input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:16px;
Enter a URL and click Generate
@@ -722,6 +742,22 @@ input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:16px;
+
+
+ Platform OverviewAll users, links, and clicks across the instance
+
+
+
+ —USERS
+ —ORGS
+ —ACTIVE LINKS
+ —CLICKS / 30D
+
+
+ QUOTA USAGE
+ Loading…
+
+
User ManagementCreate and manage user accounts
@@ -1831,37 +1867,52 @@ function clearQrLogo() {
document.getElementById('qr-logo-clear').style.display = 'none';
}
+async function postQrBlob(body) {
+ const resp = await authFetch(`${BASE}/api/qr/custom`, {method:'POST', body:JSON.stringify(body)});
+ if (!resp.ok) throw new Error('QR generation failed');
+ return await resp.blob();
+}
+
document.getElementById('qr-generate-btn').addEventListener('click', async () => {
const url = document.getElementById('qr-url-input').value.trim();
if (!url) { toast('Please enter a URL','error'); return; }
const fg = qrFgH.value.replace('#','') || '000000';
const bg = qrBgH.value.replace('#','') || 'ffffff';
const size = parseInt(qrSz.value);
+ const fmt = document.getElementById('qr-format').value;
+ const ec = document.getElementById('qr-ec').value;
const btn = document.getElementById('qr-generate-btn');
+ const dlLink = document.getElementById('qr-download-link');
+ // PDFs can't render in an
— preview those as PNG, download as PDF
+ const previewFmt = fmt === 'pdf' ? 'png' : fmt;
- if (qrLogoBase64 || qrStyle !== 'square') {
- btn.disabled = true; btn.textContent = 'Generating...';
- try {
- const body = { url, fg, bg, size, style: qrStyle };
- if (qrLogoBase64) body.logo = qrLogoBase64;
- const resp = await authFetch(`${BASE}/api/qr/custom`, {method:'POST', body:JSON.stringify(body)});
- if (!resp.ok) { toast('Failed to generate QR','error'); return; }
- const blob = await resp.blob();
- const blobUrl = URL.createObjectURL(blob);
- document.getElementById('qr-preview-wrap').innerHTML =
- `
`;
- document.getElementById('qr-dl-row').style.display = 'flex';
- document.getElementById('qr-download-link').href = blobUrl;
- } catch(e) { toast('Network error','error'); }
- finally { btn.disabled = false; btn.textContent = 'Generate QR Code'; }
- } else {
- const qrUrl = `${BASE}/api/qr/custom?url=${encodeURIComponent(url)}&fg=${fg}&bg=${bg}&size=${size}&t=${Date.now()}`;
+ btn.disabled = true; btn.textContent = 'Generating...';
+ try {
+ let previewUrl, dlUrl;
+ if (qrLogoBase64) {
+ const params = { url, fg, bg, size, style: qrStyle, ec, logo: qrLogoBase64 };
+ const previewBlob = await postQrBlob({...params, format: previewFmt});
+ previewUrl = URL.createObjectURL(previewBlob);
+ dlUrl = fmt === 'pdf'
+ ? URL.createObjectURL(await postQrBlob({...params, format: 'pdf'}))
+ : previewUrl;
+ } else {
+ const qs = `url=${encodeURIComponent(url)}&fg=${fg}&bg=${bg}&size=${size}&style=${qrStyle}&ec=${ec}`;
+ previewUrl = `${BASE}/api/qr/custom?${qs}&format=${previewFmt}&t=${Date.now()}`;
+ dlUrl = `${BASE}/api/qr/custom?${qs}&format=${fmt}&download=1`;
+ }
document.getElementById('qr-preview-wrap').innerHTML =
- `
`;
+ `
`;
document.getElementById('qr-dl-row').style.display = 'flex';
- document.getElementById('qr-download-link').href = qrUrl;
- }
- toast('QR generated!');
+ dlLink.href = dlUrl;
+ dlLink.download = `qrcode.${fmt}`;
+ dlLink.textContent = `↓ Download ${fmt.toUpperCase()}`;
+ document.getElementById('qr-dl-hint').textContent =
+ fmt === 'svg' ? 'Vector — scales losslessly for print' :
+ fmt === 'pdf' ? 'Print-ready single-page PDF' : 'Right-click to save';
+ toast('QR generated!');
+ } catch(e) { toast('Failed to generate QR','error'); }
+ finally { btn.disabled = false; btn.textContent = 'Generate QR Code'; }
});
// ── Select Mode & Bulk Ops ─────────────────────────