diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e7a697e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "idf.pythonInstallPath": "C:\\Users\\mofai\\.espressif\\tools\\idf-python\\3.11.2\\python.exe" +} \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..4cc714a --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL= \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..b485964 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,5 @@ +.env +node_modules +db.zip +package-lock.json +yarn.lock \ No newline at end of file diff --git a/backend/Controller/userControllr.js b/backend/Controller/userControllr.js new file mode 100644 index 0000000..c10e9b0 --- /dev/null +++ b/backend/Controller/userControllr.js @@ -0,0 +1,331 @@ +import prisma from "../db/db.config.js"; + +export const registerUser = async (req, res) => { + const { enrollment, name, email, password, phone, gender, batch } = req.body; + + console.log("Register Request Body: ", req.body); + + try { + const findUser = await prisma.user.findUnique({ + where: { + enrollment: enrollment, + }, + }); + + if (findUser) { + return res.json({ + status: 409, + message: "You are already registered with the portal!!", + }); + } + + const newUser = await prisma.user.create({ + data: { + enrollment, + name: name, + email: email, + password: password, + phone, + gender, + batch, + }, + }); + return res.json({ + status: 200, + message: "Student created successfully!!", + data: newUser, + }); + } catch (error) { + console.log(error); + } +}; + +export const getUsers = async (req, res) => { + try { + let page = Number(req.query.page) || 1; + let limit = Number(req.query.limit) || 15; + + if (page <= 0) { + page = 1; + } + if (limit <= 0 || limit > 20) { + limit = 10; + } + const offset = (page - 1) * limit; + const users = await prisma.user.findMany({ + skip: offset, + take: limit, + select: { + id: true, + name: true, + email: true, + enrollment: true, + phone: true, + gender: true, + batch: true, + }, + }); + + // get current user count + const tCount = await prisma.user.count(); + const totalPages = Math.ceil(tCount / limit); + return res.json({ + status: 200, + students: users.length, + data: users, + meta: { + totalPages, + currentPage: page, + limit: limit, + }, + }); + } catch (error) { + console.log(error); + } +}; + +export const getUserStatus = async (req, res) => { + const { enrollment } = req.params; + console.log(enrollment); + + try { + // Find user by enrollment + const user = await prisma.user.findUnique({ + where: { + enrollment: enrollment, + }, + include: { + logs: { + orderBy: { + timeIn: "desc", + }, + }, + }, + }); + + console.log(user); + + if (!user) { + return res.json({ + status: 404, + message: "User not found!", + }); + } + + // Use the user_status field from the database + const userStatus = user.user_status; + + // Get the most recent log entry for additional context + const latestLog = user.logs && user.logs.length > 0 ? user.logs[0] : null; + + return res.json({ + status: 200, + message: "User status retrieved successfully!", + data: { + enrollment: user.enrollment, + name: user.name, + user_status: userStatus, // 0 = inside, 1 = outside + lastActivity: latestLog ? { + timeIn: latestLog.timeIn, + timeOut: latestLog.timeOut, + purpose: latestLog.purpose + } : null + }, + }); + } catch (error) { + console.log(error); + return res.json({ + status: 500, + message: "Internal server error!", + }); + } +}; + +export const setUserStatus = async (req, res) => { + const { enrollment } = req.params; + const { purpose } = req.body; // Optional purpose for the log entry + + try { + // Find user by enrollment + const user = await prisma.user.findUnique({ + where: { + enrollment: enrollment, + }, + include: { + logs: { + orderBy: { + timeIn: "desc", + }, + }, + }, + }); + + if (!user) { + return res.json({ + status: 404, + message: "User not found!", + }); + } + + // Determine current status based on latest log + let currentStatus; + let latestLog = null; + + if (!user.logs || user.logs.length === 0) { + // No logs = outside campus + currentStatus = 1; + } else { + latestLog = user.logs[0]; + // Check if timeIn exists and timeOut is null = inside campus + // If timeIn exists and timeOut exists = outside campus + if (latestLog.timeIn && latestLog.timeOut === null) { + currentStatus = 0; // Inside campus + } else { + currentStatus = 1; // Outside campus + } + } + + let newStatus; + let logEntry; + + if (currentStatus === 0) { + // User is currently inside campus, so they're leaving + // Update the latest log with timeOut and update user_status + if (latestLog) { + logEntry = await prisma.status.update({ + where: { + id: latestLog.id, + }, + data: { + timeOut: new Date(), + }, + }); + } + + // Update user status to 1 (outside) + await prisma.user.update({ + where: { id: user.id }, + data: { user_status: 1 } + }); + + newStatus = 1; // Now outside campus + } else { + // User is currently outside campus, so they're entering + // Create a new log entry with timeIn only and update user_status + logEntry = await prisma.status.create({ + data: { + timeIn: new Date(), + purpose: purpose || null, + userId: user.id, + }, + }); + + // Update user status to 0 (inside) + await prisma.user.update({ + where: { id: user.id }, + data: { user_status: 0 } + }); + + newStatus = 0; // Now inside campus + } + + return res.json({ + status: 200, + message: `User ${newStatus === 0 ? 'entered' : 'left'} campus successfully!`, + data: { + enrollment: user.enrollment, + name: user.name, + user_status: newStatus, // 0 = inside, 1 = outside + logEntry: { + id: logEntry.id, + timeIn: logEntry.timeIn, + timeOut: logEntry.timeOut, + purpose: logEntry.purpose, + }, + }, + }); + } catch (error) { + console.log(error); + return res.json({ + status: 500, + message: "Internal server error!", + }); + } +}; + +export const getUser = async (req, res) => { + const { enrollment } = req.params; + try { + const user = await prisma.user.findUnique({ + where: { + enrollment: Number(enrollment), + }, + select: { + id: true, + name: true, + enrollment: true, + email: true, + phone: true, + gender: true, + batch: true, + }, + include: { + logs: true, + }, + }); + + if (!user) { + return res.json({ status: 404, message: "User not found!!" }); + } + return res.json({ + status: 200, + message: "User fetched successfully!!", + data: user, + }); + } catch (error) { + console.log(error); + } +}; + +export const loginUser = async (req, res) => { + const { enrollment, password } = req.body; + + try { + // Find user by enrollment number + const user = await prisma.user.findUnique({ + where: { + enrollment: enrollment, + }, + }); + + if (!user) { + return res.json({ + status: 404, + message: "User not found with this enrollment number!", + }); + } + + // Check password (no hashing as requested for hackathon) + if (user.password !== password) { + return res.json({ + status: 401, + message: "Invalid password!", + }); + } + + // Return user data (excluding password) + const { password: _, ...userWithoutPassword } = user; + + return res.json({ + status: 200, + message: "Login successful!", + data: userWithoutPassword, + }); + } catch (error) { + console.log(error); + return res.json({ + status: 500, + message: "Internal server error!", + }); + } +}; diff --git a/backend/db/db.config.js b/backend/db/db.config.js new file mode 100644 index 0000000..a7c038c --- /dev/null +++ b/backend/db/db.config.js @@ -0,0 +1,5 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient({}) + +export default prisma \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..b89d863 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,23 @@ +{ + "name": "backend", + "version": "1.0.0", + "type": "module", + "main": "server.js", + "scripts": { + "test": "nodemon server.js", + "postinstall": "prisma generate", + "db:sync": "prisma db push && prisma generate", + "db:studio": "prisma studio", + "start": "node server.js" + }, + "license": "MIT", + "dependencies": { + "@prisma/client": "^6.16.1", + "cors": "^2.8.5", + "dotenv": "^17.2.2", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "nodemon": "^3.1.10", + "prisma": "^6.16.1" + } +} diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..7819b97 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,32 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +// If person left yesterday and never scanned back qr and tried to leave campus again next day, inform admin + +model User { + id String @id @unique @default(uuid()) + enrollment String @unique + name String + password String + email String? @unique + phone String? @unique + gender String? + user_status Int @default(0) + batch String? + logs Status[] +} + +model Status { + id String @id @unique @default(uuid()) + timeIn DateTime @default(now()) + timeOut DateTime? + purpose String? + user User @relation(fields: [userId], references: [id]) + userId String +} diff --git a/backend/routes/index.js b/backend/routes/index.js new file mode 100644 index 0000000..9eae26f --- /dev/null +++ b/backend/routes/index.js @@ -0,0 +1,16 @@ +import { Router } from "express"; +import { registerUser, loginUser, getUsers, getUser, getUserStatus, setUserStatus } from "../Controller/userControllr.js"; + +const router = Router() + +// Auth routes +router.post('/v1/auth/register', registerUser) +router.post('/v1/auth/login', loginUser) + +// User routes +router.get('/v1/me/status/:enrollment', getUserStatus) +router.post('/v1/me/toggle/:enrollment', setUserStatus) +router.get('/v1/users', getUsers) +router.get('/v1/users/:enrollment', getUser) + +export default router \ No newline at end of file diff --git a/backend/routes/registerUser.js b/backend/routes/registerUser.js new file mode 100644 index 0000000..d03a90c --- /dev/null +++ b/backend/routes/registerUser.js @@ -0,0 +1,2 @@ +// This file is no longer needed as routes are now in index.js +// Keeping this file empty to avoid conflicts \ No newline at end of file diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..9416666 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,50 @@ + +import express from "express"; +import cors from "cors"; +import 'dotenv/config' +import routes from './routes/index.js' + +const app = express() +const PORT = process.env.PORT || 3000 + +app.get("/", (req, res) => { + res.send("Ayo👋! Hello World from NIT") +}) + +// CORS configuration - Explicit for development +app.use(cors({ + origin: function (origin, callback) { + // Allow requests with no origin (like mobile apps or curl requests) + if (!origin) return callback(null, true); + + // Allow all origins for development + return callback(null, true); + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'ngrok-skip-browser-warning', 'x-requested-with'], + optionsSuccessStatus: 200 +})) + +// Additional CORS headers for preflight requests +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, ngrok-skip-browser-warning, x-requested-with'); + res.header('Access-Control-Allow-Credentials', 'true'); + + if (req.method === 'OPTIONS') { + res.sendStatus(200); + } else { + next(); + } +}) + +// Middleware +app.use(express.json()) +app.use(express.urlencoded({ extended: false })) + +// Route File +app.use(routes) + +app.listen(PORT, () => console.log(`CamPass Server running on port: ${PORT} `)) diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..64b2be4 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,15 @@ +# dependencies +/node_modules +/package-lock.json + +# build outputs +/dist +/assets + +# misc +.DS_Store +*.log + + +# env +.env diff --git a/client/html/home.html b/client/html/home.html new file mode 100644 index 0000000..e8ad05e --- /dev/null +++ b/client/html/home.html @@ -0,0 +1,38 @@ + + + + + QR Scanner + + +
+
CampusPass Entry
+
+
+
+ +
+
+ +
+
+ + +
+
+
+
+ Attendence for entry marked +
+
+
+
+ + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..bd1a2e6 --- /dev/null +++ b/client/package.json @@ -0,0 +1,25 @@ +{ + "name": "campuspass_client", + "version": "1.0.0", + "license": "ISC", + "author": "sickboy", + "private": "true", + "scripts": { + "start": "webpack serve --open", + "build": "webpack --mode production" + }, + "dependencies": { + "html5-qrcode": "^2.3.8", + "qr-scanner": "^1.4.2" + }, + "devDependencies": { + "css-loader": "^7.1.2", + "dotenv-webpack": "^8.1.0", + "file-loader": "^6.2.0", + "html-webpack-plugin": "^5.6.3", + "style-loader": "^4.0.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0" + } +} diff --git a/client/public/home.bundle.js b/client/public/home.bundle.js new file mode 100644 index 0000000..cf93fc4 --- /dev/null +++ b/client/public/home.bundle.js @@ -0,0 +1,663 @@ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./node_modules/css-loader/dist/cjs.js!./styles/home.css": +/*!***************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js!./styles/home.css ***! + \***************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/sourceMaps.js */ "./node_modules/css-loader/dist/runtime/sourceMaps.js"); +/* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js"); +/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__); +// Imports + + +var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default())); +// Module +___CSS_LOADER_EXPORT___.push([module.id, `* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", Arial, sans-serif; +} + +body { + background: #f9f9f9; + color: #333; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +.app-container { + background: #fff; + padding: 20px; + border-radius: 16px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + width: 90%; + max-width: 400px; + text-align: center; +} + +h2 { + margin-bottom: 15px; + font-size: 22px; + color: #444; +} + +/* Scanner box */ +#reader { + width: 100%; + height: 300px; + border-radius: 12px; + overflow: hidden; + margin-bottom: 15px; + border: 2px solid #ddd; +} + +/* Input area */ +.input-container { + display: flex; + gap: 10px; + margin-bottom: 10px; +} + +.input-container input { + flex: 1; + padding: 10px; + border-radius: 8px; + border: 1px solid #ccc; + outline: none; +} + +.input-container button { + padding: 10px 14px; + border: none; + border-radius: 8px; + background: #007bff; + color: white; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} + +.input-container button:hover { + background: #0056cc; +} + +/* Output text */ +#result { + margin-top: 12px; + font-size: 14px; + color: #007bff; + word-wrap: break-word; +} +`, "",{"version":3,"sources":["webpack://./styles/home.css"],"names":[],"mappings":"AAAA;EACE,SAAS;EACT,UAAU;EACV,sBAAsB;EACtB,0CAA0C;AAC5C;;AAEA;EACE,mBAAmB;EACnB,WAAW;EACX,aAAa;EACb,uBAAuB;EACvB,mBAAmB;EACnB,aAAa;AACf;;AAEA;EACE,gBAAgB;EAChB,aAAa;EACb,mBAAmB;EACnB,yCAAyC;EACzC,UAAU;EACV,gBAAgB;EAChB,kBAAkB;AACpB;;AAEA;EACE,mBAAmB;EACnB,eAAe;EACf,WAAW;AACb;;AAEA,gBAAgB;AAChB;EACE,WAAW;EACX,aAAa;EACb,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,sBAAsB;AACxB;;AAEA,eAAe;AACf;EACE,aAAa;EACb,SAAS;EACT,mBAAmB;AACrB;;AAEA;EACE,OAAO;EACP,aAAa;EACb,kBAAkB;EAClB,sBAAsB;EACtB,aAAa;AACf;;AAEA;EACE,kBAAkB;EAClB,YAAY;EACZ,kBAAkB;EAClB,mBAAmB;EACnB,YAAY;EACZ,gBAAgB;EAChB,eAAe;EACf,2BAA2B;AAC7B;;AAEA;EACE,mBAAmB;AACrB;;AAEA,gBAAgB;AAChB;EACE,gBAAgB;EAChB,eAAe;EACf,cAAc;EACd,qBAAqB;AACvB","sourcesContent":["* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n font-family: \"Segoe UI\", Arial, sans-serif;\n}\n\nbody {\n background: #f9f9f9;\n color: #333;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n}\n\n.app-container {\n background: #fff;\n padding: 20px;\n border-radius: 16px;\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\n width: 90%;\n max-width: 400px;\n text-align: center;\n}\n\nh2 {\n margin-bottom: 15px;\n font-size: 22px;\n color: #444;\n}\n\n/* Scanner box */\n#reader {\n width: 100%;\n height: 300px;\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 15px;\n border: 2px solid #ddd;\n}\n\n/* Input area */\n.input-container {\n display: flex;\n gap: 10px;\n margin-bottom: 10px;\n}\n\n.input-container input {\n flex: 1;\n padding: 10px;\n border-radius: 8px;\n border: 1px solid #ccc;\n outline: none;\n}\n\n.input-container button {\n padding: 10px 14px;\n border: none;\n border-radius: 8px;\n background: #007bff;\n color: white;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n}\n\n.input-container button:hover {\n background: #0056cc;\n}\n\n/* Output text */\n#result {\n margin-top: 12px;\n font-size: 14px;\n color: #007bff;\n word-wrap: break-word;\n}\n"],"sourceRoot":""}]); +// Exports +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); + + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/api.js": +/*!*****************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/api.js ***! + \*****************************************************/ +/***/ ((module) => { + + + +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +module.exports = function (cssWithMappingToString) { + var list = []; + + // return the list of modules as css string + list.toString = function toString() { + return this.map(function (item) { + var content = ""; + var needLayer = typeof item[5] !== "undefined"; + if (item[4]) { + content += "@supports (".concat(item[4], ") {"); + } + if (item[2]) { + content += "@media ".concat(item[2], " {"); + } + if (needLayer) { + content += "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {"); + } + content += cssWithMappingToString(item); + if (needLayer) { + content += "}"; + } + if (item[2]) { + content += "}"; + } + if (item[4]) { + content += "}"; + } + return content; + }).join(""); + }; + + // import a list of modules into the list + list.i = function i(modules, media, dedupe, supports, layer) { + if (typeof modules === "string") { + modules = [[null, modules, undefined]]; + } + var alreadyImportedModules = {}; + if (dedupe) { + for (var k = 0; k < this.length; k++) { + var id = this[k][0]; + if (id != null) { + alreadyImportedModules[id] = true; + } + } + } + for (var _k = 0; _k < modules.length; _k++) { + var item = [].concat(modules[_k]); + if (dedupe && alreadyImportedModules[item[0]]) { + continue; + } + if (typeof layer !== "undefined") { + if (typeof item[5] === "undefined") { + item[5] = layer; + } else { + item[1] = "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {").concat(item[1], "}"); + item[5] = layer; + } + } + if (media) { + if (!item[2]) { + item[2] = media; + } else { + item[1] = "@media ".concat(item[2], " {").concat(item[1], "}"); + item[2] = media; + } + } + if (supports) { + if (!item[4]) { + item[4] = "".concat(supports); + } else { + item[1] = "@supports (".concat(item[4], ") {").concat(item[1], "}"); + item[4] = supports; + } + } + list.push(item); + } + }; + return list; +}; + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/sourceMaps.js": +/*!************************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/sourceMaps.js ***! + \************************************************************/ +/***/ ((module) => { + + + +module.exports = function (item) { + var content = item[1]; + var cssMapping = item[3]; + if (!cssMapping) { + return content; + } + if (typeof btoa === "function") { + var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(cssMapping)))); + var data = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(base64); + var sourceMapping = "/*# ".concat(data, " */"); + return [content].concat([sourceMapping]).join("\n"); + } + return [content].join("\n"); +}; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js": +/*!****************************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***! + \****************************************************************************/ +/***/ ((module) => { + + + +var stylesInDOM = []; +function getIndexByIdentifier(identifier) { + var result = -1; + for (var i = 0; i < stylesInDOM.length; i++) { + if (stylesInDOM[i].identifier === identifier) { + result = i; + break; + } + } + return result; +} +function modulesToDom(list, options) { + var idCountMap = {}; + var identifiers = []; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + var id = options.base ? item[0] + options.base : item[0]; + var count = idCountMap[id] || 0; + var identifier = "".concat(id, " ").concat(count); + idCountMap[id] = count + 1; + var indexByIdentifier = getIndexByIdentifier(identifier); + var obj = { + css: item[1], + media: item[2], + sourceMap: item[3], + supports: item[4], + layer: item[5] + }; + if (indexByIdentifier !== -1) { + stylesInDOM[indexByIdentifier].references++; + stylesInDOM[indexByIdentifier].updater(obj); + } else { + var updater = addElementStyle(obj, options); + options.byIndex = i; + stylesInDOM.splice(i, 0, { + identifier: identifier, + updater: updater, + references: 1 + }); + } + identifiers.push(identifier); + } + return identifiers; +} +function addElementStyle(obj, options) { + var api = options.domAPI(options); + api.update(obj); + var updater = function updater(newObj) { + if (newObj) { + if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) { + return; + } + api.update(obj = newObj); + } else { + api.remove(); + } + }; + return updater; +} +module.exports = function (list, options) { + options = options || {}; + list = list || []; + var lastIdentifiers = modulesToDom(list, options); + return function update(newList) { + newList = newList || []; + for (var i = 0; i < lastIdentifiers.length; i++) { + var identifier = lastIdentifiers[i]; + var index = getIndexByIdentifier(identifier); + stylesInDOM[index].references--; + } + var newLastIdentifiers = modulesToDom(newList, options); + for (var _i = 0; _i < lastIdentifiers.length; _i++) { + var _identifier = lastIdentifiers[_i]; + var _index = getIndexByIdentifier(_identifier); + if (stylesInDOM[_index].references === 0) { + stylesInDOM[_index].updater(); + stylesInDOM.splice(_index, 1); + } + } + lastIdentifiers = newLastIdentifiers; + }; +}; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js": +/*!********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***! + \********************************************************************/ +/***/ ((module) => { + + + +var memo = {}; + +/* istanbul ignore next */ +function getTarget(target) { + if (typeof memo[target] === "undefined") { + var styleTarget = document.querySelector(target); + + // Special case to return head of iframe instead of iframe itself + if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { + try { + // This will throw an exception if access to iframe is blocked + // due to cross-origin restrictions + styleTarget = styleTarget.contentDocument.head; + } catch (e) { + // istanbul ignore next + styleTarget = null; + } + } + memo[target] = styleTarget; + } + return memo[target]; +} + +/* istanbul ignore next */ +function insertBySelector(insert, style) { + var target = getTarget(insert); + if (!target) { + throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."); + } + target.appendChild(style); +} +module.exports = insertBySelector; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js": +/*!**********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***! + \**********************************************************************/ +/***/ ((module) => { + + + +/* istanbul ignore next */ +function insertStyleElement(options) { + var element = document.createElement("style"); + options.setAttributes(element, options.attributes); + options.insert(element, options.options); + return element; +} +module.exports = insertStyleElement; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js": +/*!**********************************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***! + \**********************************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +/* istanbul ignore next */ +function setAttributesWithoutAttributes(styleElement) { + var nonce = true ? __webpack_require__.nc : 0; + if (nonce) { + styleElement.setAttribute("nonce", nonce); + } +} +module.exports = setAttributesWithoutAttributes; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js": +/*!***************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***! + \***************************************************************/ +/***/ ((module) => { + + + +/* istanbul ignore next */ +function apply(styleElement, options, obj) { + var css = ""; + if (obj.supports) { + css += "@supports (".concat(obj.supports, ") {"); + } + if (obj.media) { + css += "@media ".concat(obj.media, " {"); + } + var needLayer = typeof obj.layer !== "undefined"; + if (needLayer) { + css += "@layer".concat(obj.layer.length > 0 ? " ".concat(obj.layer) : "", " {"); + } + css += obj.css; + if (needLayer) { + css += "}"; + } + if (obj.media) { + css += "}"; + } + if (obj.supports) { + css += "}"; + } + var sourceMap = obj.sourceMap; + if (sourceMap && typeof btoa !== "undefined") { + css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */"); + } + + // For old IE + /* istanbul ignore if */ + options.styleTagTransform(css, styleElement, options.options); +} +function removeStyleElement(styleElement) { + // istanbul ignore if + if (styleElement.parentNode === null) { + return false; + } + styleElement.parentNode.removeChild(styleElement); +} + +/* istanbul ignore next */ +function domAPI(options) { + if (typeof document === "undefined") { + return { + update: function update() {}, + remove: function remove() {} + }; + } + var styleElement = options.insertStyleElement(options); + return { + update: function update(obj) { + apply(styleElement, options, obj); + }, + remove: function remove() { + removeStyleElement(styleElement); + } + }; +} +module.exports = domAPI; + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js": +/*!*********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***! + \*********************************************************************/ +/***/ ((module) => { + + + +/* istanbul ignore next */ +function styleTagTransform(css, styleElement) { + if (styleElement.styleSheet) { + styleElement.styleSheet.cssText = css; + } else { + while (styleElement.firstChild) { + styleElement.removeChild(styleElement.firstChild); + } + styleElement.appendChild(document.createTextNode(css)); + } +} +module.exports = styleTagTransform; + +/***/ }), + +/***/ "./styles/home.css": +/*!*************************!*\ + !*** ./styles/home.css ***! + \*************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/styleDomAPI.js */ "./node_modules/style-loader/dist/runtime/styleDomAPI.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/insertBySelector.js */ "./node_modules/style-loader/dist/runtime/insertBySelector.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/insertStyleElement.js */ "./node_modules/style-loader/dist/runtime/insertStyleElement.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/styleTagTransform.js */ "./node_modules/style-loader/dist/runtime/styleTagTransform.js"); +/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var _node_modules_css_loader_dist_cjs_js_home_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../node_modules/css-loader/dist/cjs.js!./home.css */ "./node_modules/css-loader/dist/cjs.js!./styles/home.css"); + + + + + + + + + + + +var options = {}; + +options.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default()); +options.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default()); +options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, "head"); +options.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default()); +options.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default()); + +var update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_home_css__WEBPACK_IMPORTED_MODULE_6__["default"], options); + + + + + /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_home_css__WEBPACK_IMPORTED_MODULE_6__["default"] && _node_modules_css_loader_dist_cjs_js_home_css__WEBPACK_IMPORTED_MODULE_6__["default"].locals ? _node_modules_css_loader_dist_cjs_js_home_css__WEBPACK_IMPORTED_MODULE_6__["default"].locals : undefined); + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ id: moduleId, +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ (() => { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ var getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/nonce */ +/******/ (() => { +/******/ __webpack_require__.nc = undefined; +/******/ })(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!*************************!*\ + !*** ./scripts/home.js ***! + \*************************/ +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _styles_home_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../styles/home.css */ "./styles/home.css"); + +// import { Html5Qrcode } from "html5-qrcode"; + +// function startScanner() { +// const html5QrCode = new Html5Qrcode("reader"); +// const config = { fps: 10, qrbox: { width: 250, height: 250 } }; + +// html5QrCode +// .start( +// { facingMode: "environment" }, +// config, +// qrCodeMessage => { +// document.getElementById("result").innerText = +// "✅ QR Code: " + qrCodeMessage; +// html5QrCode.stop().catch(err => console.error("Stop error", err)); +// }, +// () => {} // ignore scan errors +// ) +// .catch(err => { +// document.getElementById("result").innerText = "❌ Camera error: " + err; +// }); +// } + +// document.getElementById("submitBtn").addEventListener("click", () => { +// const purpose = document.getElementById("purpose").value; +// alert("Purpose entered: " + purpose); +// }); + +// startScanner(); + +})(); + +/******/ })() +; +//# sourceMappingURL=home.bundle.js.map \ No newline at end of file diff --git a/client/public/home.bundle.js.map b/client/public/home.bundle.js.map new file mode 100644 index 0000000..c645971 --- /dev/null +++ b/client/public/home.bundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"home.bundle.js","mappings":";;;;;;;;;;;;;;;;;;AAAA;AAC0G;AACjB;AACzF,8BAA8B,mFAA2B,CAAC,4FAAqC;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,kFAAkF,UAAU,UAAU,YAAY,aAAa,OAAO,KAAK,YAAY,WAAW,UAAU,YAAY,aAAa,WAAW,MAAM,KAAK,YAAY,WAAW,YAAY,aAAa,WAAW,YAAY,aAAa,OAAO,KAAK,YAAY,WAAW,UAAU,MAAM,YAAY,MAAM,UAAU,UAAU,YAAY,aAAa,aAAa,aAAa,OAAO,UAAU,KAAK,UAAU,UAAU,YAAY,OAAO,KAAK,UAAU,UAAU,YAAY,aAAa,WAAW,MAAM,KAAK,YAAY,WAAW,YAAY,aAAa,WAAW,YAAY,WAAW,YAAY,OAAO,KAAK,YAAY,OAAO,YAAY,MAAM,YAAY,WAAW,UAAU,YAAY,6BAA6B,cAAc,eAAe,2BAA2B,iDAAiD,GAAG,UAAU,wBAAwB,gBAAgB,kBAAkB,4BAA4B,wBAAwB,kBAAkB,GAAG,oBAAoB,qBAAqB,kBAAkB,wBAAwB,8CAA8C,eAAe,qBAAqB,uBAAuB,GAAG,QAAQ,wBAAwB,oBAAoB,gBAAgB,GAAG,gCAAgC,gBAAgB,kBAAkB,wBAAwB,qBAAqB,wBAAwB,2BAA2B,GAAG,wCAAwC,kBAAkB,cAAc,wBAAwB,GAAG,4BAA4B,YAAY,kBAAkB,uBAAuB,2BAA2B,kBAAkB,GAAG,6BAA6B,uBAAuB,iBAAiB,uBAAuB,wBAAwB,iBAAiB,qBAAqB,oBAAoB,gCAAgC,GAAG,mCAAmC,wBAAwB,GAAG,gCAAgC,qBAAqB,oBAAoB,mBAAmB,0BAA0B,GAAG,qBAAqB;AACrkE;AACA,iEAAe,uBAAuB,EAAC;;;;;;;;;;;ACtF1B;;AAEb;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD;AACrD;AACA;AACA,gDAAgD;AAChD;AACA;AACA,qFAAqF;AACrF;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,iBAAiB;AACvC;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,qBAAqB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,sFAAsF,qBAAqB;AAC3G;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,iDAAiD,qBAAqB;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,sDAAsD,qBAAqB;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,E;;;;;;;;;;ACpFa;;AAEb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uDAAuD,cAAc;AACrE;AACA;AACA;AACA;AACA,E;;;;;;;;;;ACfa;;AAEb;AACA;AACA;AACA,kBAAkB,wBAAwB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,iBAAiB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,4BAA4B;AAChD;AACA;AACA;AACA;AACA;AACA,qBAAqB,6BAA6B;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,E;;;;;;;;;;ACnFa;;AAEb;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kC;;;;;;;;;;ACjCa;;AAEb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oC;;;;;;;;;;ACTa;;AAEb;AACA;AACA,cAAc,KAAwC,GAAG,sBAAiB,GAAG,CAAI;AACjF;AACA;AACA;AACA;AACA,gD;;;;;;;;;;ACTa;;AAEb;AACA;AACA;AACA;AACA,kDAAkD;AAClD;AACA;AACA,0CAA0C;AAC1C;AACA;AACA;AACA,iFAAiF;AACjF;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,yDAAyD;AACzD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,wB;;;;;;;;;;AC5Da;;AAEb;AACA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA,mC;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACZA,MAA+F;AAC/F,MAAqF;AACrF,MAA4F;AAC5F,MAA+G;AAC/G,MAAwG;AACxG,MAAwG;AACxG,MAAkG;AAClG;AACA;;AAEA;;AAEA,4BAA4B,qGAAmB;AAC/C,wBAAwB,kHAAa;AACrC,iBAAiB,uGAAa;AAC9B,iBAAiB,+FAAM;AACvB,6BAA6B,sGAAkB;;AAE/C,aAAa,0GAAG,CAAC,qFAAO;;;;AAI4C;AACpE,OAAO,iEAAe,qFAAO,IAAI,qFAAO,UAAU,qFAAO,mBAAmB,EAAC;;;;;;;UCxB7E;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCtBA;WACA;WACA;WACA;WACA;WACA,iCAAiC,WAAW;WAC5C;WACA,E;;;;;WCPA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA,E;;;;;WCPA,wF;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D,E;;;;;WCNA,mC;;;;;;;;;;;;ACA4B;AAC5B,YAAY,cAAc;;AAE1B;AACA;AACA,sBAAsB,kBAAkB;;AAExC;AACA;AACA,WAAW,2BAA2B;AACtC;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,kBAAkB;AAClB;AACA;AACA;AACA,QAAQ;AACR;;AAEA;AACA;AACA;AACA,IAAI;;AAEJ","sources":["webpack://campuspass_client/./styles/home.css","webpack://campuspass_client/./node_modules/css-loader/dist/runtime/api.js","webpack://campuspass_client/./node_modules/css-loader/dist/runtime/sourceMaps.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/insertBySelector.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/insertStyleElement.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/styleDomAPI.js","webpack://campuspass_client/./node_modules/style-loader/dist/runtime/styleTagTransform.js","webpack://campuspass_client/./styles/home.css?93a2","webpack://campuspass_client/webpack/bootstrap","webpack://campuspass_client/webpack/runtime/compat get default export","webpack://campuspass_client/webpack/runtime/define property getters","webpack://campuspass_client/webpack/runtime/hasOwnProperty shorthand","webpack://campuspass_client/webpack/runtime/make namespace object","webpack://campuspass_client/webpack/runtime/nonce","webpack://campuspass_client/./scripts/home.js"],"sourcesContent":["// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../node_modules/css-loader/dist/runtime/sourceMaps.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n font-family: \"Segoe UI\", Arial, sans-serif;\n}\n\nbody {\n background: #f9f9f9;\n color: #333;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n}\n\n.app-container {\n background: #fff;\n padding: 20px;\n border-radius: 16px;\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\n width: 90%;\n max-width: 400px;\n text-align: center;\n}\n\nh2 {\n margin-bottom: 15px;\n font-size: 22px;\n color: #444;\n}\n\n/* Scanner box */\n#reader {\n width: 100%;\n height: 300px;\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 15px;\n border: 2px solid #ddd;\n}\n\n/* Input area */\n.input-container {\n display: flex;\n gap: 10px;\n margin-bottom: 10px;\n}\n\n.input-container input {\n flex: 1;\n padding: 10px;\n border-radius: 8px;\n border: 1px solid #ccc;\n outline: none;\n}\n\n.input-container button {\n padding: 10px 14px;\n border: none;\n border-radius: 8px;\n background: #007bff;\n color: white;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n}\n\n.input-container button:hover {\n background: #0056cc;\n}\n\n/* Output text */\n#result {\n margin-top: 12px;\n font-size: 14px;\n color: #007bff;\n word-wrap: break-word;\n}\n`, \"\",{\"version\":3,\"sources\":[\"webpack://./styles/home.css\"],\"names\":[],\"mappings\":\"AAAA;EACE,SAAS;EACT,UAAU;EACV,sBAAsB;EACtB,0CAA0C;AAC5C;;AAEA;EACE,mBAAmB;EACnB,WAAW;EACX,aAAa;EACb,uBAAuB;EACvB,mBAAmB;EACnB,aAAa;AACf;;AAEA;EACE,gBAAgB;EAChB,aAAa;EACb,mBAAmB;EACnB,yCAAyC;EACzC,UAAU;EACV,gBAAgB;EAChB,kBAAkB;AACpB;;AAEA;EACE,mBAAmB;EACnB,eAAe;EACf,WAAW;AACb;;AAEA,gBAAgB;AAChB;EACE,WAAW;EACX,aAAa;EACb,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,sBAAsB;AACxB;;AAEA,eAAe;AACf;EACE,aAAa;EACb,SAAS;EACT,mBAAmB;AACrB;;AAEA;EACE,OAAO;EACP,aAAa;EACb,kBAAkB;EAClB,sBAAsB;EACtB,aAAa;AACf;;AAEA;EACE,kBAAkB;EAClB,YAAY;EACZ,kBAAkB;EAClB,mBAAmB;EACnB,YAAY;EACZ,gBAAgB;EAChB,eAAe;EACf,2BAA2B;AAC7B;;AAEA;EACE,mBAAmB;AACrB;;AAEA,gBAAgB;AAChB;EACE,gBAAgB;EAChB,eAAe;EACf,cAAc;EACd,qBAAqB;AACvB\",\"sourcesContent\":[\"* {\\n margin: 0;\\n padding: 0;\\n box-sizing: border-box;\\n font-family: \\\"Segoe UI\\\", Arial, sans-serif;\\n}\\n\\nbody {\\n background: #f9f9f9;\\n color: #333;\\n display: flex;\\n justify-content: center;\\n align-items: center;\\n height: 100vh;\\n}\\n\\n.app-container {\\n background: #fff;\\n padding: 20px;\\n border-radius: 16px;\\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\\n width: 90%;\\n max-width: 400px;\\n text-align: center;\\n}\\n\\nh2 {\\n margin-bottom: 15px;\\n font-size: 22px;\\n color: #444;\\n}\\n\\n/* Scanner box */\\n#reader {\\n width: 100%;\\n height: 300px;\\n border-radius: 12px;\\n overflow: hidden;\\n margin-bottom: 15px;\\n border: 2px solid #ddd;\\n}\\n\\n/* Input area */\\n.input-container {\\n display: flex;\\n gap: 10px;\\n margin-bottom: 10px;\\n}\\n\\n.input-container input {\\n flex: 1;\\n padding: 10px;\\n border-radius: 8px;\\n border: 1px solid #ccc;\\n outline: none;\\n}\\n\\n.input-container button {\\n padding: 10px 14px;\\n border: none;\\n border-radius: 8px;\\n background: #007bff;\\n color: white;\\n font-weight: 500;\\n cursor: pointer;\\n transition: background 0.2s;\\n}\\n\\n.input-container button:hover {\\n background: #0056cc;\\n}\\n\\n/* Output text */\\n#result {\\n margin-top: 12px;\\n font-size: 14px;\\n color: #007bff;\\n word-wrap: break-word;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","\"use strict\";\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\nmodule.exports = function (cssWithMappingToString) {\n var list = [];\n\n // return the list of modules as css string\n list.toString = function toString() {\n return this.map(function (item) {\n var content = \"\";\n var needLayer = typeof item[5] !== \"undefined\";\n if (item[4]) {\n content += \"@supports (\".concat(item[4], \") {\");\n }\n if (item[2]) {\n content += \"@media \".concat(item[2], \" {\");\n }\n if (needLayer) {\n content += \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\");\n }\n content += cssWithMappingToString(item);\n if (needLayer) {\n content += \"}\";\n }\n if (item[2]) {\n content += \"}\";\n }\n if (item[4]) {\n content += \"}\";\n }\n return content;\n }).join(\"\");\n };\n\n // import a list of modules into the list\n list.i = function i(modules, media, dedupe, supports, layer) {\n if (typeof modules === \"string\") {\n modules = [[null, modules, undefined]];\n }\n var alreadyImportedModules = {};\n if (dedupe) {\n for (var k = 0; k < this.length; k++) {\n var id = this[k][0];\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n for (var _k = 0; _k < modules.length; _k++) {\n var item = [].concat(modules[_k]);\n if (dedupe && alreadyImportedModules[item[0]]) {\n continue;\n }\n if (typeof layer !== \"undefined\") {\n if (typeof item[5] === \"undefined\") {\n item[5] = layer;\n } else {\n item[1] = \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\").concat(item[1], \"}\");\n item[5] = layer;\n }\n }\n if (media) {\n if (!item[2]) {\n item[2] = media;\n } else {\n item[1] = \"@media \".concat(item[2], \" {\").concat(item[1], \"}\");\n item[2] = media;\n }\n }\n if (supports) {\n if (!item[4]) {\n item[4] = \"\".concat(supports);\n } else {\n item[1] = \"@supports (\".concat(item[4], \") {\").concat(item[1], \"}\");\n item[4] = supports;\n }\n }\n list.push(item);\n }\n };\n return list;\n};","\"use strict\";\n\nmodule.exports = function (item) {\n var content = item[1];\n var cssMapping = item[3];\n if (!cssMapping) {\n return content;\n }\n if (typeof btoa === \"function\") {\n var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(cssMapping))));\n var data = \"sourceMappingURL=data:application/json;charset=utf-8;base64,\".concat(base64);\n var sourceMapping = \"/*# \".concat(data, \" */\");\n return [content].concat([sourceMapping]).join(\"\\n\");\n }\n return [content].join(\"\\n\");\n};","\"use strict\";\n\nvar stylesInDOM = [];\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n for (var i = 0; i < stylesInDOM.length; i++) {\n if (stylesInDOM[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n return result;\n}\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var indexByIdentifier = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3],\n supports: item[4],\n layer: item[5]\n };\n if (indexByIdentifier !== -1) {\n stylesInDOM[indexByIdentifier].references++;\n stylesInDOM[indexByIdentifier].updater(obj);\n } else {\n var updater = addElementStyle(obj, options);\n options.byIndex = i;\n stylesInDOM.splice(i, 0, {\n identifier: identifier,\n updater: updater,\n references: 1\n });\n }\n identifiers.push(identifier);\n }\n return identifiers;\n}\nfunction addElementStyle(obj, options) {\n var api = options.domAPI(options);\n api.update(obj);\n var updater = function updater(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {\n return;\n }\n api.update(obj = newObj);\n } else {\n api.remove();\n }\n };\n return updater;\n}\nmodule.exports = function (list, options) {\n options = options || {};\n list = list || [];\n var lastIdentifiers = modulesToDom(list, options);\n return function update(newList) {\n newList = newList || [];\n for (var i = 0; i < lastIdentifiers.length; i++) {\n var identifier = lastIdentifiers[i];\n var index = getIndexByIdentifier(identifier);\n stylesInDOM[index].references--;\n }\n var newLastIdentifiers = modulesToDom(newList, options);\n for (var _i = 0; _i < lastIdentifiers.length; _i++) {\n var _identifier = lastIdentifiers[_i];\n var _index = getIndexByIdentifier(_identifier);\n if (stylesInDOM[_index].references === 0) {\n stylesInDOM[_index].updater();\n stylesInDOM.splice(_index, 1);\n }\n }\n lastIdentifiers = newLastIdentifiers;\n };\n};","\"use strict\";\n\nvar memo = {};\n\n/* istanbul ignore next */\nfunction getTarget(target) {\n if (typeof memo[target] === \"undefined\") {\n var styleTarget = document.querySelector(target);\n\n // Special case to return head of iframe instead of iframe itself\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n memo[target] = styleTarget;\n }\n return memo[target];\n}\n\n/* istanbul ignore next */\nfunction insertBySelector(insert, style) {\n var target = getTarget(insert);\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n target.appendChild(style);\n}\nmodule.exports = insertBySelector;","\"use strict\";\n\n/* istanbul ignore next */\nfunction insertStyleElement(options) {\n var element = document.createElement(\"style\");\n options.setAttributes(element, options.attributes);\n options.insert(element, options.options);\n return element;\n}\nmodule.exports = insertStyleElement;","\"use strict\";\n\n/* istanbul ignore next */\nfunction setAttributesWithoutAttributes(styleElement) {\n var nonce = typeof __webpack_nonce__ !== \"undefined\" ? __webpack_nonce__ : null;\n if (nonce) {\n styleElement.setAttribute(\"nonce\", nonce);\n }\n}\nmodule.exports = setAttributesWithoutAttributes;","\"use strict\";\n\n/* istanbul ignore next */\nfunction apply(styleElement, options, obj) {\n var css = \"\";\n if (obj.supports) {\n css += \"@supports (\".concat(obj.supports, \") {\");\n }\n if (obj.media) {\n css += \"@media \".concat(obj.media, \" {\");\n }\n var needLayer = typeof obj.layer !== \"undefined\";\n if (needLayer) {\n css += \"@layer\".concat(obj.layer.length > 0 ? \" \".concat(obj.layer) : \"\", \" {\");\n }\n css += obj.css;\n if (needLayer) {\n css += \"}\";\n }\n if (obj.media) {\n css += \"}\";\n }\n if (obj.supports) {\n css += \"}\";\n }\n var sourceMap = obj.sourceMap;\n if (sourceMap && typeof btoa !== \"undefined\") {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n }\n\n // For old IE\n /* istanbul ignore if */\n options.styleTagTransform(css, styleElement, options.options);\n}\nfunction removeStyleElement(styleElement) {\n // istanbul ignore if\n if (styleElement.parentNode === null) {\n return false;\n }\n styleElement.parentNode.removeChild(styleElement);\n}\n\n/* istanbul ignore next */\nfunction domAPI(options) {\n if (typeof document === \"undefined\") {\n return {\n update: function update() {},\n remove: function remove() {}\n };\n }\n var styleElement = options.insertStyleElement(options);\n return {\n update: function update(obj) {\n apply(styleElement, options, obj);\n },\n remove: function remove() {\n removeStyleElement(styleElement);\n }\n };\n}\nmodule.exports = domAPI;","\"use strict\";\n\n/* istanbul ignore next */\nfunction styleTagTransform(css, styleElement) {\n if (styleElement.styleSheet) {\n styleElement.styleSheet.cssText = css;\n } else {\n while (styleElement.firstChild) {\n styleElement.removeChild(styleElement.firstChild);\n }\n styleElement.appendChild(document.createTextNode(css));\n }\n}\nmodule.exports = styleTagTransform;","\n import API from \"!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import domAPI from \"!../node_modules/style-loader/dist/runtime/styleDomAPI.js\";\n import insertFn from \"!../node_modules/style-loader/dist/runtime/insertBySelector.js\";\n import setAttributes from \"!../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\";\n import insertStyleElement from \"!../node_modules/style-loader/dist/runtime/insertStyleElement.js\";\n import styleTagTransformFn from \"!../node_modules/style-loader/dist/runtime/styleTagTransform.js\";\n import content, * as namedExport from \"!!../node_modules/css-loader/dist/cjs.js!./home.css\";\n \n \n\nvar options = {};\n\noptions.styleTagTransform = styleTagTransformFn;\noptions.setAttributes = setAttributes;\noptions.insert = insertFn.bind(null, \"head\");\noptions.domAPI = domAPI;\noptions.insertStyleElement = insertStyleElement;\n\nvar update = API(content, options);\n\n\n\nexport * from \"!!../node_modules/css-loader/dist/cjs.js!./home.css\";\n export default content && content.locals ? content.locals : undefined;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nc = undefined;","import \"../styles/home.css\";\n// import { Html5Qrcode } from \"html5-qrcode\";\n\n// function startScanner() {\n// const html5QrCode = new Html5Qrcode(\"reader\");\n// const config = { fps: 10, qrbox: { width: 250, height: 250 } };\n\n// html5QrCode\n// .start(\n// { facingMode: \"environment\" },\n// config,\n// qrCodeMessage => {\n// document.getElementById(\"result\").innerText =\n// \"✅ QR Code: \" + qrCodeMessage;\n// html5QrCode.stop().catch(err => console.error(\"Stop error\", err));\n// },\n// () => {} // ignore scan errors\n// )\n// .catch(err => {\n// document.getElementById(\"result\").innerText = \"❌ Camera error: \" + err;\n// });\n// }\n\n// document.getElementById(\"submitBtn\").addEventListener(\"click\", () => {\n// const purpose = document.getElementById(\"purpose\").value;\n// alert(\"Purpose entered: \" + purpose);\n// });\n\n// startScanner();\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/client/public/home.html b/client/public/home.html new file mode 100644 index 0000000..35b8c8f --- /dev/null +++ b/client/public/home.html @@ -0,0 +1,26 @@ + + + + + + QR Code Scanner + + + +
+

