creating the first version of the qr-code editor
This commit is contained in:
+164
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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 |
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
Reference in New Issue
Block a user