diff --git a/package-lock.json b/package-lock.json index e4c5cee..cd56a4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,12 @@ "bcryptjs": "^3.0.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "express": "^4.18.2", "express-rate-limit": "^8.5.2", "google-auth-library": "^10.6.2", "jsonwebtoken": "^9.0.2", + "qrcode": "^1.5.4", "razorpay": "^2.9.6", "uuid": "^9.0.0" } @@ -190,6 +191,30 @@ "node": ">= 14" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -319,6 +344,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -421,6 +484,15 @@ "ms": "2.0.0" } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -449,10 +521,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -490,6 +568,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -670,6 +754,19 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -773,6 +870,15 @@ "node": ">=18" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -988,6 +1094,15 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1046,6 +1161,18 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1243,6 +1370,42 @@ "node": ">= 0.8" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1252,12 +1415,30 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1280,6 +1461,23 @@ "node": ">=10" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/qs": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", @@ -1328,6 +1526,21 @@ "axios": "^1.6.8" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1411,6 +1624,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1498,6 +1717,32 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1581,6 +1826,26 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", @@ -1601,6 +1866,47 @@ "optional": true } } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } } } } diff --git a/package.json b/package.json index 5b55e02..acb9edc 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,12 @@ "bcryptjs": "^3.0.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "express": "^4.18.2", "express-rate-limit": "^8.5.2", "google-auth-library": "^10.6.2", "jsonwebtoken": "^9.0.2", + "qrcode": "^1.5.4", "razorpay": "^2.9.6", "uuid": "^9.0.0" } diff --git a/public/admin.html b/public/admin.html index 37f6b44..538f487 100644 --- a/public/admin.html +++ b/public/admin.html @@ -14,6 +14,7 @@ window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); }; + @@ -80,6 +81,16 @@ + + + Home Page @@ -546,6 +557,50 @@ + + + + + + + diff --git a/public/css/admin.css b/public/css/admin.css index 882ff04..1c1bed3 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -1714,3 +1714,43 @@ select.form-input::-webkit-scrollbar-thumb { padding: 60px 20px; } +/* QR Code Generator Modal Styles */ +#qrModal .modal-content { + animation: slideUp 0.3s ease; + overflow: hidden; +} + +#qrCodeContainer { + transition: all 0.3s ease; + min-height: 200px; + display: flex; + align-items: center; + justify-content: center; +} + +#qrCodeContainer img { + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +/* QR Button Hover Effect */ +#generateQRBtn:hover { + transform: translateY(-2px) scale(1.02); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); +} + +#generateQRBtn:active { + transform: translateY(0) scale(0.98); +} + +/* Slide Up Animation */ +@keyframes slideUp { + from { + transform: translateY(30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} diff --git a/public/js/admin.js b/public/js/admin.js index f2ac341..13a9067 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -1371,3 +1371,272 @@ } })(); +// ============================================= +// QR CODE GENERATOR MODULE +// ============================================= + +let currentQRCode = null; +let currentProfileURL = ''; + +/** + * Fetch current user's profile and construct public URL + */ +async function getUserProfileURL() { + try { + const response = await fetch('/api/profile'); + const data = await response.json(); + if (data.success && data.profile) { + const username = data.profile.username; + // Conn uses /u/username pattern for public profiles + currentProfileURL = `${window.location.origin}/u/${username}`; + return currentProfileURL; + } + } catch (error) { + console.error('Error fetching profile:', error); + return null; + } +} + +/** + * Open QR Modal and generate QR code + */ +async function openQRModal() { + const modal = document.getElementById('qrModal'); + const qrContainer = document.getElementById('qrCodeContainer'); + + if (!modal || !qrContainer) { + console.error('QR Modal elements not found'); + return; + } + + // Show modal with fade effect + modal.style.display = 'flex'; + modal.style.opacity = '0'; + setTimeout(() => { modal.style.opacity = '1'; }, 50); + + // Clear previous QR code + qrContainer.innerHTML = ''; + + // Get profile URL + const profileUrl = await getUserProfileURL(); + + if (!profileUrl) { + qrContainer.innerHTML = ` +

+ ⚠️ Unable to generate QR code. Please refresh and try again. +

+ `; + return; + } + + // Display URL + const urlDisplay = document.getElementById('qrProfileUrl'); + if (urlDisplay) { + urlDisplay.innerHTML = ` + Your public profile: + + ${profileUrl} + + `; + } + + // Generate QR code + try { + currentQRCode = new QRCode(qrContainer, { + text: profileUrl, + width: 250, + height: 250, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.H // High error correction for better scanning + }); + + showToast('✅ QR Code generated successfully!', 'success'); + } catch (error) { + console.error('QR generation error:', error); + qrContainer.innerHTML = ` +

