creating the first version of the qr-code editor

This commit is contained in:
James Twose
2025-05-10 22:52:40 +02:00
parent 6a32980767
commit 2c66fee65d
21 changed files with 18239 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+10
View File
@@ -1,2 +1,12 @@
# qr-code-generator # qr-code-generator
A free customisable QR code generator written in react. A free customisable QR code generator written in react.
- `npx create-react-app . --template typescript` to create a new react app with typescript
- `npm i react-qr-code` to install the QR code library
- `convert logo.svg -define icon:auto-resize=64,48,32,16 favicon.ico` to create a favicon from the logo
- `convert logo.svg -resize 192x192 -background none -gravity center -extent 192x192 logo192.png` to create a 192x192 logo from the input
- `convert logo.svg -resize 512x512 -background none -gravity center -extent 512x512 logo512.png` to create a 512x512 logo from the input
- `npm run build` to build the app
- `npm run start` to start the app
- `npm run deploy` to deploy the app
+17712
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
{
"name": "qr-code-generator",
"version": "0.1.0",
"homepage": "https://qr-code.jms.rocks",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.126",
"@types/react": "^19.1.3",
"@types/react-dom": "^19.1.3",
"html-to-image": "^1.11.13",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-qr-code": "^2.0.15",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"gh-pages": "^6.3.0"
}
}
+1
View File
@@ -0,0 +1 @@
qr-code.jms.rocks
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+164
View File
@@ -0,0 +1,164 @@
.App {
text-align: center;
background-color: #fce4ec; /* Light pastel pink */
font-family: 'Comic Sans MS', 'Chalkboard SE', cursive; /* Playful font */
}
.App-logo {
height: 40vmin;
pointer-events: none;
border-radius: 50%; /* Rounded logo */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Soft shadow */
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 10s linear; /* Slower spin */
}
}
.App-header {
background-color: #f8bbd0; /* Pastel pink */
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(12px + 2vmin);
color: #4a148c; /* Deep purple for contrast */
border: 5px solid #f48fb1; /* Cute border */
border-radius: 20px; /* Rounded corners */
padding: 20px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Soft shadow */
}
input {
padding: 10px;
font-size: 16px;
border: 2px solid #f48fb1; /* Pastel pink border */
border-radius: 10px; /* Rounded corners */
outline: none;
margin-bottom: 20px;
background-color: #fff3f4; /* Light pastel background */
color: #4a148c; /* Deep purple text */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Soft shadow */
}
input:focus {
border-color: #ce93d8; /* Light purple border on focus */
}
.App-link {
color: #ba68c8; /* Pastel purple */
text-decoration: none;
font-weight: bold;
}
.App-link:hover {
color: #ab47bc; /* Slightly darker purple on hover */
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.qr-code-container {
padding: 20px;
background-color: #fff3f4; /* Light pastel background */
border: 5px solid #f48fb1; /* Pastel pink border */
border-radius: 20px; /* Rounded corners */
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Soft shadow */
display: inline-block; /* Ensures the container wraps tightly around the QR code */
}
button {
background-color: #f48fb1; /* Pastel pink */
color: #ffffff; /* White text */
font-size: 16px;
font-weight: bold;
padding: 10px 20px;
margin-top: 20px;
border: none;
border-radius: 20px; /* Rounded corners */
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Soft shadow */
transition: all 0.3s ease; /* Smooth transition for hover effects */
}
button:hover {
background-color: #f06292; /* Slightly darker pastel pink */
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); /* Stronger shadow on hover */
transform: translateY(-2px); /* Slight lift effect */
}
button:active {
background-color: #ec407a; /* Even darker pastel pink for active state */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Reduced shadow on click */
transform: translateY(0); /* Reset lift effect */
}
.input-text,
.input-file {
margin-bottom: 20px;
padding: 10px;
font-size: 16px;
}
.color-picker-container {
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
margin-bottom: 20px;
}
.color-picker {
position: relative;
display: inline-block;
}
.color-input {
width: 90px;
height: 90px;
border: none;
cursor: pointer;
}
.color-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -30%);
font-size: 18px;
color: #000;
pointer-events: none;
}
.qr-code-container {
position: relative;
padding: 10px;
display: inline-block;
}
.uploaded-image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
border-radius: 10%;
}
.download-button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
+107
View File
@@ -0,0 +1,107 @@
import React, { useState, useRef } from "react";
import "./App.css";
import QRCode from "react-qr-code";
import { toPng } from "html-to-image";
function App() {
const [qrValue, setQrValue] = useState("https://shop.jms.rocks");
const [uploadedImage, setUploadedImage] = useState<string | null>(null);
const [bgColor, setBgColor] = useState("#ffffff");
const [fgColor, setFgColor] = useState("#f48fb1");
const [borderColor, setBorderColor] = useState("#f48fb1");
const qrCodeRef = useRef<HTMLDivElement>(null);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setQrValue(event.target.value);
};
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setUploadedImage(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const handleDownload = async () => {
if (qrCodeRef.current) {
try {
const dataUrl = await toPng(qrCodeRef.current);
const link = document.createElement("a");
link.href = dataUrl;
link.download = "qr-code.png";
link.click();
} catch (error) {
console.error("Failed to download QR code:", error);
}
}
};
return (
<div className="App">
<header className="App-header">
<h1>QR Code Generator</h1>
<input
type="text"
placeholder="Enter text for QR code"
value={qrValue}
onChange={handleInputChange}
className="input-text"
/>
<input
type="file"
accept="image/*"
onChange={handleImageUpload}
className="input-file"
/>
<div className="color-picker-container">
<div className="color-picker">
<input
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="color-input"
/>
<span className="color-label">BG</span>
</div>
<div className="color-picker">
<input
type="color"
value={fgColor}
onChange={(e) => setFgColor(e.target.value)}
className="color-input"
/>
<span className="color-label">FG</span>
</div>
<div className="color-picker">
<input
type="color"
value={borderColor}
onChange={(e) => setBorderColor(e.target.value)}
className="color-input"
/>
<span className="color-label">Border</span>
</div>
</div>
<div
className="qr-code-container"
ref={qrCodeRef}
style={{ border: `5px solid ${borderColor}` }}
>
<QRCode value={qrValue} bgColor={bgColor} fgColor={fgColor} />
{uploadedImage && (
<img src={uploadedImage} alt="Uploaded" className="uploaded-image" />
)}
</div>
<button onClick={handleDownload} className="download-button">
Download QR Code
</button>
</header>
</div>
);
}
export default App;
+13
View File
@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+19
View File
@@ -0,0 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
<rect width="100" height="100" rx="20" fill="#fffacb"/>
<rect x="15" y="15" width="25" height="25" rx="5" fill="#ffcc80"/>
<rect x="60" y="15" width="25" height="25" rx="5" fill="#ffcc80"/>
<rect x="15" y="60" width="25" height="25" rx="5" fill="#ffcc80"/>
<rect x="60" y="60" width="10" height="10" rx="2" fill="#ffcc80"/>
<rect x="75" y="60" width="10" height="10" rx="2" fill="#ffcc80"/>
<rect x="60" y="75" width="10" height="10" rx="2" fill="#ffcc80"/>
<rect x="75" y="75" width="10" height="10" rx="2" fill="#ffcc80"/>
<path d="M40 40H60V60H40V40Z" fill="#ffcc80"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

+1
View File
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
+15
View File
@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
+5
View File
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}