Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,56 @@ jobs:
NETLIFY_AUTH_TOKEN: "${{ secrets.NETLIFY_AUTH_TOKEN }}"
NETLIFY_SITE_ID: "${{ secrets.NETLIFY_SITE_ID }}"
timeout-minutes: 5

cleanup:
name: Prune old Netlify deploys
runs-on: ubuntu-latest
needs: [deploy-preview, deploy-production]
# Runs after every deploy — preview (push to main) or production (tag).
# Skipped on PRs, where nothing is deployed.
if: ${{ always() && (needs.deploy-preview.result == 'success' || needs.deploy-production.result == 'success') }}
permissions:
contents: read
steps:
- name: Keep recent deploys plus a minimum of stable/preview, delete the rest
env:
NETLIFY_AUTH_TOKEN: "${{ secrets.NETLIFY_AUTH_TOKEN }}"
NETLIFY_SITE_ID: "${{ secrets.NETLIFY_SITE_ID }}"
KEEP_STABLE: "3" # minimum stable (production) deploys to always retain
KEEP_PREVIEW: "2" # minimum preview (draft) deploys to always retain
MAX_AGE_DAYS: "7" # additionally, retain every deploy younger than this
run: |
set -euo pipefail
api="https://api.netlify.com/api/v1"
auth="Authorization: Bearer ${NETLIFY_AUTH_TOKEN}"
cutoff="$(( $(date +%s) - MAX_AGE_DAYS * 24 * 3600 ))"

deploys="$(curl -fsS -H "$auth" "$api/sites/${NETLIFY_SITE_ID}/deploys?per_page=100")"

# Delete a deploy only if it is BOTH older than the cutoff AND beyond its group's
# minimum (newest $KEEP_STABLE stable / newest $KEEP_PREVIEW previews). So every
# deploy younger than $MAX_AGE_DAYS is kept, and each group keeps at least its
# minimum regardless of age.
ids="$(printf '%s' "$deploys" | jq -r \
--argjson keep_stable "$KEEP_STABLE" \
--argjson keep_preview "$KEEP_PREVIEW" \
--argjson cutoff "$cutoff" '
def isold: (.created_at | sub("\\.[0-9]+";"") | fromdateiso8601) < $cutoff;
( [ .[] | select(.draft == false and .state == "ready") ]
| sort_by(.created_at) | reverse | .[$keep_stable:] | .[] | select(isold) | .id ),
( [ .[] | select(.draft == true and .state == "ready") ]
| sort_by(.created_at) | reverse | .[$keep_preview:] | .[] | select(isold) | .id )
')"

if [ -z "$ids" ]; then echo "Nothing to prune."; exit 0; fi

while IFS= read -r id; do
[ -n "$id" ] || continue
# `|| echo 000` so a network-level curl failure doesn't trip errexit and abort the loop
code="$(curl -sS -o /dev/null -w '%{http_code}' -X DELETE -H "$auth" "$api/deploys/${id}" || echo 000)"
if [ "$code" = "200" ] || [ "$code" = "204" ]; then
echo "Deleted $id"
else
echo "::warning::Could not delete $id (HTTP $code)"
fi
done <<< "$ids"
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"netlify": {
"command": "npx",
"args": ["-y", "@netlify/mcp"]
}
}
}
144 changes: 75 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,132 @@
# QR Code Builder
<p align="center">
<img src="public/logo.svg" alt="QR Code Builder" width="120" />
</p>

A modern, customizable QR code builder built with React, TypeScript, and Vite. Features a beautiful dark/light theme, persistent settings, and multiple export formats.
# QR Code Builder

