Skip to content

Commit 453a75a

Browse files
committed
new(app): qrcode
1 parent ec71f1f commit 453a75a

File tree

7 files changed

+226
-3
lines changed

7 files changed

+226
-3
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"framer-motion": "^12.23.24",
1414
"front-matter": "^4.0.2",
1515
"marked": "^16.4.1",
16+
"qrcode.react": "^4.2.0",
1617
"react": "^19.2.0",
1718
"react-dom": "^19.2.0",
1819
"react-icons": "^5.5.0",

src/components/AnimatedRoutes.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import ImageToolkitPage from '../pages/apps/ImageToolkitPage';
3636
import PasswordGeneratorPage from '../pages/apps/PasswordGeneratorPage';
3737
import JsonFormatterPage from '../pages/apps/JsonFormatterPage';
3838
import ColorContrastCheckerPage from '../pages/apps/ColorContrastCheckerPage';
39+
import QrCodeGeneratorPage from '../pages/apps/QrCodeGeneratorPage';
3940

4041
import UsefulLinksPage from '../pages/UsefulLinksPage';
4142

@@ -324,6 +325,7 @@ function AnimatedRoutes() {
324325
<Route path="/apps::pg" element={<Navigate to="/apps/password-generator" replace />} />
325326
<Route path="/apps::jf" element={<Navigate to="/apps/json-formatter" replace />} />
326327
<Route path="/apps::ccc" element={<Navigate to="/apps/color-contrast-checker" replace />} />
328+
<Route path="/apps::qr" element={<Navigate to="/apps/qr-code-generator" replace />} />
327329
{/* End of hardcoded redirects */}
328330
<Route
329331
path="/apps/ip"
@@ -592,6 +594,20 @@ function AnimatedRoutes() {
592594
</motion.div>
593595
}
594596
/>
597+
<Route
598+
path="/apps/qr-code-generator"
599+
element={
600+
<motion.div
601+
initial="initial"
602+
animate="in"
603+
exit="out"
604+
variants={pageVariants}
605+
transition={pageTransition}
606+
>
607+
<QrCodeGeneratorPage />
608+
</motion.div>
609+
}
610+
/>
595611
{/* D&D specific 404 page */}
596612
<Route
597613
path="/dnd/*"

src/pages/AppPage.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { Link } from 'react-router-dom';
3-
import { ArrowLeftIcon, ListNumbers, Sparkle, TextT, TextAa, Code, Link as LinkIcon, Keyboard, Fingerprint, Key, Palette, Ruler, DiceSix, CircleDashed, ShieldCheck, Image } from '@phosphor-icons/react';
3+
import { ArrowLeftIcon, ListNumbers, Sparkle, TextT, TextAa, Code, Link as LinkIcon, Keyboard, Fingerprint, Key, Palette, Ruler, DiceSix, CircleDashed, ShieldCheck, Image, QrCode } from '@phosphor-icons/react';
44
import AppCard from '../components/AppCard';
55
import usePageTitle from '../utils/usePageTitle';
66

@@ -101,6 +101,12 @@ const apps = [
101101
description: 'Check WCAG color contrast ratios for accessibility.',
102102
icon: Palette,
103103
},
104+
{
105+
to: '/apps/qr-code-generator',
106+
title: 'QR Code Generator',
107+
description: 'Generate QR codes from text or URLs with customizable versions and error correction.',
108+
icon: QrCode,
109+
},
104110
{
105111
to: '/apps/color-palette-generator',
106112
title: 'Color Palette Generator',

src/pages/apps/ColorContrastCheckerPage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useCallback, useEffect } from 'react';
22
import usePageTitle from '../../utils/usePageTitle';
33
import { Link } from 'react-router-dom';
4-
import { ArrowLeftIcon, Palette, CheckCircle, XCircle } from '@phosphor-icons/react'; // Using Palette for now
4+
import { ArrowLeftIcon, CheckCircle, XCircle } from '@phosphor-icons/react'; // Using Palette for now
55
import colors from '../../config/colors';
66
import { useToast } from '../../hooks/useToast';
77
import '../../styles/app-buttons.css';

src/pages/apps/JsonFormatterPage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useCallback } from 'react';
22
import usePageTitle from '../../utils/usePageTitle';
33
import { Link } from 'react-router-dom';
4-
import { ArrowLeftIcon, Code, CopySimple } from '@phosphor-icons/react';
4+
import { ArrowLeftIcon, CopySimple } from '@phosphor-icons/react';
55
import colors from '../../config/colors';
66
import { useToast } from '../../hooks/useToast';
77
import '../../styles/app-buttons.css';
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import React, { useState, useCallback, useEffect } from 'react';
2+
import usePageTitle from '../../utils/usePageTitle';
3+
import { Link } from 'react-router-dom';
4+
import { ArrowLeftIcon, QrCode, DownloadSimple } from '@phosphor-icons/react';
5+
import colors from '../../config/colors';
6+
import { useToast } from '../../hooks/useToast';
7+
import { QRCodeCanvas } from 'qrcode.react'; // Import the QRCodeCanvas component
8+
import '../../styles/app-buttons.css';
9+
10+
const QrCodeGeneratorPage = () => {
11+
usePageTitle('QR Code Generator');
12+
const { addToast } = useToast();
13+
14+
const [text, setText] = useState('https://fezcode.com');
15+
const [version, setVersion] = useState(7); // Default QR code version
16+
const [errorCorrectionLevel, setErrorCorrectionLevel] = useState('M'); // L, M, Q, H
17+
const [qrCodeSize, setQrCodeSize] = useState(256); // Size of the QR code in pixels
18+
19+
const cardStyle = {
20+
backgroundColor: colors['app-alpha-10'],
21+
borderColor: colors['app-alpha-50'],
22+
color: colors.app,
23+
};
24+
25+
const inputStyle = `mt-1 block w-full p-2 border rounded-md bg-gray-700 text-white focus:ring-blue-500 focus:border-blue-500 border-gray-600`;
26+
const selectStyle = `mt-1 block w-full p-2 border border-gray-600 rounded-md bg-gray-700 text-white focus:ring-blue-500 focus:border-blue-500`;
27+
const buttonStyle = `px-6 py-2 rounded-md text-lg font-arvo font-normal transition-colors duration-300 ease-in-out roll-button`;
28+
29+
const downloadQrCode = useCallback(() => {
30+
const canvas = document.getElementById('qrCodeCanvas');
31+
if (canvas) {
32+
const pngUrl = canvas.toDataURL('image/png');
33+
const downloadLink = document.createElement('a');
34+
downloadLink.href = pngUrl;
35+
downloadLink.download = 'qrcode.png';
36+
document.body.appendChild(downloadLink);
37+
downloadLink.click();
38+
document.body.removeChild(downloadLink);
39+
addToast({ title: 'Downloaded', message: 'QR Code downloaded successfully.', duration: 2000 });
40+
} else {
41+
addToast({ title: 'Error', message: 'Could not find QR Code to download.', duration: 3000, type: 'error' });
42+
}
43+
}, [addToast]);
44+
45+
return (
46+
<div className="py-16 sm:py-24">
47+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
48+
<Link
49+
to="/apps"
50+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
51+
>
52+
<ArrowLeftIcon size={24} /> Back to Apps
53+
</Link>
54+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
55+
<span className="codex-color">fc</span>
56+
<span className="separator-color">::</span>
57+
<span className="apps-color">apps</span>
58+
<span className="separator-color">::</span>
59+
<span className="single-app-color">qr</span>
60+
</h1>
61+
<hr className="border-gray-700" />
62+
<div className="flex justify-center items-center mt-16">
63+
<div
64+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform overflow-hidden h-full w-full max-w-4xl"
65+
style={cardStyle}
66+
>
67+
<div
68+
className="absolute top-0 left-0 w-full h-full opacity-10"
69+
style={{
70+
backgroundImage:
71+
'radial-gradient(circle, white 1px, transparent 1px)',
72+
backgroundSize: '10px 10px',
73+
}}
74+
></div>
75+
<div className="relative z-10 p-1">
76+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app"> QR Code Generator </h1>
77+
<hr className="border-gray-700 mb-4" />
78+
79+
{/* Client-Side Notification */}
80+
<div className="bg-yellow-900 bg-opacity-30 border border-yellow-700 text-yellow-300 px-4 py-3 rounded relative mb-6" role="alert">
81+
<strong className="font-bold">Client-Side Only:</strong>
82+
<span className="block sm:inline ml-2">This tool operates entirely within your browser. No data is sent to any server, ensuring maximum privacy.</span>
83+
</div>
84+
85+
<div className="mb-6">
86+
<label htmlFor="qrText" className="block text-sm font-medium text-gray-300 mb-2">
87+
Text or URL to Encode
88+
</label>
89+
<textarea
90+
id="qrText"
91+
rows="3"
92+
className={inputStyle}
93+
value={text}
94+
onChange={(e) => setText(e.target.value)}
95+
placeholder="Enter text or URL here..."
96+
></textarea>
97+
</div>
98+
99+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6 mb-6">
100+
<div>
101+
<label htmlFor="qrVersion" className="block text-sm font-medium text-gray-300 mb-2">
102+
QR Version (1-40)
103+
</label>
104+
<select
105+
id="qrVersion"
106+
className={selectStyle}
107+
value={version}
108+
onChange={(e) => setVersion(Number(e.target.value))}
109+
>
110+
{Array.from({ length: 40 }, (_, i) => i + 1).map((v) => (
111+
<option key={v} value={v}>
112+
Version {v}
113+
</option>
114+
))}
115+
</select>
116+
</div>
117+
<div>
118+
<label htmlFor="errorCorrection" className="block text-sm font-medium text-gray-300 mb-2">
119+
Error Correction
120+
</label>
121+
<select
122+
id="errorCorrection"
123+
className={selectStyle}
124+
value={errorCorrectionLevel}
125+
onChange={(e) => setErrorCorrectionLevel(e.target.value)}
126+
>
127+
<option value="L">L (Low ~7%)</option>
128+
<option value="M">M (Medium ~15%)</option>
129+
<option value="Q">Q (Quartile ~25%)</option>
130+
<option value="H">H (High ~30%)</option>
131+
</select>
132+
</div>
133+
<div>
134+
<label htmlFor="qrSize" className="block text-sm font-medium text-gray-300 mb-2">
135+
Size (px)
136+
</label>
137+
<input
138+
type="number"
139+
id="qrSize"
140+
className={inputStyle}
141+
value={qrCodeSize}
142+
onChange={(e) => setQrCodeSize(Number(e.target.value))}
143+
min="64"
144+
max="768"
145+
step="16"
146+
/>
147+
</div>
148+
</div>
149+
150+
{text && (
151+
<div className="flex flex-col items-center justify-center mb-6">
152+
<QRCodeCanvas
153+
id="qrCodeCanvas" // ID for canvas to download
154+
value={text}
155+
size={qrCodeSize}
156+
level={errorCorrectionLevel}
157+
version={version}
158+
bgColor="#FFFFFF"
159+
fgColor="#000000"
160+
includeMargin={true}
161+
// renderAs="canvas" is not needed for QRCodeCanvas as it defaults to canvas
162+
/>
163+
<button
164+
onClick={downloadQrCode}
165+
className={`${buttonStyle} mt-4`}
166+
style={{
167+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
168+
color: cardStyle.color,
169+
borderColor: cardStyle.borderColor,
170+
border: '1px solid',
171+
}}
172+
>
173+
<DownloadSimple size={20} className="inline-block mr-2" /> Download QR Code
174+
</button>
175+
</div>
176+
)}
177+
{!text && (
178+
<div className="text-center text-gray-500 mb-6">
179+
Enter text or URL to generate QR Code.
180+
</div>
181+
)}
182+
</div>
183+
</div>
184+
</div>
185+
</div>
186+
</div>
187+
);
188+
};
189+
190+
export default QrCodeGeneratorPage;

0 commit comments

Comments
 (0)