Znfeoqm commited on
Commit
703d6a1
·
verified ·
1 Parent(s): b5e78fd

Update src/App.jsx

Browse files
Files changed (1) hide show
  1. src/App.jsx +81 -70
src/App.jsx CHANGED
@@ -1,16 +1,32 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- // Import QRCode and jsQR directly as they should be installed via npm
3
  import QRCode from 'qrcode';
4
  import jsQR from 'jsqr';
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  // Main App component
7
  const App = () => {
8
- // console.log("App component is loading!"); // You can keep or remove this line for final deployment
9
-
10
  const [activeTab, setActiveTab] = useState('generator');
11
  const [content, setContent] = useState('');
12
  const [errorLevel, setErrorLevel] = useState('L');
13
- const [qrColor, setQrColor] = useState('#000000');
14
  const [decodedContent, setDecodedContent] = useState('');
15
  const [selectedStyleIndex, setSelectedStyleIndex] = useState(0);
16
  const [currentTheme, setCurrentTheme] = useState('dark'); // Default theme
@@ -18,9 +34,11 @@ const App = () => {
18
  const [suggestedActions, setSuggestedActions] = useState(''); // State for suggested actions
19
  const [isSuggestingActions, setIsSuggestingActions] = useState(false); // Loading state for action suggestions
20
  const [showSuggestedActions, setShowSuggestedActions] = useState(false); // State to toggle suggestions visibility
 
21
 
22
  const qrCanvasRef = useRef(null); // Ref for the main QR code canvas
23
- const styleCanvasRefs = useRef([]); // Refs for the style preview canvases
 
24
 
25
  // Define QR code styles (mimicking the Python app's styles)
26
  const qrStyles = [
@@ -41,12 +59,10 @@ const App = () => {
41
  // Define themes for the web application with Web 3.0 GUI elements
42
  const themes = {
43
  dark: {
44
- // Full-screen animated gradient background (deep charcoal to subtle dark violet)
45
  bg: 'bg-gradient-to-br from-[#0A0A0A] via-[#1A0A2A] to-[#0A0A0A] animate-gradient-shift',
46
  text: 'text-gray-100',
47
  primaryAccent: 'from-blue-500 to-purple-600',
48
  secondaryAccent: 'from-cyan-400 to-teal-500',
49
- // Stronger glassmorphism with subtle inner shadow
50
  glassBg: 'bg-white/5 backdrop-blur-3xl',
51
  glassBorder: 'border-white/10',
52
  shadow: 'shadow-blue-500/20',
@@ -56,17 +72,16 @@ const App = () => {
56
  alert: 'text-pink-500',
57
  inputBg: 'bg-white/5',
58
  inputBorder: 'border-white/10',
59
- // Tab active state with distinct background and glowing border
60
  tabActive: 'bg-white/10 border-blue-500 shadow-lg shadow-blue-500/20',
61
  tabInactive: 'bg-transparent border-transparent',
62
  cardBg: 'bg-white/5 backdrop-blur-md',
63
  cardBorder: 'border-white/10',
64
  qrPreviewBorder: 'border-blue-500/50',
65
  qrPreviewBg: 'bg-gradient-to-br from-gray-900/30 to-gray-800/30 backdrop-blur-md',
66
- headerGlow: 'from-cyan-400 to-magenta-600 animate-neon-glow' // Updated header glow
 
67
  },
68
  light: {
69
- // Platinum to ice white gradient
70
  bg: 'bg-gradient-to-br from-[#F8F8F8] via-[#E8E8E8] to-[#F8F8F8] animate-gradient-shift-light',
71
  text: 'text-gray-800',
72
  primaryAccent: 'from-blue-400 to-purple-500',
@@ -86,10 +101,9 @@ const App = () => {
86
  cardBorder: 'border-gray-200',
87
  qrPreviewBorder: 'border-blue-400/50',
88
  qrPreviewBg: 'bg-gradient-to-br from-gray-100/60 to-gray-200/60 backdrop-blur-md',
89
- headerGlow: 'from-cyan-500 to-magenta-700 animate-neon-glow' // Updated header glow
90
  },
91
  'forest-green': {
92
- // Midnight green to neon teal
93
  bg: 'bg-gradient-to-br from-[#0A2A1A] via-[#1A4A3A] to-[#0A3A2A] animate-gradient-shift',
94
  text: 'text-white',
95
  primaryAccent: 'from-green-500 to-emerald-600',
@@ -109,10 +123,9 @@ const App = () => {
109
  cardBorder: 'border-green-900/15',
110
  qrPreviewBorder: 'border-green-500/50',
111
  qrPreviewBg: 'bg-gradient-to-br from-green-900/30 to-green-800/30 backdrop-blur-md',
112
- headerGlow: 'from-cyan-400 to-green-600 animate-neon-glow' // Updated header glow
113
  },
114
  'ocean-blue': {
115
- // Deep ocean to electric blue
116
  bg: 'bg-gradient-to-br from-[#001A33] via-[#003366] to-[#002A5A] animate-gradient-shift',
117
  text: 'text-white',
118
  primaryAccent: 'from-blue-600 to-indigo-700',
@@ -132,10 +145,9 @@ const App = () => {
132
  cardBorder: 'border-blue-900/15',
133
  qrPreviewBorder: 'border-blue-600/50',
134
  qrPreviewBg: 'bg-gradient-to-br from-blue-950/30 to-blue-900/30 backdrop-blur-md',
135
- headerGlow: 'from-cyan-400 to-blue-600 animate-neon-glow' // Updated header glow
136
  },
137
  'warm-grey': {
138
- // Dark concrete to warm charcoal
139
  bg: 'bg-gradient-to-br from-[#202020] via-[#404040] to-[#303030] animate-gradient-shift',
140
  text: 'text-gray-100',
141
  primaryAccent: 'from-gray-500 to-gray-700',
@@ -155,10 +167,9 @@ const App = () => {
155
  cardBorder: 'border-gray-700/15',
156
  qrPreviewBorder: 'border-gray-500/50',
157
  qrPreviewBg: 'bg-gradient-to-br from-gray-800/30 to-gray-700/30 backdrop-blur-md',
158
- headerGlow: 'from-cyan-300 to-blue-500 animate-neon-glow' // Updated header glow
159
  },
160
  'deep-purple': {
161
- // Deepest purple to dark magenta
162
  bg: 'bg-gradient-to-br from-[#100830] via-[#201040] to-[#180C38] animate-gradient-shift',
163
  text: 'text-white',
164
  primaryAccent: 'from-purple-600 to-fuchsia-700',
@@ -178,10 +189,9 @@ const App = () => {
178
  cardBorder: 'border-purple-900/15',
179
  qrPreviewBorder: 'border-purple-600/50',
180
  qrPreviewBg: 'bg-gradient-to-br from-purple-950/30 to-purple-900/30 backdrop-blur-md',
181
- headerGlow: 'from-cyan-400 to-purple-600 animate-neon-glow' // Updated header glow
182
  },
183
  'sunrise-orange': {
184
- // Deepest orange to fiery red
185
  bg: 'bg-gradient-to-br from-[#A0300A] via-[#D0501A] to-[#B84012] animate-gradient-shift',
186
  text: 'text-white',
187
  primaryAccent: 'from-orange-600 to-red-700',
@@ -201,58 +211,55 @@ const App = () => {
201
  cardBorder: 'border-orange-800/15',
202
  qrPreviewBorder: 'border-orange-600/50',
203
  qrPreviewBg: 'bg-gradient-to-br from-orange-900/30 to-orange-800/30 backdrop-blur-md',
204
- headerGlow: 'from-cyan-500 to-orange-700 animate-neon-glow' // Updated header glow
205
  },
206
  };
207
 
208
  const currentThemeClasses = themes[currentTheme];
209
 
210
- // Effect to generate QR code on the main canvas
211
- useEffect(() => {
212
- // Use the imported QRCode directly, not window.QRCode
213
- if (qrCanvasRef.current && QRCode && content.trim() !== '') {
214
- QRCode.toCanvas(qrCanvasRef.current, content, {
215
- errorCorrectionLevel: errorLevel,
216
- color: {
217
- dark: qrStyles[selectedStyleIndex].fg,
218
- light: '#FFFFFF'
219
- }
220
- }, function (error) {
221
- if (error) console.error(error);
222
- });
223
- } else if (qrCanvasRef.current) {
224
- // Clear canvas if content is empty
225
- const context = qrCanvasRef.current.getContext('2d');
226
- context.clearRect(0, 0, qrCanvasRef.current.width, qrCanvasRef.current.height);
227
  }
228
- }, [content, errorLevel, selectedStyleIndex, qrColor]);
 
 
 
 
 
 
 
 
 
 
229
 
230
- // Effect to generate QR codes for style previews
231
  useEffect(() => {
232
- // Use the imported QRCode directly, not window.QRCode
233
- if (QRCode) {
234
- qrStyles.forEach((style, index) => {
235
- const canvas = styleCanvasRefs.current[index];
236
- if (canvas) {
237
- QRCode.toCanvas(canvas, "Sample", {
238
- errorCorrectionLevel: 'H',
239
- width: 80, // Fixed size for preview
240
- color: {
241
- dark: style.fg,
242
- light: '#FFFFFF'
243
- }
244
- }, function (error) {
245
- if (error) console.error(error);
246
- });
247
- }
248
- });
249
- }
250
- }, [QRCode]); // Dependency changed to QRCode
251
 
252
  // Function to download the generated QR code
253
  const downloadQrCode = () => {
254
  if (content.trim() === '') {
255
- alert('Please enter content to generate QR code.');
256
  return;
257
  }
258
  const canvas = qrCanvasRef.current;
@@ -284,7 +291,6 @@ const App = () => {
284
 
285
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
286
 
287
- // Use the imported jsQR directly, not window.jsQR
288
  if (jsQR) {
289
  let code = jsQR(imageData.data, imageData.width, imageData.height);
290
 
@@ -308,7 +314,7 @@ const App = () => {
308
  setShowSuggestedActions(false);
309
  }
310
  } else {
311
- setDecodedContent('QR decoder library (jsQR) not loaded. Please try again or check internet connection.');
312
  setSuggestedActions('');
313
  setShowSuggestedActions(false);
314
  }
@@ -321,7 +327,7 @@ const App = () => {
321
  // Function to summarize content using Gemini API
322
  const summarizeContent = async () => {
323
  if (!content.trim()) {
324
- alert('Please enter content to summarize.');
325
  return;
326
  }
327
 
@@ -348,11 +354,11 @@ const App = () => {
348
  const text = result.candidates[0].content.parts[0].text;
349
  setContent(text); // Update the input field with summarized content
350
  } else {
351
- alert("Failed to summarize content. Please try again.");
352
  }
353
  } catch (error) {
354
  console.error("Error summarizing content:", error);
355
- alert("An error occurred while summarizing content.");
356
  } finally {
357
  setIsSummarizing(false);
358
  }
@@ -361,7 +367,7 @@ const App = () => {
361
  // Function to suggest actions based on decoded content using Gemini API
362
  const suggestActions = async () => {
363
  if (!decodedContent.trim() || decodedContent === 'No QR code detected.') {
364
- alert('Please decode a QR code first to suggest actions.');
365
  return;
366
  }
367
 
@@ -411,7 +417,7 @@ const App = () => {
411
  rows="6"
412
  placeholder="Enter text or URL..."
413
  value={content}
414
- onChange={(e) => setContent(e.target.value)}
415
  ></textarea>
416
 
417
  <div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
@@ -453,7 +459,7 @@ const App = () => {
453
  type="color"
454
  className={`w-full h-10 rounded-xl border ${currentThemeClasses.inputBorder} p-1 cursor-pointer transition-all duration-300`}
455
  value={qrColor}
456
- onChange={(e) => setQrColor(e.target.value)}
457
  />
458
 
459
  <label className={`mt-6 mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter`}>QR Preview:</label>
@@ -483,9 +489,11 @@ const App = () => {
483
  ${currentThemeClasses.inputBg}`}
484
  onClick={() => {
485
  setSelectedStyleIndex(index);
486
- setQrColor(style.fg); // Update color picker with selected style's color
 
487
  }}
488
  >
 
489
  <canvas ref={el => styleCanvasRefs.current[index] = el} width="80" height="80" className="rounded-lg"></canvas>
490
  <span className={`mt-2 text-sm font-medium ${currentThemeClasses.labelColor} font-inter`}>{style.name}</span>
491
  </div>
@@ -623,6 +631,9 @@ const App = () => {
623
  {activeTab === 'settings' && <SettingsTab />}
624
  </div>
625
  </div>
 
 
 
626
  </div>
627
  );
628
  };
 
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
 
2
  import QRCode from 'qrcode';
3
  import jsQR from 'jsqr';
4
 
5
+ // Custom Alert Modal Component
6
+ const CustomAlert = ({ message, onClose }) => {
7
+ if (!message) return null;
8
+
9
+ return (
10
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
11
+ <div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-blue-500 rounded-lg shadow-xl p-6 max-w-sm w-full text-white text-center animate-fade-in-up">
12
+ <p className="text-lg font-semibold mb-4">{message}</p>
13
+ <button
14
+ onClick={onClose}
15
+ className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition duration-300 ease-in-out transform hover:scale-105"
16
+ >
17
+ OK
18
+ </button>
19
+ </div>
20
+ </div>
21
+ );
22
+ };
23
+
24
  // Main App component
25
  const App = () => {
 
 
26
  const [activeTab, setActiveTab] = useState('generator');
27
  const [content, setContent] = useState('');
28
  const [errorLevel, setErrorLevel] = useState('L');
29
+ const [qrColor, setQrColor] = useState('#000000'); // Default QR color
30
  const [decodedContent, setDecodedContent] = useState('');
31
  const [selectedStyleIndex, setSelectedStyleIndex] = useState(0);
32
  const [currentTheme, setCurrentTheme] = useState('dark'); // Default theme
 
34
  const [suggestedActions, setSuggestedActions] = useState(''); // State for suggested actions
35
  const [isSuggestingActions, setIsSuggestingActions] = useState(false); // Loading state for action suggestions
36
  const [showSuggestedActions, setShowSuggestedActions] = useState(false); // State to toggle suggestions visibility
37
+ const [alertMessage, setAlertMessage] = useState(null); // State for custom alert message
38
 
39
  const qrCanvasRef = useRef(null); // Ref for the main QR code canvas
40
+ // Use an array of refs for style preview canvases, initialized to null
41
+ const styleCanvasRefs = useRef(Array(12).fill(null));
42
 
43
  // Define QR code styles (mimicking the Python app's styles)
44
  const qrStyles = [
 
59
  // Define themes for the web application with Web 3.0 GUI elements
60
  const themes = {
61
  dark: {
 
62
  bg: 'bg-gradient-to-br from-[#0A0A0A] via-[#1A0A2A] to-[#0A0A0A] animate-gradient-shift',
63
  text: 'text-gray-100',
64
  primaryAccent: 'from-blue-500 to-purple-600',
65
  secondaryAccent: 'from-cyan-400 to-teal-500',
 
66
  glassBg: 'bg-white/5 backdrop-blur-3xl',
67
  glassBorder: 'border-white/10',
68
  shadow: 'shadow-blue-500/20',
 
72
  alert: 'text-pink-500',
73
  inputBg: 'bg-white/5',
74
  inputBorder: 'border-white/10',
 
75
  tabActive: 'bg-white/10 border-blue-500 shadow-lg shadow-blue-500/20',
76
  tabInactive: 'bg-transparent border-transparent',
77
  cardBg: 'bg-white/5 backdrop-blur-md',
78
  cardBorder: 'border-white/10',
79
  qrPreviewBorder: 'border-blue-500/50',
80
  qrPreviewBg: 'bg-gradient-to-br from-gray-900/30 to-gray-800/30 backdrop-blur-md',
81
+ // Enhanced rainbow glow for header
82
+ headerGlow: 'from-purple-500 via-pink-500 to-yellow-500 animate-rainbow-glow'
83
  },
84
  light: {
 
85
  bg: 'bg-gradient-to-br from-[#F8F8F8] via-[#E8E8E8] to-[#F8F8F8] animate-gradient-shift-light',
86
  text: 'text-gray-800',
87
  primaryAccent: 'from-blue-400 to-purple-500',
 
101
  cardBorder: 'border-gray-200',
102
  qrPreviewBorder: 'border-blue-400/50',
103
  qrPreviewBg: 'bg-gradient-to-br from-gray-100/60 to-gray-200/60 backdrop-blur-md',
104
+ headerGlow: 'from-blue-400 via-green-400 to-yellow-400 animate-rainbow-glow-light'
105
  },
106
  'forest-green': {
 
107
  bg: 'bg-gradient-to-br from-[#0A2A1A] via-[#1A4A3A] to-[#0A3A2A] animate-gradient-shift',
108
  text: 'text-white',
109
  primaryAccent: 'from-green-500 to-emerald-600',
 
123
  cardBorder: 'border-green-900/15',
124
  qrPreviewBorder: 'border-green-500/50',
125
  qrPreviewBg: 'bg-gradient-to-br from-green-900/30 to-green-800/30 backdrop-blur-md',
126
+ headerGlow: 'from-lime-400 via-teal-400 to-emerald-400 animate-rainbow-glow'
127
  },
128
  'ocean-blue': {
 
129
  bg: 'bg-gradient-to-br from-[#001A33] via-[#003366] to-[#002A5A] animate-gradient-shift',
130
  text: 'text-white',
131
  primaryAccent: 'from-blue-600 to-indigo-700',
 
145
  cardBorder: 'border-blue-900/15',
146
  qrPreviewBorder: 'border-blue-600/50',
147
  qrPreviewBg: 'bg-gradient-to-br from-blue-950/30 to-blue-900/30 backdrop-blur-md',
148
+ headerGlow: 'from-sky-400 via-blue-400 to-indigo-400 animate-rainbow-glow'
149
  },
150
  'warm-grey': {
 
151
  bg: 'bg-gradient-to-br from-[#202020] via-[#404040] to-[#303030] animate-gradient-shift',
152
  text: 'text-gray-100',
153
  primaryAccent: 'from-gray-500 to-gray-700',
 
167
  cardBorder: 'border-gray-700/15',
168
  qrPreviewBorder: 'border-gray-500/50',
169
  qrPreviewBg: 'bg-gradient-to-br from-gray-800/30 to-gray-700/30 backdrop-blur-md',
170
+ headerGlow: 'from-yellow-400 via-gray-400 to-orange-400 animate-rainbow-glow'
171
  },
172
  'deep-purple': {
 
173
  bg: 'bg-gradient-to-br from-[#100830] via-[#201040] to-[#180C38] animate-gradient-shift',
174
  text: 'text-white',
175
  primaryAccent: 'from-purple-600 to-fuchsia-700',
 
189
  cardBorder: 'border-purple-900/15',
190
  qrPreviewBorder: 'border-purple-600/50',
191
  qrPreviewBg: 'bg-gradient-to-br from-purple-950/30 to-purple-900/30 backdrop-blur-md',
192
+ headerGlow: 'from-fuchsia-400 via-purple-400 to-pink-400 animate-rainbow-glow'
193
  },
194
  'sunrise-orange': {
 
195
  bg: 'bg-gradient-to-br from-[#A0300A] via-[#D0501A] to-[#B84012] animate-gradient-shift',
196
  text: 'text-white',
197
  primaryAccent: 'from-orange-600 to-red-700',
 
211
  cardBorder: 'border-orange-800/15',
212
  qrPreviewBorder: 'border-orange-600/50',
213
  qrPreviewBg: 'bg-gradient-to-br from-orange-900/30 to-orange-800/30 backdrop-blur-md',
214
+ headerGlow: 'from-yellow-400 via-orange-400 to-red-400 animate-rainbow-glow'
215
  },
216
  };
217
 
218
  const currentThemeClasses = themes[currentTheme];
219
 
220
+ // Function to show custom alert
221
+ const showAlert = (message) => {
222
+ setAlertMessage(message);
223
+ };
224
+
225
+ // Callback to generate QR code on a given canvas
226
+ // Using useCallback to memoize the function, preventing unnecessary re-creations
227
+ const drawQrCode = useCallback((canvas, text, color, errorLevel, size = 256) => {
228
+ if (!canvas || !QRCode || !text.trim()) {
229
+ const context = canvas.getContext('2d');
230
+ context.clearRect(0, 0, canvas.width, canvas.height); // Clear if no text or QR code library
231
+ return;
 
 
 
 
 
232
  }
233
+ QRCode.toCanvas(canvas, text, {
234
+ errorCorrectionLevel: errorLevel,
235
+ width: size,
236
+ color: {
237
+ dark: color, // Use the provided color directly
238
+ light: '#FFFFFF'
239
+ }
240
+ }, function (error) {
241
+ if (error) console.error("QR Code drawing error:", error);
242
+ });
243
+ }, []); // Dependencies are stable (QRCode)
244
 
245
+ // Effect to generate QR code on the main canvas (Generator Tab)
246
  useEffect(() => {
247
+ drawQrCode(qrCanvasRef.current, content, qrColor, errorLevel);
248
+ }, [content, qrColor, errorLevel, drawQrCode]); // Added qrColor to dependencies
249
+
250
+ // Effect to generate QR codes for style previews (Style Selection Panel)
251
+ useEffect(() => {
252
+ qrStyles.forEach((style, index) => {
253
+ const canvas = styleCanvasRefs.current[index];
254
+ // Draw a sample QR code for each style preview
255
+ drawQrCode(canvas, "Sample", style.fg, 'H', 80);
256
+ });
257
+ }, [qrStyles, drawQrCode]); // Re-run when qrStyles or drawQrCode changes
 
 
 
 
 
 
 
 
258
 
259
  // Function to download the generated QR code
260
  const downloadQrCode = () => {
261
  if (content.trim() === '') {
262
+ showAlert('Please enter content to generate QR code.');
263
  return;
264
  }
265
  const canvas = qrCanvasRef.current;
 
291
 
292
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
293
 
 
294
  if (jsQR) {
295
  let code = jsQR(imageData.data, imageData.width, imageData.height);
296
 
 
314
  setShowSuggestedActions(false);
315
  }
316
  } else {
317
+ showAlert('QR decoder library (jsQR) not loaded. Please try again or check internet connection.');
318
  setSuggestedActions('');
319
  setShowSuggestedActions(false);
320
  }
 
327
  // Function to summarize content using Gemini API
328
  const summarizeContent = async () => {
329
  if (!content.trim()) {
330
+ showAlert('Please enter content to summarize.');
331
  return;
332
  }
333
 
 
354
  const text = result.candidates[0].content.parts[0].text;
355
  setContent(text); // Update the input field with summarized content
356
  } else {
357
+ showAlert("Failed to summarize content. Please try again.");
358
  }
359
  } catch (error) {
360
  console.error("Error summarizing content:", error);
361
+ showAlert("An error occurred while summarizing content.");
362
  } finally {
363
  setIsSummarizing(false);
364
  }
 
367
  // Function to suggest actions based on decoded content using Gemini API
368
  const suggestActions = async () => {
369
  if (!decodedContent.trim() || decodedContent === 'No QR code detected.') {
370
+ showAlert('Please decode a QR code first to suggest actions.');
371
  return;
372
  }
373
 
 
417
  rows="6"
418
  placeholder="Enter text or URL..."
419
  value={content}
420
+ onChange={(e) => setContent(e.target.value)} // This should handle continuous typing
421
  ></textarea>
422
 
423
  <div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
 
459
  type="color"
460
  className={`w-full h-10 rounded-xl border ${currentThemeClasses.inputBorder} p-1 cursor-pointer transition-all duration-300`}
461
  value={qrColor}
462
+ onChange={(e) => setQrColor(e.target.value)} // This updates qrColor state
463
  />
464
 
465
  <label className={`mt-6 mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter`}>QR Preview:</label>
 
489
  ${currentThemeClasses.inputBg}`}
490
  onClick={() => {
491
  setSelectedStyleIndex(index);
492
+ // When a style is selected, update qrColor to match the style's foreground color
493
+ setQrColor(style.fg);
494
  }}
495
  >
496
+ {/* Use a ref for each canvas in the map */}
497
  <canvas ref={el => styleCanvasRefs.current[index] = el} width="80" height="80" className="rounded-lg"></canvas>
498
  <span className={`mt-2 text-sm font-medium ${currentThemeClasses.labelColor} font-inter`}>{style.name}</span>
499
  </div>
 
631
  {activeTab === 'settings' && <SettingsTab />}
632
  </div>
633
  </div>
634
+
635
+ {/* Custom Alert Modal */}
636
+ <CustomAlert message={alertMessage} onClose={() => setAlertMessage(null)} />
637
  </div>
638
  );
639
  };