🌐 **Live Demo:** [https://free-qr-builder.netlify.app](https://free-qr-builder.netlify.app)
A modern, fully customizable QR code builder built with React, TypeScript, and Vite. Generate styled QR codes for 19 content types, personalize every detail, localize the UI, and export to PNG, SVG, or PDF — all client-side, no account required.

![QR Code Builder](https://via.placeholder.com/800x400?text=QR+Code+Builder)
🌐 **Live demo:** [https://free-qr-builder.netlify.app](https://free-qr-builder.netlify.app)

## ✨ Features

- **Fully Customizable QR Codes**
- Adjust size (100-500px) and margin
- Choose from 6 dot styles (square, dots, rounded, extra-rounded, classy, classy-rounded)
- Select corner square and corner dot styles
- Solid colors or gradients for dots
- **19 content templates**, grouped by category:
- **Links & utilities** — URL, WiFi credentials, calendar event, geo location
- **Contact & messaging** — vCard, email, SMS, phone call, WhatsApp, Telegram
- **Social** — Instagram, Twitter/X, LinkedIn, TikTok, YouTube, Facebook, Snapchat
- **Payment** — PayPal, Bitcoin
- Each template renders a dedicated form and builds a spec-compliant payload (e.g. `WIFI:`, `BEGIN:VCARD`, `BEGIN:VEVENT`, `geo:`, `bitcoin:`), with proper escaping/encoding.

- **International phone input** — dialing-code selector with country flags (powered by `libphonenumber-js`) for phone, SMS, and WhatsApp templates.

- **Multi-language UI** — English, Italiano, and Română, switchable from the header.

- **Fully customizable styling**
- Adjustable size (100–500px) and margin
- 6 dot styles (square, rounded, dots, classy, classy-rounded, extra-rounded)
- Independent corner-square and corner-dot styles
- Solid color or linear/radial gradient for dots
- Transparent or colored background
- **Color presets** (Classic, Ocean, Forest, Sunset, Purple, Monochrome) plus your own saved presets

- **Center Logo Support**
- Upload custom logo images
- Adjustable size (20-30% of QR code)
- Configurable margin around logo
- **Center logo support** — upload a custom image, tune its size and the margin around it.

- **Error Correction Levels**
- L, M, Q, H levels with helpful tooltips
- Higher levels recommended when using logos
- **Error correction levels** — L, M, Q, H with inline guidance (higher levels recommended when embedding a logo).

- **Multiple Export Formats**
- PNG for general use
- SVG for scalable graphics
- PDF for print-ready output
- **Multiple export formats** — PNG for general use, SVG for scalable graphics, PDF for print-ready output.

- **Modern UI/UX**
- Dark/Light theme toggle
- Settings persistence via localStorage
- Reset to default functionality
- Visual style pickers with previews
- Responsive design for mobile and desktop
- **Modern UX** — dark/light theme toggle (dark by default), settings and presets persisted in `localStorage`, reset-to-default, visual style pickers with SVG previews, responsive layout for mobile and desktop.

## 🚀 Getting Started

### Prerequisites

- Node.js 18+
- npm or yarn
- Node.js 22+
- npm

### Installation

```bash
# Clone the repository
git clone https://github.com/your-username/qr-code-builder.git
git clone https://github.com/davide-pi/qr-code-builder.git
cd qr-code-builder

# Install dependencies
npm install

# Start development server
# Start the development server
npm run dev
```

### Build for Production
### Build for production

```bash
npm run build
npm run preview # Preview the production build
npm run preview # Preview the production build locally
```

## 🛠️ Tech Stack

- **React 19** - UI framework
- **TypeScript** - Type safety
- **Vite 7** - Build tool and dev server
- **qr-code-styling** - QR code generation library
- **jsPDF** - PDF export functionality
- **React 19** — UI framework
- **TypeScript** — type safety
- **Vite 8** — build tool and dev server
- **qr-code-styling** — QR code generation and styling
- **jsPDF** — PDF export
- **libphonenumber-js** + **flag-icons** — international dialing codes and flags
- **lucide-react** — icons
- **react-select** — accessible dropdowns

## 📁 Project Structure

```
src/
├── components/
│ ├── Header/
│ │ ├── Header.tsx # Fixed header with logo and theme toggle
│ │ └── Header.css
│ ├── Footer/
│ │ ├── Footer.tsx # Fixed footer with credits
│ │ └── Footer.css
│ ├── QRCodeGenerator/
│ │ ├── QRCodeGenerator.tsx # Main orchestrator component
│ │ └── QRCodeGenerator.css
│ ├── QROptions/
│ │ ├── QROptions.tsx # All customization controls
│ │ └── QROptions.css
│ ├── QRPreview/
│ │ ├── QRPreview.tsx # QR preview and download buttons
│ │ └── QRPreview.css
│ └── StylePicker/
│ └── StylePicker.tsx # Reusable style picker with SVG previews
│ ├── Header/ # App header (logo, theme toggle, language switch)
│ ├── Footer/ # Footer with credits
│ ├── QRCodeGenerator/ # Main orchestrator component
│ ├── QRDataInput/ # Content-type templates and their forms
│ ├── QROptions/ # Styling controls (size, colors, dots, gradient, logo…)
│ ├── QRPreview/ # Live preview and PNG/SVG/PDF export
│ ├── StylePicker/ # Reusable style picker with SVG previews
│ ├── CountryCodeSelect/ # International dialing-code selector
│ └── Flags/ # Flag icons for the country selector
├── i18n/ # Localization (en, it, ro) and LanguageProvider
├── types/
│ └── qr.ts # Shared types and constants
├── App.tsx/css # Main app with theme logic
└── main.tsx # Entry point
│ └── qr.ts # Shared types, template definitions, presets, constants
├── App.tsx / App.css # Root app and theme logic
└── main.tsx # Entry point
```

## 🌐 Deployment

This project is configured for easy deployment to Netlify:
The app deploys to **Netlify** through the GitHub Actions pipeline in
[`.github/workflows/build-and-deploy.yml`](.github/workflows/build-and-deploy.yml):

1. **Lint** → **Build** (versioned with [GitVersion](https://gitversion.net/)).
2. **Preview deploy** — every push to `main` publishes a Netlify **draft** deploy.
3. **Production deploy** — pushing a `vX.Y.Z` tag publishes to production.
4. **Cleanup** — after an official (tagged) release, an automated step prunes old Netlify
deploys, keeping the **last 3 production releases** and only the **latest preview**.

The pipeline expects two repository secrets: `NETLIFY_AUTH_TOKEN` and `NETLIFY_SITE_ID`.

1. Connect your GitHub repository to Netlify
2. Netlify will automatically detect Vite configuration
3. Build command: `npm run build`
4. Publish directory: `dist`
> An MCP server for Netlify is configured in [`.mcp.json`](.mcp.json) for use with
> MCP-compatible AI tooling.

## 📝 Scripts

- `npm run dev` - Start development server
- `npm run build` - Build for production
- `npm run preview` - Preview production build
- `npm run lint` - Run ESLint
- `npm run dev` — start the development server
- `npm run build` — type-check and build for production
- `npm run preview` — preview the production build
- `npm run lint` — run ESLint

## 📄 License

MIT License - feel free to use this project for personal or commercial purposes.
MIT License © 2025 Davide Piccinini — free to use for personal or commercial purposes. See [LICENSE](LICENSE).

## 🙏 Credits

- [qr-code-styling](https://github.com/kozakdenys/qr-code-styling) - QR code generation
- [jsPDF](https://github.com/parallax/jsPDF) - PDF export
- [qr-code-styling](https://github.com/kozakdenys/qr-code-styling) — QR code generation
- [jsPDF](https://github.com/parallax/jsPDF) — PDF export
- [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js) — phone number handling
- [lucide](https://lucide.dev/) — icons
Loading