+ ❌ Failed to generate QR code. Please try again. +

+ `; + } +} + +/** + * Close QR Modal + */ +function closeQRModal() { + const modal = document.getElementById('qrModal'); + if (modal) { + modal.style.opacity = '0'; + setTimeout(() => { + modal.style.display = 'none'; + }, 300); + } + + // Clean up QR container + const qrContainer = document.getElementById('qrCodeContainer'); + if (qrContainer) { + setTimeout(() => { + qrContainer.innerHTML = ''; + }, 300); + } +} + +/** + * Download QR Code as High-Resolution PNG + */ +async function downloadQRCode() { + const qrContainer = document.getElementById('qrCodeContainer'); + if (!qrContainer) return; + + // Wait for QR to render + await new Promise(resolve => setTimeout(resolve, 200)); + + const qrImage = qrContainer.querySelector('img'); + + if (qrImage && qrImage.src) { + try { + // Fetch profile for filename + const profile = await fetch('/api/profile').then(r => r.json()); + const username = profile.profile?.username || 'creator'; + + // Create download link + const link = document.createElement('a'); + link.download = `conn_qrcode_${username}_${Date.now()}.png`; + link.href = qrImage.src; + link.click(); + + showToast('✅ QR Code downloaded successfully!', 'success'); + } catch (error) { + console.error('Download error:', error); + showToast('❌ Failed to download QR code', 'error'); + } + } else { + showToast('❌ Please wait for QR code to generate', 'error'); + } +} + +/** + * Copy Profile URL to Clipboard + */ +async function copyProfileURL() { + if (!currentProfileURL) { + await getUserProfileURL(); + } + + if (currentProfileURL) { + try { + await navigator.clipboard.writeText(currentProfileURL); + showToast('✅ Profile URL copied to clipboard!', 'success'); + } catch (err) { + // Fallback for older browsers + const textarea = document.createElement('textarea'); + textarea.value = currentProfileURL; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + showToast('✅ Profile URL copied to clipboard!', 'success'); + } + } else { + showToast('❌ Unable to get profile URL', 'error'); + } +} + +/** + * Initialize QR Code Module + */ +function initQRModule() { + // Generate QR button + const qrBtn = document.getElementById('generateQRBtn'); + if (qrBtn) { + qrBtn.addEventListener('click', openQRModal); + console.log('✅ QR Module initialized'); + } else { + console.warn('⚠️ QR button not found - make sure element with id="generateQRBtn" exists'); + } + + // Modal close handlers + const qrModalClose = document.getElementById('qrModalClose'); + if (qrModalClose) { + qrModalClose.addEventListener('click', closeQRModal); + } + + // Click outside modal to close + const qrModal = document.getElementById('qrModal'); + if (qrModal) { + qrModal.addEventListener('click', (e) => { + if (e.target === qrModal) closeQRModal(); + }); + } + + // Download button + const downloadBtn = document.getElementById('downloadQRBtn'); + if (downloadBtn) { + downloadBtn.addEventListener('click', downloadQRCode); + } + + // Copy URL button + const copyUrlBtn = document.getElementById('copyQRUrlBtn'); + if (copyUrlBtn) { + copyUrlBtn.addEventListener('click', copyProfileURL); + } + + // Escape key handler + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + const modal = document.getElementById('qrModal'); + if (modal && modal.style.display === 'flex') { + closeQRModal(); + } + } + }); +} + +// ============================================= +// INITIALIZE ON DOM READY +// ============================================= + +// Check if showToast exists, if not add it +if (typeof showToast !== 'function') { + window.showToast = function(message, type = 'info') { + const toastContainer = document.getElementById('toastContainer'); + if (!toastContainer) { + console.warn('Toast container not found'); + return; + } + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + toast.style.cssText = ` + position: fixed; + bottom: 20px; + right: 20px; + background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'}; + color: white; + padding: 12px 20px; + border-radius: 8px; + z-index: 10000; + animation: slideIn 0.3s ease; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 14px; + box-shadow: 0 8px 30px rgba(0,0,0,0.2); + `; + + toastContainer.appendChild(toast); + setTimeout(() => { + if (toast.parentNode) { + toast.remove(); + } + }, 3000); + }; +} + +// Initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initQRModule); +} else { + initQRModule(); +} \ No newline at end of file