parthmax commited on
Commit
189ae09
·
0 Parent(s):

updated all

Browse files
Files changed (4) hide show
  1. Dockerfile +12 -0
  2. app.py +200 -0
  3. requirements.txt +7 -0
  4. templates/index.html +395 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ build-essential \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements and install Python dependencies
12
+ COPY6
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Form, HTTPException, Depends
2
+ from fastapi.responses import RedirectResponse, HTMLResponse, StreamingResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.templating import Jinja2Templates
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ import os
7
+ import qrcode
8
+ import io
9
+ import uuid
10
+ from typing import Dict, Optional
11
+ import json
12
+ from datetime import datetime, timedelta
13
+ import uvicorn
14
+
15
+ # Initialize FastAPI app
16
+ app = FastAPI(
17
+ title="Dynamic QR Generator",
18
+ description="Enterprise-grade dynamic QR code management system",
19
+ version="1.0.0"
20
+ )
21
+
22
+ # Add CORS middleware
23
+ app.add_middleware(
24
+ CORSMiddleware,
25
+ allow_origins=["*"],
26
+ allow_credentials=True,
27
+ allow_methods=["*"],
28
+ allow_headers=["*"],
29
+ )
30
+
31
+ # Mount static files and templates
32
+ os.makedirs("static", exist_ok=True)
33
+ os.makedirs("templates", exist_ok=True)
34
+ app.mount("/static", StaticFiles(directory="static"), name="static")
35
+ templates = Jinja2Templates(directory="templates")
36
+
37
+ # In-memory storage for QR codes (use database in production)
38
+ qr_codes: Dict[str, Dict] = {}
39
+
40
+ class QRCodeManager:
41
+ """Middleware class for QR code management logic"""
42
+
43
+ @staticmethod
44
+ def generate_qr_id() -> str:
45
+ """Generate unique QR code identifier"""
46
+ return str(uuid.uuid4())[:8]
47
+
48
+ @staticmethod
49
+ def create_qr_code(qr_id: str, redirect_url: str, title: str = "Untitled") -> Dict:
50
+ """Create a new QR code entry"""
51
+ qr_data = {
52
+ "id": qr_id,
53
+ "title": title,
54
+ "redirect_url": redirect_url,
55
+ "scan_count": 0,
56
+ "created_at": datetime.now().isoformat(),
57
+ "last_scanned": None,
58
+ "is_active": True
59
+ }
60
+ qr_codes[qr_id] = qr_data
61
+ return qr_data
62
+
63
+ @staticmethod
64
+ def update_qr_code(qr_id: str, redirect_url: str, title: str = None) -> Optional[Dict]:
65
+ """Update existing QR code"""
66
+ if qr_id not in qr_codes:
67
+ return None
68
+
69
+ qr_codes[qr_id]["redirect_url"] = redirect_url
70
+ if title:
71
+ qr_codes[qr_id]["title"] = title
72
+ qr_codes[qr_id]["updated_at"] = datetime.now().isoformat()
73
+ return qr_codes[qr_id]
74
+
75
+ @staticmethod
76
+ def get_qr_code(qr_id: str) -> Optional[Dict]:
77
+ """Get QR code data"""
78
+ return qr_codes.get(qr_id)
79
+
80
+ @staticmethod
81
+ def increment_scan_count(qr_id: str) -> None:
82
+ """Increment scan count and update last scanned time"""
83
+ if qr_id in qr_codes:
84
+ qr_codes[qr_id]["scan_count"] += 1
85
+ qr_codes[qr_id]["last_scanned"] = datetime.now().isoformat()
86
+
87
+ @staticmethod
88
+ def get_all_qr_codes() -> Dict[str, Dict]:
89
+ """Get all QR codes"""
90
+ return qr_codes
91
+
92
+ @staticmethod
93
+ def generate_qr_image(qr_id: str) -> io.BytesIO:
94
+ """Generate QR code image"""
95
+ base_url = os.getenv("BASE_URL", "https://your-app.hf.space")
96
+ qr_url = f"{base_url}/r/{qr_id}"
97
+
98
+ qr = qrcode.QRCode(
99
+ version=1,
100
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
101
+ box_size=10,
102
+ border=4,
103
+ )
104
+ qr.add_data(qr_url)
105
+ qr.make(fit=True)
106
+
107
+ img = qr.make_image(fill_color="black", back_color="white")
108
+ img_io = io.BytesIO()
109
+ img.save(img_io, 'PNG')
110
+ img_io.seek(0)
111
+ return img_io
112
+
113
+ # Initialize QR manager
114
+ qr_manager = QRCodeManager()
115
+
116
+ @app.get("/", response_class=HTMLResponse)
117
+ async def home(request: Request):
118
+ """Main dashboard page"""
119
+ all_qr_codes = qr_manager.get_all_qr_codes()
120
+ return templates.TemplateResponse("index.html", {
121
+ "request": request,
122
+ "qr_codes": all_qr_codes
123
+ })
124
+
125
+ @app.post("/create")
126
+ async def create_qr(
127
+ title: str = Form(...),
128
+ redirect_url: str = Form(...),
129
+ ):
130
+ """Create a new QR code"""
131
+ if not redirect_url.startswith(('http://', 'https://')):
132
+ redirect_url = f"https://{redirect_url}"
133
+
134
+ qr_id = qr_manager.generate_qr_id()
135
+ qr_data = qr_manager.create_qr_code(qr_id, redirect_url, title)
136
+
137
+ return RedirectResponse(url="/", status_code=303)
138
+
139
+ @app.post("/update/{qr_id}")
140
+ async def update_qr(
141
+ qr_id: str,
142
+ title: str = Form(...),
143
+ redirect_url: str = Form(...),
144
+ ):
145
+ """Update existing QR code"""
146
+ if qr_id not in qr_codes:
147
+ raise HTTPException(status_code=404, detail="QR code not found")
148
+
149
+ if not redirect_url.startswith(('http://', 'https://')):
150
+ redirect_url = f"https://{redirect_url}"
151
+
152
+ qr_manager.update_qr_code(qr_id, redirect_url, title)
153
+ return RedirectResponse(url="/", status_code=303)
154
+
155
+ @app.get("/r/{qr_id}")
156
+ async def redirect_qr(qr_id: str):
157
+ """Redirect endpoint for QR code scans"""
158
+ qr_data = qr_manager.get_qr_code(qr_id)
159
+
160
+ if not qr_data or not qr_data.get("is_active"):
161
+ raise HTTPException(status_code=404, detail="QR code not found or inactive")
162
+
163
+ # Increment scan count
164
+ qr_manager.increment_scan_count(qr_id)
165
+
166
+ return RedirectResponse(url=qr_data["redirect_url"])
167
+
168
+ @app.get("/qr/{qr_id}")
169
+ async def get_qr_image(qr_id: str):
170
+ """Generate and serve QR code image"""
171
+ if qr_id not in qr_codes:
172
+ raise HTTPException(status_code=404, detail="QR code not found")
173
+
174
+ img_io = qr_manager.generate_qr_image(qr_id)
175
+ return StreamingResponse(img_io, media_type="image/png")
176
+
177
+ @app.delete("/delete/{qr_id}")
178
+ async def delete_qr(qr_id: str):
179
+ """Delete QR code"""
180
+ if qr_id not in qr_codes:
181
+ raise HTTPException(status_code=404, detail="QR code not found")
182
+
183
+ del qr_codes[qr_id]
184
+ return {"message": "QR code deleted successfully"}
185
+
186
+ @app.get("/api/qr/{qr_id}")
187
+ async def get_qr_data(qr_id: str):
188
+ """Get QR code data via API"""
189
+ qr_data = qr_manager.get_qr_code(qr_id)
190
+ if not qr_data:
191
+ raise HTTPException(status_code=404, detail="QR code not found")
192
+ return qr_data
193
+
194
+ @app.get("/health")
195
+ async def health_check():
196
+ """Health check endpoint"""
197
+ return {"status": "healthy", "timestamp": datetime.now().isoformat()}
198
+
199
+ if __name__ == "__main__":
200
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ jinja2==3.1.2
4
+ python-multipart==0.0.6
5
+ qrcode[pil]==7.4.2
6
+ pillow==10.0.1
7
+ python-dotenv==1.0.0
templates/index.html ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dynamic QR Generator - Enterprise Solution</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .glass-card {
11
+ background: rgba(255, 255, 255, 0.95);
12
+ backdrop-filter: blur(10px);
13
+ border: 1px solid rgba(255, 255, 255, 0.2);
14
+ }
15
+ .gradient-bg {
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ }
18
+ .hover-scale {
19
+ transition: transform 0.2s ease-in-out;
20
+ }
21
+ .hover-scale:hover {
22
+ transform: scale(1.02);
23
+ }
24
+ </style>
25
+ </head>
26
+ <body class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100">
27
+ <!-- Header -->
28
+ <header class="glass-card shadow-lg">
29
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
30
+ <div class="flex items-center justify-between">
31
+ <div class="flex items-center space-x-3">
32
+ <div class="w-10 h-10 gradient-bg rounded-lg flex items-center justify-center">
33
+ <i class="fas fa-qrcode text-white text-lg"></i>
34
+ </div>
35
+ <div>
36
+ <h1 class="text-2xl font-bold text-gray-900">Dynamic QR Generator</h1>
37
+ <p class="text-sm text-gray-600">Enterprise QR Code Management</p>
38
+ </div>
39
+ </div>
40
+ <div class="hidden md:flex items-center space-x-6">
41
+ <div class="text-sm text-gray-600">
42
+ <span class="font-medium">{{ qr_codes|length }}</span> Active QR Codes
43
+ </div>
44
+ <button onclick="showCreateModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition duration-200">
45
+ <i class="fas fa-plus mr-2"></i>Create QR
46
+ </button>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </header>
51
+
52
+ <!-- Main Content -->
53
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
54
+ <!-- Quick Stats -->
55
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
56
+ <div class="glass-card rounded-xl p-6 hover-scale">
57
+ <div class="flex items-center">
58
+ <div class="p-3 rounded-lg bg-blue-100 text-blue-600 mr-4">
59
+ <i class="fas fa-qrcode text-xl"></i>
60
+ </div>
61
+ <div>
62
+ <p class="text-sm font-medium text-gray-600">Total QR Codes</p>
63
+ <p class="text-2xl font-bold text-gray-900">{{ qr_codes|length }}</p>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="glass-card rounded-xl p-6 hover-scale">
69
+ <div class="flex items-center">
70
+ <div class="p-3 rounded-lg bg-green-100 text-green-600 mr-4">
71
+ <i class="fas fa-mouse-pointer text-xl"></i>
72
+ </div>
73
+ <div>
74
+ <p class="text-sm font-medium text-gray-600">Total Scans</p>
75
+ <p class="text-2xl font-bold text-gray-900">
76
+ {% set total_scans = 0 %}
77
+ {% for qr in qr_codes.values() %}
78
+ {% set total_scans = total_scans + qr.scan_count %}
79
+ {% endfor %}
80
+ {{ total_scans }}
81
+ </p>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="glass-card rounded-xl p-6 hover-scale">
87
+ <div class="flex items-center">
88
+ <div class="p-3 rounded-lg bg-purple-100 text-purple-600 mr-4">
89
+ <i class="fas fa-chart-line text-xl"></i>
90
+ </div>
91
+ <div>
92
+ <p class="text-sm font-medium text-gray-600">Active Codes</p>
93
+ <p class="text-2xl font-bold text-gray-900">
94
+ {% set active_count = 0 %}
95
+ {% for qr in qr_codes.values() %}
96
+ {% if qr.is_active %}
97
+ {% set active_count = active_count + 1 %}
98
+ {% endif %}
99
+ {% endfor %}
100
+ {{ active_count }}
101
+ </p>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Create QR Button (Mobile) -->
108
+ <div class="md:hidden mb-6">
109
+ <button onclick="showCreateModal()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 rounded-lg font-medium transition duration-200">
110
+ <i class="fas fa-plus mr-2"></i>Create New QR Code
111
+ </button>
112
+ </div>
113
+
114
+ <!-- QR Codes Grid -->
115
+ <div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
116
+ {% if qr_codes %}
117
+ {% for qr_id, qr_data in qr_codes.items() %}
118
+ <div class="glass-card rounded-xl p-6 hover-scale">
119
+ <!-- QR Header -->
120
+ <div class="flex items-center justify-between mb-4">
121
+ <h3 class="text-lg font-semibold text-gray-900 truncate">{{ qr_data.title }}</h3>
122
+ <div class="flex items-center space-x-2">
123
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
124
+ <i class="fas fa-circle mr-1 text-green-400"></i>Active
125
+ </span>
126
+ <div class="relative">
127
+ <button onclick="toggleDropdown('{{ qr_id }}')" class="p-2 text-gray-400 hover:text-gray-600 transition">
128
+ <i class="fas fa-ellipsis-v"></i>
129
+ </button>
130
+ <div id="dropdown-{{ qr_id }}" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-10 border">
131
+ <button onclick="editQR('{{ qr_id }}', '{{ qr_data.title }}', '{{ qr_data.redirect_url }}')" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 rounded-t-lg">
132
+ <i class="fas fa-edit mr-2"></i>Edit
133
+ </button>
134
+ <button onclick="deleteQR('{{ qr_id }}')" class="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-b-lg">
135
+ <i class="fas fa-trash mr-2"></i>Delete
136
+ </button>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <!-- QR Code Image -->
143
+ <div class="flex justify-center mb-4">
144
+ <div class="p-4 bg-white rounded-lg shadow-inner">
145
+ <img src="/qr/{{ qr_id }}" alt="QR Code for {{ qr_data.title }}" class="w-32 h-32 object-contain">
146
+ </div>
147
+ </div>
148
+
149
+ <!-- QR Details -->
150
+ <div class="space-y-3">
151
+ <div>
152
+ <p class="text-xs font-medium text-gray-500 uppercase tracking-wider">Redirect URL</p>
153
+ <p class="text-sm text-gray-900 break-all bg-gray-50 rounded-md p-2 mt-1">{{ qr_data.redirect_url }}</p>
154
+ </div>
155
+
156
+ <div class="flex justify-between items-center text-sm">
157
+ <div>
158
+ <p class="text-gray-500">Scans</p>
159
+ <p class="font-semibold text-gray-900">{{ qr_data.scan_count }}</p>
160
+ </div>
161
+ <div class="text-right">
162
+ <p class="text-gray-500">Created</p>
163
+ <p class="font-semibold text-gray-900">{{ qr_data.created_at[:10] }}</p>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="flex space-x-2">
168
+ <button onclick="downloadQR('{{ qr_id }}')" class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 py-2 px-3 rounded-md text-sm font-medium transition">
169
+ <i class="fas fa-download mr-1"></i>Download
170
+ </button>
171
+ <button onclick="copyLink('{{ qr_id }}')" class="flex-1 bg-blue-100 hover:bg-blue-200 text-blue-700 py-2 px-3 rounded-md text-sm font-medium transition">
172
+ <i class="fas fa-copy mr-1"></i>Copy Link
173
+ </button>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ {% endfor %}
178
+ {% else %}
179
+ <div class="col-span-full">
180
+ <div class="text-center py-16">
181
+ <div class="w-24 h-24 mx-auto bg-gray-100 rounded-full flex items-center justify-center mb-4">
182
+ <i class="fas fa-qrcode text-3xl text-gray-400"></i>
183
+ </div>
184
+ <h3 class="text-xl font-medium text-gray-900 mb-2">No QR codes yet</h3>
185
+ <p class="text-gray-600 mb-6">Create your first dynamic QR code to get started</p>
186
+ <button onclick="showCreateModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition duration-200">
187
+ <i class="fas fa-plus mr-2"></i>Create QR Code
188
+ </button>
189
+ </div>
190
+ </div>
191
+ {% endif %}
192
+ </div>
193
+ </main>
194
+
195
+ <!-- Create QR Modal -->
196
+ <div id="createModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
197
+ <div class="glass-card rounded-xl max-w-md w-full p-6">
198
+ <div class="flex items-center justify-between mb-6">
199
+ <h2 class="text-xl font-semibold text-gray-900">Create QR Code</h2>
200
+ <button onclick="hideCreateModal()" class="text-gray-400 hover:text-gray-600 transition">
201
+ <i class="fas fa-times text-xl"></i>
202
+ </button>
203
+ </div>
204
+
205
+ <form method="POST" action="/create" class="space-y-4">
206
+ <div>
207
+ <label for="title" class="block text-sm font-medium text-gray-700 mb-2">Title</label>
208
+ <input type="text" id="title" name="title" required
209
+ class="w-full rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
210
+ placeholder="My QR Code">
211
+ </div>
212
+
213
+ <div>
214
+ <label for="redirect_url" class="block text-sm font-medium text-gray-700 mb-2">Redirect URL</label>
215
+ <input type="url" id="redirect_url" name="redirect_url" required
216
+ class="w-full rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
217
+ placeholder="https://example.com">
218
+ </div>
219
+
220
+ <div class="flex space-x-3 pt-4">
221
+ <button type="button" onclick="hideCreateModal()" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded-lg font-medium transition">
222
+ Cancel
223
+ </button>
224
+ <button type="submit" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg font-medium transition">
225
+ Create QR
226
+ </button>
227
+ </div>
228
+ </form>
229
+ </div>
230
+ </div>
231
+
232
+ <!-- Edit QR Modal -->
233
+ <div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
234
+ <div class="glass-card rounded-xl max-w-md w-full p-6">
235
+ <div class="flex items-center justify-between mb-6">
236
+ <h2 class="text-xl font-semibold text-gray-900">Edit QR Code</h2>
237
+ <button onclick="hideEditModal()" class="text-gray-400 hover:text-gray-600 transition">
238
+ <i class="fas fa-times text-xl"></i>
239
+ </button>
240
+ </div>
241
+
242
+ <form id="editForm" method="POST" class="space-y-4">
243
+ <div>
244
+ <label for="edit_title" class="block text-sm font-medium text-gray-700 mb-2">Title</label>
245
+ <input type="text" id="edit_title" name="title" required
246
+ class="w-full rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500">
247
+ </div>
248
+
249
+ <div>
250
+ <label for="edit_redirect_url" class="block text-sm font-medium text-gray-700 mb-2">Redirect URL</label>
251
+ <input type="url" id="edit_redirect_url" name="redirect_url" required
252
+ class="w-full rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500">
253
+ </div>
254
+
255
+ <div class="flex space-x-3 pt-4">
256
+ <button type="button" onclick="hideEditModal()" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded-lg font-medium transition">
257
+ Cancel
258
+ </button>
259
+ <button type="submit" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg font-medium transition">
260
+ Update QR
261
+ </button>
262
+ </div>
263
+ </form>
264
+ </div>
265
+ </div>
266
+
267
+ <!-- Footer -->
268
+ <footer class="mt-16 border-t border-gray-200 bg-white">
269
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
270
+ <div class="flex flex-col md:flex-row justify-between items-center">
271
+ <div class="flex items-center space-x-2 mb-4 md:mb-0">
272
+ <div class="w-8 h-8 gradient-bg rounded-lg flex items-center justify-center">
273
+ <i class="fas fa-qrcode text-white text-sm"></i>
274
+ </div>
275
+ <span class="text-gray-600 font-medium">Dynamic QR Generator</span>
276
+ </div>
277
+ <p class="text-sm text-gray-500">© 2025 Enterprise QR Solutions. Built for scalability.</p>
278
+ </div>
279
+ </div>
280
+ </footer>
281
+
282
+ <script>
283
+ // Modal functions
284
+ function showCreateModal() {
285
+ document.getElementById('createModal').classList.remove('hidden');
286
+ }
287
+
288
+ function hideCreateModal() {
289
+ document.getElementById('createModal').classList.add('hidden');
290
+ }
291
+
292
+ function showEditModal() {
293
+ document.getElementById('editModal').classList.remove('hidden');
294
+ }
295
+
296
+ function hideEditModal() {
297
+ document.getElementById('editModal').classList.add('hidden');
298
+ }
299
+
300
+ // Dropdown functions
301
+ function toggleDropdown(qrId) {
302
+ const dropdown = document.getElementById(`dropdown-${qrId}`);
303
+ // Close all other dropdowns
304
+ document.querySelectorAll('[id^="dropdown-"]').forEach(d => {
305
+ if (d.id !== `dropdown-${qrId}`) d.classList.add('hidden');
306
+ });
307
+ dropdown.classList.toggle('hidden');
308
+ }
309
+
310
+ // Edit QR function
311
+ function editQR(qrId, title, redirectUrl) {
312
+ document.getElementById('edit_title').value = title;
313
+ document.getElementById('edit_redirect_url').value = redirectUrl;
314
+ document.getElementById('editForm').action = `/update/${qrId}`;
315
+ showEditModal();
316
+ toggleDropdown(qrId);
317
+ }
318
+
319
+ // Delete QR function
320
+ async function deleteQR(qrId) {
321
+ if (confirm('Are you sure you want to delete this QR code? This action cannot be undone.')) {
322
+ try {
323
+ const response = await fetch(`/delete/${qrId}`, {
324
+ method: 'DELETE'
325
+ });
326
+ if (response.ok) {
327
+ location.reload();
328
+ } else {
329
+ alert('Failed to delete QR code');
330
+ }
331
+ } catch (error) {
332
+ alert('Error deleting QR code');
333
+ }
334
+ }
335
+ toggleDropdown(qrId);
336
+ }
337
+
338
+ // Download QR function
339
+ function downloadQR(qrId) {
340
+ const link = document.createElement('a');
341
+ link.href = `/qr/${qrId}`;
342
+ link.download = `qr-${qrId}.png`;
343
+ link.click();
344
+ toggleDropdown(qrId);
345
+ }
346
+
347
+ // Copy link function
348
+ async function copyLink(qrId) {
349
+ const baseUrl = window.location.origin;
350
+ const link = `${baseUrl}/r/${qrId}`;
351
+ try {
352
+ await navigator.clipboard.writeText(link);
353
+ showNotification('Link copied to clipboard!');
354
+ } catch (err) {
355
+ // Fallback for older browsers
356
+ const textArea = document.createElement('textarea');
357
+ textArea.value = link;
358
+ document.body.appendChild(textArea);
359
+ textArea.select();
360
+ document.execCommand('copy');
361
+ document.body.removeChild(textArea);
362
+ showNotification('Link copied to clipboard!');
363
+ }
364
+ toggleDropdown(qrId);
365
+ }
366
+
367
+ // Show notification
368
+ function showNotification(message) {
369
+ const notification = document.createElement('div');
370
+ notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50';
371
+ notification.textContent = message;
372
+ document.body.appendChild(notification);
373
+ setTimeout(() => {
374
+ notification.remove();
375
+ }, 3000);
376
+ }
377
+
378
+ // Close dropdowns when clicking outside
379
+ document.addEventListener('click', function(event) {
380
+ if (!event.target.closest('[onclick^="toggleDropdown"]') && !event.target.closest('[id^="dropdown-"]')) {
381
+ document.querySelectorAll('[id^="dropdown-"]').forEach(d => d.classList.add('hidden'));
382
+ }
383
+ });
384
+
385
+ // Close modals when clicking outside
386
+ document.getElementById('createModal').addEventListener('click', function(event) {
387
+ if (event.target === this) hideCreateModal();
388
+ });
389
+
390
+ document.getElementById('editModal').addEventListener('click', function(event) {
391
+ if (event.target === this) hideEditModal();
392
+ });
393
+ </script>
394
+ </body>
395
+ </html>