📷 QR Code Scanner

+ + +
+ + +
+ + +
+ + +
+
+ + diff --git a/client/scripts/home.js b/client/scripts/home.js new file mode 100644 index 0000000..7382579 --- /dev/null +++ b/client/scripts/home.js @@ -0,0 +1,76 @@ +import "../styles/home.css"; +import QrScanner from "qr-scanner"; + +const STATUS_OUT = 1; +const STATUS_IN = 0; +// TODO: get real enrollment number and status from server +const enroll_num = "1231241"; +const status = 0; +let scanner = null; + +function showDoneOverlay(message) { + const overlay = document.getElementById("overlay"); + const overlayText = document.querySelector(".overlay-text .status"); + overlayText.textContent = message; + overlay.style.display = "flex"; + + setTimeout(() => { + location.reload(); + }, 3000); +} + +function showMessage(text, type = "error") { + const messageBox = document.getElementById("message"); + messageBox.textContent = text; + messageBox.style.display = "block"; + + if (type === "error") { + messageBox.style.background = "#fee2e2"; + messageBox.style.color = "#b91c1c"; + messageBox.style.border = "1px solid #fca5a5"; + } else if (type == "success") { + messageBox.style.background = "#dcfce7"; + messageBox.style.color = "#166534"; + messageBox.style.border = "1px solid #86efac"; + } +} + +function onQrScanned(qrId) { + const purposeInput = document.getElementById("purpose").value.trim(); + + const payload = { + enroll_num, + id: qrId, + }; + + if (status == STATUS_IN && !purposeInput) { + showMessage("⚠️ Please enter a purpose before scanning.", "error"); + return; + } else { + payload.purpose = purposeInput; + } + scanner.pause(); + console.log("submitting", payload); + if (status == STATUS_IN) showDoneOverlay("exit"); + else showDoneOverlay("entry"); +} + +function main() { + const videoElem = document.getElementById("qr-video"); + const statusTitle = document.querySelector(".title .status"); + if (status == STATUS_OUT) { + document.querySelector(".purpose").style.display = "none"; + statusTitle.textContent = "Exit"; + } + scanner = new QrScanner( + videoElem, + result => { + console.log(result.data); + onQrScanned(result.data); + }, + { highlightScanRegion: true, highlightCodeOutline: true } + ); + scanner.start(); +} + +main(); diff --git a/client/styles/home.css b/client/styles/home.css new file mode 100644 index 0000000..98706eb --- /dev/null +++ b/client/styles/home.css @@ -0,0 +1,193 @@ +body { + margin: 0; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: #f9fafb; /* light gray */ + font-family: "Segoe UI", sans-serif; + color: #111827; +} + +.main-container { + .title { + text-align: center; + font-size: 2rem; /* scalable for all devices */ + font-weight: 600; /* semi-bold */ + color: #282828; /* light gray */ + margin-bottom: 1.5rem; + letter-spacing: -0.02em; + + span { + text-decoration: underline; + } + } +} + +.container { + display: flex; + flex-direction: column; + gap: 1.5rem; + align-items: center; + width: 100%; + max-width: 520px; + padding: 2rem 1rem; + background: #ffffff; + border-radius: 1rem; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); +} + +.scanner-container { + position: relative; + width: 90vw; + max-width: 480px; + aspect-ratio: 1/1; + border-radius: 1rem; + overflow: hidden; + border: 2px solid #e5e7eb; + background: #f3f4f6; +} + +#qr-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.purpose { + width: 100%; + display: flex; + gap: 0.5rem; +} + +#purpose { + flex: 1; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 2px solid #d1d5db; + border-radius: 0.75rem; + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; +} + +#purpose:focus { + border-color: #2563eb; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.3); +} + +button { + padding: 0.75rem 1.25rem; + font-size: 1rem; + border: none; + border-radius: 0.75rem; + background: #2563eb; + color: #fff; + cursor: pointer; + transition: background 0.2s; +} + +button:hover { + background: #1d4ed8; +} + +#message { + padding: 0.75rem 1rem; + border-radius: 0.75rem; + font-size: 0.95rem; + display: none; +} + +/* OVERLAY */ +/* Overlay hidden by default */ +#overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.95); + z-index: 9999; +} + +/* Animation container */ +.overlay-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + animation: popIn 0.4s ease-out; +} + +/* Checkmark animation */ +.checkmark { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4px solid #22c55e; + position: relative; + animation: scaleUp 0.4s ease-out; +} + +.checkmark::after { + content: ""; + position: absolute; + width: 25px; + height: 45px; + border-right: 4px solid #22c55e; + border-bottom: 4px solid #22c55e; + transform: rotate(45deg) translate(-5px, -8px); + opacity: 0; + animation: drawCheck 0.6s ease forwards 0.3s; +} + +/* Text */ +.overlay-text { + font-size: 1.2rem; + font-weight: 600; + color: #166534; + font-family: "Segoe UI", sans-serif; + .status { + text-decoration: underline; + /* color: black; */ + } +} + +/* Animations */ +@keyframes popIn { + from { + transform: scale(0.9); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes scaleUp { + from { + transform: scale(0.5); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes drawCheck { + from { + opacity: 0; + transform: rotate(45deg) scale(0); + } + to { + opacity: 1; + transform: rotate(45deg) scale(1); + } +} diff --git a/client/webpack.config.js b/client/webpack.config.js new file mode 100644 index 0000000..7e55234 --- /dev/null +++ b/client/webpack.config.js @@ -0,0 +1,62 @@ +const path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const Dotenv = require("dotenv-webpack"); + +module.exports = { + mode: "development", + + entry: { + home: "./scripts/home.js", + }, + + output: { + path: path.resolve(__dirname, "public"), + filename: "[name].bundle.js", + clean: true, + }, + + module: { + rules: [ + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(png|jpg|jpeg|gif|svg)$/i, + type: "asset/resource", + }, + { + test: /\.(ttf|woff|woff2|eot|otf)$/, + type: "asset/resource", + generator: { + filename: "fonts/[name][ext]", + }, + }, + ], + }, + + plugins: [ + new Dotenv(), + new HtmlWebpackPlugin({ + filename: "home.html", + template: "./html/home.html", + chunks: ["home"], + }), + ], + + devtool: "source-map", + + devServer: { + static: "./public", + port: 3005, + open: "home.html", + hot: true, + historyApiFallback: true, + watchFiles: [ + "html/**/*.*", + "styles/**/*.*", + "scripts/**/*.*", + "assets/**/*.*", + ], + }, +}; diff --git a/endpoints.png b/endpoints.png new file mode 100644 index 0000000..4cedf5e Binary files /dev/null and b/endpoints.png differ