Fix, complete all tool updates
This commit is contained in:
parent
eb2faec952
commit
fb9058688e
|
|
@ -1,291 +1,291 @@
|
||||||
# 🚀 Side Project Marketing Strategy
|
# 🚀 Side Project Marketing Strategy
|
||||||
|
|
||||||
> **"Engineering as Marketing"** – Kostenlose Micro-Tools bauen, um SEO-Traffic abzufangen und in zahlende Kunden zu konvertieren.
|
> **"Engineering as Marketing"** – Kostenlose Micro-Tools bauen, um SEO-Traffic abzufangen und in zahlende Kunden zu konvertieren.
|
||||||
|
|
||||||
**Status:** Planung abgeschlossen, bereit für Implementierung
|
**Status:** Planung abgeschlossen, bereit für Implementierung
|
||||||
**Autor:** QR Master Team
|
**Autor:** QR Master Team
|
||||||
**Letzte Aktualisierung:** 2026-01-08
|
**Letzte Aktualisierung:** 2026-01-08
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Executive Summary
|
## Executive Summary
|
||||||
|
|
||||||
Wir nutzen die bewiesene "Engineering as Marketing" Strategie (bekannt von HubSpot's Website Grader, Ahrefs' Free Tools, Shopify's Business Tools), um organischen Traffic über spezialisierte, kostenlose QR-Generatoren zu gewinnen.
|
Wir nutzen die bewiesene "Engineering as Marketing" Strategie (bekannt von HubSpot's Website Grader, Ahrefs' Free Tools, Shopify's Business Tools), um organischen Traffic über spezialisierte, kostenlose QR-Generatoren zu gewinnen.
|
||||||
|
|
||||||
### Das Konzept in einem Satz
|
### Das Konzept in einem Satz
|
||||||
|
|
||||||
> Anstatt gegen "QR Code Generator" (DA 90+ Konkurrenz) zu kämpfen, bauen wir 10 spezialisierte Tools für Long-Tail-Keywords wie "WiFi QR Code erstellen" oder "VCard QR Generator".
|
> Anstatt gegen "QR Code Generator" (DA 90+ Konkurrenz) zu kämpfen, bauen wir 10 spezialisierte Tools für Long-Tail-Keywords wie "WiFi QR Code erstellen" oder "VCard QR Generator".
|
||||||
|
|
||||||
### Warum das funktioniert
|
### Warum das funktioniert
|
||||||
|
|
||||||
1. **Weniger Konkurrenz:** "WiFi QR Code Generator" hat 1/10 der Konkurrenz von "QR Code Generator"
|
1. **Weniger Konkurrenz:** "WiFi QR Code Generator" hat 1/10 der Konkurrenz von "QR Code Generator"
|
||||||
2. **Höhere Kaufabsicht:** Wer "Restaurant Menu QR Code" sucht, ist bereit für ein Premium-Tool
|
2. **Höhere Kaufabsicht:** Wer "Restaurant Menu QR Code" sucht, ist bereit für ein Premium-Tool
|
||||||
3. **Natürliche Backlinks:** Leute teilen nützliche Tools ("Hier, dieser Generator ist kostenlos")
|
3. **Natürliche Backlinks:** Leute teilen nützliche Tools ("Hier, dieser Generator ist kostenlos")
|
||||||
4. **Zero Marginal Cost:** Client-Side Generierung = 0€ Serverkosten pro User
|
4. **Zero Marginal Cost:** Client-Side Generierung = 0€ Serverkosten pro User
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ROI Projektion (Konservativ)
|
## ROI Projektion (Konservativ)
|
||||||
|
|
||||||
| Metrik | Monat 3 | Monat 6 | Monat 12 |
|
| Metrik | Monat 3 | Monat 6 | Monat 12 |
|
||||||
|--------|---------|---------|----------|
|
|--------|---------|---------|----------|
|
||||||
| Organischer Traffic (alle Tools) | 2.000 | 10.000 | 25.000 |
|
| Organischer Traffic (alle Tools) | 2.000 | 10.000 | 25.000 |
|
||||||
| Free Signups (20% Conv.) | 400 | 2.000 | 5.000 |
|
| Free Signups (20% Conv.) | 400 | 2.000 | 5.000 |
|
||||||
| Paid Customers (3% der Signups) | 12 | 60 | 150 |
|
| Paid Customers (3% der Signups) | 12 | 60 | 150 |
|
||||||
| **Zusätzlicher MRR** | **108€** | **540€** | **1.350€** |
|
| **Zusätzlicher MRR** | **108€** | **540€** | **1.350€** |
|
||||||
|
|
||||||
> **Benchmarks verwendet:** 2-3% Free-to-Paid Conversion (Industry Standard), 20% Tool-to-Signup (optimistisch, aber erreichbar mit gutem UX).
|
> **Benchmarks verwendet:** 2-3% Free-to-Paid Conversion (Industry Standard), 20% Tool-to-Signup (optimistisch, aber erreichbar mit gutem UX).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Die Tools-Roadmap
|
## Die Tools-Roadmap
|
||||||
|
|
||||||
### Phase 1: Quick Wins (Woche 1-2)
|
### Phase 1: Quick Wins (Woche 1-2)
|
||||||
|
|
||||||
Fokus auf **hohes Suchvolumen + geringe Komplexität**.
|
Fokus auf **hohes Suchvolumen + geringe Komplexität**.
|
||||||
|
|
||||||
| Tool | URL | Geschätztes SV | Implementierungs-Aufwand |
|
| Tool | URL | Geschätztes SV | Implementierungs-Aufwand |
|
||||||
|------|-----|----------------|-------------------------|
|
|------|-----|----------------|-------------------------|
|
||||||
| **WiFi QR Generator** | `/tools/wifi-qr-code` | 40.000/Monat | 4h |
|
| **WiFi QR Generator** | `/tools/wifi-qr-code` | 40.000/Monat | 4h |
|
||||||
| **VCard QR Generator** | `/tools/vcard-qr-code` | 15.000/Monat | 4h |
|
| **VCard QR Generator** | `/tools/vcard-qr-code` | 15.000/Monat | 4h |
|
||||||
| **WhatsApp QR Generator** | `/tools/whatsapp-qr-code` | 20.000/Monat | 3h |
|
| **WhatsApp QR Generator** | `/tools/whatsapp-qr-code` | 20.000/Monat | 3h |
|
||||||
|
|
||||||
### Phase 2: Monetization Focus (Woche 3-4)
|
### Phase 2: Monetization Focus (Woche 3-4)
|
||||||
|
|
||||||
Fokus auf **hohe Conversion-Wahrscheinlichkeit** (B2B Use Cases).
|
Fokus auf **hohe Conversion-Wahrscheinlichkeit** (B2B Use Cases).
|
||||||
|
|
||||||
| Tool | URL | Geschätztes SV | Upsell-Hook |
|
| Tool | URL | Geschätztes SV | Upsell-Hook |
|
||||||
|------|-----|----------------|-------------|
|
|------|-----|----------------|-------------|
|
||||||
| **App Store Link QR** | `/tools/app-store-qr-code` | 5.000/Monat | Smart Routing (iOS/Android) |
|
| **App Store Link QR** | `/tools/app-store-qr-code` | 5.000/Monat | Smart Routing (iOS/Android) |
|
||||||
| **PDF to QR** | `/tools/pdf-qr-code` | 15.000/Monat | PDF Hosting (benötigt Account) |
|
| **PDF to QR** | `/tools/pdf-qr-code` | 15.000/Monat | PDF Hosting (benötigt Account) |
|
||||||
| **Menu QR Generator** | `/tools/menu-qr-code` | 8.000/Monat | Multi-Sprache, Analytics |
|
| **Menu QR Generator** | `/tools/menu-qr-code` | 8.000/Monat | Multi-Sprache, Analytics |
|
||||||
|
|
||||||
### Phase 3: Differenzierung (Monat 2+)
|
### Phase 3: Differenzierung (Monat 2+)
|
||||||
|
|
||||||
Fokus auf **Unique Features** die Konkurrenten nicht haben.
|
Fokus auf **Unique Features** die Konkurrenten nicht haben.
|
||||||
|
|
||||||
| Tool | URL | Differenzierung |
|
| Tool | URL | Differenzierung |
|
||||||
|------|-----|-----------------|
|
|------|-----|-----------------|
|
||||||
| **Barcode Generator** | `/tools/barcode-generator` | EAN/UPC/ISBN Unterstützung |
|
| **Barcode Generator** | `/tools/barcode-generator` | EAN/UPC/ISBN Unterstützung |
|
||||||
| **Bitcoin/Crypto QR** | `/tools/bitcoin-qr-code` | Multi-Wallet Format |
|
| **Bitcoin/Crypto QR** | `/tools/bitcoin-qr-code` | Multi-Wallet Format |
|
||||||
| **AI Art QR (Viral)** | `/tools/ai-qr-code` | Stable Diffusion Integration |
|
| **AI Art QR (Viral)** | `/tools/ai-qr-code` | Stable Diffusion Integration |
|
||||||
|
|
||||||
## Geplantes Portfolio: Kostenlose Statische Generatoren (15 Typen)
|
## Geplantes Portfolio: Kostenlose Statische Generatoren (15 Typen)
|
||||||
|
|
||||||
Wir werden die folgenden 15 statischen QR-Code-Typen anbieten. Diese sind **dauerhaft kostenlos** und erfordern keine Server-Infrastruktur für Redirects (im Gegensatz zu dynamischen Codes).
|
Wir werden die folgenden 15 statischen QR-Code-Typen anbieten. Diese sind **dauerhaft kostenlos** und erfordern keine Server-Infrastruktur für Redirects (im Gegensatz zu dynamischen Codes).
|
||||||
|
|
||||||
> **Wichtig:** Alle diese Generatoren stehen sowohl **öffentlich als SEO-Landingpages** zur Verfügung (zur Neukundengewinnung), als auch im **eingeloggten Bereich** für registrierte Nutzer (für Komfort und Zentralisierung).
|
> **Wichtig:** Alle diese Generatoren stehen sowohl **öffentlich als SEO-Landingpages** zur Verfügung (zur Neukundengewinnung), als auch im **eingeloggten Bereich** für registrierte Nutzer (für Komfort und Zentralisierung).
|
||||||
|
|
||||||
1. **URL / Link**: Der Standard. Öffnet eine Webseite.
|
1. **URL / Link**: Der Standard. Öffnet eine Webseite.
|
||||||
2. **Text**: Zeigt reinen Text an (bis zu 300 Zeichen).
|
2. **Text**: Zeigt reinen Text an (bis zu 300 Zeichen).
|
||||||
3. **WiFi**: Verbindet direkt mit einem WLAN-Netzwerk (WPA/WEP/Open).
|
3. **WiFi**: Verbindet direkt mit einem WLAN-Netzwerk (WPA/WEP/Open).
|
||||||
4. **VCard / Kontakt**: Speichert einen Kontakt direkt im Adressbuch.
|
4. **VCard / Kontakt**: Speichert einen Kontakt direkt im Adressbuch.
|
||||||
5. **WhatsApp**: Startet einen Chat mit einer Nummer (und optionalem Text).
|
5. **WhatsApp**: Startet einen Chat mit einer Nummer (und optionalem Text).
|
||||||
6. **E-Mail**: Öffnet das E-Mail-Programm mit Empfänger, Betreff und Body.
|
6. **E-Mail**: Öffnet das E-Mail-Programm mit Empfänger, Betreff und Body.
|
||||||
7. **SMS**: Bereitet eine SMS an eine Nummer vor.
|
7. **SMS**: Bereitet eine SMS an eine Nummer vor.
|
||||||
8. **Anruf / Tel**: Startet einen Anruf an eine Nummer.
|
8. **Anruf / Tel**: Startet einen Anruf an eine Nummer.
|
||||||
9. **Event / Kalender**: Fügt einen Termin zum Kalender hinzu (.ics).
|
9. **Event / Kalender**: Fügt einen Termin zum Kalender hinzu (.ics).
|
||||||
10. **Geo / Maps**: Öffnet einen Standort in Google Maps/Apple Maps.
|
10. **Geo / Maps**: Öffnet einen Standort in Google Maps/Apple Maps.
|
||||||
11. **Facebook**: Öffnet ein Profil oder eine Seite.
|
11. **Facebook**: Öffnet ein Profil oder eine Seite.
|
||||||
12. **Instagram**: Öffnet ein Instagram-Profil.
|
12. **Instagram**: Öffnet ein Instagram-Profil.
|
||||||
13. **Twitter / X**: Öffnet ein Profil oder erstellt einen Tweet.
|
13. **Twitter / X**: Öffnet ein Profil oder erstellt einen Tweet.
|
||||||
14. **YouTube**: Öffnet ein Video oder einen Kanal.
|
14. **YouTube**: Öffnet ein Video oder einen Kanal.
|
||||||
15. **TikTok**: Öffnet ein TikTok-Profil.
|
15. **TikTok**: Öffnet ein TikTok-Profil.
|
||||||
|
|
||||||
Diese Breite deckt 99% der "Everyday Use Cases" ab und maximiert die SEO-Angriffsfläche.
|
Diese Breite deckt 99% der "Everyday Use Cases" ab und maximiert die SEO-Angriffsfläche.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Technische Architektur
|
## Technische Architektur
|
||||||
|
|
||||||
### Warum Client-Side Generierung?
|
### Warum Client-Side Generierung?
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ USER BROWSER │
|
│ USER BROWSER │
|
||||||
│ │
|
│ │
|
||||||
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
|
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
|
||||||
│ │ Form Input │ -> │ qrcode.js │ -> │ Canvas/SVG │ │
|
│ │ Form Input │ -> │ qrcode.js │ -> │ Canvas/SVG │ │
|
||||||
│ │ (SSID, PW) │ │ (generation) │ │ (download) │ │
|
│ │ (SSID, PW) │ │ (generation) │ │ (download) │ │
|
||||||
│ └─────────────┘ └──────────────┘ └────────────────┘ │
|
│ └─────────────┘ └──────────────┘ └────────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ KEINE Server-Calls! │
|
│ KEINE Server-Calls! │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**Vorteile:**
|
**Vorteile:**
|
||||||
- **Privatsphäre:** Passwörter verlassen nie den Browser
|
- **Privatsphäre:** Passwörter verlassen nie den Browser
|
||||||
- **Speed:** Instant Generation (kein Network Latency)
|
- **Speed:** Instant Generation (kein Network Latency)
|
||||||
- **Kosten:** 0€ pro generiertem Code
|
- **Kosten:** 0€ pro generiertem Code
|
||||||
- **Scale:** Kein Backend-Limit
|
- **Scale:** Kein Backend-Limit
|
||||||
|
|
||||||
### Datei-Struktur (Next.js)
|
### Datei-Struktur (Next.js)
|
||||||
|
|
||||||
```
|
```
|
||||||
src/app/(marketing)/tools/
|
src/app/(marketing)/tools/
|
||||||
├── wifi-qr-code/
|
├── wifi-qr-code/
|
||||||
│ ├── page.tsx # Server Component (SEO)
|
│ ├── page.tsx # Server Component (SEO)
|
||||||
│ └── WiFiGenerator.tsx # Client Component (Interaktion)
|
│ └── WiFiGenerator.tsx # Client Component (Interaktion)
|
||||||
├── vcard-qr-code/
|
├── vcard-qr-code/
|
||||||
│ ├── page.tsx
|
│ ├── page.tsx
|
||||||
│ └── VCardGenerator.tsx
|
│ └── VCardGenerator.tsx
|
||||||
└── [weitere tools]/
|
└── [weitere tools]/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shared Components
|
### Shared Components
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/components/tools/QRDownloadButtons.tsx
|
// src/components/tools/QRDownloadButtons.tsx
|
||||||
// Wiederverwendbare Download-Buttons für alle Tools
|
// Wiederverwendbare Download-Buttons für alle Tools
|
||||||
|
|
||||||
// src/components/tools/UpgradePrompt.tsx
|
// src/components/tools/UpgradePrompt.tsx
|
||||||
// "Willst du Scans tracken?" CTA Box
|
// "Willst du Scans tracken?" CTA Box
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## SEO-Strategie pro Tool-Page
|
## SEO-Strategie pro Tool-Page
|
||||||
|
|
||||||
Jede Seite folgt dem gleichen bewährten Muster:
|
Jede Seite folgt dem gleichen bewährten Muster:
|
||||||
|
|
||||||
### 1. Above the Fold: Sofort nutzbar
|
### 1. Above the Fold: Sofort nutzbar
|
||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────────────────────────┐
|
┌────────────────────────────────────────┐
|
||||||
│ H1: Free WiFi QR Code Generator │
|
│ H1: Free WiFi QR Code Generator │
|
||||||
│ Subline: Teile dein WLAN in Sekunden │
|
│ Subline: Teile dein WLAN in Sekunden │
|
||||||
│ │
|
│ │
|
||||||
│ ┌─────────────────────────────────┐ │
|
│ ┌─────────────────────────────────┐ │
|
||||||
│ │ [SSID] [Password] [WPA▼] │ │
|
│ │ [SSID] [Password] [WPA▼] │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ [ Generate QR Code ] │ │
|
│ │ [ Generate QR Code ] │ │
|
||||||
│ └─────────────────────────────────┘ │
|
│ └─────────────────────────────────┘ │
|
||||||
└────────────────────────────────────────┘
|
└────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**Regel:** Der User muss SOFORT interagieren können. Kein langer Intro-Text.
|
**Regel:** Der User muss SOFORT interagieren können. Kein langer Intro-Text.
|
||||||
|
|
||||||
### 2. Schema Markup (Pflicht!)
|
### 2. Schema Markup (Pflicht!)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "SoftwareApplication",
|
"@type": "SoftwareApplication",
|
||||||
"name": "WiFi QR Code Generator",
|
"name": "WiFi QR Code Generator",
|
||||||
"applicationCategory": "UtilitiesApplication",
|
"applicationCategory": "UtilitiesApplication",
|
||||||
"operatingSystem": "Web Browser",
|
"operatingSystem": "Web Browser",
|
||||||
"offers": {
|
"offers": {
|
||||||
"@type": "Offer",
|
"@type": "Offer",
|
||||||
"price": "0",
|
"price": "0",
|
||||||
"priceCurrency": "EUR"
|
"priceCurrency": "EUR"
|
||||||
},
|
},
|
||||||
"aggregateRating": {
|
"aggregateRating": {
|
||||||
"@type": "AggregateRating",
|
"@type": "AggregateRating",
|
||||||
"ratingValue": "4.8",
|
"ratingValue": "4.8",
|
||||||
"ratingCount": "1247"
|
"ratingCount": "1247"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. FAQ Section (Long-Tail Keywords)
|
### 3. FAQ Section (Long-Tail Keywords)
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Häufig gestellte Fragen
|
## Häufig gestellte Fragen
|
||||||
|
|
||||||
### Wie funktioniert ein WiFi QR Code?
|
### Wie funktioniert ein WiFi QR Code?
|
||||||
Der QR Code enthält deine WLAN-Daten im Format...
|
Der QR Code enthält deine WLAN-Daten im Format...
|
||||||
|
|
||||||
### Ist es sicher, mein WiFi Passwort in einem QR Code zu speichern?
|
### Ist es sicher, mein WiFi Passwort in einem QR Code zu speichern?
|
||||||
Ja, der QR Code wird nur lokal in deinem Browser generiert...
|
Ja, der QR Code wird nur lokal in deinem Browser generiert...
|
||||||
|
|
||||||
### Kann ich den QR Code später bearbeiten?
|
### Kann ich den QR Code später bearbeiten?
|
||||||
Dieser Generator erstellt statische Codes. Für editierbare...
|
Dieser Generator erstellt statische Codes. Für editierbare...
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Conversion Prompt (Der Hook)
|
### 4. Conversion Prompt (Der Hook)
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────┐
|
||||||
│ ✅ QR Code erfolgreich erstellt! │
|
│ ✅ QR Code erfolgreich erstellt! │
|
||||||
│ │
|
│ │
|
||||||
│ ⚠️ Hinweis: Dies ist ein statischer Code. │
|
│ ⚠️ Hinweis: Dies ist ein statischer Code. │
|
||||||
│ Wenn du dein Passwort änderst, musst du neu drucken. │
|
│ Wenn du dein Passwort änderst, musst du neu drucken. │
|
||||||
│ │
|
│ │
|
||||||
│ → Erstelle einen dynamischen Code (jederzeit änderbar) │
|
│ → Erstelle einen dynamischen Code (jederzeit änderbar) │
|
||||||
│ │
|
│ │
|
||||||
│ Bonus: Sieh wer deinen Code scannt (Datum, Standort) │
|
│ Bonus: Sieh wer deinen Code scannt (Datum, Standort) │
|
||||||
│ │
|
│ │
|
||||||
│ [ Kostenlos registrieren ] │
|
│ [ Kostenlos registrieren ] │
|
||||||
└─────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Conversion Optimierung
|
## Conversion Optimierung
|
||||||
|
|
||||||
### Die "Limitation Awareness" Methode
|
### Die "Limitation Awareness" Methode
|
||||||
|
|
||||||
Jedes Tool zeigt nach der Generierung **sanft** die Limitierungen auf:
|
Jedes Tool zeigt nach der Generierung **sanft** die Limitierungen auf:
|
||||||
|
|
||||||
| Tool | Statische Limitation | Upsell-Feature |
|
| Tool | Statische Limitation | Upsell-Feature |
|
||||||
|------|---------------------|----------------|
|
|------|---------------------|----------------|
|
||||||
| WiFi | Passwort-Änderung = Neudruck | Dynamischer Code (editierbar) |
|
| WiFi | Passwort-Änderung = Neudruck | Dynamischer Code (editierbar) |
|
||||||
| VCard | Kontakt-Update = Neudruck | Immer aktuelle Visitenkarte |
|
| VCard | Kontakt-Update = Neudruck | Immer aktuelle Visitenkarte |
|
||||||
| Menu | Neue Speisekarte = Neudruck | PDF-Hosting + Analytics |
|
| Menu | Neue Speisekarte = Neudruck | PDF-Hosting + Analytics |
|
||||||
| App Store | Nur ein Store-Link | Smart Device Detection |
|
| App Store | Nur ein Store-Link | Smart Device Detection |
|
||||||
|
|
||||||
### Email Capture vor Download
|
### Email Capture vor Download
|
||||||
|
|
||||||
**Optional (A/B testen):**
|
**Optional (A/B testen):**
|
||||||
```
|
```
|
||||||
"Gib deine Email ein, um den QR als hochauflösende PNG zu erhalten"
|
"Gib deine Email ein, um den QR als hochauflösende PNG zu erhalten"
|
||||||
```
|
```
|
||||||
→ Baut Email-Liste, auch wenn User nicht sofort konvertiert.
|
→ Baut Email-Liste, auch wenn User nicht sofort konvertiert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erfolgsmetriken (KPIs)
|
## Erfolgsmetriken (KPIs)
|
||||||
|
|
||||||
| KPI | Tool | Ziel (Monat 3) |
|
| KPI | Tool | Ziel (Monat 3) |
|
||||||
|-----|------|----------------|
|
|-----|------|----------------|
|
||||||
| **Organic Sessions** | Google Analytics | 2.000/Monat |
|
| **Organic Sessions** | Google Analytics | 2.000/Monat |
|
||||||
| **QR Generations** | PostHog Event | 500/Monat |
|
| **QR Generations** | PostHog Event | 500/Monat |
|
||||||
| **Signup Clicks** | PostHog Event | 100/Monat |
|
| **Signup Clicks** | PostHog Event | 100/Monat |
|
||||||
| **Actual Signups** | DB Query | 50/Monat |
|
| **Actual Signups** | DB Query | 50/Monat |
|
||||||
| **Paid Conversion** | Stripe | 5/Monat |
|
| **Paid Conversion** | Stripe | 5/Monat |
|
||||||
|
|
||||||
### Tracking Events implementieren
|
### Tracking Events implementieren
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Auf jeder Tool-Page
|
// Auf jeder Tool-Page
|
||||||
posthog.capture('tool_qr_generated', {
|
posthog.capture('tool_qr_generated', {
|
||||||
tool: 'wifi',
|
tool: 'wifi',
|
||||||
format: 'png'
|
format: 'png'
|
||||||
});
|
});
|
||||||
|
|
||||||
posthog.capture('tool_signup_cta_clicked', {
|
posthog.capture('tool_signup_cta_clicked', {
|
||||||
tool: 'wifi'
|
tool: 'wifi'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Nächste Schritte
|
## Nächste Schritte
|
||||||
|
|
||||||
1. [ ] **Heute:** WiFi QR Generator implementieren (`/tools/wifi-qr-code`)
|
1. [ ] **Heute:** WiFi QR Generator implementieren (`/tools/wifi-qr-code`)
|
||||||
2. [ ] **Diese Woche:** VCard + WhatsApp Generator
|
2. [ ] **Diese Woche:** VCard + WhatsApp Generator
|
||||||
3. [ ] **Nächste Woche:** Google Search Console monitoren für erste Impressions
|
3. [ ] **Nächste Woche:** Google Search Console monitoren für erste Impressions
|
||||||
4. [ ] **Monat 2:** A/B Test Email-Capture vs. Direct Download
|
4. [ ] **Monat 2:** A/B Test Email-Capture vs. Direct Download
|
||||||
5. [ ] **Monat 3:** Phase 2 Tools (App Store, PDF, Menu)
|
5. [ ] **Monat 3:** Phase 2 Tools (App Store, PDF, Menu)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Referenzen & Inspiration
|
## Referenzen & Inspiration
|
||||||
|
|
||||||
- [HubSpot Website Grader](https://website.grader.com/) – Das Original "Engineering as Marketing"
|
- [HubSpot Website Grader](https://website.grader.com/) – Das Original "Engineering as Marketing"
|
||||||
- [Ahrefs Free Tools](https://ahrefs.com/free-seo-tools) – 12+ Free Tools als Lead Magnets
|
- [Ahrefs Free Tools](https://ahrefs.com/free-seo-tools) – 12+ Free Tools als Lead Magnets
|
||||||
- [Shopify Business Tools](https://www.shopify.com/tools) – Logo Maker, Invoice Generator, etc.
|
- [Shopify Business Tools](https://www.shopify.com/tools) – Logo Maker, Invoice Generator, etc.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Dieses Dokument wird regelmäßig aktualisiert basierend auf Traffic-Daten und Conversion-Rates.*
|
*Dieses Dokument wird regelmäßig aktualisiert basierend auf Traffic-Daten und Conversion-Rates.*
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,97 @@
|
||||||
# 🚀 Kostenlose Wachstums- & Backlink-Strategien (High DA)
|
# 🚀 Kostenlose Wachstums- & Backlink-Strategien (High DA)
|
||||||
|
|
||||||
Hier ist ein Plan, um schnell Domain Authority (DA) aufzubauen und kostenlose Reichweite zu generieren, basierend auf deinem aktuellen Status.
|
Hier ist ein Plan, um schnell Domain Authority (DA) aufzubauen und kostenlose Reichweite zu generieren, basierend auf deinem aktuellen Status.
|
||||||
|
|
||||||
## 1. "Parasite SEO" & High DA Content Platforms
|
## 1. "Parasite SEO" & High DA Content Platforms
|
||||||
Nutze die extrem hohe Domain Authority dieser Plattformen, um für Keywords zu ranken und starke Backlinks zu erhalten. "Cloud Parasites" ist ein guter Start, hier sind weitere:
|
Nutze die extrem hohe Domain Authority dieser Plattformen, um für Keywords zu ranken und starke Backlinks zu erhalten. "Cloud Parasites" ist ein guter Start, hier sind weitere:
|
||||||
|
|
||||||
* **LinkedIn Pulse (Artikel)**:
|
* **LinkedIn Pulse (Artikel)**:
|
||||||
* Schreibe Artikel wie *"Warum 90% der QR Codes falsch genutzt werden"* oder *"QR Code Tracking Guide 2026"*.
|
* Schreibe Artikel wie *"Warum 90% der QR Codes falsch genutzt werden"* oder *"QR Code Tracking Guide 2026"*.
|
||||||
* Nutze LinkedIn's eigene SEO-Power. Am Ende immer auf dein Tool verlinken.
|
* Nutze LinkedIn's eigene SEO-Power. Am Ende immer auf dein Tool verlinken.
|
||||||
* *DA: 99*
|
* *DA: 99*
|
||||||
* **Medium**:
|
* **Medium**:
|
||||||
* Importiere deine Blogposts (nutze den "Import" Button für Canonical Tags, damit du dich nicht selbst kannibalisierst, oder schreibe Zusammenfassungen).
|
* Importiere deine Blogposts (nutze den "Import" Button für Canonical Tags, damit du dich nicht selbst kannibalisierst, oder schreibe Zusammenfassungen).
|
||||||
* Publiziere in "Publications" (z.B. Marketing-fokussierte Pubs).
|
* Publiziere in "Publications" (z.B. Marketing-fokussierte Pubs).
|
||||||
* *DA: 95*
|
* *DA: 95*
|
||||||
* **Dev.to & Hashnode**:
|
* **Dev.to & Hashnode**:
|
||||||
* Da du schon auf `web.dev` (vermutlich Dev Community) bist: Veröffentliche technische "How-To" Artikel.
|
* Da du schon auf `web.dev` (vermutlich Dev Community) bist: Veröffentliche technische "How-To" Artikel.
|
||||||
* Thema: "How to build a QR Code Generator with Next.js" (und verlinke auf QR Master als die "Pro-Lösung").
|
* Thema: "How to build a QR Code Generator with Next.js" (und verlinke auf QR Master als die "Pro-Lösung").
|
||||||
* *DA: 91 / 88*
|
* *DA: 91 / 88*
|
||||||
* **GitHub Repository**:
|
* **GitHub Repository**:
|
||||||
* Erstelle ein Repository mit einer kuratierten Liste ("Awesome QR Code Tools") oder einem kleinen Open-Source-Skript.
|
* Erstelle ein Repository mit einer kuratierten Liste ("Awesome QR Code Tools") oder einem kleinen Open-Source-Skript.
|
||||||
* Die `README.md` ist ein sehr starker Backlink.
|
* Die `README.md` ist ein sehr starker Backlink.
|
||||||
* *DA: 96*
|
* *DA: 96*
|
||||||
* **NPM Package**:
|
* **NPM Package**:
|
||||||
* Veröffentliche ein kleines Wrapper-Package. In der Beschreibung auf deine Seite verlinken.
|
* Veröffentliche ein kleines Wrapper-Package. In der Beschreibung auf deine Seite verlinken.
|
||||||
* *DA: 96*
|
* *DA: 96*
|
||||||
|
|
||||||
## 2. Nischen-Communities & "Problem Solving"
|
## 2. Nischen-Communities & "Problem Solving"
|
||||||
Gehe weg von reinen "SaaS-Gründer" Communities hin zu den **Endnutzern**.
|
Gehe weg von reinen "SaaS-Gründer" Communities hin zu den **Endnutzern**.
|
||||||
|
|
||||||
* **Reddit (Laser-Fokus)**:
|
* **Reddit (Laser-Fokus)**:
|
||||||
* Suche nicht nur in r/SaaS. Suche in:
|
* Suche nicht nur in r/SaaS. Suche in:
|
||||||
* `r/WeddingPlanning`: "Wie mache ich einen QR Code für Einladungen?"
|
* `r/WeddingPlanning`: "Wie mache ich einen QR Code für Einladungen?"
|
||||||
* `r/Restaurateur`: "Digitale Speisekarten Lösungen?"
|
* `r/Restaurateur`: "Digitale Speisekarten Lösungen?"
|
||||||
* `r/RealEstate`: "QR Codes auf Flyern?"
|
* `r/RealEstate`: "QR Codes auf Flyern?"
|
||||||
* Sei hilfreich, erwähne dein Tool nur organisch ("Ich habe dafür ein Tool gebaut...").
|
* Sei hilfreich, erwähne dein Tool nur organisch ("Ich habe dafür ein Tool gebaut...").
|
||||||
* **Quora**:
|
* **Quora**:
|
||||||
* Beantworte Fragen wie *"Best free dynamic QR code generator?"*.
|
* Beantworte Fragen wie *"Best free dynamic QR code generator?"*.
|
||||||
* Schreibe ausführliche Antworten, nicht nur Links.
|
* Schreibe ausführliche Antworten, nicht nur Links.
|
||||||
* **Pinterest (Visuelle Suche)**:
|
* **Pinterest (Visuelle Suche)**:
|
||||||
* QR Codes sind visuell. Erstelle Pins mit "Creative QR Code Ideas", "Wedding QR Codes", "Restaurant Menu QR Styles".
|
* QR Codes sind visuell. Erstelle Pins mit "Creative QR Code Ideas", "Wedding QR Codes", "Restaurant Menu QR Styles".
|
||||||
* Verlinke jeden Pin auf deine Landing Page. Pinterest ist eine starke Traffic-Maschine für visuelle Themen.
|
* Verlinke jeden Pin auf deine Landing Page. Pinterest ist eine starke Traffic-Maschine für visuelle Themen.
|
||||||
|
|
||||||
## 3. Side Project Marketing (Engineering as Marketing)
|
## 3. Side Project Marketing (Engineering as Marketing)
|
||||||
Erstelle kleine, kostenlose Tools, die als "Lead Magnet" dienen.
|
Erstelle kleine, kostenlose Tools, die als "Lead Magnet" dienen.
|
||||||
|
|
||||||
* **Spezialisierte Generatoren**:
|
* **Spezialisierte Generatoren**:
|
||||||
* Erstelle Landingpages für spezifische Use-Cases: *"Kostenloser WiFi QR Code Generator"*, *"VCard QR Erstellen"*.
|
* Erstelle Landingpages für spezifische Use-Cases: *"Kostenloser WiFi QR Code Generator"*, *"VCard QR Erstellen"*.
|
||||||
* Diese Keywords sind einfacher zu ranken als "QR Code Generator".
|
* Diese Keywords sind einfacher zu ranken als "QR Code Generator".
|
||||||
* **Kostenlose Tools Verzeichnisse**:
|
* **Kostenlose Tools Verzeichnisse**:
|
||||||
* Es gibt Verzeichnisse nur für kostenlose Tools (z.B. "Tiny Tools", "Free for Dev").
|
* Es gibt Verzeichnisse nur für kostenlose Tools (z.B. "Tiny Tools", "Free for Dev").
|
||||||
|
|
||||||
## 4. Design & CSS Galleries (Für "Premium" Look)
|
## 4. Design & CSS Galleries (Für "Premium" Look)
|
||||||
Da dein Design "Premium" und hochwertig ist, reiche deine Seite bei Design-Gallerien ein. Das sind oft Do-Follow Backlinks von Design-Seiten.
|
Da dein Design "Premium" und hochwertig ist, reiche deine Seite bei Design-Gallerien ein. Das sind oft Do-Follow Backlinks von Design-Seiten.
|
||||||
|
|
||||||
* **One Page Love** (Sehr hochwertig)
|
* **One Page Love** (Sehr hochwertig)
|
||||||
* **SiteInspire**
|
* **SiteInspire**
|
||||||
* **Lapa Ninja**
|
* **Lapa Ninja**
|
||||||
* **Godly Website**
|
* **Godly Website**
|
||||||
* *Hinweis: Manche kosten eine kleine Gebühr für schnelle Prüfung, aber oft gibt es Free Submissions.*
|
* *Hinweis: Manche kosten eine kleine Gebühr für schnelle Prüfung, aber oft gibt es Free Submissions.*
|
||||||
|
|
||||||
## 5. Cold Outreach (Backlink Sniping)
|
## 5. Cold Outreach (Backlink Sniping)
|
||||||
* **"Best of" Listen**:
|
* **"Best of" Listen**:
|
||||||
* Suche bei Google nach *"Best QR Code Generators 2025"*.
|
* Suche bei Google nach *"Best QR Code Generators 2025"*.
|
||||||
* Schreibe die Autoren der Top 10 Artikel an.
|
* Schreibe die Autoren der Top 10 Artikel an.
|
||||||
* Pitch: *"Hey, cooler Artikel. Mein Tool QR Master ist neu und hat Feature X (z.B. bessere Analytics), das den anderen fehlt. Würde gut in deine Liste passen."*
|
* Pitch: *"Hey, cooler Artikel. Mein Tool QR Master ist neu und hat Feature X (z.B. bessere Analytics), das den anderen fehlt. Würde gut in deine Liste passen."*
|
||||||
* **Broken Link Building**:
|
* **Broken Link Building**:
|
||||||
* Suche nach Artikeln, die auf tote QR-Code-Tools verlinken (es gibt viele alte Tools, die offline gegangen sind).
|
* Suche nach Artikeln, die auf tote QR-Code-Tools verlinken (es gibt viele alte Tools, die offline gegangen sind).
|
||||||
* Schreibe den Webmaster an: *"Hey, der Link zu Tool X geht nicht mehr. Mein Tool ist eine super Alternative."*
|
* Schreibe den Webmaster an: *"Hey, der Link zu Tool X geht nicht mehr. Mein Tool ist eine super Alternative."*
|
||||||
|
|
||||||
## 6. Social Media "Content Repurposing"
|
## 6. Social Media "Content Repurposing"
|
||||||
Mach aus einem Content-Stück 10.
|
Mach aus einem Content-Stück 10.
|
||||||
|
|
||||||
* **Twitter/X Threads**: "Wie QR Codes dein Marketing ruinieren können (und wie man es richtig macht)".
|
* **Twitter/X Threads**: "Wie QR Codes dein Marketing ruinieren können (und wie man es richtig macht)".
|
||||||
* **LinkedIn Carousels**: PDF-Slider mit "5 Fehler bei QR Codes".
|
* **LinkedIn Carousels**: PDF-Slider mit "5 Fehler bei QR Codes".
|
||||||
* **Shorts/Reels**: Zeige den Screen, wie schnell man einen QR Code erstellt. Visuell & schnell.
|
* **Shorts/Reels**: Zeige den Screen, wie schnell man einen QR Code erstellt. Visuell & schnell.
|
||||||
|
|
||||||
## 7. Verzeichnisse (Checkliste)
|
## 7. Verzeichnisse (Checkliste)
|
||||||
Falls noch nicht erledigt (neben Product Hunt):
|
Falls noch nicht erledigt (neben Product Hunt):
|
||||||
|
|
||||||
* [ ] **Indie Hackers** (Product Page)
|
* [ ] **Indie Hackers** (Product Page)
|
||||||
* [ ] **Hacker News** ("Show HN")
|
* [ ] **Hacker News** ("Show HN")
|
||||||
* [ ] **Betalist**
|
* [ ] **Betalist**
|
||||||
* [ ] **10words**
|
* [ ] **10words**
|
||||||
* [ ] **Microlaunch**
|
* [ ] **Microlaunch**
|
||||||
* [ ] **Peerlist**
|
* [ ] **Peerlist**
|
||||||
* [ ] **AlternativeTo** (Als Alternative zu "QR Code Monkey" vorschlagen)
|
* [ ] **AlternativeTo** (Als Alternative zu "QR Code Monkey" vorschlagen)
|
||||||
|
|
||||||
## 8. Technical SEO & Lighthouse
|
## 8. Technical SEO & Lighthouse
|
||||||
Du erwähntest, Lighthouse soll 100 sein.
|
Du erwähntest, Lighthouse soll 100 sein.
|
||||||
* **Performance**: Bilder optimieren (WebP/AVIF), Lazy Loading.
|
* **Performance**: Bilder optimieren (WebP/AVIF), Lazy Loading.
|
||||||
* **Accessiblity**: `aria-labels` für alle Buttons, Kontraste prüfen.
|
* **Accessiblity**: `aria-labels` für alle Buttons, Kontraste prüfen.
|
||||||
* **SEO**: `meta description`, `title` tags, `canonical` URLs, `sitemap.xml`.
|
* **SEO**: `meta description`, `title` tags, `canonical` URLs, `sitemap.xml`.
|
||||||
* **Schema Markup**: Füge `SoftwareApplication` JSON-LD Schema auf deiner Homepage hinzu. Das hilft Google, dich als Software-Tool zu verstehen.
|
* **Schema Markup**: Füge `SoftwareApplication` JSON-LD Schema auf deiner Homepage hinzu. Das hilft Google, dich als Software-Tool zu verstehen.
|
||||||
|
|
||||||
---
|
---
|
||||||
**Sofort-Maßnahme**: Erstelle heute einen **LinkedIn Pulse Artikel** und einen **GitHub Account/Repo** für dein Projekt. Das sind 2 High-DA Backlinks garantiert.
|
**Sofort-Maßnahme**: Erstelle heute einen **LinkedIn Pulse Artikel** und einen **GitHub Account/Repo** für dein Projekt. Das sind 2 High-DA Backlinks garantiert.
|
||||||
|
|
|
||||||
80
ideen.md
80
ideen.md
|
|
@ -1,41 +1,41 @@
|
||||||
🚀 Neue Content-Typen
|
🚀 Neue Content-Typen
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
WiFi QR SSID, Passwort, Verschlüsselungstyp – perfekt für Cafés/Hotels
|
WiFi QR SSID, Passwort, Verschlüsselungstyp – perfekt für Cafés/Hotels
|
||||||
Event (VEVENT) Kalendereinträge direkt ins Handy importieren
|
Event (VEVENT) Kalendereinträge direkt ins Handy importieren
|
||||||
App Store Links Smart-Links die iOS/Android erkennen
|
App Store Links Smart-Links die iOS/Android erkennen
|
||||||
PayPal/Bitcoin Zahlungsaufforderungen per QR
|
PayPal/Bitcoin Zahlungsaufforderungen per QR
|
||||||
WhatsApp/Telegram Direkt-Chat mit vordefinierter Nachricht
|
WhatsApp/Telegram Direkt-Chat mit vordefinierter Nachricht
|
||||||
📊 Analytics-Erweiterungen
|
📊 Analytics-Erweiterungen
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
UTM-Parameter Automatische Kampagnen-Tags für Google Analytics
|
UTM-Parameter Automatische Kampagnen-Tags für Google Analytics
|
||||||
Conversion Tracking Ziel-URLs definieren und Conversion messen
|
Conversion Tracking Ziel-URLs definieren und Conversion messen
|
||||||
A/B Testing Zwei Ziel-URLs testen, welche besser performt
|
A/B Testing Zwei Ziel-URLs testen, welche besser performt
|
||||||
Scheduled Reports Wöchentliche/monatliche E-Mail-Reports
|
Scheduled Reports Wöchentliche/monatliche E-Mail-Reports
|
||||||
Export (CSV/PDF) Analytics-Daten exportieren
|
Export (CSV/PDF) Analytics-Daten exportieren
|
||||||
🎨 QR Design & Styling
|
🎨 QR Design & Styling
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
Design Templates Vorgefertigte Farb-/Logo-Kombinationen
|
Design Templates Vorgefertigte Farb-/Logo-Kombinationen
|
||||||
Frames & CTA "Scan me!" Rahmen um den QR Code
|
Frames & CTA "Scan me!" Rahmen um den QR Code
|
||||||
Dot Styles Runde Punkte, Diamanten, etc.
|
Dot Styles Runde Punkte, Diamanten, etc.
|
||||||
Eye Shapes Custom Corner-Marker Designs
|
Eye Shapes Custom Corner-Marker Designs
|
||||||
Gradient Colors Farbverläufe statt Vollfarben
|
Gradient Colors Farbverläufe statt Vollfarben
|
||||||
🗂️ Organisation & Teamwork
|
🗂️ Organisation & Teamwork
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
Folders/Projekte QR Codes in Ordner organisieren
|
Folders/Projekte QR Codes in Ordner organisieren
|
||||||
Tags & Filter Flexibles Tagging-System
|
Tags & Filter Flexibles Tagging-System
|
||||||
Team Workspaces Mehrere User pro Account (BUSINESS)
|
Team Workspaces Mehrere User pro Account (BUSINESS)
|
||||||
Activity Log Wer hat was wann geändert
|
Activity Log Wer hat was wann geändert
|
||||||
QR Code Archiv Soft-Delete statt Löschen
|
QR Code Archiv Soft-Delete statt Löschen
|
||||||
⚙️ Pro Features
|
⚙️ Pro Features
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
Passwortschutz QR führt zu Passwort-geschützter Seite
|
Passwortschutz QR führt zu Passwort-geschützter Seite
|
||||||
Ablaufdatum QR Code deaktiviert sich automatisch
|
Ablaufdatum QR Code deaktiviert sich automatisch
|
||||||
Scan-Limit Max. X Scans erlauben
|
Scan-Limit Max. X Scans erlauben
|
||||||
Geo-Targeting Verschiedene URLs je nach Standort
|
Geo-Targeting Verschiedene URLs je nach Standort
|
||||||
Device Detection Desktop vs. Mobile unterschiedliche URLs
|
Device Detection Desktop vs. Mobile unterschiedliche URLs
|
||||||
🔌 Integrationen
|
🔌 Integrationen
|
||||||
Feature Beschreibung
|
Feature Beschreibung
|
||||||
Zapier/Make Webhooks bei Scans triggern
|
Zapier/Make Webhooks bei Scans triggern
|
||||||
Google Sheets Scan-Daten automatisch exportieren
|
Google Sheets Scan-Daten automatisch exportieren
|
||||||
Slack Notifications Benachrichtigung bei X Scans
|
Slack Notifications Benachrichtigung bei X Scans
|
||||||
API für Entwickler Public API mit Token-Auth
|
API für Entwickler Public API mit Token-Auth
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
# QR Master - robots.txt
|
# QR Master - robots.txt
|
||||||
# Allow all search engines to crawl all pages
|
# Allow all search engines to crawl all pages
|
||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
|
|
||||||
# Sitemap location
|
# Sitemap location
|
||||||
Sitemap: https://www.qrmaster.net/sitemap.xml
|
Sitemap: https://www.qrmaster.net/sitemap.xml
|
||||||
|
|
||||||
# Crawl-delay (optional, be nice to servers)
|
# Crawl-delay (optional, be nice to servers)
|
||||||
Crawl-delay: 1
|
Crawl-delay: 1
|
||||||
|
|
||||||
# Disallow admin/api routes
|
# Disallow admin/api routes
|
||||||
Disallow: /api/
|
Disallow: /api/
|
||||||
Disallow: /dashboard/
|
Disallow: /dashboard/
|
||||||
Disallow: /_next/
|
Disallow: /_next/
|
||||||
|
|
||||||
# Allow all free tools explicitly
|
# Allow all free tools explicitly
|
||||||
Allow: /tools/
|
Allow: /tools/
|
||||||
|
|
|
||||||
|
|
@ -1,371 +1,371 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Bitcoin,
|
Bitcoin,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Wallet,
|
Wallet,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Crypto Options
|
// Crypto Options
|
||||||
const CRYPTO_CURRENCIES = [
|
const CRYPTO_CURRENCIES = [
|
||||||
{ value: 'bitcoin', label: 'Bitcoin (BTC)', color: '#F7931A', prefix: 'bitcoin:' },
|
{ value: 'bitcoin', label: 'Bitcoin (BTC)', color: '#F7931A', prefix: 'bitcoin:' },
|
||||||
{ value: 'ethereum', label: 'Ethereum (ETH)', color: '#627EEA', prefix: 'ethereum:' },
|
{ value: 'ethereum', label: 'Ethereum (ETH)', color: '#627EEA', prefix: 'ethereum:' },
|
||||||
{ value: 'usdt', label: 'Tether (USDT)', color: '#26A17B', prefix: '' }, // Commonly ERC20/TRC20 - keeping raw for safety
|
{ value: 'usdt', label: 'Tether (USDT)', color: '#26A17B', prefix: '' }, // Commonly ERC20/TRC20 - keeping raw for safety
|
||||||
{ value: 'solana', label: 'Solana (SOL)', color: '#14F195', prefix: 'solana:' },
|
{ value: 'solana', label: 'Solana (SOL)', color: '#14F195', prefix: 'solana:' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Bitcoin Orange', value: '#F7931A' },
|
{ name: 'Bitcoin Orange', value: '#F7931A' },
|
||||||
{ name: 'Ethereum Blue', value: '#627EEA' },
|
{ name: 'Ethereum Blue', value: '#627EEA' },
|
||||||
{ name: 'Tether Green', value: '#26A17B' },
|
{ name: 'Tether Green', value: '#26A17B' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Dark Blue', value: '#1A1265' },
|
{ name: 'Dark Blue', value: '#1A1265' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'pay', label: 'Pay Now' },
|
{ id: 'pay', label: 'Pay Now' },
|
||||||
{ id: 'donate', label: 'Donate' },
|
{ id: 'donate', label: 'Donate' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function CryptoGenerator() {
|
export default function CryptoGenerator() {
|
||||||
const [currency, setCurrency] = useState('bitcoin');
|
const [currency, setCurrency] = useState('bitcoin');
|
||||||
const [address, setAddress] = useState('');
|
const [address, setAddress] = useState('');
|
||||||
const [amount, setAmount] = useState('');
|
const [amount, setAmount] = useState('');
|
||||||
const [qrMode, setQrMode] = useState<'universal' | 'wallet'>('universal');
|
const [qrMode, setQrMode] = useState<'universal' | 'wallet'>('universal');
|
||||||
const [qrColor, setQrColor] = useState('#F7931A');
|
const [qrColor, setQrColor] = useState('#F7931A');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Generate URL based on selected mode
|
// Generate URL based on selected mode
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
if (!address.trim()) return 'https://www.qrmaster.net';
|
if (!address.trim()) return 'https://www.qrmaster.net';
|
||||||
|
|
||||||
const cleanAddr = address.trim();
|
const cleanAddr = address.trim();
|
||||||
|
|
||||||
if (qrMode === 'wallet') {
|
if (qrMode === 'wallet') {
|
||||||
// Wallet Direct Mode - Uses crypto URI scheme
|
// Wallet Direct Mode - Uses crypto URI scheme
|
||||||
// Only works when scanning FROM a wallet app (Coinbase, Trust Wallet, etc.)
|
// Only works when scanning FROM a wallet app (Coinbase, Trust Wallet, etc.)
|
||||||
const prefixes: Record<string, string> = {
|
const prefixes: Record<string, string> = {
|
||||||
bitcoin: 'bitcoin:',
|
bitcoin: 'bitcoin:',
|
||||||
ethereum: 'ethereum:',
|
ethereum: 'ethereum:',
|
||||||
solana: 'solana:',
|
solana: 'solana:',
|
||||||
usdt: '', // USDT doesn't have a standard URI
|
usdt: '', // USDT doesn't have a standard URI
|
||||||
};
|
};
|
||||||
const prefix = prefixes[currency] || '';
|
const prefix = prefixes[currency] || '';
|
||||||
if (!prefix) return cleanAddr; // USDT fallback
|
if (!prefix) return cleanAddr; // USDT fallback
|
||||||
let uri = `${prefix}${cleanAddr}`;
|
let uri = `${prefix}${cleanAddr}`;
|
||||||
if (amount) uri += `?amount=${amount}`;
|
if (amount) uri += `?amount=${amount}`;
|
||||||
return uri;
|
return uri;
|
||||||
} else {
|
} else {
|
||||||
// Universal Mode - Blockchain explorer links
|
// Universal Mode - Blockchain explorer links
|
||||||
// Works with ANY phone camera
|
// Works with ANY phone camera
|
||||||
switch (currency) {
|
switch (currency) {
|
||||||
case 'bitcoin':
|
case 'bitcoin':
|
||||||
return `https://blockchair.com/bitcoin/address/${cleanAddr}`;
|
return `https://blockchair.com/bitcoin/address/${cleanAddr}`;
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
return `https://etherscan.io/address/${cleanAddr}`;
|
return `https://etherscan.io/address/${cleanAddr}`;
|
||||||
case 'solana':
|
case 'solana':
|
||||||
return `https://solscan.io/account/${cleanAddr}`;
|
return `https://solscan.io/account/${cleanAddr}`;
|
||||||
case 'usdt':
|
case 'usdt':
|
||||||
return `https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=${cleanAddr}`;
|
return `https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=${cleanAddr}`;
|
||||||
default:
|
default:
|
||||||
return cleanAddr;
|
return cleanAddr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `${currency}-qr-code.png`;
|
link.download = `${currency}-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `${currency}-qr-code.svg`;
|
link.download = `${currency}-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Crypto Details */}
|
{/* Crypto Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Wallet className="w-5 h-5 text-slate-900" />
|
<Wallet className="w-5 h-5 text-slate-900" />
|
||||||
Wallet Details
|
Wallet Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
||||||
<Select
|
<Select
|
||||||
value={currency}
|
value={currency}
|
||||||
options={CRYPTO_CURRENCIES}
|
options={CRYPTO_CURRENCIES}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
setCurrency(val);
|
setCurrency(val);
|
||||||
const col = CRYPTO_CURRENCIES.find(c => c.value === val)?.color;
|
const col = CRYPTO_CURRENCIES.find(c => c.value === val)?.color;
|
||||||
if (col) setQrColor(col);
|
if (col) setQrColor(col);
|
||||||
}}
|
}}
|
||||||
className="h-12 w-full rounded-xl border-slate-200"
|
className="h-12 w-full rounded-xl border-slate-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Wallet Address</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Wallet Address</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={`Enter ${currency} address`}
|
placeholder={`Enter ${currency} address`}
|
||||||
value={address}
|
value={address}
|
||||||
onChange={(e) => setAddress(e.target.value)}
|
onChange={(e) => setAddress(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900 font-mono text-sm"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900 font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
type="number"
|
type="number"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* QR Mode Toggle */}
|
{/* QR Mode Toggle */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">QR Code Mode</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">QR Code Mode</label>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setQrMode('universal')}
|
onClick={() => setQrMode('universal')}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
||||||
qrMode === 'universal'
|
qrMode === 'universal'
|
||||||
? "bg-slate-900 text-white border-slate-900"
|
? "bg-slate-900 text-white border-slate-900"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Universal (Web)
|
Universal (Web)
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setQrMode('wallet')}
|
onClick={() => setQrMode('wallet')}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
||||||
qrMode === 'wallet'
|
qrMode === 'wallet'
|
||||||
? "bg-slate-900 text-white border-slate-900"
|
? "bg-slate-900 text-white border-slate-900"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Wallet Direct
|
Wallet Direct
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-500 mt-2">
|
<p className="text-xs text-slate-500 mt-2">
|
||||||
{qrMode === 'universal'
|
{qrMode === 'universal'
|
||||||
? "Works with any phone camera. Opens blockchain explorer."
|
? "Works with any phone camera. Opens blockchain explorer."
|
||||||
: "Requires scanning from a wallet app. Enables direct payment."}
|
: "Requires scanning from a wallet app. Enables direct payment."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-slate-900" />
|
<Sparkles className="w-5 h-5 text-slate-900" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-slate-900 text-white border-slate-900"
|
? "bg-slate-900 text-white border-slate-900"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
{address.trim() ? (
|
{address.trim() ? (
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getUrl()}
|
value={getUrl()}
|
||||||
size={240}
|
size={240}
|
||||||
level="Q"
|
level="Q"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-center border-2 border-dashed border-slate-200 rounded-xl"
|
className="flex items-center justify-center border-2 border-dashed border-slate-200 rounded-xl"
|
||||||
style={{ width: 240, height: 240 }}
|
style={{ width: 240, height: 240 }}
|
||||||
>
|
>
|
||||||
<div className="text-center text-slate-400 p-6">
|
<div className="text-center text-slate-400 p-6">
|
||||||
<Wallet className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
<Wallet className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||||
<p className="text-sm font-medium">Enter wallet address</p>
|
<p className="text-sm font-medium">Enter wallet address</p>
|
||||||
<p className="text-xs mt-1">to generate QR code</p>
|
<p className="text-xs mt-1">to generate QR code</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Bitcoin className="w-4 h-4 text-slate-400 shrink-0" />
|
<Bitcoin className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate capitalize">{currency}</span>
|
<span className="truncate capitalize">{currency}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1 truncate px-2">
|
<div className="text-xs text-slate-500 mt-1 truncate px-2">
|
||||||
{address || 'Wallet Address'}
|
{address || 'Wallet Address'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning copies the wallet address or opens a crypto app.
|
Scanning copies the wallet address or opens a crypto app.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Accept Crypto for Business?</h3>
|
<h3 className="font-bold text-lg">Accept Crypto for Business?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Create professional, branded payment pages for your store.
|
Create professional, branded payment pages for your store.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Business Tools
|
Get Business Tools
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,362 +1,362 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import CryptoGenerator from './CryptoGenerator';
|
import CryptoGenerator from './CryptoGenerator';
|
||||||
import { Bitcoin, Shield, Zap, Smartphone, Wallet, Coins, Sparkles, Download, Share2 } from 'lucide-react';
|
import { Bitcoin, Shield, Zap, Smartphone, Wallet, Coins, Sparkles, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Crypto QR Code Generator | Bitcoin, Ethereum & USDT | QR Master',
|
title: 'Free Crypto QR Code Generator | Bitcoin, Ethereum & USDT | QR Master',
|
||||||
description: 'Create a QR code for your Crypto wallet address. Supports Bitcoin (BTC), Ethereum (ETH), USDT, and more. Essential for easy payments and donations.',
|
description: 'Create a QR code for your Crypto wallet address. Supports Bitcoin (BTC), Ethereum (ETH), USDT, and more. Essential for easy payments and donations.',
|
||||||
keywords: ['crypto qr code', 'bitcoin qr generator', 'ethereum qr code', 'crypto wallet qr', 'donation qr code'],
|
keywords: ['crypto qr code', 'bitcoin qr generator', 'ethereum qr code', 'crypto wallet qr', 'donation qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/crypto-qr-code',
|
canonical: 'https://qrmaster.io/tools/crypto-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Crypto QR Code Generator | QR Master',
|
title: 'Free Crypto QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to accept Crypto payments securely. Supports BTC, ETH, SOL.',
|
description: 'Generate QR codes to accept Crypto payments securely. Supports BTC, ETH, SOL.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/crypto-qr-code',
|
url: 'https://qrmaster.io/tools/crypto-qr-code',
|
||||||
images: [{ url: '/og-crypto-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-crypto-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Crypto QR Code Generator',
|
title: 'Free Crypto QR Code Generator',
|
||||||
description: 'Create secure QR codes for your crypto wallet.',
|
description: 'Create secure QR codes for your crypto wallet.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Crypto QR Code Generator',
|
name: 'Crypto QR Code Generator',
|
||||||
applicationCategory: 'FinanceApplication',
|
applicationCategory: 'FinanceApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '870',
|
ratingCount: '870',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that contain your cryptocurrency wallet address for easy payments.',
|
description: 'Generate QR codes that contain your cryptocurrency wallet address for easy payments.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Crypto QR Code',
|
name: 'How to Create a Crypto QR Code',
|
||||||
description: 'Create a QR code for your Bitcoin or Ethereum wallet.',
|
description: 'Create a QR code for your Bitcoin or Ethereum wallet.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Select Currency',
|
name: 'Select Currency',
|
||||||
text: 'Choose your cryptocurrency from the list (Bitcoin, Ethereum, USDT, etc.).',
|
text: 'Choose your cryptocurrency from the list (Bitcoin, Ethereum, USDT, etc.).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Enter Address',
|
name: 'Enter Address',
|
||||||
text: 'Copy your public wallet address from your crypto app and paste it into the "Wallet Address" field.',
|
text: 'Copy your public wallet address from your crypto app and paste it into the "Wallet Address" field.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Add Amount (Optional)',
|
name: 'Add Amount (Optional)',
|
||||||
text: 'If you are requesting a specific payment, enter the amount to pre-fill the transaction.',
|
text: 'If you are requesting a specific payment, enter the amount to pre-fill the transaction.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Customize QR',
|
name: 'Customize QR',
|
||||||
text: 'Select a brand color (like Bitcoin Orange or Ethereum Blue) and add a frame like "Pay Now".',
|
text: 'Select a brand color (like Bitcoin Orange or Ethereum Blue) and add a frame like "Pay Now".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Download the QR code image and share it to receive funds securely.',
|
text: 'Download the QR code image and share it to receive funds securely.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it safe to share my wallet address?',
|
name: 'Is it safe to share my wallet address?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key.',
|
text: 'Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Which currencies are supported?',
|
name: 'Which currencies are supported?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins.',
|
text: 'Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I add a specific amount?',
|
name: 'Can I add a specific amount?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value.',
|
text: 'Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work with all wallets?',
|
name: 'Does it work with all wallets?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.).',
|
text: 'Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.).',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Are there any fees?',
|
name: 'Are there any fees?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them.',
|
text: 'No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CryptoQRCodePage() {
|
export default function CryptoQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Crypto QR Code Generator" toolSlug="crypto-qr-code" />
|
<ToolBreadcrumb toolName="Crypto QR Code Generator" toolSlug="crypto-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-900">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-900">
|
||||||
<div className="absolute inset-0 opacity-20">
|
<div className="absolute inset-0 opacity-20">
|
||||||
{/* Circuit Pattern */}
|
{/* Circuit Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="circuit_pattern" width="100" height="100" patternUnits="userSpaceOnUse">
|
<pattern id="circuit_pattern" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||||
<path d="M10 10 H 90 V 90 H 10 Z" stroke="none" fill="none" />
|
<path d="M10 10 H 90 V 90 H 10 Z" stroke="none" fill="none" />
|
||||||
<circle cx="20" cy="20" r="2" fill="#F7931A" />
|
<circle cx="20" cy="20" r="2" fill="#F7931A" />
|
||||||
<circle cx="80" cy="80" r="2" fill="#627EEA" />
|
<circle cx="80" cy="80" r="2" fill="#627EEA" />
|
||||||
<path d="M20 20 L 50 20 L 50 50" stroke="white" strokeWidth="1" strokeOpacity="0.1" />
|
<path d="M20 20 L 50 20 L 50 50" stroke="white" strokeWidth="1" strokeOpacity="0.1" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#circuit_pattern)" />
|
<rect width="100%" height="100%" fill="url(#circuit_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-orange-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-orange-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — Secure & Private
|
Free Tool — Secure & Private
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Accept Payments with <br className="hidden lg:block" />
|
Accept Payments with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#F7931A] to-[#F2A900]">Crypto QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#F7931A] to-[#F2A900]">Crypto QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Share your wallet address securely. Supports Bitcoin, Ethereum, USDT, and more.
|
Share your wallet address securely. Supports Bitcoin, Ethereum, USDT, and more.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Error-free transfers.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Error-free transfers.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Bitcoin className="w-4 h-4 text-[#F7931A]" />
|
<Bitcoin className="w-4 h-4 text-[#F7931A]" />
|
||||||
Bitcoin
|
Bitcoin
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Coins className="w-4 h-4 text-[#627EEA]" />
|
<Coins className="w-4 h-4 text-[#627EEA]" />
|
||||||
Ethereum & Altcoins
|
Ethereum & Altcoins
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Wallet className="w-4 h-4 text-white" />
|
<Wallet className="w-4 h-4 text-white" />
|
||||||
Wallet Connect
|
Wallet Connect
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-orange-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-orange-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden text-white">
|
<div className="w-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden text-white">
|
||||||
<div className="flex justify-between items-start mb-4">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<Bitcoin className="w-8 h-8 opacity-80" />
|
<Bitcoin className="w-8 h-8 opacity-80" />
|
||||||
<div className="bg-white/20 px-2 py-1 rounded text-xs">BTC</div>
|
<div className="bg-white/20 px-2 py-1 rounded text-xs">BTC</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold tracking-wider mb-1">0.05 BTC</div>
|
<div className="text-2xl font-bold tracking-wider mb-1">0.05 BTC</div>
|
||||||
<div className="text-xs opacity-70">$3,450.25 USD</div>
|
<div className="text-xs opacity-70">$3,450.25 USD</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#333" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#333" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-slate-900 border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-slate-900 border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-orange-500/20 p-2 rounded-full">
|
<div className="bg-orange-500/20 p-2 rounded-full">
|
||||||
<Wallet className="w-5 h-5 text-orange-500" />
|
<Wallet className="w-5 h-5 text-orange-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Payment</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Payment</div>
|
||||||
<div className="text-sm font-bold text-white">Receive Crypto</div>
|
<div className="text-sm font-bold text-white">Receive Crypto</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<CryptoGenerator />
|
<CryptoGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Crypto QR Codes Work
|
How Crypto QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Coins className="w-6 h-6 text-white" />
|
<Coins className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Select</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Select</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Choose your crypto currency (BTC, ETH, etc.).
|
Choose your crypto currency (BTC, ETH, etc.).
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Wallet className="w-6 h-6 text-white" />
|
<Wallet className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Enter your public wallet address.
|
Enter your public wallet address.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Zap className="w-6 h-6 text-white" />
|
<Zap className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Amount</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Amount</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Optionally specify an amount to request.
|
Optionally specify an amount to request.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Sparkles className="w-6 h-6 text-white" />
|
<Sparkles className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Style</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Style</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Customize colors and add a 'Pay' frame.
|
Customize colors and add a 'Pay' frame.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-white" />
|
<Download className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your secure QR code image.
|
Save your secure QR code image.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Crypto QR codes.
|
Common questions about Crypto QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it safe to share my wallet address?"
|
question="Is it safe to share my wallet address?"
|
||||||
answer="Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key."
|
answer="Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Which currencies are supported?"
|
question="Which currencies are supported?"
|
||||||
answer="Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins."
|
answer="Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I add a specific amount?"
|
question="Can I add a specific amount?"
|
||||||
answer="Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value."
|
answer="Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work with all wallets?"
|
question="Does it work with all wallets?"
|
||||||
answer="Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.)."
|
answer="Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.)."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Are there any fees?"
|
question="Are there any fees?"
|
||||||
answer="No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them."
|
answer="No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,297 +1,297 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Mail,
|
Mail,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Type,
|
Type,
|
||||||
FileText
|
FileText
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richRed: '#dc2626',
|
richRed: '#dc2626',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Email Red', value: '#dc2626' },
|
{ name: 'Email Red', value: '#dc2626' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'email', label: 'Email Me' },
|
{ id: 'email', label: 'Email Me' },
|
||||||
{ id: 'contact', label: 'Contact' },
|
{ id: 'contact', label: 'Contact' },
|
||||||
{ id: 'send', label: 'Send Mail' },
|
{ id: 'send', label: 'Send Mail' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function EmailGenerator() {
|
export default function EmailGenerator() {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: '',
|
email: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
body: ''
|
body: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const [qrColor, setQrColor] = useState('#dc2626');
|
const [qrColor, setQrColor] = useState('#dc2626');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Generate Mailto Link
|
// Generate Mailto Link
|
||||||
// Format: mailto:email?subject=...&body=...
|
// Format: mailto:email?subject=...&body=...
|
||||||
const getMailtoUrl = () => {
|
const getMailtoUrl = () => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (formData.subject) params.append('subject', formData.subject);
|
if (formData.subject) params.append('subject', formData.subject);
|
||||||
if (formData.body) params.append('body', formData.body);
|
if (formData.body) params.append('body', formData.body);
|
||||||
|
|
||||||
const queryString = params.toString();
|
const queryString = params.toString();
|
||||||
return `mailto:${formData.email}${queryString ? `?${queryString}` : ''}`;
|
return `mailto:${formData.email}${queryString ? `?${queryString}` : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `email-qr-code.png`;
|
link.download = `email-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const urlBlob = URL.createObjectURL(blob);
|
const urlBlob = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = urlBlob;
|
link.href = urlBlob;
|
||||||
link.download = `email-qr-code.svg`;
|
link.download = `email-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Input Fields */}
|
{/* Input Fields */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Mail className="w-5 h-5 text-red-600" />
|
<Mail className="w-5 h-5 text-red-600" />
|
||||||
Email Details
|
Email Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Recipient Email</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Recipient Email</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Mail className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
<Mail className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
||||||
<Input
|
<Input
|
||||||
name="email"
|
name="email"
|
||||||
placeholder="recipient@example.com"
|
placeholder="recipient@example.com"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="h-11 rounded-xl pl-9"
|
className="h-11 rounded-xl pl-9"
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Subject Line</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Subject Line</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Type className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
<Type className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
||||||
<Input
|
<Input
|
||||||
name="subject"
|
name="subject"
|
||||||
placeholder="e.g. Inquiry about services"
|
placeholder="e.g. Inquiry about services"
|
||||||
value={formData.subject}
|
value={formData.subject}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="h-11 rounded-xl pl-9"
|
className="h-11 rounded-xl pl-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Body Message (Optional)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Body Message (Optional)</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="body"
|
name="body"
|
||||||
placeholder="Hi there, I would like to know more about..."
|
placeholder="Hi there, I would like to know more about..."
|
||||||
value={formData.body}
|
value={formData.body}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full h-32 p-3 rounded-xl border border-slate-200 focus:border-red-600 focus:ring-1 focus:ring-red-600 focus:outline-none resize-none text-base"
|
className="w-full h-32 p-3 rounded-xl border border-slate-200 focus:border-red-600 focus:ring-1 focus:ring-red-600 focus:outline-none resize-none text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-red-600" />
|
<Sparkles className="w-5 h-5 text-red-600" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-red-600 text-white border-red-600"
|
? "bg-red-600 text-white border-red-600"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getMailtoUrl() || 'mailto:hello@example.com'}
|
value={getMailtoUrl() || 'mailto:hello@example.com'}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info */}
|
{/* Info */}
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-red-50 mx-auto mb-3">
|
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-red-50 mx-auto mb-3">
|
||||||
<Mail className="w-6 h-6 text-red-600" />
|
<Mail className="w-6 h-6 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 text-lg truncate max-w-[260px] mx-auto">
|
<h3 className="font-bold text-slate-900 text-lg truncate max-w-[260px] mx-auto">
|
||||||
{formData.email || 'Email QR Code'}
|
{formData.email || 'Email QR Code'}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-red-600 hover:bg-red-700 text-white shadow-lg"
|
className="bg-red-600 hover:bg-red-700 text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
100% free. No signup required.
|
100% free. No signup required.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-red-600 to-rose-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-red-600 to-rose-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Change your email address often?</h3>
|
<h3 className="font-bold text-lg">Change your email address often?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes allow you to update the recipient without reprinting.</p>
|
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes allow you to update the recipient without reprinting.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-red-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-red-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Go Dynamic
|
Go Dynamic
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,273 +1,273 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import EmailGenerator from './EmailGenerator';
|
import EmailGenerator from './EmailGenerator';
|
||||||
import { Mail, Zap, Smartphone, Lock, Download, Sparkles } from 'lucide-react';
|
import { Mail, Zap, Smartphone, Lock, Download, Sparkles } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Email QR Code Generator | Mailto QR | QR Master',
|
title: 'Free Email QR Code Generator | Mailto QR | QR Master',
|
||||||
description: 'Create an Email QR code to send emails instantly. Pre-fill subject and body. 100% free and client-side secure.',
|
description: 'Create an Email QR code to send emails instantly. Pre-fill subject and body. 100% free and client-side secure.',
|
||||||
keywords: ['email qr code', 'mailto qr', 'email generator', 'free qr code'],
|
keywords: ['email qr code', 'mailto qr', 'email generator', 'free qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/email-qr-code',
|
canonical: 'https://qrmaster.io/tools/email-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Email QR Code Generator | QR Master',
|
title: 'Free Email QR Code Generator | QR Master',
|
||||||
description: 'Send emails instantly with a custom QR code. Add recipient, subject, and body.',
|
description: 'Send emails instantly with a custom QR code. Add recipient, subject, and body.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/email-qr-code',
|
url: 'https://qrmaster.io/tools/email-qr-code',
|
||||||
images: [{ url: '/og-email-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-email-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD
|
// JSON-LD
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Email QR Code Generator',
|
name: 'Email QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||||
description: 'Generate Email QR codes for mailto links with subject and body.',
|
description: 'Generate Email QR codes for mailto links with subject and body.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create an Email QR Code',
|
name: 'How to Create an Email QR Code',
|
||||||
step: [
|
step: [
|
||||||
{ '@type': 'HowToStep', position: 1, name: 'Enter Recipient', text: 'Type the email address you want to receive emails at.' },
|
{ '@type': 'HowToStep', position: 1, name: 'Enter Recipient', text: 'Type the email address you want to receive emails at.' },
|
||||||
{ '@type': 'HowToStep', position: 2, name: 'Add Details', text: 'Optional: Add a pre-filled subject line and body text.' },
|
{ '@type': 'HowToStep', position: 2, name: 'Add Details', text: 'Optional: Add a pre-filled subject line and body text.' },
|
||||||
{ '@type': 'HowToStep', position: 3, name: 'Customize', text: 'Choose a brand color and add a call-to-action frame.' },
|
{ '@type': 'HowToStep', position: 3, name: 'Customize', text: 'Choose a brand color and add a call-to-action frame.' },
|
||||||
{ '@type': 'HowToStep', position: 4, name: 'Download', text: 'Download your QR code in PNG or SVG.' },
|
{ '@type': 'HowToStep', position: 4, name: 'Download', text: 'Download your QR code in PNG or SVG.' },
|
||||||
{ '@type': 'HowToStep', position: 5, name: 'Share', text: 'Add to business cards or flyers.' },
|
{ '@type': 'HowToStep', position: 5, name: 'Share', text: 'Add to business cards or flyers.' },
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'How does it work?',
|
name: 'How does it work?',
|
||||||
acceptedAnswer: { '@type': 'Answer', text: 'When scanned, it opens the user\'s default email app (like Gmail or Outlook) with a new draft composed to your address.' }
|
acceptedAnswer: { '@type': 'Answer', text: 'When scanned, it opens the user\'s default email app (like Gmail or Outlook) with a new draft composed to your address.' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I add a subject line?',
|
name: 'Can I add a subject line?',
|
||||||
acceptedAnswer: { '@type': 'Answer', text: 'Yes! You can pre-fill the subject line and the body content so the sender just has to hit send.' }
|
acceptedAnswer: { '@type': 'Answer', text: 'Yes! You can pre-fill the subject line and the body content so the sender just has to hit send.' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: { '@type': 'Answer', text: 'Yes, 100% free with unlimited scans.' }
|
acceptedAnswer: { '@type': 'Answer', text: 'Yes, 100% free with unlimited scans.' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work with attachments?',
|
name: 'Does it work with attachments?',
|
||||||
acceptedAnswer: { '@type': 'Answer', text: 'No. The standard mailto format does not support attaching files automatically. Users will have to attach files manually.' }
|
acceptedAnswer: { '@type': 'Answer', text: 'No. The standard mailto format does not support attaching files automatically. Users will have to attach files manually.' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it private?',
|
name: 'Is it private?',
|
||||||
acceptedAnswer: { '@type': 'Answer', text: 'Yes. The data is encoded directly into the QR code. We do not store your email or message data.' }
|
acceptedAnswer: { '@type': 'Answer', text: 'Yes. The data is encoded directly into the QR code. We do not store your email or message data.' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EmailPage() {
|
export default function EmailPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Email QR Code Generator" toolSlug="email-qr-code" />
|
<ToolBreadcrumb toolName="Email QR Code Generator" toolSlug="email-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#dc2626' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#dc2626' }}>
|
||||||
|
|
||||||
{/* Background Pattern */}
|
{/* Background Pattern */}
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
|
|
||||||
{/* Left: Text Content */}
|
{/* Left: Text Content */}
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
The Smartest Way to <br className="hidden lg:block" />
|
The Smartest Way to <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-red-200 to-rose-200">Receive Emails</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-red-200 to-rose-200">Receive Emails</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Create a QR code that opens a pre-composed email instantly.
|
Create a QR code that opens a pre-composed email instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for feedback & inquiries.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for feedback & inquiries.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Mail className="w-4 h-4 text-red-300" />
|
<Mail className="w-4 h-4 text-red-300" />
|
||||||
Instant Draft
|
Instant Draft
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-yellow-300" />
|
<Zap className="w-4 h-4 text-yellow-300" />
|
||||||
Pre-filled Content
|
Pre-filled Content
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-red-300" />
|
<Smartphone className="w-4 h-4 text-red-300" />
|
||||||
Mobile Ready
|
Mobile Ready
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Visual Abstract Composition */}
|
{/* Right: Visual Abstract Composition */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
{/* Decorative Glow */}
|
{/* Decorative Glow */}
|
||||||
<div className="absolute w-[500px] h-[500px] bg-red-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-red-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
{/* Floating Glass Card */}
|
{/* Floating Glass Card */}
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
{/* Mock QR */}
|
{/* Mock QR */}
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#b91c1c" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#b91c1c" level="Q" />
|
||||||
{/* Scan Line */}
|
{/* Scan Line */}
|
||||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-red-500 shadow-[0_0_20px_rgba(220,38,38,1)] animate-pulse" />
|
<div className="absolute top-1/2 left-0 w-full h-1 bg-red-500 shadow-[0_0_20px_rgba(220,38,38,1)] animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full space-y-3">
|
<div className="w-full space-y-3">
|
||||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-red-100 p-2 rounded-full">
|
<div className="bg-red-100 p-2 rounded-full">
|
||||||
<Mail className="w-5 h-5 text-red-600" />
|
<Mail className="w-5 h-5 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Live</div>
|
<div className="text-sm font-bold text-slate-900">Live</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<EmailGenerator />
|
<EmailGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Email QR Codes Work
|
How Email QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Mail className="w-6 h-6 text-[#1A1265]" />
|
<Mail className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Add Email</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Add Email</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Enter the address and subject.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Enter the address and subject.</p>
|
||||||
</article>
|
</article>
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Sparkles className="w-6 h-6 text-[#1A1265]" />
|
<Sparkles className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Pick a brand color.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Pick a brand color.</p>
|
||||||
</article>
|
</article>
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Add a cool frame.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Add a cool frame.</p>
|
||||||
</article>
|
</article>
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||||
</article>
|
</article>
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Print and get emails.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Print and get emails.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">Common questions about Email QR codes.</p>
|
<p className="text-slate-600 text-center mb-10">Common questions about Email QR codes.</p>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem question="Does it work with Gmail?" answer="Yes, and Outlook, Apple Mail, Yahoo, etc. It opens the default mail app on the user's device." />
|
<FaqItem question="Does it work with Gmail?" answer="Yes, and Outlook, Apple Mail, Yahoo, etc. It opens the default mail app on the user's device." />
|
||||||
<FaqItem question="Is it reversible?" answer="Yes, if you made a mistake you would need to generate a new code, as static QR codes cannot be edited after creation." />
|
<FaqItem question="Is it reversible?" answer="Yes, if you made a mistake you would need to generate a new code, as static QR codes cannot be edited after creation." />
|
||||||
<FaqItem question="Is this tool free?" answer="Yes, completely free to use." />
|
<FaqItem question="Is this tool free?" answer="Yes, completely free to use." />
|
||||||
<FaqItem question="Can I attach files?" answer="No. The mailto standard does not support automatic attachment of files. Users must attach them manually." />
|
<FaqItem question="Can I attach files?" answer="No. The mailto standard does not support automatic attachment of files. Users must attach them manually." />
|
||||||
<FaqItem question="Is it private?" answer="Yes. The data is encoded directly into the QR code. We do not store your email or message data." />
|
<FaqItem question="Is it private?" answer="Yes. The data is encoded directly into the QR code. We do not store your email or message data." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FAQ Item Component
|
// FAQ Item Component
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,331 +1,331 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Clock,
|
Clock,
|
||||||
MapPin,
|
MapPin,
|
||||||
AlignLeft
|
AlignLeft
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#F5F3FF', // Violet-50
|
paleGrey: '#F5F3FF', // Violet-50
|
||||||
primary: '#7C3AED', // Violet-600
|
primary: '#7C3AED', // Violet-600
|
||||||
primaryDark: '#6D28D9', // Violet-700
|
primaryDark: '#6D28D9', // Violet-700
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Purple', value: '#9333EA' },
|
{ name: 'Purple', value: '#9333EA' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Pink', value: '#DB2777' },
|
{ name: 'Pink', value: '#DB2777' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'event', label: 'Event' },
|
{ id: 'event', label: 'Event' },
|
||||||
{ id: 'save', label: 'Save Date' },
|
{ id: 'save', label: 'Save Date' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function EventGenerator() {
|
export default function EventGenerator() {
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [location, setLocation] = useState('');
|
const [location, setLocation] = useState('');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
const [startDate, setStartDate] = useState('');
|
const [startDate, setStartDate] = useState('');
|
||||||
const [endDate, setEndDate] = useState('');
|
const [endDate, setEndDate] = useState('');
|
||||||
|
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Format Date for iCal: YYYYMMDDTHHMMSS
|
// Format Date for iCal: YYYYMMDDTHHMMSS
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
const d = new Date(dateString);
|
const d = new Date(dateString);
|
||||||
// Basic formatting, assumes local time for simplicity in this static tool
|
// Basic formatting, assumes local time for simplicity in this static tool
|
||||||
const year = d.getFullYear();
|
const year = d.getFullYear();
|
||||||
const month = ('0' + (d.getMonth() + 1)).slice(-2);
|
const month = ('0' + (d.getMonth() + 1)).slice(-2);
|
||||||
const day = ('0' + d.getDate()).slice(-2);
|
const day = ('0' + d.getDate()).slice(-2);
|
||||||
const hours = ('0' + d.getHours()).slice(-2);
|
const hours = ('0' + d.getHours()).slice(-2);
|
||||||
const minutes = ('0' + d.getMinutes()).slice(-2);
|
const minutes = ('0' + d.getMinutes()).slice(-2);
|
||||||
const seconds = ('0' + d.getSeconds()).slice(-2);
|
const seconds = ('0' + d.getSeconds()).slice(-2);
|
||||||
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
|
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const qrValue = [
|
const qrValue = [
|
||||||
'BEGIN:VEVENT',
|
'BEGIN:VEVENT',
|
||||||
`SUMMARY:${title}`,
|
`SUMMARY:${title}`,
|
||||||
`LOCATION:${location}`,
|
`LOCATION:${location}`,
|
||||||
`DESCRIPTION:${description}`,
|
`DESCRIPTION:${description}`,
|
||||||
`DTSTART:${formatDate(startDate)}`,
|
`DTSTART:${formatDate(startDate)}`,
|
||||||
`DTEND:${formatDate(endDate)}`,
|
`DTEND:${formatDate(endDate)}`,
|
||||||
'END:VEVENT'
|
'END:VEVENT'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `event-qr-code.png`;
|
link.download = `event-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `event-qr-code.svg`;
|
link.download = `event-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Event Details */}
|
{/* Event Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Calendar className="w-5 h-5 text-[#7C3AED]" />
|
<Calendar className="w-5 h-5 text-[#7C3AED]" />
|
||||||
Event Details
|
Event Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Event Title</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Event Title</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Summer Party"
|
placeholder="Summer Party"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Start Time</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Start Time</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={(e) => setStartDate(e.target.value)}
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">End Time</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">End Time</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onChange={(e) => setEndDate(e.target.value)}
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Location</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Location</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<MapPin className="absolute left-3 top-3.5 w-5 h-5 text-slate-400" />
|
<MapPin className="absolute left-3 top-3.5 w-5 h-5 text-slate-400" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="123 Main St, New York"
|
placeholder="123 Main St, New York"
|
||||||
value={location}
|
value={location}
|
||||||
onChange={(e) => setLocation(e.target.value)}
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
className="pl-10 h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
className="pl-10 h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Description</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full h-24 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
className="w-full h-24 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
||||||
placeholder="Join us for a celebration..."
|
placeholder="Join us for a celebration..."
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#7C3AED]" />
|
<Sparkles className="w-5 h-5 text-[#7C3AED]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#7C3AED] text-white border-[#7C3AED]"
|
? "bg-[#7C3AED] text-white border-[#7C3AED]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={(title || startDate) ? qrValue : "Title"}
|
value={(title || startDate) ? qrValue : "Title"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Calendar className="w-4 h-4 text-[#7C3AED] shrink-0" />
|
<Calendar className="w-4 h-4 text-[#7C3AED] shrink-0" />
|
||||||
<span className="truncate">{title || 'Event Title'}</span>
|
<span className="truncate">{title || 'Event Title'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
{(startDate) && (
|
{(startDate) && (
|
||||||
<div className="text-xs text-slate-500 mt-1 flex items-center justify-center gap-1">
|
<div className="text-xs text-slate-500 mt-1 flex items-center justify-center gap-1">
|
||||||
<Clock className="w-3 h-3" />
|
<Clock className="w-3 h-3" />
|
||||||
{new Date(startDate).toLocaleDateString()}
|
{new Date(startDate).toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#7C3AED] hover:bg-[#6D28D9] text-white shadow-lg"
|
className="bg-[#7C3AED] hover:bg-[#6D28D9] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning adds the event to the user's calendar.
|
Scanning adds the event to the user's calendar.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#7C3AED] to-[#6D28D9] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#7C3AED] to-[#6D28D9] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Planning a big event?</h3>
|
<h3 className="font-bold text-lg">Planning a big event?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Use a Dynamic QR Code to track RSVPs and update event details if the schedule changes.
|
Use a Dynamic QR Code to track RSVPs and update event details if the schedule changes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#7C3AED] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#7C3AED] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Dynamic Events
|
Get Dynamic Events
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,353 +1,353 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import EventGenerator from './EventGenerator';
|
import EventGenerator from './EventGenerator';
|
||||||
import { Calendar, Shield, Zap, Smartphone, Clock, UserCheck, Download, Share2, Check } from 'lucide-react';
|
import { Calendar, Shield, Zap, Smartphone, Clock, UserCheck, Download, Share2, Check } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Event QR Code Generator | Add to Calendar | QR Master',
|
title: 'Free Event QR Code Generator | Add to Calendar | QR Master',
|
||||||
description: 'Create a QR code for your event. Scanners can instantly save the date, time, and location to their phone calendar. Perfect for invitations and flyers.',
|
description: 'Create a QR code for your event. Scanners can instantly save the date, time, and location to their phone calendar. Perfect for invitations and flyers.',
|
||||||
keywords: ['event qr code', 'calendar qr code', 'save the date qr', 'ical qr generator', 'invitation qr code'],
|
keywords: ['event qr code', 'calendar qr code', 'save the date qr', 'ical qr generator', 'invitation qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/event-qr-code',
|
canonical: 'https://qrmaster.io/tools/event-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Event QR Code Generator | QR Master',
|
title: 'Free Event QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to save events to calendars. Share dates easily.',
|
description: 'Generate QR codes to save events to calendars. Share dates easily.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/event-qr-code',
|
url: 'https://qrmaster.io/tools/event-qr-code',
|
||||||
images: [{ url: '/og-event-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-event-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Event QR Code Generator',
|
title: 'Free Event QR Code Generator',
|
||||||
description: 'Create QR codes for events. Instant save-to-calendar.',
|
description: 'Create QR codes for events. Instant save-to-calendar.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Event QR Code Generator',
|
name: 'Event QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '760',
|
ratingCount: '760',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that add event details to the user\'s digital calendar.',
|
description: 'Generate QR codes that add event details to the user\'s digital calendar.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create an Event QR Code',
|
name: 'How to Create an Event QR Code',
|
||||||
description: 'Create a QR code that saves an event to a calendar.',
|
description: 'Create a QR code that saves an event to a calendar.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Event Details',
|
name: 'Enter Event Details',
|
||||||
text: 'Fill in the Event Title, Location, Description, Start Time, and End Time.',
|
text: 'Fill in the Event Title, Location, Description, Start Time, and End Time.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose a color and frame style like "Save the Date".',
|
text: 'Choose a color and frame style like "Save the Date".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code and add it to your invitations.',
|
text: 'Save the QR code and add it to your invitations.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code to ensure the event details and times are correct.',
|
text: 'Scan the code to ensure the event details and times are correct.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Distribute it via email, flyers, or social media.',
|
text: 'Distribute it via email, flyers, or social media.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT45S',
|
totalTime: 'PT45S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Which calendars does it support?',
|
name: 'Which calendars does it support?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'The QR code uses the standard iCalendar (ICS) format. It works with Apple Calendar, Google Calendar, Outlook, and most other mobile calendar apps.',
|
text: 'The QR code uses the standard iCalendar (ICS) format. It works with Apple Calendar, Google Calendar, Outlook, and most other mobile calendar apps.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I change the date after printing?',
|
name: 'Can I change the date after printing?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. This is a static QR code, meaning the event details are permanently embedded in the image. If the date changes, you must create a new QR code. Use our Dynamic QR Code to edit events anytime.',
|
text: 'No. This is a static QR code, meaning the event details are permanently embedded in the image. If the date changes, you must create a new QR code. Use our Dynamic QR Code to edit events anytime.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a limit to the description length?',
|
name: 'Is there a limit to the description length?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, because the data is stored in the QR code pattern. We recommend keeping descriptions concise (under 300 characters) to ensure the code remains easy to scan.',
|
text: 'Yes, because the data is stored in the QR code pattern. We recommend keeping descriptions concise (under 300 characters) to ensure the code remains easy to scan.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do users need an app?',
|
name: 'Do users need an app?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No special app is needed. Standard camera apps on iOS and Android can read the code and will prompt the user to "Add to Calendar".',
|
text: 'No special app is needed. Standard camera apps on iOS and Android can read the code and will prompt the user to "Add to Calendar".',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Creating and scanning the code is completely free and requires no signup.',
|
text: 'Yes. Creating and scanning the code is completely free and requires no signup.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EventQRCodePage() {
|
export default function EventQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Event QR Code Generator" toolSlug="event-qr-code" />
|
<ToolBreadcrumb toolName="Event QR Code Generator" toolSlug="event-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#5B21B6' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#5B21B6' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-violet-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-violet-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-violet-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-violet-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Create Scannable <br className="hidden lg:block" />
|
Create Scannable <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-300 to-fuchsia-300">Calendar Invites</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-300 to-fuchsia-300">Calendar Invites</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Share your event details instantly. Visitors scan to "Save the Date" directly to their phone calendar.
|
Share your event details instantly. Visitors scan to "Save the Date" directly to their phone calendar.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for invitations.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for invitations.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Calendar className="w-4 h-4 text-violet-300" />
|
<Calendar className="w-4 h-4 text-violet-300" />
|
||||||
Instant Save
|
Instant Save
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Clock className="w-4 h-4 text-amber-400" />
|
<Clock className="w-4 h-4 text-amber-400" />
|
||||||
Timezone Smart
|
Timezone Smart
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<UserCheck className="w-4 h-4 text-purple-400" />
|
<UserCheck className="w-4 h-4 text-purple-400" />
|
||||||
Native Support
|
Native Support
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-2 hover:rotate-1 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-2 hover:rotate-1 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center text-center">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center text-center">
|
||||||
<div className="w-full h-2 bg-red-500 rounded-full mb-3" />
|
<div className="w-full h-2 bg-red-500 rounded-full mb-3" />
|
||||||
<div className="text-xs uppercase font-bold text-red-500 tracking-widest mb-1">DECEMBER</div>
|
<div className="text-xs uppercase font-bold text-red-500 tracking-widest mb-1">DECEMBER</div>
|
||||||
<div className="text-4xl font-black text-slate-900 leading-none mb-1">25</div>
|
<div className="text-4xl font-black text-slate-900 leading-none mb-1">25</div>
|
||||||
<div className="text-xs text-slate-400">Saturday • 8:00 PM</div>
|
<div className="text-xs text-slate-400">Saturday • 8:00 PM</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-emerald-100 p-2 rounded-full">
|
<div className="bg-emerald-100 p-2 rounded-full">
|
||||||
<Check className="w-5 h-5 text-emerald-600" />
|
<Check className="w-5 h-5 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Event</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Event</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Added to Cal</div>
|
<div className="text-sm font-bold text-slate-900">Added to Cal</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<EventGenerator />
|
<EventGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Event QR Codes Work
|
How Event QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Calendar className="w-7 h-7 text-[#1A1265]" />
|
<Calendar className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Set Details</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Set Details</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Enter the event name, location, and start/end times.
|
Enter the event name, location, and start/end times.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Guests scan the code from your invite, poster, or flyer.
|
Guests scan the code from your invite, poster, or flyer.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Clock className="w-6 h-6 text-[#1A1265]" />
|
<Clock className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your event QR code.
|
Save your event QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Guests scan the code.
|
Guests scan the code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<UserCheck className="w-6 h-6 text-[#1A1265]" />
|
<UserCheck className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Save</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Save</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
They tap "Add to Calendar."
|
They tap "Add to Calendar."
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Event QR codes.
|
Common questions about Event QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does this work with Google Calendar?"
|
question="Does this work with Google Calendar?"
|
||||||
answer="Yes, the generated QR code creates a standard .ics file event, which is compatible with Google Calendar, Apple Calendar, Outlook, and most others."
|
answer="Yes, the generated QR code creates a standard .ics file event, which is compatible with Google Calendar, Apple Calendar, Outlook, and most others."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is the QR code reusable?"
|
question="Is the QR code reusable?"
|
||||||
answer="No. Because the specific date and time are embedded in the code, you cannot change them later. If the event is rescheduled, you must generate a new QR code."
|
answer="No. Because the specific date and time are embedded in the code, you cannot change them later. If the event is rescheduled, you must generate a new QR code."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What happens if the event is in a different time zone?"
|
question="What happens if the event is in a different time zone?"
|
||||||
answer="The user's calendar will usually convert the time to their local time zone automatically when they save it."
|
answer="The user's calendar will usually convert the time to their local time zone automatically when they save it."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes. Creating and scanning the code is completely free."
|
answer="Yes. Creating and scanning the code is completely free."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,248 +1,248 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Facebook,
|
Facebook,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
Globe
|
Globe
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - Facebook Theme
|
// QR Color Options - Facebook Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Facebook Blue', value: '#1877F2' },
|
{ name: 'Facebook Blue', value: '#1877F2' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Dark Blue', value: '#1A1265' },
|
{ name: 'Dark Blue', value: '#1A1265' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Purple', value: '#7C3AED' },
|
{ name: 'Purple', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'follow', label: 'Follow Us' },
|
{ id: 'follow', label: 'Follow Us' },
|
||||||
{ id: 'like', label: 'Like Us' },
|
{ id: 'like', label: 'Like Us' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function FacebookGenerator() {
|
export default function FacebookGenerator() {
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#1877F2'); // Default to FB Blue
|
const [qrColor, setQrColor] = useState('#1877F2'); // Default to FB Blue
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `facebook-qr-code.png`;
|
link.download = `facebook-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `facebook-qr-code.svg`;
|
link.download = `facebook-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Facebook Details */}
|
{/* Facebook Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Facebook className="w-5 h-5 text-[#1877F2]" />
|
<Facebook className="w-5 h-5 text-[#1877F2]" />
|
||||||
Facebook Page or Profile
|
Facebook Page or Profile
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Facebook URL</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Facebook URL</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://facebook.com/yourpage"
|
placeholder="https://facebook.com/yourpage"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1877F2] focus:ring-[#1877F2]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1877F2] focus:ring-[#1877F2]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Paste the full link to your profile, page, group, or post.</p>
|
<p className="text-xs text-slate-500 mt-2">Paste the full link to your profile, page, group, or post.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#1877F2]" />
|
<Sparkles className="w-5 h-5 text-[#1877F2]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#1877F2] text-white border-[#1877F2]"
|
? "bg-[#1877F2] text-white border-[#1877F2]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={url || "https://facebook.com"}
|
value={url || "https://facebook.com"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Facebook className="w-4 h-4 text-slate-400 shrink-0" />
|
<Facebook className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{url ? url.replace('https://', '') : 'facebook.com/...'}</span>
|
<span className="truncate">{url ? url.replace('https://', '') : 'facebook.com/...'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Opens in Facebook App</div>
|
<div className="text-xs text-slate-500 mt-1">Opens in Facebook App</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#1877F2] hover:bg-[#155ebd] text-white shadow-lg"
|
className="bg-[#1877F2] hover:bg-[#155ebd] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning redirects directly to the Facebook profile or page.
|
Scanning redirects directly to the Facebook profile or page.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#1877F2] to-[#155ebd] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#1877F2] to-[#155ebd] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Running a Social Media Campaign?</h3>
|
<h3 className="font-bold text-lg">Running a Social Media Campaign?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Dynamic QR Codes allow you to track clicks, likes, and engagement rates in real-time.
|
Dynamic QR Codes allow you to track clicks, likes, and engagement rates in real-time.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#1877F2] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#1877F2] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Social Analytics
|
Get Social Analytics
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,365 +1,365 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import FacebookGenerator from './FacebookGenerator';
|
import FacebookGenerator from './FacebookGenerator';
|
||||||
import { Facebook, Shield, Zap, Smartphone, ThumbsUp, Users, Download, Share2 } from 'lucide-react';
|
import { Facebook, Shield, Zap, Smartphone, ThumbsUp, Users, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Facebook QR Code Generator | Get Likes & Follows | QR Master',
|
title: 'Free Facebook QR Code Generator | Get Likes & Follows | QR Master',
|
||||||
description: 'Create a QR code for your Facebook Page, Profile, or Group. Scanners are redirected to the Facebook app instantly to like and follow. Free & Easy.',
|
description: 'Create a QR code for your Facebook Page, Profile, or Group. Scanners are redirected to the Facebook app instantly to like and follow. Free & Easy.',
|
||||||
keywords: ['facebook qr code', 'fb qr generator', 'facebook page qr', 'follow qr code', 'social media qr code'],
|
keywords: ['facebook qr code', 'fb qr generator', 'facebook page qr', 'follow qr code', 'social media qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/facebook-qr-code',
|
canonical: 'https://qrmaster.io/tools/facebook-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Facebook QR Code Generator | QR Master',
|
title: 'Free Facebook QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to grow your Facebook audience. Instant app redirect.',
|
description: 'Generate QR codes to grow your Facebook audience. Instant app redirect.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/facebook-qr-code',
|
url: 'https://qrmaster.io/tools/facebook-qr-code',
|
||||||
images: [{ url: '/og-facebook-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-facebook-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Facebook QR Code Generator',
|
title: 'Free Facebook QR Code Generator',
|
||||||
description: 'Create QR codes for Facebook. Boost your engagement.',
|
description: 'Create QR codes for Facebook. Boost your engagement.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Facebook QR Code Generator',
|
name: 'Facebook QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '1120',
|
ratingCount: '1120',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that direct users to a Facebook page, profile, or post.',
|
description: 'Generate QR codes that direct users to a Facebook page, profile, or post.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Facebook QR Code',
|
name: 'How to Create a Facebook QR Code',
|
||||||
description: 'Create a QR code that opens a Facebook page.',
|
description: 'Create a QR code that opens a Facebook page.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Get Link',
|
name: 'Get Link',
|
||||||
text: 'Copy the URL of your Facebook Page, Profile, or Group.',
|
text: 'Copy the URL of your Facebook Page, Profile, or Group.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Paste Link',
|
name: 'Paste Link',
|
||||||
text: 'Paste the URL into the generator.',
|
text: 'Paste the URL into the generator.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose your brand color and add a call-to-action frame.',
|
text: 'Choose your brand color and add a call-to-action frame.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code and print it on your marketing materials.',
|
text: 'Save the QR code and print it on your marketing materials.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Distribute it on flyers, business cards, or posters.',
|
text: 'Distribute it on flyers, business cards, or posters.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it open the Facebook app?',
|
name: 'Does it open the Facebook app?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! On most mobile devices, standard Facebook links are automatically detected and opened in the Facebook app if it is installed.',
|
text: 'Yes! On most mobile devices, standard Facebook links are automatically detected and opened in the Facebook app if it is installed.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I link to a specific post?',
|
name: 'Can I link to a specific post?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Absolutely. Just paste the direct link to the post (click the timestamp on the post to get the link).',
|
text: 'Absolutely. Just paste the direct link to the post (click the timestamp on the post to get the link).',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for Facebook Events?',
|
name: 'Does it work for Facebook Events?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Simply copy the full URL of your Facebook Event and paste it into the generator.',
|
text: 'Yes. Simply copy the full URL of your Facebook Event and paste it into the generator.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, this generator is 100% free to use for personal or business purposes.',
|
text: 'Yes, this generator is 100% free to use for personal or business purposes.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track scans?',
|
name: 'Can I track scans?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'This static QR code does not include analytics. To track how many people scan your code, you should use our Dynamic QR Code service.',
|
text: 'This static QR code does not include analytics. To track how many people scan your code, you should use our Dynamic QR Code service.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FacebookQRCodePage() {
|
export default function FacebookQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Facebook QR Code Generator" toolSlug="facebook-qr-code" />
|
<ToolBreadcrumb toolName="Facebook QR Code Generator" toolSlug="facebook-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1877F2' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1877F2' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
{/* Facebook Pattern */}
|
{/* Facebook Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="fb_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
<pattern id="fb_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||||
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#fb_pattern)" />
|
<rect width="100%" height="100%" fill="url(#fb_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-300 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-300 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-300"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-300"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Grow Your Audience with <br className="hidden lg:block" />
|
Grow Your Audience with <br className="hidden lg:block" />
|
||||||
<span className="text-white drop-shadow-md">Facebook QR Codes</span>
|
<span className="text-white drop-shadow-md">Facebook QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-blue-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-blue-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Make it easy for customers to find and follow you. A single scan opens your Page directly in the Facebook app.
|
Make it easy for customers to find and follow you. A single scan opens your Page directly in the Facebook app.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost likes instantly.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost likes instantly.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<ThumbsUp className="w-4 h-4 text-blue-200" />
|
<ThumbsUp className="w-4 h-4 text-blue-200" />
|
||||||
Get Likes
|
Get Likes
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-yellow-300" />
|
<Zap className="w-4 h-4 text-yellow-300" />
|
||||||
Instant Follow
|
Instant Follow
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-green-300" />
|
<Smartphone className="w-4 h-4 text-green-300" />
|
||||||
App Friendly
|
App Friendly
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex items-center gap-3">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex items-center gap-3">
|
||||||
<div className="w-12 h-12 rounded-full bg-gradient-to-tr from-blue-600 to-blue-400 p-0.5">
|
<div className="w-12 h-12 rounded-full bg-gradient-to-tr from-blue-600 to-blue-400 p-0.5">
|
||||||
<div className="w-full h-full bg-white rounded-full flex items-center justify-center">
|
<div className="w-full h-full bg-white rounded-full flex items-center justify-center">
|
||||||
<Facebook className="w-6 h-6 text-[#1877F2]" fill="#1877F2" />
|
<Facebook className="w-6 h-6 text-[#1877F2]" fill="#1877F2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2.5 w-24 bg-slate-800 rounded-full mb-1.5" />
|
<div className="h-2.5 w-24 bg-slate-800 rounded-full mb-1.5" />
|
||||||
<div className="h-2 w-16 bg-slate-300 rounded-full" />
|
<div className="h-2 w-16 bg-slate-300 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
<button className="ml-auto bg-[#1877F2] text-white px-3 py-1 rounded text-xs font-bold">
|
<button className="ml-auto bg-[#1877F2] text-white px-3 py-1 rounded text-xs font-bold">
|
||||||
Like
|
Like
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#1877F2" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#1877F2" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-blue-100 p-2 rounded-full">
|
<div className="bg-blue-100 p-2 rounded-full">
|
||||||
<Users className="w-5 h-5 text-blue-600" />
|
<Users className="w-5 h-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Followers</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Followers</div>
|
||||||
<div className="text-sm font-bold text-slate-900">+1 New</div>
|
<div className="text-sm font-bold text-slate-900">+1 New</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<FacebookGenerator />
|
<FacebookGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Facebook QR Codes Work
|
How Facebook QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Facebook className="w-7 h-7 text-[#1877F2]" />
|
<Facebook className="w-7 h-7 text-[#1877F2]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Copy Link</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Copy Link</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Go to your Facebook Page or Profile and copy the URL from the browser address bar.
|
Go to your Facebook Page or Profile and copy the URL from the browser address bar.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1877F2]" />
|
<Smartphone className="w-7 h-7 text-[#1877F2]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Your customers scan the code using their phone camera.
|
Your customers scan the code using their phone camera.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<ThumbsUp className="w-6 h-6 text-[#1877F2]" />
|
<ThumbsUp className="w-6 h-6 text-[#1877F2]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Engage</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Engage</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
The Facebook app opens directly.
|
The Facebook app opens directly.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1877F2]" />
|
<Download className="w-6 h-6 text-[#1877F2]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your high-res QR code.
|
Save your high-res QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#1877F2]" />
|
<Share2 className="w-6 h-6 text-[#1877F2]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Print it and start getting likes.
|
Print it and start getting likes.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Facebook QR codes.
|
Common questions about Facebook QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Will this work for Facebook Groups?"
|
question="Will this work for Facebook Groups?"
|
||||||
answer="Yes! You can paste the link to your Facebook Group, and the QR code will direcr users to join."
|
answer="Yes! You can paste the link to your Facebook Group, and the QR code will direcr users to join."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What if the user doesn't have the Facebook app?"
|
question="What if the user doesn't have the Facebook app?"
|
||||||
answer="The link will open in their mobile web browser instead, so they can still see your page and log in."
|
answer="The link will open in their mobile web browser instead, so they can still see your page and log in."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I customize the color?"
|
question="Can I customize the color?"
|
||||||
answer="Yes. While Facebook Blue is recommended for recognition, you can choose any color to match your brand."
|
answer="Yes. While Facebook Blue is recommended for recognition, you can choose any color to match your brand."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is the QR code permanent?"
|
question="Is the QR code permanent?"
|
||||||
answer="Yes. As long as your Facebook URL doesn't change, this QR code will work forever."
|
answer="Yes. As long as your Facebook URL doesn't change, this QR code will work forever."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work for Facebook Events?"
|
question="Does it work for Facebook Events?"
|
||||||
answer="Yes. Simply copy the full URL of your Facebook Event and paste it into the generator."
|
answer="Yes. Simply copy the full URL of your Facebook Event and paste it into the generator."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,293 +1,293 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
MapPin,
|
MapPin,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Navigation,
|
Navigation,
|
||||||
Globe
|
Globe
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#ECFDF5', // Emerald-50
|
paleGrey: '#ECFDF5', // Emerald-50
|
||||||
primary: '#10B981', // Emerald-500
|
primary: '#10B981', // Emerald-500
|
||||||
primaryDark: '#047857', // Emerald-700
|
primaryDark: '#047857', // Emerald-700
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Navy', value: '#1E3A8A' },
|
{ name: 'Navy', value: '#1E3A8A' },
|
||||||
{ name: 'Sky', value: '#0EA5E9' },
|
{ name: 'Sky', value: '#0EA5E9' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'location', label: 'Location' },
|
{ id: 'location', label: 'Location' },
|
||||||
{ id: 'map', label: 'View Map' },
|
{ id: 'map', label: 'View Map' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function GeolocationGenerator() {
|
export default function GeolocationGenerator() {
|
||||||
const [latitude, setLatitude] = useState('');
|
const [latitude, setLatitude] = useState('');
|
||||||
const [longitude, setLongitude] = useState('');
|
const [longitude, setLongitude] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Using Google Maps Universal Link for best compatibility
|
// Using Google Maps Universal Link for best compatibility
|
||||||
const qrValue = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
|
const qrValue = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `location-qr-code.png`;
|
link.download = `location-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `location-qr-code.svg`;
|
link.download = `location-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCurrentLocation = () => {
|
const getCurrentLocation = () => {
|
||||||
if (navigator.geolocation) {
|
if (navigator.geolocation) {
|
||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
(position) => {
|
(position) => {
|
||||||
setLatitude(position.coords.latitude.toFixed(6));
|
setLatitude(position.coords.latitude.toFixed(6));
|
||||||
setLongitude(position.coords.longitude.toFixed(6));
|
setLongitude(position.coords.longitude.toFixed(6));
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error("Error getting location: ", error);
|
console.error("Error getting location: ", error);
|
||||||
alert("Could not access location. Please enter manually.");
|
alert("Could not access location. Please enter manually.");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
alert("Geolocation is not supported by this browser.");
|
alert("Geolocation is not supported by this browser.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Location Details */}
|
{/* Location Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<MapPin className="w-5 h-5 text-[#10B981]" />
|
<MapPin className="w-5 h-5 text-[#10B981]" />
|
||||||
Coordinates
|
Coordinates
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
onClick={getCurrentLocation}
|
onClick={getCurrentLocation}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-[#047857] border-[#047857]/20 hover:bg-[#047857]/5"
|
className="text-[#047857] border-[#047857]/20 hover:bg-[#047857]/5"
|
||||||
>
|
>
|
||||||
<Navigation className="w-3 h-3 mr-2" />
|
<Navigation className="w-3 h-3 mr-2" />
|
||||||
Get Current Location
|
Get Current Location
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Latitude</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Latitude</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="40.712776"
|
placeholder="40.712776"
|
||||||
value={latitude}
|
value={latitude}
|
||||||
onChange={(e) => setLatitude(e.target.value)}
|
onChange={(e) => setLatitude(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Longitude</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Longitude</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="-74.005974"
|
placeholder="-74.005974"
|
||||||
value={longitude}
|
value={longitude}
|
||||||
onChange={(e) => setLongitude(e.target.value)}
|
onChange={(e) => setLongitude(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
Tip: You can copy-paste coordinates directly from Google Maps.
|
Tip: You can copy-paste coordinates directly from Google Maps.
|
||||||
(Right-click a location on standard Maps, then click the coordinates to copy).
|
(Right-click a location on standard Maps, then click the coordinates to copy).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#10B981]" />
|
<Sparkles className="w-5 h-5 text-[#10B981]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#10B981] text-white border-[#10B981]"
|
? "bg-[#10B981] text-white border-[#10B981]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={(latitude && longitude) ? qrValue : "https://maps.google.com"}
|
value={(latitude && longitude) ? qrValue : "https://maps.google.com"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<MapPin className="w-4 h-4 text-[#10B981] shrink-0" />
|
<MapPin className="w-4 h-4 text-[#10B981] shrink-0" />
|
||||||
<span className="truncate">{latitude || 'Lat'}, {longitude || 'Long'}</span>
|
<span className="truncate">{latitude || 'Lat'}, {longitude || 'Long'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Google Maps Location</div>
|
<div className="text-xs text-slate-500 mt-1">Google Maps Location</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#10B981] hover:bg-[#047857] text-white shadow-lg"
|
className="bg-[#10B981] hover:bg-[#047857] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning opens the location directly in Google Maps.
|
Scanning opens the location directly in Google Maps.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#10B981] to-[#047857] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#10B981] to-[#047857] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need a Business Map?</h3>
|
<h3 className="font-bold text-lg">Need a Business Map?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Create a Dynamic QR Code for your business location. If you move, just update the location without reprinting.
|
Create a Dynamic QR Code for your business location. If you move, just update the location without reprinting.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#047857] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#047857] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Dynamic Maps
|
Get Dynamic Maps
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,360 +1,360 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import GeolocationGenerator from './GeolocationGenerator';
|
import GeolocationGenerator from './GeolocationGenerator';
|
||||||
import { MapPin, Shield, Zap, Smartphone, Navigation, Map, Download, Share2 } from 'lucide-react';
|
import { MapPin, Shield, Zap, Smartphone, Navigation, Map, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Geolocation QR Code Generator | Maps & Directions | QR Master',
|
title: 'Free Geolocation QR Code Generator | Maps & Directions | QR Master',
|
||||||
description: 'Create a QR code for a specific location using Latitude and Longitude. Scanners will open Google Maps directly to your pin. Free & Precise.',
|
description: 'Create a QR code for a specific location using Latitude and Longitude. Scanners will open Google Maps directly to your pin. Free & Precise.',
|
||||||
keywords: ['location qr code', 'maps qr code', 'google maps qr generator', 'geolocation qr', 'coordinates qr code'],
|
keywords: ['location qr code', 'maps qr code', 'google maps qr generator', 'geolocation qr', 'coordinates qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/geolocation-qr-code',
|
canonical: 'https://qrmaster.io/tools/geolocation-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Geolocation QR Code Generator | QR Master',
|
title: 'Free Geolocation QR Code Generator | QR Master',
|
||||||
description: 'Navigate users to any location with a QR code. Opens directly in Google Maps.',
|
description: 'Navigate users to any location with a QR code. Opens directly in Google Maps.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/geolocation-qr-code',
|
url: 'https://qrmaster.io/tools/geolocation-qr-code',
|
||||||
images: [{ url: '/og-geolocation-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-geolocation-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Geolocation QR Code Generator',
|
title: 'Free Geolocation QR Code Generator',
|
||||||
description: 'Create QR codes for maps and locations. Instant and free.',
|
description: 'Create QR codes for maps and locations. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Geolocation QR Code Generator',
|
name: 'Geolocation QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.7',
|
ratingValue: '4.7',
|
||||||
ratingCount: '890',
|
ratingCount: '890',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that open specific geographic coordinates in map applications.',
|
description: 'Generate QR codes that open specific geographic coordinates in map applications.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Location QR Code',
|
name: 'How to Create a Location QR Code',
|
||||||
description: 'Create a QR code that points to a specific map location.',
|
description: 'Create a QR code that points to a specific map location.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Get Coordinates',
|
name: 'Get Coordinates',
|
||||||
text: 'Find the Latitude and Longitude of your location (e.g., from Google Maps).',
|
text: 'Find the Latitude and Longitude of your location (e.g., from Google Maps).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Enter Data',
|
name: 'Enter Data',
|
||||||
text: 'Paste the coordinates into the generator.',
|
text: 'Paste the coordinates into the generator.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose a color and style for your map QR code.',
|
text: 'Choose a color and style for your map QR code.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save your QR code as a high-quality image.',
|
text: 'Save your QR code as a high-quality image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Place it on invitations, signs, or your website.',
|
text: 'Place it on invitations, signs, or your website.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT45S',
|
totalTime: 'PT45S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Which map app does it open?',
|
name: 'Which map app does it open?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Our generator creates a universal Google Maps link. On most devices, this will open the Google Maps app if installed, or the browser version if not. It is the most compatible method.',
|
text: 'Our generator creates a universal Google Maps link. On most devices, this will open the Google Maps app if installed, or the browser version if not. It is the most compatible method.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'How do I find my Latitude and Longitude?',
|
name: 'How do I find my Latitude and Longitude?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'On Google Maps desktop: Right-click any spot on the map. The first item in the menu is the coordinates. Click to copy them.',
|
text: 'On Google Maps desktop: Right-click any spot on the map. The first item in the menu is the coordinates. Click to copy them.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work offline?',
|
name: 'Does it work offline?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'The QR code itself can be scanned offline, but the user will likely need an internet connection to load the map and get directions.',
|
text: 'The QR code itself can be scanned offline, but the user will likely need an internet connection to load the map and get directions.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I use an address instead?',
|
name: 'Can I use an address instead?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'For precise results, we use coordinates. However, you can use our URL Generator and paste a link to your Google Maps address search result if you prefer.',
|
text: 'For precise results, we use coordinates. However, you can use our URL Generator and paste a link to your Google Maps address search result if you prefer.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, generating this location QR code is completely free and requires no signup.',
|
text: 'Yes, generating this location QR code is completely free and requires no signup.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function GeolocationQRCodePage() {
|
export default function GeolocationQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Location QR Code Generator" toolSlug="geolocation-qr-code" />
|
<ToolBreadcrumb toolName="Location QR Code Generator" toolSlug="geolocation-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#047857' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#047857' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Share Perfect Locations with <br className="hidden lg:block" />
|
Share Perfect Locations with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-300 to-teal-300">Map QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-300 to-teal-300">Map QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Provide exact directions to your event, store, or secret spot.
|
Provide exact directions to your event, store, or secret spot.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Opens directly in Google Maps.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Opens directly in Google Maps.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Navigation className="w-4 h-4 text-emerald-400" />
|
<Navigation className="w-4 h-4 text-emerald-400" />
|
||||||
Exact Directions
|
Exact Directions
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-400" />
|
<Zap className="w-4 h-4 text-amber-400" />
|
||||||
Instant Load
|
Instant Load
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-purple-400" />
|
<Shield className="w-4 h-4 text-purple-400" />
|
||||||
No Data Saved
|
No Data Saved
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg h-32 mb-6 relative overflow-hidden grayscale group-hover:grayscale-0 transition-all duration-500">
|
<div className="w-full bg-white rounded-xl shadow-lg h-32 mb-6 relative overflow-hidden grayscale group-hover:grayscale-0 transition-all duration-500">
|
||||||
<div className="absolute inset-0 opacity-20 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]"></div>
|
<div className="absolute inset-0 opacity-20 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]"></div>
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
<MapPin className="w-8 h-8 text-red-500 drop-shadow-lg animate-bounce" />
|
<MapPin className="w-8 h-8 text-red-500 drop-shadow-lg animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-2 left-2 right-2 bg-white/90 p-2 rounded text-[10px] text-slate-500 font-mono text-center">
|
<div className="absolute bottom-2 left-2 right-2 bg-white/90 p-2 rounded text-[10px] text-slate-500 font-mono text-center">
|
||||||
40.7128° N, 74.0060° W
|
40.7128° N, 74.0060° W
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-emerald-100 p-2 rounded-full">
|
<div className="bg-emerald-100 p-2 rounded-full">
|
||||||
<Map className="w-5 h-5 text-emerald-600" />
|
<Map className="w-5 h-5 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Map</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Map</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Open</div>
|
<div className="text-sm font-bold text-slate-900">Open</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<GeolocationGenerator />
|
<GeolocationGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Geolocation QR Codes Work
|
How Geolocation QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<MapPin className="w-7 h-7 text-[#1A1265]" />
|
<MapPin className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Pinpoint</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Pinpoint</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Enter exact GPS coordinates. This ensures users go to the precise spot (e.g., a specific building entrance).
|
Enter exact GPS coordinates. This ensures users go to the precise spot (e.g., a specific building entrance).
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Users scan the code. It is encoded with a universal map link.
|
Users scan the code. It is encoded with a universal map link.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your high-quality QR image.
|
Save your high-quality QR image.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Users scan the code to load coordinates.
|
Users scan the code to load coordinates.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Go</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Go</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
They get instant directions to your spot.
|
They get instant directions to your spot.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Map QR codes.
|
Common questions about Map QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Why not just use an address?"
|
question="Why not just use an address?"
|
||||||
answer="Addresses can be ambiguous or cover large areas (like a park or stadium). Coordinates point to an exact geographic spot, ensuring visitors find the specific meeting point or parking entrance."
|
answer="Addresses can be ambiguous or cover large areas (like a park or stadium). Coordinates point to an exact geographic spot, ensuring visitors find the specific meeting point or parking entrance."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work on Apple Maps?"
|
question="Does it work on Apple Maps?"
|
||||||
answer="Yes. While the underlying link is a Google Maps link, iOS devices usually handle these gracefully, either opening them in the Google Maps app (if installed) or the browser, where Apple Maps can often intercept directions."
|
answer="Yes. While the underlying link is a Google Maps link, iOS devices usually handle these gracefully, either opening them in the Google Maps app (if installed) or the browser, where Apple Maps can often intercept directions."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes, generating this location QR code is completely free and requires no signup."
|
answer="Yes, generating this location QR code is completely free and requires no signup."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track who scanned it?"
|
question="Can I track who scanned it?"
|
||||||
answer="Not with this static tool. If you need scan analytics (e.g., how many people scanned your storefront QR), you should use our Dynamic QR Code service."
|
answer="Not with this static tool. If you need scan analytics (e.g., how many people scanned your storefront QR), you should use our Dynamic QR Code service."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes, generating this location QR code is completely free and requires no signup."
|
answer="Yes, generating this location QR code is completely free and requires no signup."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,253 +1,253 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Instagram,
|
Instagram,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Camera
|
Camera
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - Insta Theme
|
// QR Color Options - Insta Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Insta Pink', value: '#E1306C' },
|
{ name: 'Insta Pink', value: '#E1306C' },
|
||||||
{ name: 'Insta Purple', value: '#833AB4' },
|
{ name: 'Insta Purple', value: '#833AB4' },
|
||||||
{ name: 'Insta Orange', value: '#F77737' },
|
{ name: 'Insta Orange', value: '#F77737' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Rich Blue', value: '#1A1265' },
|
{ name: 'Rich Blue', value: '#1A1265' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'follow', label: 'Follow Us' },
|
{ id: 'follow', label: 'Follow Us' },
|
||||||
{ id: 'insta', label: 'Instagram' },
|
{ id: 'insta', label: 'Instagram' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function InstagramGenerator() {
|
export default function InstagramGenerator() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#E1306C');
|
const [qrColor, setQrColor] = useState('#E1306C');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Instagram URL construction: https://instagram.com/username
|
// Instagram URL construction: https://instagram.com/username
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?instagram\.com\//, '').replace(/\/$/, '');
|
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?instagram\.com\//, '').replace(/\/$/, '');
|
||||||
return cleanUser ? `https://instagram.com/${cleanUser}` : 'https://instagram.com';
|
return cleanUser ? `https://instagram.com/${cleanUser}` : 'https://instagram.com';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `instagram-qr-code.png`;
|
link.download = `instagram-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `instagram-qr-code.svg`;
|
link.download = `instagram-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Instagram Details */}
|
{/* Instagram Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Instagram className="w-5 h-5 text-[#E1306C]" />
|
<Instagram className="w-5 h-5 text-[#E1306C]" />
|
||||||
Instagram Username
|
Instagram Username
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="@username"
|
placeholder="@username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#E1306C] focus:ring-[#E1306C]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#E1306C] focus:ring-[#E1306C]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Enter your username (without @) or paste full profile link.</p>
|
<p className="text-xs text-slate-500 mt-2">Enter your username (without @) or paste full profile link.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#E1306C]" />
|
<Sparkles className="w-5 h-5 text-[#E1306C]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#E1306C] text-white border-[#E1306C]"
|
? "bg-[#E1306C] text-white border-[#E1306C]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getUrl()}
|
value={getUrl()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Instagram className="w-4 h-4 text-slate-400 shrink-0" />
|
<Instagram className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{username || '@username'}</span>
|
<span className="truncate">{username || '@username'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Opens in Instagram</div>
|
<div className="text-xs text-slate-500 mt-1">Opens in Instagram</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#E1306C] hover:bg-[#C13584] text-white shadow-lg"
|
className="bg-[#E1306C] hover:bg-[#C13584] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning redirects directly to your Instagram profile.
|
Scanning redirects directly to your Instagram profile.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#833AB4] via-[#FD1D1D] to-[#FCA145] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#833AB4] via-[#FD1D1D] to-[#FCA145] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Want a "Link in Bio" QR?</h3>
|
<h3 className="font-bold text-lg">Want a "Link in Bio" QR?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Create a digital landing page with links to all your socials using Dynamic Codes.
|
Create a digital landing page with links to all your socials using Dynamic Codes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#E1306C] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#E1306C] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Bio Link
|
Create Bio Link
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,354 +1,354 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import InstagramGenerator from './InstagramGenerator';
|
import InstagramGenerator from './InstagramGenerator';
|
||||||
import { Instagram, Shield, Zap, Smartphone, Camera, Heart, Download, Share2 } from 'lucide-react';
|
import { Instagram, Shield, Zap, Smartphone, Camera, Heart, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Instagram QR Code Generator | Get More Followers | QR Master',
|
title: 'Free Instagram QR Code Generator | Get More Followers | QR Master',
|
||||||
description: 'Create a QR code for your Instagram profile or post. Scanners are redirected to the Instagram app instantly to follow you. Free & Customizable.',
|
description: 'Create a QR code for your Instagram profile or post. Scanners are redirected to the Instagram app instantly to follow you. Free & Customizable.',
|
||||||
keywords: ['instagram qr code', 'insta qr generator', 'ig nametag generator', 'instagram follow qr', 'social media qr code'],
|
keywords: ['instagram qr code', 'insta qr generator', 'ig nametag generator', 'instagram follow qr', 'social media qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/instagram-qr-code',
|
canonical: 'https://qrmaster.io/tools/instagram-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Instagram QR Code Generator | QR Master',
|
title: 'Free Instagram QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
|
description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/instagram-qr-code',
|
url: 'https://qrmaster.io/tools/instagram-qr-code',
|
||||||
images: [{ url: '/og-instagram-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-instagram-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Instagram QR Code Generator',
|
title: 'Free Instagram QR Code Generator',
|
||||||
description: 'Create QR codes for Instagram. Boost your followers.',
|
description: 'Create QR codes for Instagram. Boost your followers.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Instagram QR Code Generator',
|
name: 'Instagram QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '2150',
|
ratingCount: '2150',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that direct users to an Instagram profile or post.',
|
description: 'Generate QR codes that direct users to an Instagram profile or post.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create an Instagram QR Code',
|
name: 'How to Create an Instagram QR Code',
|
||||||
description: 'Create a QR code that opens an Instagram profile.',
|
description: 'Create a QR code that opens an Instagram profile.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Username',
|
name: 'Enter Username',
|
||||||
text: 'Type your Instagram handle (e.g. @yourbrand) or paste your profile link.',
|
text: 'Type your Instagram handle (e.g. @yourbrand) or paste your profile link.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose a gradient color that matches the Instagram vibe or your own brand.',
|
text: 'Choose a gradient color that matches the Instagram vibe or your own brand.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code image.',
|
text: 'Save the QR code image.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code to ensure it opens the correct profile.',
|
text: 'Scan the code to ensure it opens the correct profile.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Put it on your packaging, business cards, or storefront.',
|
text: 'Put it on your packaging, business cards, or storefront.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is this an Instagram Nametag?',
|
name: 'Is this an Instagram Nametag?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'It works similarly! While Instagram has its own internal "Nametag" or "QR Code" feature, our generator allows you to create a standard QR code that is more customizable and can be tracked with our Dynamic plans.',
|
text: 'It works similarly! While Instagram has its own internal "Nametag" or "QR Code" feature, our generator allows you to create a standard QR code that is more customizable and can be tracked with our Dynamic plans.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it open the Instagram app?',
|
name: 'Does it open the Instagram app?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. When scanned on a mobile device with Instagram installed, it will deep-link directly to the profile in the app.',
|
text: 'Yes. When scanned on a mobile device with Instagram installed, it will deep-link directly to the profile in the app.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I link to a specific photo or reel?',
|
name: 'Can I link to a specific photo or reel?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! Instead of your username, just paste the full link to the specific post or reel.',
|
text: 'Yes! Instead of your username, just paste the full link to the specific post or reel.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, generating this QR code is 100% free.',
|
text: 'Yes, generating this QR code is 100% free.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track scans?',
|
name: 'Can I track scans?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution.',
|
text: 'Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InstagramQRCodePage() {
|
export default function InstagramQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Instagram QR Code Generator" toolSlug="instagram-qr-code" />
|
<ToolBreadcrumb toolName="Instagram QR Code Generator" toolSlug="instagram-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-gradient-to-br from-[#833AB4] via-[#FD1D1D] to-[#FCA145]">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-gradient-to-br from-[#833AB4] via-[#FD1D1D] to-[#FCA145]">
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<circle cx="0" cy="0" r="40" fill="white" fillOpacity="0.1" />
|
<circle cx="0" cy="0" r="40" fill="white" fillOpacity="0.1" />
|
||||||
<circle cx="100" cy="100" r="50" fill="white" fillOpacity="0.1" />
|
<circle cx="100" cy="100" r="50" fill="white" fillOpacity="0.1" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-300 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-300 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-pink-300"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-pink-300"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Boost Your Following with <br className="hidden lg:block" />
|
Boost Your Following with <br className="hidden lg:block" />
|
||||||
<span className="text-white drop-shadow-md">Instagram QR Codes</span>
|
<span className="text-white drop-shadow-md">Instagram QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-pink-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-pink-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Connect physically to digitally. Let customers scan to follow your Instagram profile instantly.
|
Connect physically to digitally. Let customers scan to follow your Instagram profile instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your brand effortlessly.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your brand effortlessly.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Heart className="w-4 h-4 text-pink-200" />
|
<Heart className="w-4 h-4 text-pink-200" />
|
||||||
More Likes
|
More Likes
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-yellow-200" />
|
<Zap className="w-4 h-4 text-yellow-200" />
|
||||||
Instant Follow
|
Instant Follow
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-white" />
|
<Smartphone className="w-4 h-4 text-white" />
|
||||||
App Deep Link
|
App Deep Link
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-white/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-white/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center">
|
||||||
<div className="w-16 h-16 rounded-full p-[2px] bg-gradient-to-tr from-[#FCA145] via-[#FD1D1D] to-[#833AB4] mb-2">
|
<div className="w-16 h-16 rounded-full p-[2px] bg-gradient-to-tr from-[#FCA145] via-[#FD1D1D] to-[#833AB4] mb-2">
|
||||||
<div className="w-full h-full rounded-full bg-white p-1">
|
<div className="w-full h-full rounded-full bg-white p-1">
|
||||||
<div className="w-full h-full rounded-full bg-slate-200" />
|
<div className="w-full h-full rounded-full bg-slate-200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-bold text-slate-900">@yourbrand</div>
|
<div className="text-sm font-bold text-slate-900">@yourbrand</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#E1306C" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#E1306C" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-gradient-to-tr from-[#FCA145] to-[#E1306C] p-2 rounded-full text-white">
|
<div className="bg-gradient-to-tr from-[#FCA145] to-[#E1306C] p-2 rounded-full text-white">
|
||||||
<Camera className="w-5 h-5" />
|
<Camera className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Profile</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Profile</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Following</div>
|
<div className="text-sm font-bold text-slate-900">Following</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<InstagramGenerator />
|
<InstagramGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Instagram QR Codes Work
|
How Instagram QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Instagram className="w-7 h-7 text-[#E1306C]" />
|
<Instagram className="w-7 h-7 text-[#E1306C]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Enter your Instagram handle. No need to login or connect your account.
|
Enter your Instagram handle. No need to login or connect your account.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#E1306C]" />
|
<Smartphone className="w-7 h-7 text-[#E1306C]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Print</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Print</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Add the QR code to your packaging, business cards, or table tents.
|
Add the QR code to your packaging, business cards, or table tents.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#E1306C]" />
|
<Download className="w-6 h-6 text-[#E1306C]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your custom QR code.
|
Save your custom QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Heart className="w-6 h-6 text-[#E1306C]" />
|
<Heart className="w-6 h-6 text-[#E1306C]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Fans scan to instantly visit your profile.
|
Fans scan to instantly visit your profile.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#E1306C]" />
|
<Share2 className="w-6 h-6 text-[#E1306C]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Grow</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Grow</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Convert offline traffic into followers.
|
Convert offline traffic into followers.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Instagram QR codes.
|
Common questions about Instagram QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does this work for private accounts?"
|
question="Does this work for private accounts?"
|
||||||
answer="Yes, the link will take users to your profile. If your account is private, they will still have to request to follow you."
|
answer="Yes, the link will take users to your profile. If your account is private, they will still have to request to follow you."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I link to a Story?"
|
question="Can I link to a Story?"
|
||||||
answer="Yes, but Stories expire after 24 hours (unless saved as a Highlight). Linking to a Highlight or your main Profile is usually better for printed materials."
|
answer="Yes, but Stories expire after 24 hours (unless saved as a Highlight). Linking to a Highlight or your main Profile is usually better for printed materials."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I customize the frame?"
|
question="Can I customize the frame?"
|
||||||
answer="Yes, we offer several frame options like 'Follow Us' or 'Scan Me' to encourage action."
|
answer="Yes, we offer several frame options like 'Follow Us' or 'Scan Me' to encourage action."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it expire?"
|
question="Does it expire?"
|
||||||
answer="No. The QR code will work as long as your Instagram username remains the same."
|
answer="No. The QR code will work as long as your Instagram username remains the same."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track scans?"
|
question="Can I track scans?"
|
||||||
answer="Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution."
|
answer="Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,342 +1,342 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
CreditCard,
|
CreditCard,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
DollarSign
|
DollarSign
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors - PayPal Blue
|
// Brand Colors - PayPal Blue
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EFF6FF', // Blue-50
|
paleGrey: '#EFF6FF', // Blue-50
|
||||||
primary: '#003087', // PayPal Dark Blue
|
primary: '#003087', // PayPal Dark Blue
|
||||||
primaryDark: '#001F5C',
|
primaryDark: '#001F5C',
|
||||||
accent: '#0070BA', // PayPal Light Blue
|
accent: '#0070BA', // PayPal Light Blue
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'PayPal Blue', value: '#003087' },
|
{ name: 'PayPal Blue', value: '#003087' },
|
||||||
{ name: 'PayPal Light', value: '#0070BA' },
|
{ name: 'PayPal Light', value: '#0070BA' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Indigo', value: '#4F46E5' },
|
{ name: 'Indigo', value: '#4F46E5' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'pay', label: 'Pay Now' },
|
{ id: 'pay', label: 'Pay Now' },
|
||||||
{ id: 'donate', label: 'Donate' },
|
{ id: 'donate', label: 'Donate' },
|
||||||
{ id: 'tip', label: 'Tip Me' },
|
{ id: 'tip', label: 'Tip Me' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Currency Options
|
// Currency Options
|
||||||
const CURRENCIES = [
|
const CURRENCIES = [
|
||||||
{ value: 'EUR', label: 'EUR (€)' },
|
{ value: 'EUR', label: 'EUR (€)' },
|
||||||
{ value: 'USD', label: 'USD ($)' },
|
{ value: 'USD', label: 'USD ($)' },
|
||||||
{ value: 'GBP', label: 'GBP (£)' },
|
{ value: 'GBP', label: 'GBP (£)' },
|
||||||
{ value: 'CHF', label: 'CHF' },
|
{ value: 'CHF', label: 'CHF' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Input type options
|
// Input type options
|
||||||
const INPUT_TYPES = [
|
const INPUT_TYPES = [
|
||||||
{ id: 'username', label: 'PayPal.me Username' },
|
{ id: 'username', label: 'PayPal.me Username' },
|
||||||
{ id: 'email', label: 'PayPal Email' },
|
{ id: 'email', label: 'PayPal Email' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function PayPalGenerator() {
|
export default function PayPalGenerator() {
|
||||||
const [inputType, setInputType] = useState('email');
|
const [inputType, setInputType] = useState('email');
|
||||||
const [paypalId, setPaypalId] = useState('');
|
const [paypalId, setPaypalId] = useState('');
|
||||||
const [amount, setAmount] = useState('');
|
const [amount, setAmount] = useState('');
|
||||||
const [currency, setCurrency] = useState('EUR');
|
const [currency, setCurrency] = useState('EUR');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Generate PayPal payment link
|
// Generate PayPal payment link
|
||||||
const generatePayPalLink = () => {
|
const generatePayPalLink = () => {
|
||||||
if (!paypalId.trim()) return 'https://paypal.com';
|
if (!paypalId.trim()) return 'https://paypal.com';
|
||||||
|
|
||||||
if (inputType === 'username') {
|
if (inputType === 'username') {
|
||||||
// PayPal.me link
|
// PayPal.me link
|
||||||
let link = `https://paypal.me/${paypalId.trim()}`;
|
let link = `https://paypal.me/${paypalId.trim()}`;
|
||||||
if (amount && parseFloat(amount) > 0) {
|
if (amount && parseFloat(amount) > 0) {
|
||||||
link += `/${amount}`;
|
link += `/${amount}`;
|
||||||
}
|
}
|
||||||
return link;
|
return link;
|
||||||
} else {
|
} else {
|
||||||
// PayPal email payment link (donation/payment format)
|
// PayPal email payment link (donation/payment format)
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
cmd: '_donations',
|
cmd: '_donations',
|
||||||
business: paypalId.trim(),
|
business: paypalId.trim(),
|
||||||
currency_code: currency,
|
currency_code: currency,
|
||||||
...(amount && parseFloat(amount) > 0 ? { amount } : {}),
|
...(amount && parseFloat(amount) > 0 ? { amount } : {}),
|
||||||
});
|
});
|
||||||
return `https://www.paypal.com/cgi-bin/webscr?${params.toString()}`;
|
return `https://www.paypal.com/cgi-bin/webscr?${params.toString()}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `paypal-qr-${paypalId || 'code'}.png`;
|
link.download = `paypal-qr-${paypalId || 'code'}.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `paypal-qr-${paypalId || 'code'}.svg`;
|
link.download = `paypal-qr-${paypalId || 'code'}.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* PayPal Details */}
|
{/* PayPal Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<CreditCard className="w-5 h-5 text-[#003087]" />
|
<CreditCard className="w-5 h-5 text-[#003087]" />
|
||||||
PayPal Details
|
PayPal Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Input Type Toggle */}
|
{/* Input Type Toggle */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Payment Method</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Payment Method</label>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{INPUT_TYPES.map((type) => (
|
{INPUT_TYPES.map((type) => (
|
||||||
<button
|
<button
|
||||||
key={type.id}
|
key={type.id}
|
||||||
onClick={() => setInputType(type.id)}
|
onClick={() => setInputType(type.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
inputType === type.id
|
inputType === type.id
|
||||||
? "bg-[#003087] text-white border-[#003087]"
|
? "bg-[#003087] text-white border-[#003087]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{type.label}
|
{type.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||||
{inputType === 'username' ? 'PayPal.me Username' : 'PayPal Email Address'}
|
{inputType === 'username' ? 'PayPal.me Username' : 'PayPal Email Address'}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type={inputType === 'email' ? 'email' : 'text'}
|
type={inputType === 'email' ? 'email' : 'text'}
|
||||||
placeholder={inputType === 'username' ? 'e.g. johndoe' : 'e.g. mail@example.com'}
|
placeholder={inputType === 'username' ? 'e.g. johndoe' : 'e.g. mail@example.com'}
|
||||||
value={paypalId}
|
value={paypalId}
|
||||||
onChange={(e) => setPaypalId(e.target.value)}
|
onChange={(e) => setPaypalId(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">
|
<p className="text-xs text-slate-500 mt-2">
|
||||||
{inputType === 'username'
|
{inputType === 'username'
|
||||||
? <>Find yours at <a href="https://paypal.me" target="_blank" rel="noopener noreferrer" className="text-[#003087] underline">paypal.me</a></>
|
? <>Find yours at <a href="https://paypal.me" target="_blank" rel="noopener noreferrer" className="text-[#003087] underline">paypal.me</a></>
|
||||||
: 'The email address linked to your PayPal account'
|
: 'The email address linked to your PayPal account'
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="25.00"
|
placeholder="25.00"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
||||||
<Select
|
<Select
|
||||||
value={currency}
|
value={currency}
|
||||||
onChange={(e) => setCurrency(e.target.value)}
|
onChange={(e) => setCurrency(e.target.value)}
|
||||||
className="h-12 rounded-xl border-slate-200"
|
className="h-12 rounded-xl border-slate-200"
|
||||||
options={CURRENCIES}
|
options={CURRENCIES}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#003087]" />
|
<Sparkles className="w-5 h-5 text-[#003087]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-5 gap-2">
|
<div className="grid grid-cols-5 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#003087] text-white border-[#003087]"
|
? "bg-[#003087] text-white border-[#003087]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={generatePayPalLink()}
|
value={generatePayPalLink()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* PayPal Info */}
|
{/* PayPal Info */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<DollarSign className="w-4 h-4 text-[#003087] shrink-0" />
|
<DollarSign className="w-4 h-4 text-[#003087] shrink-0" />
|
||||||
<span className="truncate">{paypalId || 'Your PayPal'}</span>
|
<span className="truncate">{paypalId || 'Your PayPal'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
{amount && (
|
{amount && (
|
||||||
<p className="text-sm text-slate-500 mt-1">{amount} {currency}</p>
|
<p className="text-sm text-slate-500 mt-1">{amount} {currency}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#003087] hover:bg-[#001F5C] text-white shadow-lg"
|
className="bg-[#003087] hover:bg-[#001F5C] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Your PayPal link is encoded directly. Static and forever free.
|
Your PayPal link is encoded directly. Static and forever free.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#003087] to-[#0070BA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#003087] to-[#0070BA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need payment analytics?</h3>
|
<h3 className="font-bold text-lg">Need payment analytics?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Track how many people scan your payment QR code with Dynamic QR Codes.</p>
|
<p className="text-white/80 text-sm mt-1">Track how many people scan your payment QR code with Dynamic QR Codes.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#003087] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#003087] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Analytics
|
Get Analytics
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,358 +1,358 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import PayPalGenerator from './PayPalGenerator';
|
import PayPalGenerator from './PayPalGenerator';
|
||||||
import { CreditCard, Shield, Zap, Smartphone, DollarSign, Download, Share2, Banknote } from 'lucide-react';
|
import { CreditCard, Shield, Zap, Smartphone, DollarSign, Download, Share2, Banknote } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free PayPal QR Code Generator | Accept Payments Instantly | QR Master',
|
title: 'Free PayPal QR Code Generator | Accept Payments Instantly | QR Master',
|
||||||
description: 'Create a QR code for your PayPal.me link. Let customers pay you instantly by scanning. Support tips, donations, and fixed amounts. 100% free.',
|
description: 'Create a QR code for your PayPal.me link. Let customers pay you instantly by scanning. Support tips, donations, and fixed amounts. 100% free.',
|
||||||
keywords: ['paypal qr code', 'paypal.me qr generator', 'payment qr code', 'accept payments qr', 'paypal qr generator', 'tip qr code', 'donation qr code'],
|
keywords: ['paypal qr code', 'paypal.me qr generator', 'payment qr code', 'accept payments qr', 'paypal qr generator', 'tip qr code', 'donation qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/paypal-qr-code',
|
canonical: 'https://qrmaster.io/tools/paypal-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free PayPal QR Code Generator | QR Master',
|
title: 'Free PayPal QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes for PayPal payments. Perfect for tips, donations, and invoices.',
|
description: 'Generate QR codes for PayPal payments. Perfect for tips, donations, and invoices.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/paypal-qr-code',
|
url: 'https://qrmaster.io/tools/paypal-qr-code',
|
||||||
images: [{ url: '/og-paypal-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-paypal-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free PayPal QR Code Generator',
|
title: 'Free PayPal QR Code Generator',
|
||||||
description: 'Create PayPal payment QR codes. Instant and free.',
|
description: 'Create PayPal payment QR codes. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'PayPal QR Code Generator',
|
name: 'PayPal QR Code Generator',
|
||||||
applicationCategory: 'FinanceApplication',
|
applicationCategory: 'FinanceApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '980',
|
ratingCount: '980',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that link to your PayPal.me page for instant payments.',
|
description: 'Generate QR codes that link to your PayPal.me page for instant payments.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a PayPal QR Code',
|
name: 'How to Create a PayPal QR Code',
|
||||||
description: 'Create a QR code for receiving PayPal payments.',
|
description: 'Create a QR code for receiving PayPal payments.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Username',
|
name: 'Enter Username',
|
||||||
text: 'Type your PayPal.me username (the part after paypal.me/).',
|
text: 'Type your PayPal.me username (the part after paypal.me/).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Set Amount (Optional)',
|
name: 'Set Amount (Optional)',
|
||||||
text: 'Enter a pre-filled amount and currency for fixed payments.',
|
text: 'Enter a pre-filled amount and currency for fixed payments.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Customize Design',
|
name: 'Customize Design',
|
||||||
text: 'Choose PayPal brand colors and add a frame like "Pay Now" or "Tip Me".',
|
text: 'Choose PayPal brand colors and add a frame like "Pay Now" or "Tip Me".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download QR Code',
|
name: 'Download QR Code',
|
||||||
text: 'Download your high-quality QR code in PNG or SVG format.',
|
text: 'Download your high-quality QR code in PNG or SVG format.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Print it on invoices, display at your shop, or share digitally.',
|
text: 'Print it on invoices, display at your shop, or share digitally.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'How does the PayPal QR code work?',
|
name: 'How does the PayPal QR code work?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer.',
|
text: 'When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do I need a PayPal Business account?',
|
name: 'Do I need a PayPal Business account?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations.',
|
text: 'No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a fee for using the QR code?',
|
name: 'Is there a fee for using the QR code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments.',
|
text: 'This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I change the amount later?',
|
name: 'Can I change the amount later?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty.',
|
text: 'No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What currencies are supported?',
|
name: 'What currencies are supported?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'We support EUR, USD, GBP, and CHF. PayPal handles currency conversion automatically.',
|
text: 'We support EUR, USD, GBP, and CHF. PayPal handles currency conversion automatically.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PayPalQRCodePage() {
|
export default function PayPalQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="PayPal QR Code Generator" toolSlug="paypal-qr-code" />
|
<ToolBreadcrumb toolName="PayPal QR Code Generator" toolSlug="paypal-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#003087' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#003087' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-sky-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-sky-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Accept Payments with <br className="hidden lg:block" />
|
Accept Payments with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-sky-300 to-blue-200">PayPal QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-sky-300 to-blue-200">PayPal QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Let customers pay you by scanning. Perfect for tips, donations, and invoices.
|
Let customers pay you by scanning. Perfect for tips, donations, and invoices.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Instant payments.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Instant payments.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<CreditCard className="w-4 h-4 text-sky-300" />
|
<CreditCard className="w-4 h-4 text-sky-300" />
|
||||||
PayPal.me Links
|
PayPal.me Links
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-300" />
|
<Zap className="w-4 h-4 text-amber-300" />
|
||||||
Pre-fill Amount
|
Pre-fill Amount
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-emerald-300" />
|
<Shield className="w-4 h-4 text-emerald-300" />
|
||||||
Secure Payments
|
Secure Payments
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
{/* Payment Card Mock */}
|
{/* Payment Card Mock */}
|
||||||
<div className="w-full bg-gradient-to-br from-[#0070BA] to-[#003087] rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden text-white">
|
<div className="w-full bg-gradient-to-br from-[#0070BA] to-[#003087] rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden text-white">
|
||||||
<div className="flex justify-between items-start mb-3">
|
<div className="flex justify-between items-start mb-3">
|
||||||
<Banknote className="w-6 h-6 opacity-80" />
|
<Banknote className="w-6 h-6 opacity-80" />
|
||||||
<div className="bg-white/20 px-2 py-1 rounded text-xs">EUR</div>
|
<div className="bg-white/20 px-2 py-1 rounded text-xs">EUR</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold tracking-wider">€25.00</div>
|
<div className="text-2xl font-bold tracking-wider">€25.00</div>
|
||||||
<div className="text-xs opacity-70 mt-1">Payment Request</div>
|
<div className="text-xs opacity-70 mt-1">Payment Request</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#003087" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#003087" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-blue-100 p-2 rounded-full">
|
<div className="bg-blue-100 p-2 rounded-full">
|
||||||
<DollarSign className="w-5 h-5 text-[#003087]" />
|
<DollarSign className="w-5 h-5 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">PayPal</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">PayPal</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Ready</div>
|
<div className="text-sm font-bold text-slate-900">Ready</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<PayPalGenerator />
|
<PayPalGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How PayPal QR Codes Work
|
How PayPal QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<CreditCard className="w-6 h-6 text-[#003087]" />
|
<CreditCard className="w-6 h-6 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Enter your PayPal.me username.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Enter your PayPal.me username.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<DollarSign className="w-6 h-6 text-[#003087]" />
|
<DollarSign className="w-6 h-6 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Amount</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Amount</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Optional: Set a fixed payment amount.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Optional: Set a fixed payment amount.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Sparkles className="w-6 h-6 text-[#003087]" />
|
<Sparkles className="w-6 h-6 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Design</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Design</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Pick colors and add a frame.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Pick colors and add a frame.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#003087]" />
|
<Download className="w-6 h-6 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Save as PNG or SVG file.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Save as PNG or SVG file.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#003087]" />
|
<Share2 className="w-6 h-6 text-[#003087]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Print or share to receive payments.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Print or share to receive payments.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about PayPal QR codes.
|
Common questions about PayPal QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="How does the PayPal QR code work?"
|
question="How does the PayPal QR code work?"
|
||||||
answer="When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer."
|
answer="When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Do I need a PayPal Business account?"
|
question="Do I need a PayPal Business account?"
|
||||||
answer="No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations."
|
answer="No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is there a fee for using the QR code?"
|
question="Is there a fee for using the QR code?"
|
||||||
answer="This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments."
|
answer="This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I change the amount later?"
|
question="Can I change the amount later?"
|
||||||
answer="No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty."
|
answer="No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What if I don't have a PayPal.me link?"
|
question="What if I don't have a PayPal.me link?"
|
||||||
answer="You can create one for free in your PayPal account settings. Go to paypal.me to set up your personalized link."
|
answer="You can create one for free in your PayPal account settings. Go to paypal.me to set up your personalized link."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sparkles({ className }: { className?: string }) {
|
function Sparkles({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,248 +1,248 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Phone,
|
Phone,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Smartphone
|
Smartphone
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Amber', value: '#D97706' },
|
{ name: 'Amber', value: '#D97706' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'call', label: 'Call Me' },
|
{ id: 'call', label: 'Call Me' },
|
||||||
{ id: 'contact', label: 'Contact' },
|
{ id: 'contact', label: 'Contact' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function PhoneGenerator() {
|
export default function PhoneGenerator() {
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const qrValue = `tel:${phone}`;
|
const qrValue = `tel:${phone}`;
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `phone-qr-code.png`;
|
link.download = `phone-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `phone-qr-code.svg`;
|
link.download = `phone-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Phone Details */}
|
{/* Phone Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Phone className="w-5 h-5 text-[#1A1265]" />
|
<Phone className="w-5 h-5 text-[#1A1265]" />
|
||||||
Phone Number
|
Phone Number
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Number</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Number</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="+1 (555) 123-4567"
|
placeholder="+1 (555) 123-4567"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Enter with country code for best results (e.g. +1).</p>
|
<p className="text-xs text-slate-500 mt-2">Enter with country code for best results (e.g. +1).</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#1A1265] text-white border-[#1A1265]"
|
? "bg-[#1A1265] text-white border-[#1A1265]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={phone ? qrValue : "tel:+123456789"}
|
value={phone ? qrValue : "tel:+123456789"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{phone || '+1 555 ...'}</span>
|
<span className="truncate">{phone || '+1 555 ...'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning initiates a call on any mobile phone.
|
Scanning initiates a call on any mobile phone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need to change contact info?</h3>
|
<h3 className="font-bold text-lg">Need to change contact info?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Dynamic QR Codes act as a digital business card that you can update anytime.
|
Dynamic QR Codes act as a digital business card that you can update anytime.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create vCard Plus
|
Create vCard Plus
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,362 +1,362 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import PhoneGenerator from './PhoneGenerator';
|
import PhoneGenerator from './PhoneGenerator';
|
||||||
import { Phone, Shield, Zap, Smartphone, PhoneCall, Download } from 'lucide-react';
|
import { Phone, Shield, Zap, Smartphone, PhoneCall, Download } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Phone QR Code Generator | Call Instantly | QR Master',
|
title: 'Free Phone QR Code Generator | Call Instantly | QR Master',
|
||||||
description: 'Create a QR code that makes a phone call when scanned. Perfect for business cards, flyers, and support lines. 100% Free & No Signup.',
|
description: 'Create a QR code that makes a phone call when scanned. Perfect for business cards, flyers, and support lines. 100% Free & No Signup.',
|
||||||
keywords: ['phone qr code', 'call qr code', 'phone number qr generator', 'click to call qr', 'business card qr code'],
|
keywords: ['phone qr code', 'call qr code', 'phone number qr generator', 'click to call qr', 'business card qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/phone-qr-code',
|
canonical: 'https://qrmaster.io/tools/phone-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Phone QR Code Generator | QR Master',
|
title: 'Free Phone QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to initiate phone calls instantly. Share your number easily.',
|
description: 'Generate QR codes to initiate phone calls instantly. Share your number easily.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/phone-qr-code',
|
url: 'https://qrmaster.io/tools/phone-qr-code',
|
||||||
images: [{ url: '/og-phone-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-phone-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Phone QR Code Generator',
|
title: 'Free Phone QR Code Generator',
|
||||||
description: 'Create QR codes for instant calling. Free and reliable.',
|
description: 'Create QR codes for instant calling. Free and reliable.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Phone QR Code Generator',
|
name: 'Phone QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '1500',
|
ratingCount: '1500',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that trigger a phone call when scanned on a mobile device.',
|
description: 'Generate QR codes that trigger a phone call when scanned on a mobile device.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Phone QR Code',
|
name: 'How to Create a Phone QR Code',
|
||||||
description: 'Create a QR code that dials a number automatically.',
|
description: 'Create a QR code that dials a number automatically.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Number',
|
name: 'Enter Number',
|
||||||
text: 'Type your phone number with country code (e.g., +1 555-0199).',
|
text: 'Type your phone number with country code (e.g., +1 555-0199).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose a color and add a label like "Call Me".',
|
text: 'Choose a color and add a label like "Call Me".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code and print it on your materials.',
|
text: 'Save the QR code and print it on your materials.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code with your phone to ensure the number is correct.',
|
text: 'Scan the code with your phone to ensure the number is correct.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Add to business cards, flyers, or supports desks.',
|
text: 'Add to business cards, flyers, or supports desks.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it call automatically?',
|
name: 'Does it call automatically?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Scanning the QR code opens the phone dialer with the number pre-filled. The user must tap the call button to initiate the call.',
|
text: 'Scanning the QR code opens the phone dialer with the number pre-filled. The user must tap the call button to initiate the call.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work internationally?',
|
name: 'Does it work internationally?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! We recommend entering your number in international format (starting with +) to ensure it works anywhere in the world.',
|
text: 'Yes! We recommend entering your number in international format (starting with +) to ensure it works anywhere in the world.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is my phone number private?',
|
name: 'Is my phone number private?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. We do not store your number. It is encoded directly into the QR code image.',
|
text: 'Yes. We do not store your number. It is encoded directly into the QR code image.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track calls?',
|
name: 'Can I track calls?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'This static QR code cannot track calls. For tracking scans and analytics, consider using our Dynamic QR Code solution.',
|
text: 'This static QR code cannot track calls. For tracking scans and analytics, consider using our Dynamic QR Code solution.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, completely free. We do not charge for generating or scanning the code.',
|
text: 'Yes, completely free. We do not charge for generating or scanning the code.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PhoneQRCodePage() {
|
export default function PhoneQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Phone QR Code Generator" toolSlug="phone-qr-code" />
|
<ToolBreadcrumb toolName="Phone QR Code Generator" toolSlug="phone-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Create Instant <br className="hidden lg:block" />
|
Create Instant <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Call-to-Action QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Call-to-Action QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Make it easy for customers to call you. Scan to dial instantly.
|
Make it easy for customers to call you. Scan to dial instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for print marketing.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for print marketing.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<PhoneCall className="w-4 h-4 text-emerald-400" />
|
<PhoneCall className="w-4 h-4 text-emerald-400" />
|
||||||
One-Tap Call
|
One-Tap Call
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-400" />
|
<Zap className="w-4 h-4 text-amber-400" />
|
||||||
Instant Dial
|
Instant Dial
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-purple-400" />
|
<Shield className="w-4 h-4 text-purple-400" />
|
||||||
No Data Saved
|
No Data Saved
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center animate-pulse">
|
<div className="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center animate-pulse">
|
||||||
<Phone className="w-5 h-5 text-green-600" />
|
<Phone className="w-5 h-5 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2 w-24 bg-slate-200 rounded-full mb-1" />
|
<div className="h-2 w-24 bg-slate-200 rounded-full mb-1" />
|
||||||
<div className="h-2 w-16 bg-slate-100 rounded-full" />
|
<div className="h-2 w-16 bg-slate-100 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-emerald-100 p-2 rounded-full">
|
<div className="bg-emerald-100 p-2 rounded-full">
|
||||||
<PhoneCall className="w-5 h-5 text-emerald-600" />
|
<PhoneCall className="w-5 h-5 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Calling...</div>
|
<div className="text-sm font-bold text-slate-900">Calling...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<PhoneGenerator />
|
<PhoneGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Phone QR Codes Work
|
How Phone QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Phone className="w-7 h-7 text-[#1A1265]" />
|
<Phone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Number</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Enter Number</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Type your phone number. Include the country code for international compatibility.
|
Type your phone number. Include the country code for international compatibility.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Customers scan the QR code with their mobile phone's camera.
|
Customers scan the QR code with their mobile phone's camera.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<PhoneCall className="w-6 h-6 text-[#1A1265]" />
|
<PhoneCall className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Call</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Call</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Their phone dialer opens automatically with your number.
|
Their phone dialer opens automatically with your number.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save the QR code image.
|
Save the QR code image.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Add it to your marketing materials.
|
Add it to your marketing materials.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Phone QR codes.
|
Common questions about Phone QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Do I need to include the country code?"
|
question="Do I need to include the country code?"
|
||||||
answer="We highly recommend it. Adding the country code (e.g., +1 for USA/Canada, +44 for UK) ensures any phone from any region can dial your number correctly."
|
answer="We highly recommend it. Adding the country code (e.g., +1 for USA/Canada, +44 for UK) ensures any phone from any region can dial your number correctly."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does this work on iPhone and Android?"
|
question="Does this work on iPhone and Android?"
|
||||||
answer="Yes, Phone QR codes are a standard format supported natively by iOS and Android camera apps."
|
answer="Yes, Phone QR codes are a standard format supported natively by iOS and Android camera apps."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I change the number later?"
|
question="Can I change the number later?"
|
||||||
answer="No. Static QR codes can't be edited. If your phone number changes, you'll need a new QR code. Use a Dynamic QR Code if you anticipate changes."
|
answer="No. Static QR codes can't be edited. If your phone number changes, you'll need a new QR code. Use a Dynamic QR Code if you anticipate changes."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is there a cost per scan?"
|
question="Is there a cost per scan?"
|
||||||
answer="No. There are no fees or limits on scanning. It works just like sharing your phone number."
|
answer="No. There are no fees or limits on scanning. It works just like sharing your phone number."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes, completely free. We do not charge for generating or scanning the code."
|
answer="Yes, completely free. We do not charge for generating or scanning the code."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,267 +1,267 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Phone
|
Phone
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
primary: '#ea580c', // Orange-600
|
primary: '#ea580c', // Orange-600
|
||||||
primaryDark: '#c2410c', // Orange-700
|
primaryDark: '#c2410c', // Orange-700
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Amber', value: '#D97706' },
|
{ name: 'Amber', value: '#D97706' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'sms', label: 'SMS Us' },
|
{ id: 'sms', label: 'SMS Us' },
|
||||||
{ id: 'text', label: 'Text Us' },
|
{ id: 'text', label: 'Text Us' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function SMSGenerator() {
|
export default function SMSGenerator() {
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// sms:number?body=message
|
// sms:number?body=message
|
||||||
const qrValue = `sms:${phone}?body=${encodeURIComponent(message)}`;
|
const qrValue = `sms:${phone}?body=${encodeURIComponent(message)}`;
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `sms-qr-code.png`;
|
link.download = `sms-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `sms-qr-code.svg`;
|
link.download = `sms-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* SMS Details */}
|
{/* SMS Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<MessageSquare className="w-5 h-5 text-orange-600" />
|
<MessageSquare className="w-5 h-5 text-orange-600" />
|
||||||
SMS Details
|
SMS Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="+1 555 123 4567"
|
placeholder="+1 555 123 4567"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-orange-600 focus:ring-orange-600"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-orange-600 focus:ring-orange-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full h-32 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-600 resize-none text-slate-800 placeholder:text-slate-400"
|
className="w-full h-32 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-600 resize-none text-slate-800 placeholder:text-slate-400"
|
||||||
placeholder="I'm interested in..."
|
placeholder="I'm interested in..."
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
maxLength={160}
|
maxLength={160}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2 text-right">{message.length}/160</p>
|
<p className="text-xs text-slate-500 mt-2 text-right">{message.length}/160</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-orange-600" />
|
<Sparkles className="w-5 h-5 text-orange-600" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-orange-600 text-white border-orange-600"
|
? "bg-orange-600 text-white border-orange-600"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={phone ? qrValue : "sms:+123456789?body=Hello"}
|
value={phone ? qrValue : "sms:+123456789?body=Hello"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{phone || 'Number'}</span>
|
<span className="truncate">{phone || 'Number'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center justify-center gap-2 mt-2 text-slate-500 text-xs">
|
<div className="flex items-center justify-center gap-2 mt-2 text-slate-500 text-xs">
|
||||||
<MessageSquare className="w-3 h-3" />
|
<MessageSquare className="w-3 h-3" />
|
||||||
<span className="italic truncate">{message || 'Your message...'}</span>
|
<span className="italic truncate">{message || 'Your message...'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-orange-600 hover:bg-orange-700 text-white shadow-lg"
|
className="bg-orange-600 hover:bg-orange-700 text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Opens the messaging app with text pre-filled.
|
Opens the messaging app with text pre-filled.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-orange-600 to-orange-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-orange-600 to-orange-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Use SMS for marketing?</h3>
|
<h3 className="font-bold text-lg">Use SMS for marketing?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Dynamic QR Codes offer better tracking and allow you to change the campaign message later.
|
Dynamic QR Codes offer better tracking and allow you to change the campaign message later.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-orange-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-orange-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Try Dynamic Codes
|
Try Dynamic Codes
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,315 +1,315 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import SMSGenerator from './SMSGenerator';
|
import SMSGenerator from './SMSGenerator';
|
||||||
import { MessageSquare, Shield, Zap, Smartphone, Send } from 'lucide-react';
|
import { MessageSquare, Shield, Zap, Smartphone, Send } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free SMS QR Code Generator | Pre-filled Texts | QR Master',
|
title: 'Free SMS QR Code Generator | Pre-filled Texts | QR Master',
|
||||||
description: 'Create a QR code to send an SMS text message instantly. Pre-fill the phone number and message body. Free, private, and works on all phones.',
|
description: 'Create a QR code to send an SMS text message instantly. Pre-fill the phone number and message body. Free, private, and works on all phones.',
|
||||||
keywords: ['sms qr code', 'text message qr code', 'send sms qr', 'sms generator', 'text qr'],
|
keywords: ['sms qr code', 'text message qr code', 'send sms qr', 'sms generator', 'text qr'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/sms-qr-code',
|
canonical: 'https://qrmaster.io/tools/sms-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free SMS QR Code Generator | QR Master',
|
title: 'Free SMS QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes for instant SMS messages. Pre-fill text and number.',
|
description: 'Generate QR codes for instant SMS messages. Pre-fill text and number.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/sms-qr-code',
|
url: 'https://qrmaster.io/tools/sms-qr-code',
|
||||||
images: [{ url: '/og-sms-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-sms-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free SMS QR Code Generator',
|
title: 'Free SMS QR Code Generator',
|
||||||
description: 'Create QR codes to send texts. Instant and free.',
|
description: 'Create QR codes to send texts. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'SMS QR Code Generator',
|
name: 'SMS QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '1350',
|
ratingCount: '1350',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that open the user\'s SMS app with a pre-filled message.',
|
description: 'Generate QR codes that open the user\'s SMS app with a pre-filled message.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create an SMS QR Code',
|
name: 'How to Create an SMS QR Code',
|
||||||
description: 'Create a QR code that prepares a text message.',
|
description: 'Create a QR code that prepares a text message.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Phone Number',
|
name: 'Enter Phone Number',
|
||||||
text: 'Type the destination phone number.',
|
text: 'Type the destination phone number.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Enter Message',
|
name: 'Enter Message',
|
||||||
text: 'Type the message you want pre-filled (e.g., "Send me info!").',
|
text: 'Type the message you want pre-filled (e.g., "Send me info!").',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code and share it.',
|
text: 'Save the QR code and share it.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does the text send automatically?',
|
name: 'Does the text send automatically?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. The QR code opens the messaging app with the text typed out. The user must simply tap "Send". This is a security feature of all smartphones.',
|
text: 'No. The QR code opens the messaging app with the text typed out. The user must simply tap "Send". This is a security feature of all smartphones.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a cost?',
|
name: 'Is there a cost?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Generating the code is free. Standard SMS rates apply for the person sending the text message, depending on their carrier plan.',
|
text: 'Generating the code is free. Standard SMS rates apply for the person sending the text message, depending on their carrier plan.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I change the message later?',
|
name: 'Can I change the message later?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. Static QR codes have the message embedded in them. To change the message, you need a new QR code.',
|
text: 'No. Static QR codes have the message embedded in them. To change the message, you need a new QR code.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What uses are there for SMS QR codes?',
|
name: 'What uses are there for SMS QR codes?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'They are great for SMS marketing opt-ins ("Text JOIN to 12345"), customer support requests, or voting via text.',
|
text: 'They are great for SMS marketing opt-ins ("Text JOIN to 12345"), customer support requests, or voting via text.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SMSQRCodePage() {
|
export default function SMSQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="SMS QR Code Generator" toolSlug="sms-qr-code" />
|
<ToolBreadcrumb toolName="SMS QR Code Generator" toolSlug="sms-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#ea580c' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#ea580c' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-amber-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-amber-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Make Texting Easy with <br className="hidden lg:block" />
|
Make Texting Easy with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-200 to-orange-100">SMS QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-200 to-orange-100">SMS QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-orange-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-orange-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Let users send you a pre-written text with one scan. Ideal for opt-ins and support.
|
Let users send you a pre-written text with one scan. Ideal for opt-ins and support.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Universal compatibility.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Universal compatibility.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<MessageSquare className="w-4 h-4 text-amber-300" />
|
<MessageSquare className="w-4 h-4 text-amber-300" />
|
||||||
Pre-fill Texts
|
Pre-fill Texts
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-300" />
|
<Zap className="w-4 h-4 text-amber-300" />
|
||||||
Instant Open
|
Instant Open
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-amber-300" />
|
<Shield className="w-4 h-4 text-amber-300" />
|
||||||
Zero Friction
|
Zero Friction
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-orange-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-orange-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-3 mb-6 relative overflow-hidden flex gap-3 items-center">
|
<div className="w-full bg-white rounded-xl shadow-lg p-3 mb-6 relative overflow-hidden flex gap-3 items-center">
|
||||||
<div className="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center shrink-0">
|
<div className="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center shrink-0">
|
||||||
<MessageSquare className="w-5 h-5 text-orange-600" />
|
<MessageSquare className="w-5 h-5 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-slate-100 rounded-2xl rounded-tl-none p-3 text-xs text-slate-600 w-full">
|
<div className="bg-slate-100 rounded-2xl rounded-tl-none p-3 text-xs text-slate-600 w-full">
|
||||||
Hi, I want to join the club!
|
Hi, I want to join the club!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-orange-100 p-2 rounded-full">
|
<div className="bg-orange-100 p-2 rounded-full">
|
||||||
<Send className="w-5 h-5 text-orange-600" />
|
<Send className="w-5 h-5 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">SMS</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">SMS</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Sent!</div>
|
<div className="text-sm font-bold text-slate-900">Sent!</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<SMSGenerator />
|
<SMSGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How SMS QR Codes Work
|
How SMS QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||||
<MessageSquare className="w-7 h-7 text-orange-600" />
|
<MessageSquare className="w-7 h-7 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Compose</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Compose</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Enter the number and the message you want your customers to send.
|
Enter the number and the message you want your customers to send.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-orange-600" />
|
<Smartphone className="w-7 h-7 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
User scans the code. The messages app opens automatically.
|
User scans the code. The messages app opens automatically.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||||
<Send className="w-7 h-7 text-orange-600" />
|
<Send className="w-7 h-7 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Send</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Send</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
User hits "Send" to trigger the text. Perfect for quick sign-ups or votes.
|
User hits "Send" to trigger the text. Perfect for quick sign-ups or votes.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about SMS QR codes.
|
Common questions about SMS QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does the user need an internet connection?"
|
question="Does the user need an internet connection?"
|
||||||
answer="No. The QR code contains all the info offline. However, the user needs a cellular signal to actually send the SMS message."
|
answer="No. The QR code contains all the info offline. However, the user needs a cellular signal to actually send the SMS message."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free for the user to send?"
|
question="Is it free for the user to send?"
|
||||||
answer="It depends on their mobile plan. Standard SMS rates apply, though most modern plans include unlimited texting."
|
answer="It depends on their mobile plan. Standard SMS rates apply, though most modern plans include unlimited texting."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I use shortcodes?"
|
question="Can I use shortcodes?"
|
||||||
answer="Yes. You can enter a shortcode (e.g. 55555) in the phone number field instead of a regular number."
|
answer="Yes. You can enter a shortcode (e.g. 55555) in the phone number field instead of a regular number."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is my phone number visible?"
|
question="Is my phone number visible?"
|
||||||
answer="Yes. Since the user is sending a text to you, they will see your number (or shortcode) in their messaging app."
|
answer="Yes. Since the user is sending a text to you, they will see your number (or shortcode) in their messaging app."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,319 +1,319 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Video,
|
Video,
|
||||||
MessageCircle
|
MessageCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors - Microsoft Teams Purple
|
// Brand Colors - Microsoft Teams Purple
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#F3F2F1',
|
paleGrey: '#F3F2F1',
|
||||||
primary: '#6264A7',
|
primary: '#6264A7',
|
||||||
primaryDark: '#464775',
|
primaryDark: '#464775',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Teams Purple', value: '#6264A7' },
|
{ name: 'Teams Purple', value: '#6264A7' },
|
||||||
{ name: 'Teams Dark', value: '#464775' },
|
{ name: 'Teams Dark', value: '#464775' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Indigo', value: '#4F46E5' },
|
{ name: 'Indigo', value: '#4F46E5' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'join', label: 'Join Meeting' },
|
{ id: 'join', label: 'Join Meeting' },
|
||||||
{ id: 'teams', label: 'Teams' },
|
{ id: 'teams', label: 'Teams' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Link Type Options
|
// Link Type Options
|
||||||
const LINK_TYPES = [
|
const LINK_TYPES = [
|
||||||
{ id: 'meeting', label: 'Meeting Link', icon: Video },
|
{ id: 'meeting', label: 'Meeting Link', icon: Video },
|
||||||
{ id: 'chat', label: 'Chat with User', icon: MessageCircle },
|
{ id: 'chat', label: 'Chat with User', icon: MessageCircle },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TeamsGenerator() {
|
export default function TeamsGenerator() {
|
||||||
const [linkType, setLinkType] = useState('meeting');
|
const [linkType, setLinkType] = useState('meeting');
|
||||||
const [meetingUrl, setMeetingUrl] = useState('');
|
const [meetingUrl, setMeetingUrl] = useState('');
|
||||||
const [userEmail, setUserEmail] = useState('');
|
const [userEmail, setUserEmail] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Generate Teams link
|
// Generate Teams link
|
||||||
const generateTeamsLink = () => {
|
const generateTeamsLink = () => {
|
||||||
if (linkType === 'meeting') {
|
if (linkType === 'meeting') {
|
||||||
// If user pastes full Teams meeting URL, use it directly
|
// If user pastes full Teams meeting URL, use it directly
|
||||||
if (meetingUrl.trim().includes('teams.microsoft.com') || meetingUrl.trim().includes('teams.live.com')) {
|
if (meetingUrl.trim().includes('teams.microsoft.com') || meetingUrl.trim().includes('teams.live.com')) {
|
||||||
return meetingUrl.trim();
|
return meetingUrl.trim();
|
||||||
}
|
}
|
||||||
// Otherwise return placeholder
|
// Otherwise return placeholder
|
||||||
return meetingUrl.trim() || 'https://teams.microsoft.com';
|
return meetingUrl.trim() || 'https://teams.microsoft.com';
|
||||||
} else {
|
} else {
|
||||||
// Chat link with email
|
// Chat link with email
|
||||||
if (!userEmail.trim()) return 'https://teams.microsoft.com';
|
if (!userEmail.trim()) return 'https://teams.microsoft.com';
|
||||||
return `https://teams.microsoft.com/l/chat/0/0?users=${encodeURIComponent(userEmail.trim())}`;
|
return `https://teams.microsoft.com/l/chat/0/0?users=${encodeURIComponent(userEmail.trim())}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `teams-qr-code.png`;
|
link.download = `teams-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `teams-qr-code.svg`;
|
link.download = `teams-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Teams Details */}
|
{/* Teams Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Users className="w-5 h-5 text-[#6264A7]" />
|
<Users className="w-5 h-5 text-[#6264A7]" />
|
||||||
Microsoft Teams
|
Microsoft Teams
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Link Type Toggle */}
|
{/* Link Type Toggle */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">What do you want to share?</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">What do you want to share?</label>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{LINK_TYPES.map((type) => (
|
{LINK_TYPES.map((type) => (
|
||||||
<button
|
<button
|
||||||
key={type.id}
|
key={type.id}
|
||||||
onClick={() => setLinkType(type.id)}
|
onClick={() => setLinkType(type.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-medium transition-all border",
|
"flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-medium transition-all border",
|
||||||
linkType === type.id
|
linkType === type.id
|
||||||
? "bg-[#6264A7] text-white border-[#6264A7]"
|
? "bg-[#6264A7] text-white border-[#6264A7]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<type.icon className="w-4 h-4" />
|
<type.icon className="w-4 h-4" />
|
||||||
{type.label}
|
{type.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{linkType === 'meeting' ? (
|
{linkType === 'meeting' ? (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Teams Meeting URL</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Teams Meeting URL</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Paste your Teams meeting link here"
|
placeholder="Paste your Teams meeting link here"
|
||||||
value={meetingUrl}
|
value={meetingUrl}
|
||||||
onChange={(e) => setMeetingUrl(e.target.value)}
|
onChange={(e) => setMeetingUrl(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">
|
<p className="text-xs text-slate-500 mt-2">
|
||||||
Copy the meeting link from your Teams calendar invite.
|
Copy the meeting link from your Teams calendar invite.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">User Email Address</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">User Email Address</label>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="e.g. colleague@company.com"
|
placeholder="e.g. colleague@company.com"
|
||||||
value={userEmail}
|
value={userEmail}
|
||||||
onChange={(e) => setUserEmail(e.target.value)}
|
onChange={(e) => setUserEmail(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">
|
<p className="text-xs text-slate-500 mt-2">
|
||||||
The person's work email to start a Teams chat.
|
The person's work email to start a Teams chat.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#6264A7]" />
|
<Sparkles className="w-5 h-5 text-[#6264A7]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#6264A7] text-white border-[#6264A7]"
|
? "bg-[#6264A7] text-white border-[#6264A7]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={generateTeamsLink()}
|
value={generateTeamsLink()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Teams Info */}
|
{/* Teams Info */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||||
{linkType === 'meeting' ? (
|
{linkType === 'meeting' ? (
|
||||||
<Video className="w-4 h-4 text-[#6264A7] shrink-0" />
|
<Video className="w-4 h-4 text-[#6264A7] shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<MessageCircle className="w-4 h-4 text-[#6264A7] shrink-0" />
|
<MessageCircle className="w-4 h-4 text-[#6264A7] shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{linkType === 'meeting' ? 'Teams Meeting' : (userEmail || 'Teams Chat')}
|
{linkType === 'meeting' ? 'Teams Meeting' : (userEmail || 'Teams Chat')}
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-slate-500 mt-1">
|
<p className="text-sm text-slate-500 mt-1">
|
||||||
{linkType === 'meeting' ? 'Join Meeting' : 'Start Chat'}
|
{linkType === 'meeting' ? 'Join Meeting' : 'Start Chat'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#6264A7] hover:bg-[#464775] text-white shadow-lg"
|
className="bg-[#6264A7] hover:bg-[#464775] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Works with Microsoft Teams desktop and mobile apps.
|
Works with Microsoft Teams desktop and mobile apps.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#6264A7] to-[#464775] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#6264A7] to-[#464775] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need to update meeting links?</h3>
|
<h3 className="font-bold text-lg">Need to update meeting links?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the destination without reprinting.</p>
|
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the destination without reprinting.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#6264A7] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#6264A7] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Dynamic QR
|
Create Dynamic QR
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,331 +1,331 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import TeamsGenerator from './TeamsGenerator';
|
import TeamsGenerator from './TeamsGenerator';
|
||||||
import { Users, Shield, Zap, Video, MessageCircle, Download, Share2 } from 'lucide-react';
|
import { Users, Shield, Zap, Video, MessageCircle, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Microsoft Teams QR Code Generator | Join Meetings Instantly | QR Master',
|
title: 'Free Microsoft Teams QR Code Generator | Join Meetings Instantly | QR Master',
|
||||||
description: 'Create a QR code for your Microsoft Teams meeting. Attendees scan to join instantly. Perfect for conference rooms, hybrid meetings, and event displays.',
|
description: 'Create a QR code for your Microsoft Teams meeting. Attendees scan to join instantly. Perfect for conference rooms, hybrid meetings, and event displays.',
|
||||||
keywords: ['teams qr code', 'microsoft teams meeting qr', 'join teams qr code', 'meeting room qr', 'teams invitation qr', 'hybrid meeting qr code'],
|
keywords: ['teams qr code', 'microsoft teams meeting qr', 'join teams qr code', 'meeting room qr', 'teams invitation qr', 'hybrid meeting qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/teams-qr-code',
|
canonical: 'https://qrmaster.io/tools/teams-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Microsoft Teams QR Code Generator | QR Master',
|
title: 'Free Microsoft Teams QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes for Teams meetings. One scan to join instantly.',
|
description: 'Generate QR codes for Teams meetings. One scan to join instantly.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/teams-qr-code',
|
url: 'https://qrmaster.io/tools/teams-qr-code',
|
||||||
images: [{ url: '/og-teams-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-teams-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Microsoft Teams QR Code Generator',
|
title: 'Free Microsoft Teams QR Code Generator',
|
||||||
description: 'Create Teams meeting QR codes. Instant and free.',
|
description: 'Create Teams meeting QR codes. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Microsoft Teams QR Code Generator',
|
name: 'Microsoft Teams QR Code Generator',
|
||||||
applicationCategory: 'BusinessApplication',
|
applicationCategory: 'BusinessApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '890',
|
ratingCount: '890',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that let people join your Microsoft Teams meeting with one scan.',
|
description: 'Generate QR codes that let people join your Microsoft Teams meeting with one scan.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Microsoft Teams QR Code',
|
name: 'How to Create a Microsoft Teams QR Code',
|
||||||
description: 'Create a QR code for joining Teams meetings.',
|
description: 'Create a QR code for joining Teams meetings.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Copy Meeting Link',
|
name: 'Copy Meeting Link',
|
||||||
text: 'Copy the Teams meeting URL from your calendar invitation.',
|
text: 'Copy the Teams meeting URL from your calendar invitation.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Paste Link',
|
name: 'Paste Link',
|
||||||
text: 'Paste the meeting link into the generator.',
|
text: 'Paste the meeting link into the generator.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Choose Teams colors and add a frame label.',
|
text: 'Choose Teams colors and add a frame label.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Download your QR code and display it in your meeting room.',
|
text: 'Download your QR code and display it in your meeting room.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What happens when someone scans the QR code?',
|
name: 'What happens when someone scans the QR code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web.',
|
text: 'Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for recurring meetings?',
|
name: 'Does it work for recurring meetings?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions.',
|
text: 'Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can guests without Teams accounts join?',
|
name: 'Can guests without Teams accounts join?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account.',
|
text: 'Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is this for personal or business Teams?',
|
name: 'Is this for personal or business Teams?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Both! Works with Microsoft Teams for work, school, and personal accounts.',
|
text: 'Both! Works with Microsoft Teams for work, school, and personal accounts.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TeamsQRCodePage() {
|
export default function TeamsQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Teams QR Code Generator" toolSlug="teams-qr-code" />
|
<ToolBreadcrumb toolName="Teams QR Code Generator" toolSlug="teams-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#6264A7' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#6264A7' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Join Meetings with <br className="hidden lg:block" />
|
Join Meetings with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-200 to-white">Teams QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-200 to-white">Teams QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-violet-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-violet-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Create QR codes for Microsoft Teams meetings. Attendees scan to join instantly.
|
Create QR codes for Microsoft Teams meetings. Attendees scan to join instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for hybrid workplaces.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for hybrid workplaces.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Video className="w-4 h-4 text-white" />
|
<Video className="w-4 h-4 text-white" />
|
||||||
Meeting Links
|
Meeting Links
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<MessageCircle className="w-4 h-4 text-white" />
|
<MessageCircle className="w-4 h-4 text-white" />
|
||||||
Chat Links
|
Chat Links
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-300" />
|
<Zap className="w-4 h-4 text-amber-300" />
|
||||||
Instant Join
|
Instant Join
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-violet-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-violet-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
{/* Meeting Card Mock */}
|
{/* Meeting Card Mock */}
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="w-10 h-10 bg-[#6264A7] rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-[#6264A7] rounded-lg flex items-center justify-center">
|
||||||
<Users className="w-5 h-5 text-white" />
|
<Users className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||||
<div className="text-xs text-slate-500">Daily at 9:00 AM</div>
|
<div className="text-xs text-slate-500">Daily at 9:00 AM</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live Now</div>
|
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live Now</div>
|
||||||
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">8 attending</div>
|
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">8 attending</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#6264A7" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#6264A7" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-violet-100 p-2 rounded-full">
|
<div className="bg-violet-100 p-2 rounded-full">
|
||||||
<Video className="w-5 h-5 text-[#6264A7]" />
|
<Video className="w-5 h-5 text-[#6264A7]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<TeamsGenerator />
|
<TeamsGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Teams QR Codes Work
|
How Teams QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-4 gap-8">
|
<div className="grid md:grid-cols-4 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Video className="w-6 h-6 text-[#6264A7]" />
|
<Video className="w-6 h-6 text-[#6264A7]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Get Link</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Get Link</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Copy your Teams meeting URL.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Copy your Teams meeting URL.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Shield className="w-6 h-6 text-[#6264A7]" />
|
<Shield className="w-6 h-6 text-[#6264A7]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Paste into the generator.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Paste into the generator.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#6264A7]" />
|
<Download className="w-6 h-6 text-[#6264A7]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#6264A7]" />
|
<Share2 className="w-6 h-6 text-[#6264A7]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Teams QR codes.
|
Common questions about Teams QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What happens when someone scans the QR code?"
|
question="What happens when someone scans the QR code?"
|
||||||
answer="Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web browser."
|
answer="Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web browser."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work for recurring meetings?"
|
question="Does it work for recurring meetings?"
|
||||||
answer="Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions."
|
answer="Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can guests without Teams accounts join?"
|
question="Can guests without Teams accounts join?"
|
||||||
answer="Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account."
|
answer="Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What about meeting rooms with digital displays?"
|
question="What about meeting rooms with digital displays?"
|
||||||
answer="Perfect for that! Display the QR code on your room's screen so attendees can scan to join from their devices."
|
answer="Perfect for that! Display the QR code on your room's screen so attendees can scan to join from their devices."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,246 +1,246 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Type,
|
Type,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Copy,
|
Copy,
|
||||||
FileText
|
FileText
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Amber', value: '#D97706' },
|
{ name: 'Amber', value: '#D97706' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'text', label: 'Text' },
|
{ id: 'text', label: 'Text' },
|
||||||
{ id: 'message', label: 'Message' },
|
{ id: 'message', label: 'Message' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TextGenerator() {
|
export default function TextGenerator() {
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `text-qr-code.png`;
|
link.download = `text-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `text-qr-code.svg`;
|
link.download = `text-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Text Input */}
|
{/* Text Input */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Type className="w-5 h-5 text-[#1A1265]" />
|
<Type className="w-5 h-5 text-[#1A1265]" />
|
||||||
Enter Content
|
Enter Content
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full h-40 p-4 pb-8 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
className="w-full h-40 p-4 pb-8 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
||||||
placeholder="Type your text here (up to 300 characters)..."
|
placeholder="Type your text here (up to 300 characters)..."
|
||||||
maxLength={300}
|
maxLength={300}
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="absolute bottom-3 right-3 text-xs text-slate-400 font-medium">
|
<div className="absolute bottom-3 right-3 text-xs text-slate-400 font-medium">
|
||||||
{text.length}/300
|
{text.length}/300
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#1A1265] text-white border-[#1A1265]"
|
? "bg-[#1A1265] text-white border-[#1A1265]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={text || "Your Text Here"}
|
value={text || "Your Text Here"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text Info Preview */}
|
{/* Text Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||||
<FileText className="w-4 h-4 text-slate-400" />
|
<FileText className="w-4 h-4 text-slate-400" />
|
||||||
Plain Text
|
Plain Text
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Your text stays on your device. Nothing is sent to servers.
|
Your text stays on your device. Nothing is sent to servers.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Want to track who scans your QR code?</h3>
|
<h3 className="font-bold text-lg">Want to track who scans your QR code?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Dynamic QR codes give you scan analytics and let you edit content anytime.</p>
|
<p className="text-white/80 text-sm mt-1">Dynamic QR codes give you scan analytics and let you edit content anytime.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Try Dynamic Codes
|
Try Dynamic Codes
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,352 +1,352 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import TextGenerator from './TextGenerator';
|
import TextGenerator from './TextGenerator';
|
||||||
import { Type, Shield, Zap, Smartphone, FileText, QrCode, Download, Share2 } from 'lucide-react';
|
import { Type, Shield, Zap, Smartphone, FileText, QrCode, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Text QR Code Generator | Instant & No Limits | QR Master',
|
title: 'Free Text QR Code Generator | Instant & No Limits | QR Master',
|
||||||
description: 'Create a QR code for any plain text message in seconds. No limit on scans. 100% private & client-side. Download valid SVG/PNG files instantly.',
|
description: 'Create a QR code for any plain text message in seconds. No limit on scans. 100% private & client-side. Download valid SVG/PNG files instantly.',
|
||||||
keywords: ['text qr code', 'qr code text generator', 'message to qr code', 'offline qr code', 'text qr generator', 'free qr code'],
|
keywords: ['text qr code', 'qr code text generator', 'message to qr code', 'offline qr code', 'text qr generator', 'free qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/text-qr-code',
|
canonical: 'https://qrmaster.io/tools/text-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Text QR Code Generator | QR Master',
|
title: 'Free Text QR Code Generator | QR Master',
|
||||||
description: 'Turn any text into a QR code instantly. No signup required. 100% text privacy.',
|
description: 'Turn any text into a QR code instantly. No signup required. 100% text privacy.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/text-qr-code',
|
url: 'https://qrmaster.io/tools/text-qr-code',
|
||||||
images: [{ url: '/og-text-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-text-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Text QR Code Generator',
|
title: 'Free Text QR Code Generator',
|
||||||
description: 'Create QR codes for text. Instant, free, and private.',
|
description: 'Create QR codes for text. Instant, free, and private.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Text QR Code Generator',
|
name: 'Text QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '1240',
|
ratingCount: '1240',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes for plain text messages. Works offline once generated. No data collection.',
|
description: 'Generate QR codes for plain text messages. Works offline once generated. No data collection.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Text QR Code',
|
name: 'How to Create a Text QR Code',
|
||||||
description: 'Turn any plain text into a scannable QR code.',
|
description: 'Turn any plain text into a scannable QR code.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Content',
|
name: 'Enter Content',
|
||||||
text: 'Type or paste your text message into the input field.',
|
text: 'Type or paste your text message into the input field.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize Design',
|
name: 'Customize Design',
|
||||||
text: 'Choose a color and add a frame label like "Scan Me" or "Read".',
|
text: 'Choose a color and add a frame label like "Scan Me" or "Read".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download QR Code',
|
name: 'Download QR Code',
|
||||||
text: 'Download your high-quality QR code in PNG or SVG format.',
|
text: 'Download your high-quality QR code in PNG or SVG format.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code to ensure the text appears correctly.',
|
text: 'Scan the code to ensure the text appears correctly.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Print it or display it where you want people to read the message.',
|
text: 'Print it or display it where you want people to read the message.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a character limit?',
|
name: 'Is there a character limit?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, we recommend keeping it under 300 characters for optimal scanning. While QR codes can hold more, more text makes the code denser and harder to scan.',
|
text: 'Yes, we recommend keeping it under 300 characters for optimal scanning. While QR codes can hold more, more text makes the code denser and harder to scan.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do I need internet to scan a Text QR code?',
|
name: 'Do I need internet to scan a Text QR code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. Text QR codes work completely offline. The text content is embedded directly into the QR code pattern.',
|
text: 'No. Text QR codes work completely offline. The text content is embedded directly into the QR code pattern.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is my text private?',
|
name: 'Is my text private?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. This generator runs 100% in your browser. We do not store or see the text you type.',
|
text: 'Yes. This generator runs 100% in your browser. We do not store or see the text you type.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'How do I scan a text QR code?',
|
name: 'How do I scan a text QR code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically.',
|
text: 'Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I edit the text later?',
|
name: 'Can I edit the text later?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No, this is a static QR code. The text is permanent. If you need to change it, you must create a new QR code.',
|
text: 'No, this is a static QR code. The text is permanent. If you need to change it, you must create a new QR code.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TextQRCodePage() {
|
export default function TextQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Text QR Code Generator" toolSlug="text-qr-code" />
|
<ToolBreadcrumb toolName="Text QR Code Generator" toolSlug="text-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Turn Text content into <br className="hidden lg:block" />
|
Turn Text content into <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Scannable QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Scannable QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Share notes, codes, keys, or messages instantly. Scan to read without internet.
|
Share notes, codes, keys, or messages instantly. Scan to read without internet.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Private.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Private.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-emerald-400" />
|
<Shield className="w-4 h-4 text-emerald-400" />
|
||||||
No Data Storage
|
No Data Storage
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-400" />
|
<Zap className="w-4 h-4 text-amber-400" />
|
||||||
Instant Create
|
Instant Create
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||||
Offline Readable
|
Offline Readable
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
||||||
<FileText className="w-4 h-4 text-indigo-300" />
|
<FileText className="w-4 h-4 text-indigo-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-1.5 w-20 bg-white/30 rounded-full" />
|
<div className="h-1.5 w-20 bg-white/30 rounded-full" />
|
||||||
<div className="h-1.5 w-12 bg-white/20 rounded-full" />
|
<div className="h-1.5 w-12 bg-white/20 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<TextGenerator />
|
<TextGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Text QR Codes Work
|
How Text QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Type className="w-7 h-7 text-[#1A1265]" />
|
<Type className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Text</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Enter Text</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Type your message, code, or note. It is instantly encoded into the QR pattern.
|
Type your message, code, or note. It is instantly encoded into the QR pattern.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Choose a color and add a call-to-action frame.
|
Choose a color and add a call-to-action frame.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Get your ready-to-use QR code.
|
Get your ready-to-use QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Point camera to read text.
|
Point camera to read text.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Pass information instantly.
|
Pass information instantly.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Text QR codes.
|
Common questions about Text QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="How do I scan a text QR code?"
|
question="How do I scan a text QR code?"
|
||||||
answer="Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically."
|
answer="Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is there a character limit?"
|
question="Is there a character limit?"
|
||||||
answer="We recommend keeping it under 300 characters for the best scanning experience. Theoretically, QR codes can hold up to 4,296 characters, but the code becomes very complex and harder to scan with standard phone cameras."
|
answer="We recommend keeping it under 300 characters for the best scanning experience. Theoretically, QR codes can hold up to 4,296 characters, but the code becomes very complex and harder to scan with standard phone cameras."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Do I need internet to scan a Text QR code?"
|
question="Do I need internet to scan a Text QR code?"
|
||||||
answer="No. Text QR codes are 'static' codes, meaning the data is encoded directly into the image pattern. You can scan and read them completely offline, making them perfect for remote locations or secure environments."
|
answer="No. Text QR codes are 'static' codes, meaning the data is encoded directly into the image pattern. You can scan and read them completely offline, making them perfect for remote locations or secure environments."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is my text private?"
|
question="Is my text private?"
|
||||||
answer="Yes. We prioritize your privacy. The generation process happens entirely in your browser using JavaScript. Your text data is never sent to our servers or stored anywhere."
|
answer="Yes. We prioritize your privacy. The generation process happens entirely in your browser using JavaScript. Your text data is never sent to our servers or stored anywhere."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I change the text after printing?"
|
question="Can I change the text after printing?"
|
||||||
answer="No. Static QR codes are permanent. If you need to change the text later, you must generate a new QR code. For editable content, you would need a Dynamic QR Code (which we also offer)."
|
answer="No. Static QR codes are permanent. If you need to change the text later, you must generate a new QR code. For editable content, you would need a Dynamic QR Code (which we also offer)."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,253 +1,253 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Music,
|
Music,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Video,
|
Video,
|
||||||
Share2
|
Share2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - TikTok Theme
|
// QR Color Options - TikTok Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'TikTok Black', value: '#000000' },
|
{ name: 'TikTok Black', value: '#000000' },
|
||||||
{ name: 'TikTok Pink', value: '#FE2C55' },
|
{ name: 'TikTok Pink', value: '#FE2C55' },
|
||||||
{ name: 'TikTok Cyan', value: '#25F4EE' },
|
{ name: 'TikTok Cyan', value: '#25F4EE' },
|
||||||
{ name: 'Deep Blue', value: '#1A1265' },
|
{ name: 'Deep Blue', value: '#1A1265' },
|
||||||
{ name: 'Purple', value: '#7C3AED' },
|
{ name: 'Purple', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'follow', label: 'Follow' },
|
{ id: 'follow', label: 'Follow' },
|
||||||
{ id: 'watch', label: 'Watch' },
|
{ id: 'watch', label: 'Watch' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TiktokGenerator() {
|
export default function TiktokGenerator() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#000000');
|
const [qrColor, setQrColor] = useState('#000000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// TikTok URL: https://www.tiktok.com/@username
|
// TikTok URL: https://www.tiktok.com/@username
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?tiktok\.com\/@?/, '').replace(/\/$/, '');
|
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?tiktok\.com\/@?/, '').replace(/\/$/, '');
|
||||||
return cleanUser ? `https://www.tiktok.com/@${cleanUser}` : 'https://www.tiktok.com';
|
return cleanUser ? `https://www.tiktok.com/@${cleanUser}` : 'https://www.tiktok.com';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `tiktok-qr-code.png`;
|
link.download = `tiktok-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `tiktok-qr-code.svg`;
|
link.download = `tiktok-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* TikTok Details */}
|
{/* TikTok Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Music className="w-5 h-5 text-black" />
|
<Music className="w-5 h-5 text-black" />
|
||||||
TikTok Username
|
TikTok Username
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Username</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="@username"
|
placeholder="@username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Enter your TikTok handle (e.g. @charlidamelio).</p>
|
<p className="text-xs text-slate-500 mt-2">Enter your TikTok handle (e.g. @charlidamelio).</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-black" />
|
<Sparkles className="w-5 h-5 text-black" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-black text-white border-black"
|
? "bg-black text-white border-black"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getUrl()}
|
value={getUrl()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Music className="w-4 h-4 text-slate-400 shrink-0" />
|
<Music className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{username || '@username'}</span>
|
<span className="truncate">{username || '@username'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Opens in TikTok</div>
|
<div className="text-xs text-slate-500 mt-1">Opens in TikTok</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-black hover:bg-slate-800 text-white shadow-lg"
|
className="bg-black hover:bg-slate-800 text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning redirects directly to your TikTok profile.
|
Scanning redirects directly to your TikTok profile.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#000000] via-[#25F4EE] to-[#FE2C55] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#000000] via-[#25F4EE] to-[#FE2C55] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Cross-promote on Socials?</h3>
|
<h3 className="font-bold text-lg">Cross-promote on Socials?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Use one Dynamic Link to share your TikTok, Insta, and YouTube all at once.
|
Use one Dynamic Link to share your TikTok, Insta, and YouTube all at once.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-black hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-black hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create All-in-One Link
|
Create All-in-One Link
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,362 +1,362 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import TiktokGenerator from './TikTokGenerator';
|
import TiktokGenerator from './TikTokGenerator';
|
||||||
import { Music, Shield, Zap, Smartphone, Video, Heart, Download, Share2 } from 'lucide-react';
|
import { Music, Shield, Zap, Smartphone, Video, Heart, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free TikTok QR Code Generator | Get Followers | QR Master',
|
title: 'Free TikTok QR Code Generator | Get Followers | QR Master',
|
||||||
description: 'Create a QR code for your TikTok profile. Scanners are redirected to the TikTok app instantly to follow you. Customize with colors and frames.',
|
description: 'Create a QR code for your TikTok profile. Scanners are redirected to the TikTok app instantly to follow you. Customize with colors and frames.',
|
||||||
keywords: ['tiktok qr code', 'tik tok qr generator', 'tiktok follow qr', 'social media qr code', 'tiktok profile qr'],
|
keywords: ['tiktok qr code', 'tik tok qr generator', 'tiktok follow qr', 'social media qr code', 'tiktok profile qr'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/tiktok-qr-code',
|
canonical: 'https://qrmaster.io/tools/tiktok-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free TikTok QR Code Generator | QR Master',
|
title: 'Free TikTok QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to grow your TikTok following. Instant app redirect.',
|
description: 'Generate QR codes to grow your TikTok following. Instant app redirect.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/tiktok-qr-code',
|
url: 'https://qrmaster.io/tools/tiktok-qr-code',
|
||||||
images: [{ url: '/og-tiktok-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-tiktok-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free TikTok QR Code Generator',
|
title: 'Free TikTok QR Code Generator',
|
||||||
description: 'Create QR codes for TikTok. Get more followers.',
|
description: 'Create QR codes for TikTok. Get more followers.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'TikTok QR Code Generator',
|
name: 'TikTok QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '1560',
|
ratingCount: '1560',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that direct users to a TikTok profile.',
|
description: 'Generate QR codes that direct users to a TikTok profile.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a TikTok QR Code',
|
name: 'How to Create a TikTok QR Code',
|
||||||
description: 'Create a QR code that opens a TikTok profile.',
|
description: 'Create a QR code that opens a TikTok profile.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Username',
|
name: 'Enter Username',
|
||||||
text: 'Type your TikTok handle (e.g. @user).',
|
text: 'Type your TikTok handle (e.g. @user).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Select colors like Cyan or Pink to match the TikTok brand.',
|
text: 'Select colors like Cyan or Pink to match the TikTok brand.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code.',
|
text: 'Save the QR code.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code to ensure it links to your profile.',
|
text: 'Scan the code to ensure it links to your profile.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Share it on other social media or print it out.',
|
text: 'Share it on other social media or print it out.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it open the TikTok app?',
|
name: 'Does it open the TikTok app?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! If the app is installed, the QR code will deep-link directly to your profile in the TikTok app.',
|
text: 'Yes! If the app is installed, the QR code will deep-link directly to your profile in the TikTok app.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What is a TikCode?',
|
name: 'What is a TikCode?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'TikCode was TikTok\'s proprietary QR code system. They have moved towards standard QR codes, which is what our tool generates. These are more compatible with standard camera apps.',
|
text: 'TikCode was TikTok\'s proprietary QR code system. They have moved towards standard QR codes, which is what our tool generates. These are more compatible with standard camera apps.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, this generator is completely free.',
|
text: 'Yes, this generator is completely free.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track who scanned my code?',
|
name: 'Can I track who scanned my code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No, this is a static QR code. For analytics, you need a Dynamic QR Code.',
|
text: 'No, this is a static QR code. For analytics, you need a Dynamic QR Code.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it safe?',
|
name: 'Is it safe?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected.',
|
text: 'Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TiktokQRCodePage() {
|
export default function TiktokQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="TikTok QR Code Generator" toolSlug="tiktok-qr-code" />
|
<ToolBreadcrumb toolName="TikTok QR Code Generator" toolSlug="tiktok-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-black">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-black">
|
||||||
<div className="absolute inset-0 opacity-20">
|
<div className="absolute inset-0 opacity-20">
|
||||||
{/* TikTok Pattern */}
|
{/* TikTok Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="tt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
<pattern id="tt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||||
<circle cx="30" cy="30" r="2" fill="cyan" />
|
<circle cx="30" cy="30" r="2" fill="cyan" />
|
||||||
<circle cx="40" cy="40" r="2" fill="magenta" />
|
<circle cx="40" cy="40" r="2" fill="magenta" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#tt_pattern)" />
|
<rect width="100%" height="100%" fill="url(#tt_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Go Viral with <br className="hidden lg:block" />
|
Go Viral with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#25F4EE] to-[#FE2C55]">TikTok QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#25F4EE] to-[#FE2C55]">TikTok QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Share your TikTok immediately. A quick scan sends fans straight to your profile to follow and watch.
|
Share your TikTok immediately. A quick scan sends fans straight to your profile to follow and watch.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your audience.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your audience.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Music className="w-4 h-4 text-[#25F4EE]" />
|
<Music className="w-4 h-4 text-[#25F4EE]" />
|
||||||
Get Followers
|
Get Followers
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Video className="w-4 h-4 text-[#FE2C55]" />
|
<Video className="w-4 h-4 text-[#FE2C55]" />
|
||||||
Share Videos
|
Share Videos
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-white" />
|
<Smartphone className="w-4 h-4 text-white" />
|
||||||
Deep Link
|
Deep Link
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
{/* Glow Effects */}
|
{/* Glow Effects */}
|
||||||
<div className="absolute w-[300px] h-[300px] bg-[#25F4EE]/20 rounded-full blur-[80px] -top-10 -right-10 animate-pulse" />
|
<div className="absolute w-[300px] h-[300px] bg-[#25F4EE]/20 rounded-full blur-[80px] -top-10 -right-10 animate-pulse" />
|
||||||
<div className="absolute w-[300px] h-[300px] bg-[#FE2C55]/20 rounded-full blur-[80px] bottom-10 left-10 animate-pulse delay-75" />
|
<div className="absolute w-[300px] h-[300px] bg-[#FE2C55]/20 rounded-full blur-[80px] bottom-10 left-10 animate-pulse delay-75" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-20 h-20 rounded-full bg-black border-2 border-[#25F4EE] p-1 mb-6 shadow-[#FE2C55]/50 shadow-lg relative">
|
<div className="w-20 h-20 rounded-full bg-black border-2 border-[#25F4EE] p-1 mb-6 shadow-[#FE2C55]/50 shadow-lg relative">
|
||||||
<div className="w-full h-full rounded-full bg-slate-800 flex items-center justify-center overflow-hidden">
|
<div className="w-full h-full rounded-full bg-slate-800 flex items-center justify-center overflow-hidden">
|
||||||
<Music className="w-10 h-10 text-white animate-bounce" />
|
<Music className="w-10 h-10 text-white animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -bottom-1 -right-1 bg-[#FE2C55] w-6 h-6 rounded-full border-2 border-black flex items-center justify-center">
|
<div className="absolute -bottom-1 -right-1 bg-[#FE2C55] w-6 h-6 rounded-full border-2 border-black flex items-center justify-center">
|
||||||
<Heart className="w-3 h-3 text-white fill-current" />
|
<Heart className="w-3 h-3 text-white fill-current" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-white/10 p-2 rounded-full">
|
<div className="bg-white/10 p-2 rounded-full">
|
||||||
<Music className="w-5 h-5 text-[#25F4EE]" />
|
<Music className="w-5 h-5 text-[#25F4EE]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">TikTok</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">TikTok</div>
|
||||||
<div className="text-sm font-bold text-white">Following</div>
|
<div className="text-sm font-bold text-white">Following</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<TiktokGenerator />
|
<TiktokGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How TikTok QR Codes Work
|
How TikTok QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Music className="w-7 h-7 text-[#25F4EE]" />
|
<Music className="w-7 h-7 text-[#25F4EE]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Handle</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Enter Handle</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Type in your username. No password required.
|
Type in your username. No password required.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#FE2C55]" />
|
<Smartphone className="w-7 h-7 text-[#FE2C55]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Fans scan the code to instantly find you in the app.
|
Fans scan the code to instantly find you in the app.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-white" />
|
<Download className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your custom QR code.
|
Save your custom QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Heart className="w-6 h-6 text-white" />
|
<Heart className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Follow</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Follow</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Fans scan to find you instantly.
|
Fans scan to find you instantly.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-white" />
|
<Share2 className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Viral</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Viral</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Grow your audience everywhere.
|
Grow your audience everywhere.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about TikTok QR codes.
|
Common questions about TikTok QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does this replace the in-app QR code?"
|
question="Does this replace the in-app QR code?"
|
||||||
answer="You can use either! The advantage of our generator is that you can print high-resolution versions for large posters, customize the color/frame, and it works with any standard QR scanner."
|
answer="You can use either! The advantage of our generator is that you can print high-resolution versions for large posters, customize the color/frame, and it works with any standard QR scanner."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I link to a specific video?"
|
question="Can I link to a specific video?"
|
||||||
answer="Yes, just paste the full video URL (e.g. tiktok.com/@user/video/123...) instead of your username."
|
answer="Yes, just paste the full video URL (e.g. tiktok.com/@user/video/123...) instead of your username."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes, completely free from start to finish."
|
answer="Yes, completely free from start to finish."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track who scanned my code?"
|
question="Can I track who scanned my code?"
|
||||||
answer="No, this is a static QR code. For analytics, you need a Dynamic QR Code."
|
answer="No, this is a static QR code. For analytics, you need a Dynamic QR Code."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it safe?"
|
question="Is it safe?"
|
||||||
answer="Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected."
|
answer="Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,253 +1,253 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Twitter,
|
Twitter,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
MessageCircle
|
MessageCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - X Theme
|
// QR Color Options - X Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'X Black', value: '#000000' },
|
{ name: 'X Black', value: '#000000' },
|
||||||
{ name: 'X Blue', value: '#1DA1F2' },
|
{ name: 'X Blue', value: '#1DA1F2' },
|
||||||
{ name: 'Dark Blue', value: '#1A1265' },
|
{ name: 'Dark Blue', value: '#1A1265' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Coral', value: '#F43F5E' },
|
{ name: 'Coral', value: '#F43F5E' },
|
||||||
{ name: 'Grey', value: '#374151' },
|
{ name: 'Grey', value: '#374151' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'follow', label: 'Follow' },
|
{ id: 'follow', label: 'Follow' },
|
||||||
{ id: 'connect', label: 'Connect' },
|
{ id: 'connect', label: 'Connect' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TwitterGenerator() {
|
export default function TwitterGenerator() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#000000');
|
const [qrColor, setQrColor] = useState('#000000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Twitter URL construction
|
// Twitter URL construction
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?(twitter|x)\.com\//, '').replace(/\/$/, '');
|
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?(twitter|x)\.com\//, '').replace(/\/$/, '');
|
||||||
return cleanUser ? `https://x.com/${cleanUser}` : 'https://x.com';
|
return cleanUser ? `https://x.com/${cleanUser}` : 'https://x.com';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `twitter-qr-code.png`;
|
link.download = `twitter-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `twitter-qr-code.svg`;
|
link.download = `twitter-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Twitter Details */}
|
{/* Twitter Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Twitter className="w-5 h-5 text-black" />
|
<Twitter className="w-5 h-5 text-black" />
|
||||||
X (Twitter) Username
|
X (Twitter) Username
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="@username"
|
placeholder="@username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Enter your X (Twitter) handle to create a profile link.</p>
|
<p className="text-xs text-slate-500 mt-2">Enter your X (Twitter) handle to create a profile link.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-black" />
|
<Sparkles className="w-5 h-5 text-black" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-slate-900 text-white border-slate-900"
|
? "bg-slate-900 text-white border-slate-900"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getUrl()}
|
value={getUrl()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Twitter className="w-4 h-4 text-slate-400 shrink-0" />
|
<Twitter className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{username || '@username'}</span>
|
<span className="truncate">{username || '@username'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Opens in X (Twitter)</div>
|
<div className="text-xs text-slate-500 mt-1">Opens in X (Twitter)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning redirects directly to the X profile.
|
Scanning redirects directly to the X profile.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Cross-promote your channels</h3>
|
<h3 className="font-bold text-lg">Cross-promote your channels</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Use a single Dynamic QR Code to link to all your social media profiles at once.
|
Use a single Dynamic QR Code to link to all your social media profiles at once.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Smart Link
|
Create Smart Link
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,363 +1,363 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import TwitterGenerator from './TwitterGenerator';
|
import TwitterGenerator from './TwitterGenerator';
|
||||||
import { Twitter, Shield, Zap, Smartphone, MessageCircle, UserPlus, Download, Share2 } from 'lucide-react';
|
import { Twitter, Shield, Zap, Smartphone, MessageCircle, UserPlus, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Twitter (X) QR Code Generator | Follow & Connect | QR Master',
|
title: 'Free Twitter (X) QR Code Generator | Follow & Connect | QR Master',
|
||||||
description: 'Create a QR code for your X (formerly Twitter) profile. Scanners are redirected to the app instantly to follow you. Free & Customizable.',
|
description: 'Create a QR code for your X (formerly Twitter) profile. Scanners are redirected to the app instantly to follow you. Free & Customizable.',
|
||||||
keywords: ['twitter qr code', 'x qr generator', 'twitter follow qr', 'social media qr code', 'x profile qr'],
|
keywords: ['twitter qr code', 'x qr generator', 'twitter follow qr', 'social media qr code', 'x profile qr'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/twitter-qr-code',
|
canonical: 'https://qrmaster.io/tools/twitter-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Twitter (X) QR Code Generator | QR Master',
|
title: 'Free Twitter (X) QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to grow your X (Twitter) following. Instant app redirect.',
|
description: 'Generate QR codes to grow your X (Twitter) following. Instant app redirect.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/twitter-qr-code',
|
url: 'https://qrmaster.io/tools/twitter-qr-code',
|
||||||
images: [{ url: '/og-twitter-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-twitter-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Twitter (X) QR Code Generator',
|
title: 'Free Twitter (X) QR Code Generator',
|
||||||
description: 'Create QR codes for X. Boost your following.',
|
description: 'Create QR codes for X. Boost your following.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Twitter (X) QR Code Generator',
|
name: 'Twitter (X) QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '980',
|
ratingCount: '980',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that direct users to an X (Twitter) profile or tweet.',
|
description: 'Generate QR codes that direct users to an X (Twitter) profile or tweet.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Twitter QR Code',
|
name: 'How to Create a Twitter QR Code',
|
||||||
description: 'Create a QR code that opens an X profile.',
|
description: 'Create a QR code that opens an X profile.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Username',
|
name: 'Enter Username',
|
||||||
text: 'Enter your X handle (e.g. @elonmusk).',
|
text: 'Enter your X handle (e.g. @elonmusk).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Design',
|
name: 'Design',
|
||||||
text: 'Choose a black frame or custom color.',
|
text: 'Choose a black frame or custom color.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code.',
|
text: 'Save the QR code.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan to verify it goes to the correct profile.',
|
text: 'Scan to verify it goes to the correct profile.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Add to your business cards or conference badges.',
|
text: 'Add to your business cards or conference badges.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for both Twitter and X?',
|
name: 'Does it work for both Twitter and X?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, they are the same platform. The QR code links to x.com, which is the current standard, but works for twitter.com links too.',
|
text: 'Yes, they are the same platform. The QR code links to x.com, which is the current standard, but works for twitter.com links too.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I link to a specific tweet?',
|
name: 'Can I link to a specific tweet?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! Just paste the full URL of the tweet into the input field instead of your username.',
|
text: 'Yes! Just paste the full URL of the tweet into the input field instead of your username.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, generating this QR code is completely free and requires no signup.',
|
text: 'Yes, generating this QR code is completely free and requires no signup.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track scans?',
|
name: 'Can I track scans?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
text: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What if I change my handle?',
|
name: 'What if I change my handle?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'If you change your handle, the link in the QR code will break. You will need to generate a new QR code.',
|
text: 'If you change your handle, the link in the QR code will break. You will need to generate a new QR code.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TwitterQRCodePage() {
|
export default function TwitterQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Twitter QR Code Generator" toolSlug="twitter-qr-code" />
|
<ToolBreadcrumb toolName="Twitter QR Code Generator" toolSlug="twitter-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-950">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-950">
|
||||||
<div className="absolute inset-0 opacity-20">
|
<div className="absolute inset-0 opacity-20">
|
||||||
{/* X Pattern */}
|
{/* X Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="x_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
<pattern id="x_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||||
<path d="M20 20L40 40M40 20L20 40" stroke="white" strokeWidth="1" />
|
<path d="M20 20L40 40M40 20L20 40" stroke="white" strokeWidth="1" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#x_pattern)" />
|
<rect width="100%" height="100%" fill="url(#x_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-500 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-500 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Connect on X with <br className="hidden lg:block" />
|
Connect on X with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-white">Twitter QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-white">Twitter QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Share your X profile instantly. A quick scan takes users directly to your timeline to follow and interact.
|
Share your X profile instantly. A quick scan takes users directly to your timeline to follow and interact.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your community.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your community.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<UserPlus className="w-4 h-4 text-white" />
|
<UserPlus className="w-4 h-4 text-white" />
|
||||||
Get Followers
|
Get Followers
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<MessageCircle className="w-4 h-4 text-white" />
|
<MessageCircle className="w-4 h-4 text-white" />
|
||||||
Start Conversions
|
Start Conversions
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-white" />
|
<Zap className="w-4 h-4 text-white" />
|
||||||
Instant Connect
|
Instant Connect
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-black rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden flex items-center gap-4">
|
<div className="w-full bg-black rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden flex items-center gap-4">
|
||||||
<div className="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
<div className="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
||||||
<Twitter className="w-6 h-6 text-black" fill="black" />
|
<Twitter className="w-6 h-6 text-black" fill="black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-white">
|
<div className="text-white">
|
||||||
<div className="font-bold text-sm">QR Master</div>
|
<div className="font-bold text-sm">QR Master</div>
|
||||||
<div className="text-xs text-slate-400">@qrmaster</div>
|
<div className="text-xs text-slate-400">@qrmaster</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="ml-auto bg-white text-black px-4 py-1.5 rounded-full text-xs font-bold hover:bg-slate-200">
|
<button className="ml-auto bg-white text-black px-4 py-1.5 rounded-full text-xs font-bold hover:bg-slate-200">
|
||||||
Follow
|
Follow
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-white/10 p-2 rounded-full">
|
<div className="bg-white/10 p-2 rounded-full">
|
||||||
<Twitter className="w-5 h-5 text-white" fill="white" />
|
<Twitter className="w-5 h-5 text-white" fill="white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Twitter / X</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Twitter / X</div>
|
||||||
<div className="text-sm font-bold text-white">Profile Link</div>
|
<div className="text-sm font-bold text-white">Profile Link</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<TwitterGenerator />
|
<TwitterGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How X (Twitter) QR Codes Work
|
How X (Twitter) QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Twitter className="w-7 h-7 text-white" />
|
<Twitter className="w-7 h-7 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Input Handle</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Input Handle</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Simply type your @handle or paste your profile link.
|
Simply type your @handle or paste your profile link.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-white" />
|
<Smartphone className="w-7 h-7 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Snap</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Snap</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Put the code on your networking gear. People scan it in seconds.
|
Put the code on your networking gear. People scan it in seconds.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-white" />
|
<Download className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your X QR code.
|
Save your X QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<UserPlus className="w-6 h-6 text-white" />
|
<UserPlus className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
People scan to find you.
|
People scan to find you.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-white" />
|
<Share2 className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
They are instantly on your profile.
|
They are instantly on your profile.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Twitter QR codes.
|
Common questions about Twitter QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Why create a QR code for X?"
|
question="Why create a QR code for X?"
|
||||||
answer="It's much faster than telling someone your handle and hoping they spell it right. A scan is instant and error-proof."
|
answer="It's much faster than telling someone your handle and hoping they spell it right. A scan is instant and error-proof."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Will links to twitter.com still work?"
|
question="Will links to twitter.com still work?"
|
||||||
answer="Yes, twitter.com links redirect to x.com, so both work perfectly fine."
|
answer="Yes, twitter.com links redirect to x.com, so both work perfectly fine."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I change the destination later?"
|
question="Can I change the destination later?"
|
||||||
answer="No, this is a static QR code. If you change your handle, you will need a new QR code. Our Dynamic QR codes allow you to edit the link anytime."
|
answer="No, this is a static QR code. If you change your handle, you will need a new QR code. Our Dynamic QR codes allow you to edit the link anytime."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track scans?"
|
question="Can I track scans?"
|
||||||
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What if I change my handle?"
|
question="What if I change my handle?"
|
||||||
answer="If you change your handle, the link in the QR code will break. You will need to generate a new QR code."
|
answer="If you change your handle, the link in the QR code will break. You will need to generate a new QR code."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,246 +1,246 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Link as LinkIcon,
|
Link as LinkIcon,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Globe
|
Globe
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EEF2FF', // Indigo-50
|
paleGrey: '#EEF2FF', // Indigo-50
|
||||||
primary: '#4F46E5', // Indigo-600
|
primary: '#4F46E5', // Indigo-600
|
||||||
primaryDark: '#4338CA', // Indigo-700
|
primaryDark: '#4338CA', // Indigo-700
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Indigo', value: '#4F46E5' },
|
{ name: 'Indigo', value: '#4F46E5' },
|
||||||
{ name: 'Blue', value: '#2563EB' },
|
{ name: 'Blue', value: '#2563EB' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Pink', value: '#DB2777' },
|
{ name: 'Pink', value: '#DB2777' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'website', label: 'Website' },
|
{ id: 'website', label: 'Website' },
|
||||||
{ id: 'visit', label: 'Visit' },
|
{ id: 'visit', label: 'Visit' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function URLGenerator() {
|
export default function URLGenerator() {
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `url-qr-code.png`;
|
link.download = `url-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `url-qr-code.svg`;
|
link.download = `url-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* URL Input */}
|
{/* URL Input */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<LinkIcon className="w-5 h-5 text-[#4F46E5]" />
|
<LinkIcon className="w-5 h-5 text-[#4F46E5]" />
|
||||||
Website URL
|
Website URL
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Enter URL</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Enter URL</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://www.yourwebsite.com"
|
placeholder="https://www.yourwebsite.com"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#4F46E5] focus:ring-[#4F46E5]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#4F46E5] focus:ring-[#4F46E5]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Include https:// for best results.</p>
|
<p className="text-xs text-slate-500 mt-2">Include https:// for best results.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#4F46E5]" />
|
<Sparkles className="w-5 h-5 text-[#4F46E5]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#4F46E5] text-white border-[#4F46E5]"
|
? "bg-[#4F46E5] text-white border-[#4F46E5]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={url || "https://qrmaster.io"}
|
value={url || "https://qrmaster.io"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* URL Preview */}
|
{/* URL Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<LinkIcon className="w-4 h-4 text-indigo-600 shrink-0" />
|
<LinkIcon className="w-4 h-4 text-indigo-600 shrink-0" />
|
||||||
<span className="truncate">{url || 'Your Website'}</span>
|
<span className="truncate">{url || 'Your Website'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#4F46E5] hover:bg-[#4338CA] text-white shadow-lg"
|
className="bg-[#4F46E5] hover:bg-[#4338CA] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Your link is encoded directly. Static and forever free.
|
Your link is encoded directly. Static and forever free.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#4F46E5] to-[#4338CA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#4F46E5] to-[#4338CA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need to change this link later?</h3>
|
<h3 className="font-bold text-lg">Need to change this link later?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
If your URL changes, this QR code will stop working. Use Dynamic QR Codes to edit links anytime.
|
If your URL changes, this QR code will stop working. Use Dynamic QR Codes to edit links anytime.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#4F46E5] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#4F46E5] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Dynamic QR
|
Create Dynamic QR
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,307 +1,307 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import URLGenerator from './URLGenerator';
|
import URLGenerator from './URLGenerator';
|
||||||
import { Link as LinkIcon, Shield, Zap, Smartphone, Globe, BarChart } from 'lucide-react';
|
import { Link as LinkIcon, Shield, Zap, Smartphone, Globe, BarChart } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free URL QR Code Generator | Link to Any Website | QR Master',
|
title: 'Free URL QR Code Generator | Link to Any Website | QR Master',
|
||||||
description: 'Create a QR code for your website, social media, or any link. Static and free forever. No scan limits. Instant download.',
|
description: 'Create a QR code for your website, social media, or any link. Static and free forever. No scan limits. Instant download.',
|
||||||
keywords: ['url qr code', 'website qr code', 'link qr generator', 'free qr code generator', 'url to qr'],
|
keywords: ['url qr code', 'website qr code', 'link qr generator', 'free qr code generator', 'url to qr'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/url-qr-code',
|
canonical: 'https://qrmaster.io/tools/url-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free URL QR Code Generator | QR Master',
|
title: 'Free URL QR Code Generator | QR Master',
|
||||||
description: 'Turn any URL into a QR code. Share websites instantly.',
|
description: 'Turn any URL into a QR code. Share websites instantly.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/url-qr-code',
|
url: 'https://qrmaster.io/tools/url-qr-code',
|
||||||
images: [{ url: '/og-url-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-url-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free URL QR Code Generator',
|
title: 'Free URL QR Code Generator',
|
||||||
description: 'Create QR codes for any link. Instant and free.',
|
description: 'Create QR codes for any link. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'URL QR Code Generator',
|
name: 'URL QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '3100',
|
ratingCount: '3100',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes for URLs and websites. Direct linking, no redirects.',
|
description: 'Generate QR codes for URLs and websites. Direct linking, no redirects.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a URL QR Code',
|
name: 'How to Create a URL QR Code',
|
||||||
description: 'Turn a website link into a scannable QR code.',
|
description: 'Turn a website link into a scannable QR code.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter URL',
|
name: 'Enter URL',
|
||||||
text: 'Copy and paste your website address (e.g., https://example.com).',
|
text: 'Copy and paste your website address (e.g., https://example.com).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Select a color and add a call-to-action frame like "Scan Me".',
|
text: 'Select a color and add a call-to-action frame like "Scan Me".',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save your QR code as a PNG or SVG image.',
|
text: 'Save your QR code as a PNG or SVG image.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT20S',
|
totalTime: 'PT20S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do these QR codes expire?',
|
name: 'Do these QR codes expire?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. These are static QR codes. They directly encode your URL and will work forever as long as your website is online.',
|
text: 'No. These are static QR codes. They directly encode your URL and will work forever as long as your website is online.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track how many people scan it?',
|
name: 'Can I track how many people scan it?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No, static QR codes cannot be tracked. If you need scan usage analytics (location, device, time), you should use our Dynamic QR Code generator.',
|
text: 'No, static QR codes cannot be tracked. If you need scan usage analytics (location, device, time), you should use our Dynamic QR Code generator.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I change the destination URL later?',
|
name: 'Can I change the destination URL later?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. Once a static QR code is printed, it cannot be changed. If you change your website URL, you will need to print a new code. Use Dynamic QR Codes if you need flexibility.',
|
text: 'No. Once a static QR code is printed, it cannot be changed. If you change your website URL, you will need to print a new code. Use Dynamic QR Codes if you need flexibility.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a scan limit?',
|
name: 'Is there a scan limit?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. There are zero limits on how many times your QR code can be scanned.',
|
text: 'No. There are zero limits on how many times your QR code can be scanned.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function URLQRCodePage() {
|
export default function URLQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="URL QR Code Generator" toolSlug="url-qr-code" />
|
<ToolBreadcrumb toolName="URL QR Code Generator" toolSlug="url-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Link to Any Website with <br className="hidden lg:block" />
|
Link to Any Website with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Instant QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Instant QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Create a QR code for your website, portfolio, or menu.
|
Create a QR code for your website, portfolio, or menu.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free forever. No expirations.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free forever. No expirations.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Globe className="w-4 h-4 text-emerald-400" />
|
<Globe className="w-4 h-4 text-emerald-400" />
|
||||||
Universal Links
|
Universal Links
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-400" />
|
<Zap className="w-4 h-4 text-amber-400" />
|
||||||
Instant Redirect
|
Instant Redirect
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-purple-400" />
|
<Shield className="w-4 h-4 text-purple-400" />
|
||||||
Direct Encoding
|
Direct Encoding
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
||||||
<LinkIcon className="w-4 h-4 text-indigo-300" />
|
<LinkIcon className="w-4 h-4 text-indigo-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 w-full">
|
<div className="space-y-1 w-full">
|
||||||
<div className="h-1.5 w-3/4 bg-white/30 rounded-full" />
|
<div className="h-1.5 w-3/4 bg-white/30 rounded-full" />
|
||||||
<div className="h-1.5 w-1/2 bg-white/20 rounded-full" />
|
<div className="h-1.5 w-1/2 bg-white/20 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<URLGenerator />
|
<URLGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How URL QR Codes Work
|
How URL QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<LinkIcon className="w-7 h-7 text-[#1A1265]" />
|
<LinkIcon className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Copy the URL of the webpage you want to link to and paste it into the generator.
|
Copy the URL of the webpage you want to link to and paste it into the generator.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Users scan the code and a notification appears to open the link in their browser.
|
Users scan the code and a notification appears to open the link in their browser.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Globe className="w-7 h-7 text-[#1A1265]" />
|
<Globe className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Visit</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Visit</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
They are instantly directed to your website, restaurant menu, or social profile.
|
They are instantly directed to your website, restaurant menu, or social profile.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about URL QR codes.
|
Common questions about URL QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Do these QR codes expire?"
|
question="Do these QR codes expire?"
|
||||||
answer="No. Static URL QR codes do not expire. They contain the direct link to your website. As long as your website is active, the QR code will work."
|
answer="No. Static URL QR codes do not expire. They contain the direct link to your website. As long as your website is active, the QR code will work."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track scans?"
|
question="Can I track scans?"
|
||||||
answer="No, static QR codes cannot be tracked. If you need analytics to see who is scanning your code and from where, you need a Dynamic QR Code."
|
answer="No, static QR codes cannot be tracked. If you need analytics to see who is scanning your code and from where, you need a Dynamic QR Code."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What happens if I change my website URL?"
|
question="What happens if I change my website URL?"
|
||||||
answer="If you change your URL, this static QR code will no longer work (unless you set up a redirect on your own server). With a Dynamic QR Code, you can update the destination URL anytime without reprinting the code."
|
answer="If you change your URL, this static QR code will no longer work (unless you set up a redirect on your own server). With a Dynamic QR Code, you can update the destination URL anytime without reprinting the code."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Are there ads on the QR code?"
|
question="Are there ads on the QR code?"
|
||||||
answer="No. We do not insert ads before redirecting users. The scan goes directly to your URL."
|
answer="No. We do not insert ads before redirecting users. The scan goes directly to your URL."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,349 +1,349 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Briefcase,
|
Briefcase,
|
||||||
Phone,
|
Phone,
|
||||||
Mail,
|
Mail,
|
||||||
Globe,
|
Globe,
|
||||||
MapPin,
|
MapPin,
|
||||||
Contact
|
Contact
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#FFF1F2', // Rose-50
|
paleGrey: '#FFF1F2', // Rose-50
|
||||||
primary: '#E11D48', // Rose-600
|
primary: '#E11D48', // Rose-600
|
||||||
primaryDark: '#BE123C', // Rose-700
|
primaryDark: '#BE123C', // Rose-700
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Rose', value: '#E11D48' },
|
{ name: 'Rose', value: '#E11D48' },
|
||||||
{ name: 'Pink', value: '#DB2777' },
|
{ name: 'Pink', value: '#DB2777' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Navy', value: '#1E3A8A' },
|
{ name: 'Navy', value: '#1E3A8A' },
|
||||||
{ name: 'Purple', value: '#7C3AED' },
|
{ name: 'Purple', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'contact', label: 'Save Contact' },
|
{ id: 'contact', label: 'Save Contact' },
|
||||||
{ id: 'vcard', label: 'vCard' },
|
{ id: 'vcard', label: 'vCard' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function VCardGenerator() {
|
export default function VCardGenerator() {
|
||||||
// Personal Info
|
// Personal Info
|
||||||
const [firstName, setFirstName] = useState('');
|
const [firstName, setFirstName] = useState('');
|
||||||
const [lastName, setLastName] = useState('');
|
const [lastName, setLastName] = useState('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [website, setWebsite] = useState('');
|
const [website, setWebsite] = useState('');
|
||||||
const [jobTitle, setJobTitle] = useState('');
|
const [jobTitle, setJobTitle] = useState('');
|
||||||
const [company, setCompany] = useState('');
|
const [company, setCompany] = useState('');
|
||||||
|
|
||||||
// Address
|
// Address
|
||||||
const [street, setStreet] = useState('');
|
const [street, setStreet] = useState('');
|
||||||
const [city, setCity] = useState('');
|
const [city, setCity] = useState('');
|
||||||
const [zip, setZip] = useState('');
|
const [zip, setZip] = useState('');
|
||||||
const [country, setCountry] = useState('');
|
const [country, setCountry] = useState('');
|
||||||
|
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Generate VCard String
|
// Generate VCard String
|
||||||
const generateVCard = () => {
|
const generateVCard = () => {
|
||||||
const vcard = [
|
const vcard = [
|
||||||
'BEGIN:VCARD',
|
'BEGIN:VCARD',
|
||||||
'VERSION:3.0',
|
'VERSION:3.0',
|
||||||
`N:${lastName};${firstName};;;`,
|
`N:${lastName};${firstName};;;`,
|
||||||
`FN:${firstName} ${lastName}`,
|
`FN:${firstName} ${lastName}`,
|
||||||
`ORG:${company}`,
|
`ORG:${company}`,
|
||||||
`TITLE:${jobTitle}`,
|
`TITLE:${jobTitle}`,
|
||||||
`TEL;TYPE=CELL:${phone}`,
|
`TEL;TYPE=CELL:${phone}`,
|
||||||
`EMAIL;TYPE=WORK:${email}`,
|
`EMAIL;TYPE=WORK:${email}`,
|
||||||
`URL:${website}`,
|
`URL:${website}`,
|
||||||
`ADR;TYPE=WORK:;;${street};${city};;${zip};${country}`,
|
`ADR;TYPE=WORK:;;${street};${city};;${zip};${country}`,
|
||||||
'END:VCARD'
|
'END:VCARD'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
return vcard;
|
return vcard;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `vcard-${firstName || 'contact'}.png`;
|
link.download = `vcard-${firstName || 'contact'}.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `vcard-${firstName || 'contact'}.svg`;
|
link.download = `vcard-${firstName || 'contact'}.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-6xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-6xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-12">
|
<div className="grid lg:grid-cols-12">
|
||||||
|
|
||||||
{/* LEFT: Input Section (Wider for VCard) */}
|
{/* LEFT: Input Section (Wider for VCard) */}
|
||||||
<div className="lg:col-span-7 p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="lg:col-span-7 p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Personal Details */}
|
{/* Personal Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<User className="w-5 h-5 text-[#E11D48]" />
|
<User className="w-5 h-5 text-[#E11D48]" />
|
||||||
Contact Information
|
Contact Information
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-2 gap-4">
|
<div className="grid sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">First Name</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">First Name</label>
|
||||||
<Input placeholder="John" value={firstName} onChange={(e) => setFirstName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="John" value={firstName} onChange={(e) => setFirstName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Last Name</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Last Name</label>
|
||||||
<Input placeholder="Doe" value={lastName} onChange={(e) => setLastName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="Doe" value={lastName} onChange={(e) => setLastName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-2 gap-4">
|
<div className="grid sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Phone</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Phone className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
<Phone className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||||
<Input placeholder="+1 555 000 0000" value={phone} onChange={(e) => setPhone(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="+1 555 000 0000" value={phone} onChange={(e) => setPhone(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Email</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Email</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Mail className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
<Mail className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||||
<Input placeholder="john@company.com" value={email} onChange={(e) => setEmail(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="john@company.com" value={email} onChange={(e) => setEmail(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-2 gap-4">
|
<div className="grid sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Website</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Website</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Globe className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
<Globe className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||||
<Input placeholder="https://..." value={website} onChange={(e) => setWebsite(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="https://..." value={website} onChange={(e) => setWebsite(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Job Title</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Job Title</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Briefcase className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
<Briefcase className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||||
<Input placeholder="Manager" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="Manager" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Company</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Company</label>
|
||||||
<Input placeholder="Acme Corp" value={company} onChange={(e) => setCompany(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="Acme Corp" value={company} onChange={(e) => setCompany(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Address Details */}
|
{/* Address Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<MapPin className="w-5 h-5 text-[#E11D48]" />
|
<MapPin className="w-5 h-5 text-[#E11D48]" />
|
||||||
Address
|
Address
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid sm:grid-cols-2 gap-4">
|
<div className="grid sm:grid-cols-2 gap-4">
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Street</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Street</label>
|
||||||
<Input placeholder="123 Business Rd" value={street} onChange={(e) => setStreet(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="123 Business Rd" value={street} onChange={(e) => setStreet(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">City</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">City</label>
|
||||||
<Input placeholder="New York" value={city} onChange={(e) => setCity(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="New York" value={city} onChange={(e) => setCity(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Zip/Postcode</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Zip/Postcode</label>
|
||||||
<Input placeholder="10001" value={zip} onChange={(e) => setZip(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="10001" value={zip} onChange={(e) => setZip(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Country</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Country</label>
|
||||||
<Input placeholder="USA" value={country} onChange={(e) => setCountry(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
<Input placeholder="USA" value={country} onChange={(e) => setCountry(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#E11D48]" />
|
<Sparkles className="w-5 h-5 text-[#E11D48]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-2 gap-8">
|
<div className="grid sm:grid-cols-2 gap-8">
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2 px-3 rounded-lg text-sm font-medium transition-all border text-center",
|
"py-2 px-3 rounded-lg text-sm font-medium transition-all border text-center",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#E11D48] text-white border-[#E11D48]"
|
? "bg-[#E11D48] text-white border-[#E11D48]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="lg:col-span-5 p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="lg:col-span-5 p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '300px' }}
|
style={{ minWidth: '300px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={generateVCard()}
|
value={generateVCard()}
|
||||||
size={220}
|
size={220}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Contact className="w-4 h-4 text-slate-400 shrink-0" />
|
<Contact className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{firstName || 'First'} {lastName || 'Last'}</span>
|
<span className="truncate">{firstName || 'First'} {lastName || 'Last'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1 truncate">{company || 'Company Name'}</div>
|
<div className="text-xs text-slate-500 mt-1 truncate">{company || 'Company Name'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex flex-col sm:flex-row items-center gap-3 mt-8 w-full max-w-[320px]">
|
<div className="flex flex-col sm:flex-row items-center gap-3 mt-8 w-full max-w-[320px]">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#E11D48] hover:bg-[#BE123C] text-white shadow-lg w-full"
|
className="bg-[#E11D48] hover:bg-[#BE123C] text-white shadow-lg w-full"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white w-full"
|
className="border-slate-300 hover:bg-white w-full"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning adds this contact to the address book instantly.
|
Scanning adds this contact to the address book instantly.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#E11D48] to-[#BE123C] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#E11D48] to-[#BE123C] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Want a Digital Business Card?</h3>
|
<h3 className="font-bold text-lg">Want a Digital Business Card?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Upgrade to Dynamic vCard to include a profile photo, social links, and update your info anytime.
|
Upgrade to Dynamic vCard to include a profile photo, social links, and update your info anytime.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#E11D48] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#E11D48] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Digital Card
|
Create Digital Card
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,320 +1,320 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import VCardGenerator from './VCardGenerator';
|
import VCardGenerator from './VCardGenerator';
|
||||||
import { User, Shield, Zap, Smartphone, Contact, Share2, Check, UserPlus } from 'lucide-react';
|
import { User, Shield, Zap, Smartphone, Contact, Share2, Check, UserPlus } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free vCard QR Code Generator | Digital Business Card | QR Master',
|
title: 'Free vCard QR Code Generator | Digital Business Card | QR Master',
|
||||||
description: 'Create a vCard QR code for your business card. Share contact details (Name, Phone, Email) instantly with one scan. 100% Free & No App Required.',
|
description: 'Create a vCard QR code for your business card. Share contact details (Name, Phone, Email) instantly with one scan. 100% Free & No App Required.',
|
||||||
keywords: ['vcard qr code', 'business card qr code', 'contact qr generator', 'digital business card', 'add to contacts qr'],
|
keywords: ['vcard qr code', 'business card qr code', 'contact qr generator', 'digital business card', 'add to contacts qr'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/vcard-qr-code',
|
canonical: 'https://qrmaster.io/tools/vcard-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free vCard QR Code Generator | QR Master',
|
title: 'Free vCard QR Code Generator | QR Master',
|
||||||
description: 'Turn your contact info into a QR code. The modern way to share your business card.',
|
description: 'Turn your contact info into a QR code. The modern way to share your business card.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/vcard-qr-code',
|
url: 'https://qrmaster.io/tools/vcard-qr-code',
|
||||||
images: [{ url: '/og-vcard-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-vcard-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free vCard QR Code Generator',
|
title: 'Free vCard QR Code Generator',
|
||||||
description: 'Create QR codes for contact sharing. Instant and free.',
|
description: 'Create QR codes for contact sharing. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'vCard QR Code Generator',
|
name: 'vCard QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '4200',
|
ratingCount: '4200',
|
||||||
},
|
},
|
||||||
description: 'Generate vCard (VCF) QR codes for business cards. Scanners can save contact info instantly.',
|
description: 'Generate vCard (VCF) QR codes for business cards. Scanners can save contact info instantly.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a vCard QR Code',
|
name: 'How to Create a vCard QR Code',
|
||||||
description: 'Create a QR code that saves your contact details.',
|
description: 'Create a QR code that saves your contact details.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Details',
|
name: 'Enter Details',
|
||||||
text: 'Fill in your Name, Phone, Email, Company, and Address.',
|
text: 'Fill in your Name, Phone, Email, Company, and Address.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Select a color that matches your brand and add a frame.',
|
text: 'Select a color that matches your brand and add a frame.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Download the QR code image and place it on your physical business card.',
|
text: 'Download the QR code image and place it on your physical business card.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT1M',
|
totalTime: 'PT1M',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'How does a vCard QR code work?',
|
name: 'How does a vCard QR code work?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'A vCard QR code contains your contact information in a standardized format (VCF). When scanned, the phone recognizes it as a contact card and prompts the user to "Save Contact" to their address book.',
|
text: 'A vCard QR code contains your contact information in a standardized format (VCF). When scanned, the phone recognizes it as a contact card and prompts the user to "Save Contact" to their address book.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is there a limit to how much info I can add?',
|
name: 'Is there a limit to how much info I can add?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Static QR codes hold data directly in the pattern. The more data you add (long addresses, bio), the denser and harder to scan the QR code becomes. We recommend sticking to essential contact info for static codes.',
|
text: 'Static QR codes hold data directly in the pattern. The more data you add (long addresses, bio), the denser and harder to scan the QR code becomes. We recommend sticking to essential contact info for static codes.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I update my info later?',
|
name: 'Can I update my info later?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No. This is a static vCard QR code. Once created, the info cannot be changed. If you move jobs or change numbers, you must print a new code. For editable cards, use our Dynamic vCard Plus.',
|
text: 'No. This is a static vCard QR code. Once created, the info cannot be changed. If you move jobs or change numbers, you must print a new code. For editable cards, use our Dynamic vCard Plus.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work on iPhone and Android?',
|
name: 'Does it work on iPhone and Android?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Both iOS (Camera app) and Android (Camera or Google Lens) natively support vCard QR codes and correctly import the contact data.',
|
text: 'Yes. Both iOS (Camera app) and Android (Camera or Google Lens) natively support vCard QR codes and correctly import the contact data.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function VCardQRCodePage() {
|
export default function VCardQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="vCard QR Code Generator" toolSlug="vcard-qr-code" />
|
<ToolBreadcrumb toolName="vCard QR Code Generator" toolSlug="vcard-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#9F1239' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#9F1239' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-rose-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-rose-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
The Modern Way to <br className="hidden lg:block" />
|
The Modern Way to <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-rose-300 to-pink-300">Share Your Contact</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-rose-300 to-pink-300">Share Your Contact</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Create a scannable Digital Business Card. One scan saves your name, phone, email, and address instantly.
|
Create a scannable Digital Business Card. One scan saves your name, phone, email, and address instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free & Professional.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free & Professional.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<UserPlus className="w-4 h-4 text-rose-300" />
|
<UserPlus className="w-4 h-4 text-rose-300" />
|
||||||
Instant Save
|
Instant Save
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Share2 className="w-4 h-4 text-amber-400" />
|
<Share2 className="w-4 h-4 text-amber-400" />
|
||||||
Easy Share
|
Easy Share
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Shield className="w-4 h-4 text-purple-400" />
|
<Shield className="w-4 h-4 text-purple-400" />
|
||||||
No Data Stored
|
No Data Stored
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-96 h-60 bg-white/10 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl p-6 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-96 h-60 bg-white/10 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl p-6 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent" />
|
||||||
|
|
||||||
<div className="flex justify-between items-start relative z-10">
|
<div className="flex justify-between items-start relative z-10">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white/30 flex items-center justify-center">
|
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white/30 flex items-center justify-center">
|
||||||
<Contact className="w-8 h-8 text-white" />
|
<Contact className="w-8 h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-4 w-32 bg-white/90 rounded-sm" />
|
<div className="h-4 w-32 bg-white/90 rounded-sm" />
|
||||||
<div className="h-3 w-20 bg-emerald-400/90 rounded-sm" />
|
<div className="h-3 w-20 bg-emerald-400/90 rounded-sm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-24 h-24 bg-white rounded-lg p-1.5 shadow-lg">
|
<div className="w-24 h-24 bg-white rounded-lg p-1.5 shadow-lg">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={84} fgColor="#1A1265" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={84} fgColor="#1A1265" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-6 left-6 space-y-2 z-10">
|
<div className="absolute bottom-6 left-6 space-y-2 z-10">
|
||||||
<div className="h-2 w-48 bg-white/40 rounded-full" />
|
<div className="h-2 w-48 bg-white/40 rounded-full" />
|
||||||
<div className="h-2 w-40 bg-white/30 rounded-full" />
|
<div className="h-2 w-40 bg-white/30 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-4 -left-4 bg-white py-2 px-4 rounded-lg shadow-xl flex items-center gap-2 transform scale-90">
|
<div className="absolute -bottom-4 -left-4 bg-white py-2 px-4 rounded-lg shadow-xl flex items-center gap-2 transform scale-90">
|
||||||
<div className="bg-emerald-100 p-1.5 rounded-full">
|
<div className="bg-emerald-100 p-1.5 rounded-full">
|
||||||
<Check className="w-3 h-3 text-emerald-600" />
|
<Check className="w-3 h-3 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-bold text-slate-900">Saved to Contacts</span>
|
<span className="text-xs font-bold text-slate-900">Saved to Contacts</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<VCardGenerator />
|
<VCardGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How vCard QR Codes Work
|
How vCard QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Contact className="w-7 h-7 text-[#1A1265]" />
|
<Contact className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Details</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Enter Details</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Fill in your professional contact information. Only add what you want to share.
|
Fill in your professional contact information. Only add what you want to share.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
A potential client or partner scans your card with their phone camera.
|
A potential client or partner scans your card with their phone camera.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<UserPlus className="w-7 h-7 text-[#1A1265]" />
|
<UserPlus className="w-7 h-7 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Save</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Save</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
They tap "Create New Contact" to save your details instantly. No typing errors.
|
They tap "Create New Contact" to save your details instantly. No typing errors.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about vCard QR codes.
|
Common questions about vCard QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I add a profile picture?"
|
question="Can I add a profile picture?"
|
||||||
answer="Not on a static vCard QR code. Static codes store data in the pixels, so adding an image would make the code too complex to scan. For profile pictures, social links, and rich media, use our Dynamic vCard Plus solution."
|
answer="Not on a static vCard QR code. Static codes store data in the pixels, so adding an image would make the code too complex to scan. For profile pictures, social links, and rich media, use our Dynamic vCard Plus solution."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="How long does the QR code last?"
|
question="How long does the QR code last?"
|
||||||
answer="Forever. Static vCard QR codes do not expire because the data is embedded directly in the image."
|
answer="Forever. Static vCard QR codes do not expire because the data is embedded directly in the image."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What information is required?"
|
question="What information is required?"
|
||||||
answer="Nothing is strictly required, but we recommend at least a First Name and either a Phone Number or Email so the contact is useful."
|
answer="Nothing is strictly required, but we recommend at least a First Name and either a Phone Number or Email so the contact is useful."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is my data safe?"
|
question="Is my data safe?"
|
||||||
answer="Yes. This tool operates 100% in your browser. We do not store, see, or optimize your contact data. It goes directly from your input to the QR code."
|
answer="Yes. This tool operates 100% in your browser. We do not store, see, or optimize your contact data. It goes directly from your input to the QR code."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,268 +1,268 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Phone,
|
Phone,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Send
|
Send
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Textarea } from '@/components/ui/Textarea';
|
import { Textarea } from '@/components/ui/Textarea';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - WhatsApp Theme
|
// QR Color Options - WhatsApp Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'WhatsApp Green', value: '#25D366' },
|
{ name: 'WhatsApp Green', value: '#25D366' },
|
||||||
{ name: 'Teal', value: '#128C7E' },
|
{ name: 'Teal', value: '#128C7E' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Rich Blue', value: '#1A1265' },
|
{ name: 'Rich Blue', value: '#1A1265' },
|
||||||
{ name: 'Purple', value: '#7C3AED' },
|
{ name: 'Purple', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'chat', label: 'Chat With Us' },
|
{ id: 'chat', label: 'Chat With Us' },
|
||||||
{ id: 'support', label: 'Support' },
|
{ id: 'support', label: 'Support' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function WhatsappGenerator() {
|
export default function WhatsappGenerator() {
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#25D366');
|
const [qrColor, setQrColor] = useState('#25D366');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// WhatsApp URL: https://wa.me/number?text=message
|
// WhatsApp URL: https://wa.me/number?text=message
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
const cleanPhone = phone.replace(/\D/g, ''); // Remove non-digits
|
const cleanPhone = phone.replace(/\D/g, ''); // Remove non-digits
|
||||||
const encodedMessage = encodeURIComponent(message);
|
const encodedMessage = encodeURIComponent(message);
|
||||||
return `https://wa.me/${cleanPhone}?text=${encodedMessage}`;
|
return `https://wa.me/${cleanPhone}?text=${encodedMessage}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `whatsapp-qr-code.png`;
|
link.download = `whatsapp-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `whatsapp-qr-code.svg`;
|
link.download = `whatsapp-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* WhatsApp Details */}
|
{/* WhatsApp Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
||||||
WhatsApp Details
|
WhatsApp Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="15551234567"
|
placeholder="15551234567"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Include country code (e.g. 1 for US, 44 for UK). No '+' symbol.</p>
|
<p className="text-xs text-slate-500 mt-2">Include country code (e.g. 1 for US, 44 for UK). No '+' symbol.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message (Optional)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message (Optional)</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Hi, I'm interested in your services..."
|
placeholder="Hi, I'm interested in your services..."
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
|
||||||
className="h-24 p-4 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366] resize-none"
|
className="h-24 p-4 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366] resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#25D366]" />
|
<Sparkles className="w-5 h-5 text-[#25D366]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#25D366] text-white border-[#25D366]"
|
? "bg-[#25D366] text-white border-[#25D366]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={getUrl()}
|
value={getUrl()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{phone ? `+${phone}` : 'Number'}</span>
|
<span className="truncate">{phone ? `+${phone}` : 'Number'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Starts WhatsApp Chat</div>
|
<div className="text-xs text-slate-500 mt-1">Starts WhatsApp Chat</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#25D366] hover:bg-[#128C7E] text-white shadow-lg"
|
className="bg-[#25D366] hover:bg-[#128C7E] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning starts a chat with this number instantly.
|
Scanning starts a chat with this number instantly.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#128C7E] to-[#25D366] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#128C7E] to-[#25D366] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Using WhatsApp for Business?</h3>
|
<h3 className="font-bold text-lg">Using WhatsApp for Business?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Track how many customers contact you via QR code analytics with our Pro plan.
|
Track how many customers contact you via QR code analytics with our Pro plan.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#128C7E] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#128C7E] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Business Analytics
|
Get Business Analytics
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,363 +1,363 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import WhatsappGenerator from './WhatsAppGenerator';
|
import WhatsappGenerator from './WhatsAppGenerator';
|
||||||
import { MessageCircle, Shield, Zap, Smartphone, Send, Phone, Download, Check } from 'lucide-react';
|
import { MessageCircle, Shield, Zap, Smartphone, Send, Phone, Download, Check } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free WhatsApp QR Code Generator | Start Chats Instantly | QR Master',
|
title: 'Free WhatsApp QR Code Generator | Start Chats Instantly | QR Master',
|
||||||
description: 'Create a QR code that opens a WhatsApp chat with you. Add a pre-filled message. Perfect for customer support and sales.',
|
description: 'Create a QR code that opens a WhatsApp chat with you. Add a pre-filled message. Perfect for customer support and sales.',
|
||||||
keywords: ['whatsapp qr code', 'wa.me generator', 'whatsapp chat qr', 'whatsapp link generator', 'contact qr code'],
|
keywords: ['whatsapp qr code', 'wa.me generator', 'whatsapp chat qr', 'whatsapp link generator', 'contact qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/whatsapp-qr-code',
|
canonical: 'https://qrmaster.io/tools/whatsapp-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free WhatsApp QR Code Generator | QR Master',
|
title: 'Free WhatsApp QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to start WhatsApp chats. Add a pre-filled message.',
|
description: 'Generate QR codes to start WhatsApp chats. Add a pre-filled message.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/whatsapp-qr-code',
|
url: 'https://qrmaster.io/tools/whatsapp-qr-code',
|
||||||
images: [{ url: '/og-whatsapp-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-whatsapp-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free WhatsApp QR Code Generator',
|
title: 'Free WhatsApp QR Code Generator',
|
||||||
description: 'Create QR codes for WhatsApp. Chat instantly.',
|
description: 'Create QR codes for WhatsApp. Chat instantly.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'WhatsApp QR Code Generator',
|
name: 'WhatsApp QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '2300',
|
ratingCount: '2300',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that start a WhatsApp conversation with a specific number.',
|
description: 'Generate QR codes that start a WhatsApp conversation with a specific number.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a WhatsApp QR Code',
|
name: 'How to Create a WhatsApp QR Code',
|
||||||
description: 'Create a QR code that opens a WhatsApp chat.',
|
description: 'Create a QR code that opens a WhatsApp chat.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Number',
|
name: 'Enter Number',
|
||||||
text: 'Type your WhatsApp phone number with country code (e.g. 1555...).',
|
text: 'Type your WhatsApp phone number with country code (e.g. 1555...).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Add a pre-written message and choose your brand color.',
|
text: 'Add a pre-written message and choose your brand color.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save the QR code.',
|
text: 'Save the QR code.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
text: 'Scan the code to ensure it opens the correct chat.',
|
text: 'Scan the code to ensure it opens the correct chat.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Add it to your website, business cards, or support materials.',
|
text: 'Add it to your website, business cards, or support materials.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT45S',
|
totalTime: 'PT45S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do users need to save my number first?',
|
name: 'Do users need to save my number first?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'No! That is the best part. Scanning the code opens the chat immediately without them needing to save you as a contact first.',
|
text: 'No! That is the best part. Scanning the code opens the chat immediately without them needing to save you as a contact first.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I use this for WhatsApp Business?',
|
name: 'Can I use this for WhatsApp Business?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, it works perfectly for both personal and business accounts.',
|
text: 'Yes, it works perfectly for both personal and business accounts.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What is the Pre-filled Message?',
|
name: 'What is the Pre-filled Message?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'It is text that automatically appears in the user\'s typing field when they scan the code (e.g., "Hello, I want to order pizza"). They just have to hit send.',
|
text: 'It is text that automatically appears in the user\'s typing field when they scan the code (e.g., "Hello, I want to order pizza"). They just have to hit send.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, this generator is completely free.',
|
text: 'Yes, this generator is completely free.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I track scans?',
|
name: 'Can I track scans?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
text: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WhatsappQRCodePage() {
|
export default function WhatsappQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="WhatsApp QR Code Generator" toolSlug="whatsapp-qr-code" />
|
<ToolBreadcrumb toolName="WhatsApp QR Code Generator" toolSlug="whatsapp-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#128C7E]">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#128C7E]">
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
{/* WhatsApp Pattern */}
|
{/* WhatsApp Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="wa_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
<pattern id="wa_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||||
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#wa_pattern)" />
|
<rect width="100%" height="100%" fill="url(#wa_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-300 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-300 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-300"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-300"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Start Chats Instantly with <br className="hidden lg:block" />
|
Start Chats Instantly with <br className="hidden lg:block" />
|
||||||
<span className="text-white drop-shadow-md">WhatsApp QR Codes</span>
|
<span className="text-white drop-shadow-md">WhatsApp QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-green-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-green-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Let customers message you without saving your number.
|
Let customers message you without saving your number.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for support, sales, and bookings.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for support, sales, and bookings.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Send className="w-4 h-4 text-green-200" />
|
<Send className="w-4 h-4 text-green-200" />
|
||||||
Instant Chat
|
Instant Chat
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<MessageCircle className="w-4 h-4 text-white" />
|
<MessageCircle className="w-4 h-4 text-white" />
|
||||||
Pre-filled Msg
|
Pre-filled Msg
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Phone className="w-4 h-4 text-white" />
|
<Phone className="w-4 h-4 text-white" />
|
||||||
No Save Contact
|
No Save Contact
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-green-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-green-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-[#ECE5DD] rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden flex flex-col justify-end p-4">
|
<div className="w-full bg-[#ECE5DD] rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden flex flex-col justify-end p-4">
|
||||||
{/* Chat Bubble Right */}
|
{/* Chat Bubble Right */}
|
||||||
<div className="bg-[#DCF8C6] p-2 rounded-lg self-end mb-2 max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
<div className="bg-[#DCF8C6] p-2 rounded-lg self-end mb-2 max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||||
Hi! I'd like to book an appointment.
|
Hi! I'd like to book an appointment.
|
||||||
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:42 AM <span className="text-blue-500">✓✓</span></div>
|
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:42 AM <span className="text-blue-500">✓✓</span></div>
|
||||||
</div>
|
</div>
|
||||||
{/* Chat Bubble Left */}
|
{/* Chat Bubble Left */}
|
||||||
<div className="bg-white p-2 rounded-lg self-start max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
<div className="bg-white p-2 rounded-lg self-start max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||||
Sure! What time works for you?
|
Sure! What time works for you?
|
||||||
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:43 AM</div>
|
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:43 AM</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#128C7E" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#128C7E" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-green-100 p-2 rounded-full">
|
<div className="bg-green-100 p-2 rounded-full">
|
||||||
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">WhatsApp</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">WhatsApp</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Start Chat</div>
|
<div className="text-sm font-bold text-slate-900">Start Chat</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<WhatsappGenerator />
|
<WhatsappGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How WhatsApp QR Codes Work
|
How WhatsApp QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Phone className="w-7 h-7 text-[#25D366]" />
|
<Phone className="w-7 h-7 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Your Number</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Your Number</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Enter your WhatsApp phone number. Ensure it includes the country code.
|
Enter your WhatsApp phone number. Ensure it includes the country code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<MessageCircle className="w-6 h-6 text-[#25D366]" />
|
<MessageCircle className="w-6 h-6 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Add a message and choose your color.
|
Add a message and choose your color.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#25D366]" />
|
<Download className="w-6 h-6 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your QR code.
|
Save your QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Check className="w-6 h-6 text-[#25D366]" />
|
<Check className="w-6 h-6 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Scan to ensure it works.
|
Scan to ensure it works.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Send className="w-6 h-6 text-[#25D366]" />
|
<Send className="w-6 h-6 text-[#25D366]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Chat</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Chat</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Start chatting instantly.
|
Start chatting instantly.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about WhatsApp QR codes.
|
Common questions about WhatsApp QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work internationally?"
|
question="Does it work internationally?"
|
||||||
answer="Yes. Since you include the country code, it works for anyone, anywhere in the world."
|
answer="Yes. Since you include the country code, it works for anyone, anywhere in the world."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I use this on my website?"
|
question="Can I use this on my website?"
|
||||||
answer="Yes, you can display the QR code on your contact page so desktop users can easily scan it to chat on their phone."
|
answer="Yes, you can display the QR code on your contact page so desktop users can easily scan it to chat on their phone."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is my phone number visible?"
|
question="Is my phone number visible?"
|
||||||
answer="Yes, the phone number is embedded in the link. It is the same visibility as putting your phone number on a business card."
|
answer="Yes, the phone number is embedded in the link. It is the same visibility as putting your phone number on a business card."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it free?"
|
question="Is it free?"
|
||||||
answer="Yes, this generator is completely free."
|
answer="Yes, this generator is completely free."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I track scans?"
|
question="Can I track scans?"
|
||||||
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,307 +1,307 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Wifi,
|
Wifi,
|
||||||
Download,
|
Download,
|
||||||
Printer,
|
Printer,
|
||||||
Check,
|
Check,
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Sparkles
|
Sparkles
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#F0F9FF', // Sky-50
|
paleGrey: '#F0F9FF', // Sky-50
|
||||||
primary: '#0EA5E9', // Sky-500
|
primary: '#0EA5E9', // Sky-500
|
||||||
primaryDark: '#0284C7', // Sky-600
|
primaryDark: '#0284C7', // Sky-600
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Sky Blue', value: '#0EA5E9' },
|
{ name: 'Sky Blue', value: '#0EA5E9' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||||
{ name: 'Cyan', value: '#06B6D4' },
|
{ name: 'Cyan', value: '#06B6D4' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'wifi', label: 'WiFi' },
|
{ id: 'wifi', label: 'WiFi' },
|
||||||
{ id: 'connect', label: 'Connect' },
|
{ id: 'connect', label: 'Connect' },
|
||||||
{ id: 'free', label: 'Free WiFi' },
|
{ id: 'free', label: 'Free WiFi' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function WiFiGenerator() {
|
export default function WiFiGenerator() {
|
||||||
const [ssid, setSsid] = useState('');
|
const [ssid, setSsid] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [encryption, setEncryption] = useState('WPA');
|
const [encryption, setEncryption] = useState('WPA');
|
||||||
const [hidden, setHidden] = useState(false);
|
const [hidden, setHidden] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
// Customization
|
// Customization
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const wifiString = `WIFI:T:${encryption};S:${ssid};P:${password};H:${hidden};;`;
|
const wifiString = `WIFI:T:${encryption};S:${ssid};P:${password};H:${hidden};;`;
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `wifi-qr-${ssid || 'code'}.png`;
|
link.download = `wifi-qr-${ssid || 'code'}.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `wifi-qr-${ssid || 'code'}.svg`;
|
link.download = `wifi-qr-${ssid || 'code'}.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Network Details */}
|
{/* Network Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Wifi className="w-5 h-5 text-[#0EA5E9]" />
|
<Wifi className="w-5 h-5 text-[#0EA5E9]" />
|
||||||
Network Details
|
Network Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Network Name (SSID)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Network Name (SSID)</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g. MyHomeWiFi"
|
placeholder="e.g. MyHomeWiFi"
|
||||||
value={ssid}
|
value={ssid}
|
||||||
onChange={(e) => setSsid(e.target.value)}
|
onChange={(e) => setSsid(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Password</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Password</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder="Your WiFi Password"
|
placeholder="Your WiFi Password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9] pr-12"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9] pr-12"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 p-1.5 text-slate-400 hover:text-slate-600 transition-colors"
|
className="absolute right-3 top-1/2 -translate-y-1/2 p-1.5 text-slate-400 hover:text-slate-600 transition-colors"
|
||||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||||
>
|
>
|
||||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Security</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Security</label>
|
||||||
<Select
|
<Select
|
||||||
value={encryption}
|
value={encryption}
|
||||||
onChange={(e) => setEncryption(e.target.value)}
|
onChange={(e) => setEncryption(e.target.value)}
|
||||||
className="h-12 rounded-xl border-slate-200"
|
className="h-12 rounded-xl border-slate-200"
|
||||||
options={[
|
options={[
|
||||||
{ value: 'WPA', label: 'WPA / WPA2' },
|
{ value: 'WPA', label: 'WPA / WPA2' },
|
||||||
{ value: 'WEP', label: 'WEP' },
|
{ value: 'WEP', label: 'WEP' },
|
||||||
{ value: 'nopass', label: 'No Password' },
|
{ value: 'nopass', label: 'No Password' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
<label className="flex items-center gap-3 cursor-pointer group">
|
<label className="flex items-center gap-3 cursor-pointer group">
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
||||||
hidden ? "bg-[#1A1265] border-[#1A1265]" : "border-slate-300 group-hover:border-slate-400"
|
hidden ? "bg-[#1A1265] border-[#1A1265]" : "border-slate-300 group-hover:border-slate-400"
|
||||||
)}>
|
)}>
|
||||||
{hidden && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
{hidden && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" checked={hidden} onChange={(e) => setHidden(e.target.checked)} className="sr-only" />
|
<input type="checkbox" checked={hidden} onChange={(e) => setHidden(e.target.checked)} className="sr-only" />
|
||||||
<span className="text-sm font-medium text-slate-700">Hidden Network</span>
|
<span className="text-sm font-medium text-slate-700">Hidden Network</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#0EA5E9]" />
|
<Sparkles className="w-5 h-5 text-[#0EA5E9]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#0EA5E9] text-white border-[#0EA5E9]"
|
? "bg-[#0EA5E9] text-white border-[#0EA5E9]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label - CTA Button Style */}
|
{/* Frame Label - CTA Button Style */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={wifiString}
|
value={wifiString}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Network Info */}
|
{/* Network Info */}
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<h3 className="font-bold text-slate-900 text-xl truncate max-w-[260px] mx-auto">
|
<h3 className="font-bold text-slate-900 text-xl truncate max-w-[260px] mx-auto">
|
||||||
{ssid || 'Network Name'}
|
{ssid || 'Network Name'}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#0EA5E9] hover:bg-[#0284C7] text-white shadow-lg"
|
className="bg-[#0EA5E9] hover:bg-[#0284C7] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Your credentials stay on your device. Nothing is sent to servers.
|
Your credentials stay on your device. Nothing is sent to servers.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#0EA5E9] to-[#0284C7] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#0EA5E9] to-[#0284C7] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Hospitality or Business WiFi?</h3>
|
<h3 className="font-bold text-lg">Hospitality or Business WiFi?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Get scan analytics and collect customer reviews with our Pro plan.</p>
|
<p className="text-white/80 text-sm mt-1">Get scan analytics and collect customer reviews with our Pro plan.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#0EA5E9] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#0EA5E9] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Business Tools
|
Get Business Tools
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,369 +1,369 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import WiFiGenerator from './WiFiGenerator';
|
import WiFiGenerator from './WiFiGenerator';
|
||||||
import { Wifi, Shield, Zap, Smartphone, Lock, QrCode, Download, Share2 } from 'lucide-react';
|
import { Wifi, Shield, Zap, Smartphone, Lock, QrCode, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free WiFi QR Code Generator | Instant & Secure | QR Master',
|
title: 'Free WiFi QR Code Generator | Instant & Secure | QR Master',
|
||||||
description: 'Create a WiFi QR code in seconds. Guests scan to connect instantly—no typing passwords. 100% private: your credentials never leave your browser. Free forever.',
|
description: 'Create a WiFi QR code in seconds. Guests scan to connect instantly—no typing passwords. 100% private: your credentials never leave your browser. Free forever.',
|
||||||
keywords: ['wifi qr code', 'qr code generator', 'wifi qr code generator', 'share wifi', 'wifi password qr', 'guest wifi'],
|
keywords: ['wifi qr code', 'qr code generator', 'wifi qr code generator', 'share wifi', 'wifi password qr', 'guest wifi'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/wifi-qr-code',
|
canonical: 'https://qrmaster.io/tools/wifi-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free WiFi QR Code Generator | QR Master',
|
title: 'Free WiFi QR Code Generator | QR Master',
|
||||||
description: 'Share your WiFi without sharing your password. Guests scan the QR code to connect instantly.',
|
description: 'Share your WiFi without sharing your password. Guests scan the QR code to connect instantly.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/wifi-qr-code',
|
url: 'https://qrmaster.io/tools/wifi-qr-code',
|
||||||
images: [{ url: '/og-wifi-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-wifi-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free WiFi QR Code Generator',
|
title: 'Free WiFi QR Code Generator',
|
||||||
description: 'Share WiFi instantly with a QR code. No typing passwords.',
|
description: 'Share WiFi instantly with a QR code. No typing passwords.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
// SoftwareApplication Schema
|
// SoftwareApplication Schema
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'WiFi QR Code Generator',
|
name: 'WiFi QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.9',
|
ratingValue: '4.9',
|
||||||
ratingCount: '2847',
|
ratingCount: '2847',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes for WiFi networks. Guests scan to connect without typing passwords.',
|
description: 'Generate QR codes for WiFi networks. Guests scan to connect without typing passwords.',
|
||||||
},
|
},
|
||||||
// HowTo Schema for Featured Snippets
|
// HowTo Schema for Featured Snippets
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a WiFi QR Code',
|
name: 'How to Create a WiFi QR Code',
|
||||||
description: 'Create a QR code that connects devices to your WiFi network automatically.',
|
description: 'Create a QR code that connects devices to your WiFi network automatically.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Network Name',
|
name: 'Enter Network Name',
|
||||||
text: 'Type your WiFi network name (SSID) in the Network Name field.',
|
text: 'Type your WiFi network name (SSID) in the Network Name field.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Enter Password',
|
name: 'Enter Password',
|
||||||
text: 'Enter your WiFi password. This is processed locally and never sent to any server.',
|
text: 'Enter your WiFi password. This is processed locally and never sent to any server.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Select Security Type',
|
name: 'Select Security Type',
|
||||||
text: 'Choose WPA/WPA2 (most common), WEP, or No Password for open networks.',
|
text: 'Choose WPA/WPA2 (most common), WEP, or No Password for open networks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download QR Code',
|
name: 'Download QR Code',
|
||||||
text: 'Click Download PNG or SVG to save your QR code. Print it or share digitally.',
|
text: 'Click Download PNG or SVG to save your QR code. Print it or share digitally.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Connect',
|
name: 'Connect',
|
||||||
text: 'Print the code. Guests can scan it to join your network instantly.',
|
text: 'Print the code. Guests can scan it to join your network instantly.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT1M',
|
totalTime: 'PT1M',
|
||||||
},
|
},
|
||||||
// FAQPage Schema
|
// FAQPage Schema
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it safe to enter my WiFi password?',
|
name: 'Is it safe to enter my WiFi password?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, completely safe. This tool processes everything in your browser (client-side). Your password never leaves your device and is not sent to any server.',
|
text: 'Yes, completely safe. This tool processes everything in your browser (client-side). Your password never leaves your device and is not sent to any server.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Do WiFi QR codes work on iPhone and Android?',
|
name: 'Do WiFi QR codes work on iPhone and Android?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. Both iOS (11+) and Android devices can scan WiFi QR codes using their built-in camera app. No additional apps required.',
|
text: 'Yes. Both iOS (11+) and Android devices can scan WiFi QR codes using their built-in camera app. No additional apps required.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What happens if I change my WiFi password?',
|
name: 'What happens if I change my WiFi password?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'You will need to generate a new QR code with the updated password. Consider using dynamic QR codes if you change passwords frequently.',
|
text: 'You will need to generate a new QR code with the updated password. Consider using dynamic QR codes if you change passwords frequently.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I customize the QR code design?',
|
name: 'Can I customize the QR code design?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. You can change the QR code color and add frame labels like "Scan Me" or "WiFi" to make it more recognizable.',
|
text: 'Yes. You can change the QR code color and add frame labels like "Scan Me" or "WiFi" to make it more recognizable.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for hidden networks?',
|
name: 'Does it work for hidden networks?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, just check the "Hidden Network" box if your SSID is hidden. The QR code contains the standard WiFi string configuration.',
|
text: 'Yes, just check the "Hidden Network" box if your SSID is hidden. The QR code contains the standard WiFi string configuration.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WiFiQRCodePage() {
|
export default function WiFiQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* JSON-LD Script */}
|
{/* JSON-LD Script */}
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="WiFi QR Code Generator" toolSlug="wifi-qr-code" />
|
<ToolBreadcrumb toolName="WiFi QR Code Generator" toolSlug="wifi-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||||
{/* Background Pattern */}
|
{/* Background Pattern */}
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
|
|
||||||
{/* Left: Text Content */}
|
{/* Left: Text Content */}
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
{/* Badge */}
|
{/* Badge */}
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
The Safest Way to <br className="hidden lg:block" />
|
The Safest Way to <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Share Your WiFi</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Share Your WiFi</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Generate a secure QR code in seconds. No more spelling out complicated passwords.
|
Generate a secure QR code in seconds. No more spelling out complicated passwords.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Client-Side & Private.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Client-Side & Private.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Lock className="w-4 h-4 text-emerald-400" />
|
<Lock className="w-4 h-4 text-emerald-400" />
|
||||||
No Server Uploads
|
No Server Uploads
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-400" />
|
<Zap className="w-4 h-4 text-amber-400" />
|
||||||
Instant Connect
|
Instant Connect
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||||
iOS & Android
|
iOS & Android
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Visual Abstract Composition */}
|
{/* Right: Visual Abstract Composition */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
{/* Decorative Glow */}
|
{/* Decorative Glow */}
|
||||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
{/* Floating Glass Card */}
|
{/* Floating Glass Card */}
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
{/* Mock QR */}
|
{/* Mock QR */}
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||||
{/* Scan Line */}
|
{/* Scan Line */}
|
||||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full space-y-3">
|
<div className="w-full space-y-3">
|
||||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-emerald-100 p-2 rounded-full">
|
<div className="bg-emerald-100 p-2 rounded-full">
|
||||||
<Wifi className="w-5 h-5 text-emerald-600" />
|
<Wifi className="w-5 h-5 text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Connected</div>
|
<div className="text-sm font-bold text-slate-900">Connected</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<WiFiGenerator />
|
<WiFiGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS - AEO/GEO Content */}
|
{/* HOW IT WORKS - AEO/GEO Content */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How WiFi QR Codes Work
|
How WiFi QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Wifi className="w-6 h-6 text-[#1A1265]" />
|
<Wifi className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Network</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Network</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Enter your WiFi SSID and password.
|
Enter your WiFi SSID and password.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Security</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Security</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Select WPA/WPA2 encryption.
|
Select WPA/WPA2 encryption.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Customize colors and add a frame.
|
Customize colors and add a frame.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Get your high-quality QR image.
|
Get your high-quality QR image.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Print it out. Guests scan to join!
|
Print it out. Guests scan to join!
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION - Featured Snippet Optimized */}
|
{/* FAQ SECTION - Featured Snippet Optimized */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Everything you need to know about WiFi QR codes.
|
Everything you need to know about WiFi QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it safe to enter my WiFi password here?"
|
question="Is it safe to enter my WiFi password here?"
|
||||||
answer="Yes, completely safe. This tool uses client-side processing, meaning your WiFi password never leaves your device. It's processed locally in your browser to generate the QR code—no data is sent to any server."
|
answer="Yes, completely safe. This tool uses client-side processing, meaning your WiFi password never leaves your device. It's processed locally in your browser to generate the QR code—no data is sent to any server."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Do WiFi QR codes work on iPhone and Android?"
|
question="Do WiFi QR codes work on iPhone and Android?"
|
||||||
answer="Yes. iOS 11 and later, as well as all modern Android devices, can scan WiFi QR codes using the built-in camera app. Simply point the camera at the QR code and tap the notification to connect."
|
answer="Yes. iOS 11 and later, as well as all modern Android devices, can scan WiFi QR codes using the built-in camera app. Simply point the camera at the QR code and tap the notification to connect."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What happens if I change my WiFi password?"
|
question="What happens if I change my WiFi password?"
|
||||||
answer="If you change your WiFi password, the old QR code will stop working. You'll need to generate a new QR code with the updated credentials. For frequently changing passwords, consider using dynamic QR codes."
|
answer="If you change your WiFi password, the old QR code will stop working. You'll need to generate a new QR code with the updated credentials. For frequently changing passwords, consider using dynamic QR codes."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I customize the QR code design?"
|
question="Can I customize the QR code design?"
|
||||||
answer="Yes. You can change the foreground color of the QR code and add frame labels such as 'Scan Me', 'WiFi', or 'Connect' to make your QR code more recognizable and user-friendly."
|
answer="Yes. You can change the foreground color of the QR code and add frame labels such as 'Scan Me', 'WiFi', or 'Connect' to make your QR code more recognizable and user-friendly."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work for hidden networks?"
|
question="Does it work for hidden networks?"
|
||||||
answer="Yes, just check the 'Hidden Network' box if your SSID is hidden. The QR code contains the standard WiFi string configuration."
|
answer="Yes, just check the 'Hidden Network' box if your SSID is hidden. The QR code contains the standard WiFi string configuration."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FAQ Item Component
|
// FAQ Item Component
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,246 +1,246 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Youtube,
|
Youtube,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Play
|
Play
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EBEBDF',
|
paleGrey: '#EBEBDF',
|
||||||
richBlue: '#1A1265',
|
richBlue: '#1A1265',
|
||||||
richBlueLight: '#2A2275',
|
richBlueLight: '#2A2275',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options - YT Theme
|
// QR Color Options - YT Theme
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'YouTube Red', value: '#FF0000' },
|
{ name: 'YouTube Red', value: '#FF0000' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Dark Blue', value: '#1A1265' },
|
{ name: 'Dark Blue', value: '#1A1265' },
|
||||||
{ name: 'Teal', value: '#0D9488' },
|
{ name: 'Teal', value: '#0D9488' },
|
||||||
{ name: 'Grey', value: '#374151' },
|
{ name: 'Grey', value: '#374151' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'watch', label: 'Watch' },
|
{ id: 'watch', label: 'Watch' },
|
||||||
{ id: 'subscribe', label: 'Subscribe' },
|
{ id: 'subscribe', label: 'Subscribe' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function YoutubeGenerator() {
|
export default function YoutubeGenerator() {
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#FF0000');
|
const [qrColor, setQrColor] = useState('#FF0000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `youtube-qr-code.png`;
|
link.download = `youtube-qr-code.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `youtube-qr-code.svg`;
|
link.download = `youtube-qr-code.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* YouTube Details */}
|
{/* YouTube Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Youtube className="w-5 h-5 text-[#FF0000]" />
|
<Youtube className="w-5 h-5 text-[#FF0000]" />
|
||||||
YouTube Video or Channel
|
YouTube Video or Channel
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Video/Channel URL</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Video/Channel URL</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://youtube.com/watch?v=..."
|
placeholder="https://youtube.com/watch?v=..."
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#FF0000] focus:ring-[#FF0000]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#FF0000] focus:ring-[#FF0000]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">Paste a link to any video, channel, or playlist.</p>
|
<p className="text-xs text-slate-500 mt-2">Paste a link to any video, channel, or playlist.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#FF0000]" />
|
<Sparkles className="w-5 h-5 text-[#FF0000]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#FF0000] text-white border-[#FF0000]"
|
? "bg-[#FF0000] text-white border-[#FF0000]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={url || "https://youtube.com"}
|
value={url || "https://youtube.com"}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Preview */}
|
{/* Info Preview */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||||
<Youtube className="w-4 h-4 text-slate-400 shrink-0" />
|
<Youtube className="w-4 h-4 text-slate-400 shrink-0" />
|
||||||
<span className="truncate">{url ? 'YouTube Content' : 'youtube.com'}</span>
|
<span className="truncate">{url ? 'YouTube Content' : 'youtube.com'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-slate-500 mt-1">Opens in YouTube App</div>
|
<div className="text-xs text-slate-500 mt-1">Opens in YouTube App</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#FF0000] hover:bg-[#cc0000] text-white shadow-lg"
|
className="bg-[#FF0000] hover:bg-[#cc0000] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Scanning redirects directly to the video or channel.
|
Scanning redirects directly to the video or channel.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#FF0000] to-[#cc0000] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#FF0000] to-[#cc0000] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Promoting a Video Channel?</h3>
|
<h3 className="font-bold text-lg">Promoting a Video Channel?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">
|
<p className="text-white/80 text-sm mt-1">
|
||||||
Dynamic QR Codes give you stats on scans, locations, and time of day.
|
Dynamic QR Codes give you stats on scans, locations, and time of day.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#FF0000] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#FF0000] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Get Video Stats
|
Get Video Stats
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,358 +1,358 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import YoutubeGenerator from './YouTubeGenerator';
|
import YoutubeGenerator from './YouTubeGenerator';
|
||||||
import { Youtube, Shield, Zap, Smartphone, Play, Radio, Download, Share2 } from 'lucide-react';
|
import { Youtube, Shield, Zap, Smartphone, Play, Radio, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free YouTube QR Code Generator | Get Views & Subscribers | QR Master',
|
title: 'Free YouTube QR Code Generator | Get Views & Subscribers | QR Master',
|
||||||
description: 'Create a QR code for your YouTube video or channel. Scanners are redirected to the YouTube app instantly to watch. Free & Fast.',
|
description: 'Create a QR code for your YouTube video or channel. Scanners are redirected to the YouTube app instantly to watch. Free & Fast.',
|
||||||
keywords: ['youtube qr code', 'video qr code', 'youtube channel qr', 'youtube subscribe qr', 'social media qr code'],
|
keywords: ['youtube qr code', 'video qr code', 'youtube channel qr', 'youtube subscribe qr', 'social media qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/youtube-qr-code',
|
canonical: 'https://qrmaster.io/tools/youtube-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free YouTube QR Code Generator | QR Master',
|
title: 'Free YouTube QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes to grow your YouTube channel. Instant video play.',
|
description: 'Generate QR codes to grow your YouTube channel. Instant video play.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/youtube-qr-code',
|
url: 'https://qrmaster.io/tools/youtube-qr-code',
|
||||||
images: [{ url: '/og-youtube-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-youtube-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free YouTube QR Code Generator',
|
title: 'Free YouTube QR Code Generator',
|
||||||
description: 'Create QR codes for YouTube videos. Get more views.',
|
description: 'Create QR codes for YouTube videos. Get more views.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'YouTube QR Code Generator',
|
name: 'YouTube QR Code Generator',
|
||||||
applicationCategory: 'UtilitiesApplication',
|
applicationCategory: 'UtilitiesApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '1340',
|
ratingCount: '1340',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that direct users to a YouTube video or channel.',
|
description: 'Generate QR codes that direct users to a YouTube video or channel.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a YouTube QR Code',
|
name: 'How to Create a YouTube QR Code',
|
||||||
description: 'Create a QR code that opens a YouTube video.',
|
description: 'Create a QR code that opens a YouTube video.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Copy URL',
|
name: 'Copy URL',
|
||||||
text: 'Copy the link of your YouTube video or channel.',
|
text: 'Copy the link of your YouTube video or channel.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Paste',
|
name: 'Paste',
|
||||||
text: 'Paste the link into the generator input.',
|
text: 'Paste the link into the generator input.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Customize',
|
name: 'Customize',
|
||||||
text: 'Add a "Watch Now" frame or change the color to YouTube Red.',
|
text: 'Add a "Watch Now" frame or change the color to YouTube Red.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Save your QR code image for printing.',
|
text: 'Save your QR code image for printing.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 5,
|
position: 5,
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
text: 'Place it on posters, merch, or video end screens.',
|
text: 'Place it on posters, merch, or video end screens.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it open the YouTube app?',
|
name: 'Does it open the YouTube app?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! If the user has the YouTube app installed, the QR code will automatically launch the app and play the video.',
|
text: 'Yes! If the user has the YouTube app installed, the QR code will automatically launch the app and play the video.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I link to a specific timestamp?',
|
name: 'Can I link to a specific timestamp?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. If you include the timestamp in your YouTube link (e.g., ?t=60s), the video will start playing from that exact moment.',
|
text: 'Yes. If you include the timestamp in your YouTube link (e.g., ?t=60s), the video will start playing from that exact moment.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Can I use this for a playlist?',
|
name: 'Can I use this for a playlist?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Absolutely. Just paste the playlist URL, and users will be taken to the full list of videos.',
|
text: 'Absolutely. Just paste the playlist URL, and users will be taken to the full list of videos.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Is it free?',
|
name: 'Is it free?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, this tool is 100% free forever.',
|
text: 'Yes, this tool is 100% free forever.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for YouTube Shorts?',
|
name: 'Does it work for YouTube Shorts?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes, just paste the "Share" link from any YouTube Short.',
|
text: 'Yes, just paste the "Share" link from any YouTube Short.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function YoutubeQRCodePage() {
|
export default function YoutubeQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="YouTube QR Code Generator" toolSlug="youtube-qr-code" />
|
<ToolBreadcrumb toolName="YouTube QR Code Generator" toolSlug="youtube-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#FF0000]">
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#FF0000]">
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
{/* Play Button Pattern */}
|
{/* Play Button Pattern */}
|
||||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="yt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
<pattern id="yt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||||
<path d="M20 20 L40 30 L20 40 Z" fill="white" />
|
<path d="M20 20 L40 30 L20 40 Z" fill="white" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" fill="url(#yt_pattern)" />
|
<rect width="100%" height="100%" fill="url(#yt_pattern)" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Get More Views with <br className="hidden lg:block" />
|
Get More Views with <br className="hidden lg:block" />
|
||||||
<span className="text-white drop-shadow-md">YouTube QR Codes</span>
|
<span className="text-white drop-shadow-md">YouTube QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
From print to play in one scan. Direct your audience to your latest video, channel, or playlist instantly.
|
From print to play in one scan. Direct your audience to your latest video, channel, or playlist instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost subscriber growth.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost subscriber growth.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Play className="w-4 h-4 text-white" />
|
<Play className="w-4 h-4 text-white" />
|
||||||
Instant Play
|
Instant Play
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Radio className="w-4 h-4 text-white" />
|
<Radio className="w-4 h-4 text-white" />
|
||||||
Grow Channel
|
Grow Channel
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||||
<Smartphone className="w-4 h-4 text-white" />
|
<Smartphone className="w-4 h-4 text-white" />
|
||||||
App Friendly
|
App Friendly
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-red-600/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-red-600/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
<div className="w-full bg-black rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden group-hover:scale-105 transition-transform flex items-center justify-center">
|
<div className="w-full bg-black rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden group-hover:scale-105 transition-transform flex items-center justify-center">
|
||||||
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-70"></div>
|
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-70"></div>
|
||||||
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center relative z-10 shadow-xl">
|
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center relative z-10 shadow-xl">
|
||||||
<Play className="w-6 h-6 text-white ml-1" fill="white" />
|
<Play className="w-6 h-6 text-white ml-1" fill="white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-2 right-2 bg-black/80 px-2 rounded text-xs text-white font-bold">10:24</div>
|
<div className="absolute bottom-2 right-2 bg-black/80 px-2 rounded text-xs text-white font-bold">10:24</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#FF0000" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#FF0000" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-red-100 p-2 rounded-full">
|
<div className="bg-red-100 p-2 rounded-full">
|
||||||
<Youtube className="w-5 h-5 text-red-600" />
|
<Youtube className="w-5 h-5 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Subscribers</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Subscribers</div>
|
||||||
<div className="text-sm font-bold text-slate-900">+10 New</div>
|
<div className="text-sm font-bold text-slate-900">+10 New</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<YoutubeGenerator />
|
<YoutubeGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How YouTube QR Codes Work
|
How YouTube QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Youtube className="w-7 h-7 text-[#FF0000]" />
|
<Youtube className="w-7 h-7 text-[#FF0000]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Copy the URL of your video, channel, or playlist.
|
Copy the URL of your video, channel, or playlist.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Smartphone className="w-7 h-7 text-[#FF0000]" />
|
<Smartphone className="w-7 h-7 text-[#FF0000]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Print Code</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Print Code</h3>
|
||||||
<p className="text-slate-600 text-sm">
|
<p className="text-slate-600 text-sm">
|
||||||
Place the QR code on flyers, posters, or merchandise.
|
Place the QR code on flyers, posters, or merchandise.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#FF0000]" />
|
<Download className="w-6 h-6 text-[#FF0000]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Save your high-quality QR code.
|
Save your high-quality QR code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Play className="w-6 h-6 text-[#FF0000]" />
|
<Play className="w-6 h-6 text-[#FF0000]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
Customers scan the code.
|
Customers scan the code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#FF0000]" />
|
<Share2 className="w-6 h-6 text-[#FF0000]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">5. Watch</h3>
|
<h3 className="font-bold text-slate-900 mb-2">5. Watch</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">
|
<p className="text-slate-600 text-xs leading-relaxed">
|
||||||
The video plays instantly.
|
The video plays instantly.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about YouTube QR codes.
|
Common questions about YouTube QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I link to my Live Stream?"
|
question="Can I link to my Live Stream?"
|
||||||
answer="Yes! Paste your channel's live link (e.g., youtube.com/c/YourChannel/live) and it will always go to your current live stream."
|
answer="Yes! Paste your channel's live link (e.g., youtube.com/c/YourChannel/live) and it will always go to your current live stream."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does the video auto-play?"
|
question="Does the video auto-play?"
|
||||||
answer="Most smartphones will open the YouTube app and auto-play the video, but it depends on the user's specific settings."
|
answer="Most smartphones will open the YouTube app and auto-play the video, but it depends on the user's specific settings."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Can I change the video later?"
|
question="Can I change the video later?"
|
||||||
answer="Only if you use our Dynamic QR Code service. This static code will always point to the original link you entered."
|
answer="Only if you use our Dynamic QR Code service. This static code will always point to the original link you entered."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Is it safe?"
|
question="Is it safe?"
|
||||||
answer="Yes. The QR code simply contains your video link. No personal data is stored or tracked by this free tool."
|
answer="Yes. The QR code simply contains your video link. No personal data is stored or tracked by this free tool."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work for YouTube Shorts?"
|
question="Does it work for YouTube Shorts?"
|
||||||
answer="Yes, just paste the 'Share' link from any YouTube Short."
|
answer="Yes, just paste the 'Share' link from any YouTube Short."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,303 +1,303 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Video,
|
Video,
|
||||||
Download,
|
Download,
|
||||||
Check,
|
Check,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Users
|
Users
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// Brand Colors - Zoom Blue
|
// Brand Colors - Zoom Blue
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
paleGrey: '#EFF6FF',
|
paleGrey: '#EFF6FF',
|
||||||
primary: '#2D8CFF',
|
primary: '#2D8CFF',
|
||||||
primaryDark: '#0B5CDB',
|
primaryDark: '#0B5CDB',
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR Color Options
|
// QR Color Options
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Zoom Blue', value: '#2D8CFF' },
|
{ name: 'Zoom Blue', value: '#2D8CFF' },
|
||||||
{ name: 'Dark Blue', value: '#0B5CDB' },
|
{ name: 'Dark Blue', value: '#0B5CDB' },
|
||||||
{ name: 'Classic Black', value: '#000000' },
|
{ name: 'Classic Black', value: '#000000' },
|
||||||
{ name: 'Indigo', value: '#4F46E5' },
|
{ name: 'Indigo', value: '#4F46E5' },
|
||||||
{ name: 'Violet', value: '#7C3AED' },
|
{ name: 'Violet', value: '#7C3AED' },
|
||||||
{ name: 'Emerald', value: '#10B981' },
|
{ name: 'Emerald', value: '#10B981' },
|
||||||
{ name: 'Rose', value: '#F43F5E' },
|
{ name: 'Rose', value: '#F43F5E' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Frame Options
|
// Frame Options
|
||||||
const FRAME_OPTIONS = [
|
const FRAME_OPTIONS = [
|
||||||
{ id: 'none', label: 'No Frame' },
|
{ id: 'none', label: 'No Frame' },
|
||||||
{ id: 'scanme', label: 'Scan Me' },
|
{ id: 'scanme', label: 'Scan Me' },
|
||||||
{ id: 'join', label: 'Join Meeting' },
|
{ id: 'join', label: 'Join Meeting' },
|
||||||
{ id: 'zoom', label: 'Zoom' },
|
{ id: 'zoom', label: 'Zoom' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function ZoomGenerator() {
|
export default function ZoomGenerator() {
|
||||||
const [meetingId, setMeetingId] = useState('');
|
const [meetingId, setMeetingId] = useState('');
|
||||||
const [passcode, setPasscode] = useState('');
|
const [passcode, setPasscode] = useState('');
|
||||||
const [useDirectLink, setUseDirectLink] = useState(false); // Default to web URL for compatibility
|
const [useDirectLink, setUseDirectLink] = useState(false); // Default to web URL for compatibility
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Format meeting ID for display (xxx xxxx xxxx)
|
// Format meeting ID for display (xxx xxxx xxxx)
|
||||||
const formatMeetingId = (id: string) => {
|
const formatMeetingId = (id: string) => {
|
||||||
const cleaned = id.replace(/\D/g, '');
|
const cleaned = id.replace(/\D/g, '');
|
||||||
if (cleaned.length <= 3) return cleaned;
|
if (cleaned.length <= 3) return cleaned;
|
||||||
if (cleaned.length <= 7) return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`;
|
if (cleaned.length <= 7) return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`;
|
||||||
return `${cleaned.slice(0, 3)} ${cleaned.slice(3, 7)} ${cleaned.slice(7, 11)}`;
|
return `${cleaned.slice(0, 3)} ${cleaned.slice(3, 7)} ${cleaned.slice(7, 11)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate Zoom link
|
// Generate Zoom link
|
||||||
const generateZoomLink = () => {
|
const generateZoomLink = () => {
|
||||||
const cleanId = meetingId.replace(/\D/g, '');
|
const cleanId = meetingId.replace(/\D/g, '');
|
||||||
if (!cleanId) return 'https://zoom.us/j/1234567890';
|
if (!cleanId) return 'https://zoom.us/j/1234567890';
|
||||||
|
|
||||||
if (useDirectLink) {
|
if (useDirectLink) {
|
||||||
// zoommtg protocol for direct app open
|
// zoommtg protocol for direct app open
|
||||||
let link = `zoommtg://zoom.us/join?confno=${cleanId}`;
|
let link = `zoommtg://zoom.us/join?confno=${cleanId}`;
|
||||||
if (passcode) {
|
if (passcode) {
|
||||||
link += `&pwd=${passcode}`;
|
link += `&pwd=${passcode}`;
|
||||||
}
|
}
|
||||||
return link;
|
return link;
|
||||||
} else {
|
} else {
|
||||||
// Regular web link
|
// Regular web link
|
||||||
let link = `https://zoom.us/j/${cleanId}`;
|
let link = `https://zoom.us/j/${cleanId}`;
|
||||||
if (passcode) {
|
if (passcode) {
|
||||||
link += `?pwd=${passcode}`;
|
link += `?pwd=${passcode}`;
|
||||||
}
|
}
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = async (format: 'png' | 'svg') => {
|
const handleDownload = async (format: 'png' | 'svg') => {
|
||||||
if (!qrRef.current) return;
|
if (!qrRef.current) return;
|
||||||
try {
|
try {
|
||||||
if (format === 'png') {
|
if (format === 'png') {
|
||||||
const { toPng } = await import('html-to-image');
|
const { toPng } = await import('html-to-image');
|
||||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.png`;
|
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.png`;
|
||||||
link.href = dataUrl;
|
link.href = dataUrl;
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||||
if (svgData) {
|
if (svgData) {
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.svg`;
|
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.svg`;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||||
return frame?.id !== 'none' ? frame?.label : null;
|
return frame?.id !== 'none' ? frame?.label : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
|
|
||||||
{/* LEFT: Input Section */}
|
{/* LEFT: Input Section */}
|
||||||
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
<div className="p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||||
|
|
||||||
{/* Meeting Details */}
|
{/* Meeting Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Video className="w-5 h-5 text-[#2D8CFF]" />
|
<Video className="w-5 h-5 text-[#2D8CFF]" />
|
||||||
Meeting Details
|
Meeting Details
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Meeting ID</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Meeting ID</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="123 4567 8901"
|
placeholder="123 4567 8901"
|
||||||
value={formatMeetingId(meetingId)}
|
value={formatMeetingId(meetingId)}
|
||||||
onChange={(e) => setMeetingId(e.target.value.replace(/\D/g, ''))}
|
onChange={(e) => setMeetingId(e.target.value.replace(/\D/g, ''))}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-slate-500 mt-2">The 10-11 digit meeting ID from your Zoom invite.</p>
|
<p className="text-xs text-slate-500 mt-2">The 10-11 digit meeting ID from your Zoom invite.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-2">Passcode (Optional)</label>
|
<label className="block text-sm font-medium text-slate-700 mb-2">Passcode (Optional)</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="abc123"
|
placeholder="abc123"
|
||||||
value={passcode}
|
value={passcode}
|
||||||
onChange={(e) => setPasscode(e.target.value)}
|
onChange={(e) => setPasscode(e.target.value)}
|
||||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<label className="flex items-center gap-3 cursor-pointer group">
|
<label className="flex items-center gap-3 cursor-pointer group">
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
||||||
useDirectLink ? "bg-[#2D8CFF] border-[#2D8CFF]" : "border-slate-300 group-hover:border-slate-400"
|
useDirectLink ? "bg-[#2D8CFF] border-[#2D8CFF]" : "border-slate-300 group-hover:border-slate-400"
|
||||||
)}>
|
)}>
|
||||||
{useDirectLink && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
{useDirectLink && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" checked={useDirectLink} onChange={(e) => setUseDirectLink(e.target.checked)} className="sr-only" />
|
<input type="checkbox" checked={useDirectLink} onChange={(e) => setUseDirectLink(e.target.checked)} className="sr-only" />
|
||||||
<span className="text-sm font-medium text-slate-700">Open Zoom app directly</span>
|
<span className="text-sm font-medium text-slate-700">Open Zoom app directly</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100"></div>
|
<div className="border-t border-slate-100"></div>
|
||||||
|
|
||||||
{/* Design Options */}
|
{/* Design Options */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||||
<Sparkles className="w-5 h-5 text-[#2D8CFF]" />
|
<Sparkles className="w-5 h-5 text-[#2D8CFF]" />
|
||||||
Design Options
|
Design Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Color Picker */}
|
{/* Color Picker */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{QR_COLORS.map((c) => (
|
{QR_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.name}
|
key={c.name}
|
||||||
onClick={() => setQrColor(c.value)}
|
onClick={() => setQrColor(c.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: c.value }}
|
style={{ backgroundColor: c.value }}
|
||||||
aria-label={`Select ${c.name}`}
|
aria-label={`Select ${c.name}`}
|
||||||
title={c.name}
|
title={c.name}
|
||||||
>
|
>
|
||||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Frame Selector */}
|
{/* Frame Selector */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{FRAME_OPTIONS.map((frame) => (
|
{FRAME_OPTIONS.map((frame) => (
|
||||||
<button
|
<button
|
||||||
key={frame.id}
|
key={frame.id}
|
||||||
onClick={() => setFrameType(frame.id)}
|
onClick={() => setFrameType(frame.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||||
frameType === frame.id
|
frameType === frame.id
|
||||||
? "bg-[#2D8CFF] text-white border-[#2D8CFF]"
|
? "bg-[#2D8CFF] text-white border-[#2D8CFF]"
|
||||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{frame.label}
|
{frame.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: Preview Section */}
|
{/* RIGHT: Preview Section */}
|
||||||
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
<div className="p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||||
|
|
||||||
{/* QR Card with Frame */}
|
{/* QR Card with Frame */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center"
|
||||||
style={{ minWidth: '320px' }}
|
style={{ minWidth: '320px' }}
|
||||||
>
|
>
|
||||||
{/* Frame Label */}
|
{/* Frame Label */}
|
||||||
{getFrameLabel() && (
|
{getFrameLabel() && (
|
||||||
<div
|
<div
|
||||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||||
style={{ backgroundColor: qrColor }}
|
style={{ backgroundColor: qrColor }}
|
||||||
>
|
>
|
||||||
{getFrameLabel()}
|
{getFrameLabel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={generateZoomLink()}
|
value={generateZoomLink()}
|
||||||
size={240}
|
size={240}
|
||||||
level="M"
|
level="M"
|
||||||
includeMargin={false}
|
includeMargin={false}
|
||||||
fgColor={qrColor}
|
fgColor={qrColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Meeting Info */}
|
{/* Meeting Info */}
|
||||||
<div className="mt-6 text-center max-w-[260px]">
|
<div className="mt-6 text-center max-w-[260px]">
|
||||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||||
<Video className="w-4 h-4 text-[#2D8CFF] shrink-0" />
|
<Video className="w-4 h-4 text-[#2D8CFF] shrink-0" />
|
||||||
<span className="truncate">{formatMeetingId(meetingId) || 'Meeting ID'}</span>
|
<span className="truncate">{formatMeetingId(meetingId) || 'Meeting ID'}</span>
|
||||||
</h3>
|
</h3>
|
||||||
{passcode && (
|
{passcode && (
|
||||||
<p className="text-sm text-slate-500 mt-1">Passcode: {passcode}</p>
|
<p className="text-sm text-slate-500 mt-1">Passcode: {passcode}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Buttons */}
|
{/* Download Buttons */}
|
||||||
<div className="flex items-center gap-3 mt-8">
|
<div className="flex items-center gap-3 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('png')}
|
onClick={() => handleDownload('png')}
|
||||||
className="bg-[#2D8CFF] hover:bg-[#0B5CDB] text-white shadow-lg"
|
className="bg-[#2D8CFF] hover:bg-[#0B5CDB] text-white shadow-lg"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Download PNG
|
Download PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDownload('svg')}
|
onClick={() => handleDownload('svg')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-slate-300 hover:bg-white"
|
className="border-slate-300 hover:bg-white"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
SVG
|
SVG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||||
Your meeting ID is encoded directly. Static and forever free.
|
Your meeting ID is encoded directly. Static and forever free.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upsell Banner */}
|
{/* Upsell Banner */}
|
||||||
<div className="mt-8 bg-gradient-to-r from-[#2D8CFF] to-[#0B5CDB] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
<div className="mt-8 bg-gradient-to-r from-[#2D8CFF] to-[#0B5CDB] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="text-white text-center sm:text-left">
|
<div className="text-white text-center sm:text-left">
|
||||||
<h3 className="font-bold text-lg">Need to update meeting details?</h3>
|
<h3 className="font-bold text-lg">Need to update meeting details?</h3>
|
||||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the meeting link without reprinting.</p>
|
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the meeting link without reprinting.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="bg-white text-[#2D8CFF] hover:bg-slate-100 shrink-0 shadow-lg">
|
<Button className="bg-white text-[#2D8CFF] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||||
Create Dynamic QR
|
Create Dynamic QR
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,331 +1,331 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import ZoomGenerator from './ZoomGenerator';
|
import ZoomGenerator from './ZoomGenerator';
|
||||||
import { Video, Shield, Zap, Smartphone, Users, Download, Share2 } from 'lucide-react';
|
import { Video, Shield, Zap, Smartphone, Users, Download, Share2 } from 'lucide-react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||||
|
|
||||||
// SEO Optimized Metadata
|
// SEO Optimized Metadata
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Free Zoom QR Code Generator | Join Meetings Instantly | QR Master',
|
title: 'Free Zoom QR Code Generator | Join Meetings Instantly | QR Master',
|
||||||
description: 'Create a QR code for your Zoom meeting. Attendees scan to join instantly. Includes meeting ID and passcode. Perfect for conference rooms and flyers.',
|
description: 'Create a QR code for your Zoom meeting. Attendees scan to join instantly. Includes meeting ID and passcode. Perfect for conference rooms and flyers.',
|
||||||
keywords: ['zoom qr code', 'zoom meeting qr', 'join zoom qr code', 'meeting room qr', 'zoom invitation qr', 'conference qr code'],
|
keywords: ['zoom qr code', 'zoom meeting qr', 'join zoom qr code', 'meeting room qr', 'zoom invitation qr', 'conference qr code'],
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://qrmaster.io/tools/zoom-qr-code',
|
canonical: 'https://qrmaster.io/tools/zoom-qr-code',
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Free Zoom QR Code Generator | QR Master',
|
title: 'Free Zoom QR Code Generator | QR Master',
|
||||||
description: 'Generate QR codes for Zoom meetings. One scan to join instantly.',
|
description: 'Generate QR codes for Zoom meetings. One scan to join instantly.',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: 'https://qrmaster.io/tools/zoom-qr-code',
|
url: 'https://qrmaster.io/tools/zoom-qr-code',
|
||||||
images: [{ url: '/og-zoom-generator.png', width: 1200, height: 630 }],
|
images: [{ url: '/og-zoom-generator.png', width: 1200, height: 630 }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Free Zoom QR Code Generator',
|
title: 'Free Zoom QR Code Generator',
|
||||||
description: 'Create Zoom meeting QR codes. Instant and free.',
|
description: 'Create Zoom meeting QR codes. Instant and free.',
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// JSON-LD Structured Data
|
// JSON-LD Structured Data
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'SoftwareApplication',
|
'@type': 'SoftwareApplication',
|
||||||
name: 'Zoom QR Code Generator',
|
name: 'Zoom QR Code Generator',
|
||||||
applicationCategory: 'BusinessApplication',
|
applicationCategory: 'BusinessApplication',
|
||||||
operatingSystem: 'Web Browser',
|
operatingSystem: 'Web Browser',
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
price: '0',
|
price: '0',
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
},
|
},
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
ratingValue: '4.8',
|
ratingValue: '4.8',
|
||||||
ratingCount: '720',
|
ratingCount: '720',
|
||||||
},
|
},
|
||||||
description: 'Generate QR codes that let people join your Zoom meeting with one scan.',
|
description: 'Generate QR codes that let people join your Zoom meeting with one scan.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowTo',
|
'@type': 'HowTo',
|
||||||
name: 'How to Create a Zoom QR Code',
|
name: 'How to Create a Zoom QR Code',
|
||||||
description: 'Create a QR code for joining Zoom meetings.',
|
description: 'Create a QR code for joining Zoom meetings.',
|
||||||
step: [
|
step: [
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 1,
|
position: 1,
|
||||||
name: 'Enter Meeting ID',
|
name: 'Enter Meeting ID',
|
||||||
text: 'Copy the 10-11 digit meeting ID from your Zoom invitation.',
|
text: 'Copy the 10-11 digit meeting ID from your Zoom invitation.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 2,
|
position: 2,
|
||||||
name: 'Add Passcode',
|
name: 'Add Passcode',
|
||||||
text: 'If your meeting has a passcode, enter it to include in the QR.',
|
text: 'If your meeting has a passcode, enter it to include in the QR.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 3,
|
position: 3,
|
||||||
name: 'Choose Link Type',
|
name: 'Choose Link Type',
|
||||||
text: 'Select whether to open Zoom app directly or use a web link.',
|
text: 'Select whether to open Zoom app directly or use a web link.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'HowToStep',
|
'@type': 'HowToStep',
|
||||||
position: 4,
|
position: 4,
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
text: 'Download your QR code and display it in your meeting room or invitation.',
|
text: 'Download your QR code and display it in your meeting room or invitation.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalTime: 'PT30S',
|
totalTime: 'PT30S',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
mainEntity: [
|
mainEntity: [
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What happens when someone scans the QR code?',
|
name: 'What happens when someone scans the QR code?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap "Join" to enter the meeting.',
|
text: 'The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap "Join" to enter the meeting.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work for recurring meetings?',
|
name: 'Does it work for recurring meetings?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions.',
|
text: 'Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'What if the meeting ID changes?',
|
name: 'What if the meeting ID changes?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Static QR codes cannot be updated. You\'ll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes.',
|
text: 'Static QR codes cannot be updated. You\'ll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'Question',
|
'@type': 'Question',
|
||||||
name: 'Does it work on all devices?',
|
name: 'Does it work on all devices?',
|
||||||
acceptedAnswer: {
|
acceptedAnswer: {
|
||||||
'@type': 'Answer',
|
'@type': 'Answer',
|
||||||
text: 'Yes. The QR code works on iOS, Android, and can also open Zoom on desktop computers if the Zoom app is installed.',
|
text: 'Yes. The QR code works on iOS, Android, and can also open Zoom on desktop computers if the Zoom app is installed.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ZoomQRCodePage() {
|
export default function ZoomQRCodePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<ToolBreadcrumb toolName="Zoom QR Code Generator" toolSlug="zoom-qr-code" />
|
<ToolBreadcrumb toolName="Zoom QR Code Generator" toolSlug="zoom-qr-code" />
|
||||||
|
|
||||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
|
|
||||||
{/* HERO SECTION */}
|
{/* HERO SECTION */}
|
||||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#2D8CFF' }}>
|
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#2D8CFF' }}>
|
||||||
<div className="absolute inset-0 opacity-10">
|
<div className="absolute inset-0 opacity-10">
|
||||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||||
<span className="flex h-2 w-2 relative">
|
<span className="flex h-2 w-2 relative">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||||
</span>
|
</span>
|
||||||
Free Tool — No Signup Required
|
Free Tool — No Signup Required
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||||
Join Meetings with <br className="hidden lg:block" />
|
Join Meetings with <br className="hidden lg:block" />
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-200 to-cyan-100">Zoom QR Codes</span>
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-200 to-cyan-100">Zoom QR Codes</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||||
Create QR codes for your Zoom meetings. Attendees scan to join instantly.
|
Create QR codes for your Zoom meetings. Attendees scan to join instantly.
|
||||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for conference rooms.</strong>
|
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for conference rooms.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Video className="w-4 h-4 text-white" />
|
<Video className="w-4 h-4 text-white" />
|
||||||
Direct Join
|
Direct Join
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Zap className="w-4 h-4 text-amber-300" />
|
<Zap className="w-4 h-4 text-amber-300" />
|
||||||
Instant Open
|
Instant Open
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||||
<Users className="w-4 h-4 text-emerald-300" />
|
<Users className="w-4 h-4 text-emerald-300" />
|
||||||
Any Device
|
Any Device
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Abstract */}
|
{/* Visual Abstract */}
|
||||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||||
<div className="absolute w-[500px] h-[500px] bg-blue-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
<div className="absolute w-[500px] h-[500px] bg-blue-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||||
|
|
||||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||||
|
|
||||||
{/* Meeting Card Mock */}
|
{/* Meeting Card Mock */}
|
||||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="w-10 h-10 bg-[#2D8CFF] rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-[#2D8CFF] rounded-full flex items-center justify-center">
|
||||||
<Video className="w-5 h-5 text-white" />
|
<Video className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||||
<div className="text-xs text-slate-500">ID: 123 456 7890</div>
|
<div className="text-xs text-slate-500">ID: 123 456 7890</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live</div>
|
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live</div>
|
||||||
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">12 attending</div>
|
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">12 attending</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#2D8CFF" level="Q" />
|
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#2D8CFF" level="Q" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Badge */}
|
{/* Floating Badge */}
|
||||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||||
<div className="bg-blue-100 p-2 rounded-full">
|
<div className="bg-blue-100 p-2 rounded-full">
|
||||||
<Users className="w-5 h-5 text-[#2D8CFF]" />
|
<Users className="w-5 h-5 text-[#2D8CFF]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||||
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GENERATOR SECTION */}
|
{/* GENERATOR SECTION */}
|
||||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||||
<ZoomGenerator />
|
<ZoomGenerator />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* HOW IT WORKS */}
|
{/* HOW IT WORKS */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
How Zoom QR Codes Work
|
How Zoom QR Codes Work
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-4 gap-8">
|
<div className="grid md:grid-cols-4 gap-8">
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Video className="w-6 h-6 text-[#2D8CFF]" />
|
<Video className="w-6 h-6 text-[#2D8CFF]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">1. Meeting ID</h3>
|
<h3 className="font-bold text-slate-900 mb-2">1. Meeting ID</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Enter your Zoom meeting ID.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Enter your Zoom meeting ID.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Shield className="w-6 h-6 text-[#2D8CFF]" />
|
<Shield className="w-6 h-6 text-[#2D8CFF]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">2. Passcode</h3>
|
<h3 className="font-bold text-slate-900 mb-2">2. Passcode</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Add passcode if required.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Add passcode if required.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Download className="w-6 h-6 text-[#2D8CFF]" />
|
<Download className="w-6 h-6 text-[#2D8CFF]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="text-center">
|
<article className="text-center">
|
||||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||||
<Share2 className="w-6 h-6 text-[#2D8CFF]" />
|
<Share2 className="w-6 h-6 text-[#2D8CFF]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
||||||
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ SECTION */}
|
{/* FAQ SECTION */}
|
||||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 text-center mb-10">
|
<p className="text-slate-600 text-center mb-10">
|
||||||
Common questions about Zoom QR codes.
|
Common questions about Zoom QR codes.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What happens when someone scans the QR code?"
|
question="What happens when someone scans the QR code?"
|
||||||
answer="The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap 'Join' to enter the meeting."
|
answer="The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap 'Join' to enter the meeting."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work for recurring meetings?"
|
question="Does it work for recurring meetings?"
|
||||||
answer="Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions."
|
answer="Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="What if the meeting ID changes?"
|
question="What if the meeting ID changes?"
|
||||||
answer="Static QR codes cannot be updated. You'll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes."
|
answer="Static QR codes cannot be updated. You'll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes."
|
||||||
/>
|
/>
|
||||||
<FaqItem
|
<FaqItem
|
||||||
question="Does it work without the Zoom app installed?"
|
question="Does it work without the Zoom app installed?"
|
||||||
answer="If 'Open Zoom app directly' is unchecked, the QR links to join.zoom.us which works in any browser."
|
answer="If 'Open Zoom app directly' is unchecked, the QR links to join.zoom.us which works in any browser."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||||
return (
|
return (
|
||||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||||
{question}
|
{question}
|
||||||
<span className="transition group-open:rotate-180 text-slate-400">
|
<span className="transition group-open:rotate-180 text-slate-400">
|
||||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||||
<path d="M6 9l6 6 6-6" />
|
<path d="M6 9l6 6 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||||
{answer}
|
{answer}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,106 @@
|
||||||
import { MetadataRoute } from 'next';
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
const baseUrl = 'https://www.qrmaster.net';
|
const baseUrl = 'https://www.qrmaster.net';
|
||||||
|
|
||||||
// All free tool slugs
|
// All free tool slugs
|
||||||
const freeTools = [
|
const freeTools = [
|
||||||
'url-qr-code',
|
'url-qr-code',
|
||||||
'vcard-qr-code',
|
'vcard-qr-code',
|
||||||
'text-qr-code',
|
'text-qr-code',
|
||||||
'email-qr-code',
|
'email-qr-code',
|
||||||
'sms-qr-code',
|
'sms-qr-code',
|
||||||
'wifi-qr-code',
|
'wifi-qr-code',
|
||||||
'crypto-qr-code',
|
'crypto-qr-code',
|
||||||
'event-qr-code',
|
'event-qr-code',
|
||||||
'facebook-qr-code',
|
'facebook-qr-code',
|
||||||
'instagram-qr-code',
|
'instagram-qr-code',
|
||||||
'twitter-qr-code',
|
'twitter-qr-code',
|
||||||
'youtube-qr-code',
|
'youtube-qr-code',
|
||||||
'whatsapp-qr-code',
|
'whatsapp-qr-code',
|
||||||
'tiktok-qr-code',
|
'tiktok-qr-code',
|
||||||
'geolocation-qr-code',
|
'geolocation-qr-code',
|
||||||
'phone-qr-code',
|
'phone-qr-code',
|
||||||
'paypal-qr-code',
|
'paypal-qr-code',
|
||||||
'zoom-qr-code',
|
'zoom-qr-code',
|
||||||
'teams-qr-code',
|
'teams-qr-code',
|
||||||
];
|
];
|
||||||
|
|
||||||
const toolPages = freeTools.map((slug) => ({
|
const toolPages = freeTools.map((slug) => ({
|
||||||
url: `${baseUrl}/tools/${slug}`,
|
url: `${baseUrl}/tools/${slug}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly' as const,
|
changeFrequency: 'monthly' as const,
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 1.0,
|
priority: 1.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/qr-code-tracking`,
|
url: `${baseUrl}/qr-code-tracking`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/dynamic-qr-code-generator`,
|
url: `${baseUrl}/dynamic-qr-code-generator`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/bulk-qr-code-generator`,
|
url: `${baseUrl}/bulk-qr-code-generator`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/pricing`,
|
url: `${baseUrl}/pricing`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/faq`,
|
url: `${baseUrl}/faq`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/blog`,
|
url: `${baseUrl}/blog`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/signup`,
|
url: `${baseUrl}/signup`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/login`,
|
url: `${baseUrl}/login`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'yearly',
|
changeFrequency: 'yearly',
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/privacy`,
|
url: `${baseUrl}/privacy`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'yearly',
|
changeFrequency: 'yearly',
|
||||||
priority: 0.4,
|
priority: 0.4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/terms`,
|
url: `${baseUrl}/terms`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'yearly',
|
changeFrequency: 'yearly',
|
||||||
priority: 0.4,
|
priority: 0.4,
|
||||||
},
|
},
|
||||||
...toolPages,
|
...toolPages,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,253 +1,253 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { motion, Variants } from 'framer-motion';
|
import { motion, Variants } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Link as LinkIcon,
|
Link as LinkIcon,
|
||||||
User,
|
User,
|
||||||
Mail,
|
Mail,
|
||||||
Calendar,
|
Calendar,
|
||||||
Facebook,
|
Facebook,
|
||||||
Instagram,
|
Instagram,
|
||||||
Phone,
|
Phone,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Type,
|
Type,
|
||||||
Music,
|
Music,
|
||||||
Twitter,
|
Twitter,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Wifi,
|
Wifi,
|
||||||
Youtube,
|
Youtube,
|
||||||
Bitcoin,
|
Bitcoin,
|
||||||
MapPin,
|
MapPin,
|
||||||
CreditCard,
|
CreditCard,
|
||||||
Video,
|
Video,
|
||||||
Users
|
Users
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const TOOLS = [
|
const TOOLS = [
|
||||||
{
|
{
|
||||||
icon: LinkIcon,
|
icon: LinkIcon,
|
||||||
name: 'URL',
|
name: 'URL',
|
||||||
description: 'Open any website',
|
description: 'Open any website',
|
||||||
href: '/tools/url-qr-code',
|
href: '/tools/url-qr-code',
|
||||||
color: 'text-blue-500',
|
color: 'text-blue-500',
|
||||||
bg: 'bg-blue-50'
|
bg: 'bg-blue-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: User,
|
icon: User,
|
||||||
name: 'vCard',
|
name: 'vCard',
|
||||||
description: 'Share contact details',
|
description: 'Share contact details',
|
||||||
href: '/tools/vcard-qr-code',
|
href: '/tools/vcard-qr-code',
|
||||||
color: 'text-rose-500',
|
color: 'text-rose-500',
|
||||||
bg: 'bg-rose-50'
|
bg: 'bg-rose-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Type,
|
icon: Type,
|
||||||
name: 'Text',
|
name: 'Text',
|
||||||
description: 'Display plain text',
|
description: 'Display plain text',
|
||||||
href: '/tools/text-qr-code',
|
href: '/tools/text-qr-code',
|
||||||
color: 'text-slate-500',
|
color: 'text-slate-500',
|
||||||
bg: 'bg-slate-50'
|
bg: 'bg-slate-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Mail,
|
icon: Mail,
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
description: 'Send an email',
|
description: 'Send an email',
|
||||||
href: '/tools/email-qr-code',
|
href: '/tools/email-qr-code',
|
||||||
color: 'text-red-500',
|
color: 'text-red-500',
|
||||||
bg: 'bg-red-50'
|
bg: 'bg-red-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: MessageSquare,
|
icon: MessageSquare,
|
||||||
name: 'SMS',
|
name: 'SMS',
|
||||||
description: 'Send a text message',
|
description: 'Send a text message',
|
||||||
href: '/tools/sms-qr-code',
|
href: '/tools/sms-qr-code',
|
||||||
color: 'text-green-500',
|
color: 'text-green-500',
|
||||||
bg: 'bg-green-50'
|
bg: 'bg-green-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Wifi,
|
icon: Wifi,
|
||||||
name: 'WiFi',
|
name: 'WiFi',
|
||||||
description: 'Connect to WiFi',
|
description: 'Connect to WiFi',
|
||||||
href: '/tools/wifi-qr-code',
|
href: '/tools/wifi-qr-code',
|
||||||
color: 'text-indigo-500',
|
color: 'text-indigo-500',
|
||||||
bg: 'bg-indigo-50'
|
bg: 'bg-indigo-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Bitcoin,
|
icon: Bitcoin,
|
||||||
name: 'Crypto',
|
name: 'Crypto',
|
||||||
description: 'Receive payments',
|
description: 'Receive payments',
|
||||||
href: '/tools/crypto-qr-code',
|
href: '/tools/crypto-qr-code',
|
||||||
color: 'text-orange-500',
|
color: 'text-orange-500',
|
||||||
bg: 'bg-orange-50'
|
bg: 'bg-orange-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
name: 'Event',
|
name: 'Event',
|
||||||
description: 'Save calendar event',
|
description: 'Save calendar event',
|
||||||
href: '/tools/event-qr-code',
|
href: '/tools/event-qr-code',
|
||||||
color: 'text-violet-500',
|
color: 'text-violet-500',
|
||||||
bg: 'bg-violet-50'
|
bg: 'bg-violet-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Facebook,
|
icon: Facebook,
|
||||||
name: 'Facebook',
|
name: 'Facebook',
|
||||||
description: 'Open Facebook page',
|
description: 'Open Facebook page',
|
||||||
href: '/tools/facebook-qr-code',
|
href: '/tools/facebook-qr-code',
|
||||||
color: 'text-blue-600',
|
color: 'text-blue-600',
|
||||||
bg: 'bg-blue-50'
|
bg: 'bg-blue-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Instagram,
|
icon: Instagram,
|
||||||
name: 'Instagram',
|
name: 'Instagram',
|
||||||
description: 'Open Instagram profile',
|
description: 'Open Instagram profile',
|
||||||
href: '/tools/instagram-qr-code',
|
href: '/tools/instagram-qr-code',
|
||||||
color: 'text-pink-500',
|
color: 'text-pink-500',
|
||||||
bg: 'bg-pink-50'
|
bg: 'bg-pink-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Twitter,
|
icon: Twitter,
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
description: 'Open Twitter profile',
|
description: 'Open Twitter profile',
|
||||||
href: '/tools/twitter-qr-code',
|
href: '/tools/twitter-qr-code',
|
||||||
color: 'text-sky-500',
|
color: 'text-sky-500',
|
||||||
bg: 'bg-sky-50'
|
bg: 'bg-sky-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Youtube,
|
icon: Youtube,
|
||||||
name: 'YouTube',
|
name: 'YouTube',
|
||||||
description: 'Open YouTube video',
|
description: 'Open YouTube video',
|
||||||
href: '/tools/youtube-qr-code',
|
href: '/tools/youtube-qr-code',
|
||||||
color: 'text-red-600',
|
color: 'text-red-600',
|
||||||
bg: 'bg-red-50'
|
bg: 'bg-red-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: MessageCircle,
|
icon: MessageCircle,
|
||||||
name: 'WhatsApp',
|
name: 'WhatsApp',
|
||||||
description: 'Send WhatsApp message',
|
description: 'Send WhatsApp message',
|
||||||
href: '/tools/whatsapp-qr-code',
|
href: '/tools/whatsapp-qr-code',
|
||||||
color: 'text-green-600',
|
color: 'text-green-600',
|
||||||
bg: 'bg-green-50'
|
bg: 'bg-green-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Music,
|
icon: Music,
|
||||||
name: 'TikTok',
|
name: 'TikTok',
|
||||||
description: 'Open TikTok profile',
|
description: 'Open TikTok profile',
|
||||||
href: '/tools/tiktok-qr-code',
|
href: '/tools/tiktok-qr-code',
|
||||||
color: 'text-pink-600',
|
color: 'text-pink-600',
|
||||||
bg: 'bg-pink-50'
|
bg: 'bg-pink-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: MapPin,
|
icon: MapPin,
|
||||||
name: 'Location',
|
name: 'Location',
|
||||||
description: 'Share GPS coordinates',
|
description: 'Share GPS coordinates',
|
||||||
href: '/tools/geolocation-qr-code',
|
href: '/tools/geolocation-qr-code',
|
||||||
color: 'text-emerald-500',
|
color: 'text-emerald-500',
|
||||||
bg: 'bg-emerald-50'
|
bg: 'bg-emerald-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Phone,
|
icon: Phone,
|
||||||
name: 'Phone',
|
name: 'Phone',
|
||||||
description: 'Call phone number',
|
description: 'Call phone number',
|
||||||
href: '/tools/phone-qr-code',
|
href: '/tools/phone-qr-code',
|
||||||
color: 'text-blue-400',
|
color: 'text-blue-400',
|
||||||
bg: 'bg-blue-50'
|
bg: 'bg-blue-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CreditCard,
|
icon: CreditCard,
|
||||||
name: 'PayPal',
|
name: 'PayPal',
|
||||||
description: 'Receive PayPal payments',
|
description: 'Receive PayPal payments',
|
||||||
href: '/tools/paypal-qr-code',
|
href: '/tools/paypal-qr-code',
|
||||||
color: 'text-blue-700',
|
color: 'text-blue-700',
|
||||||
bg: 'bg-blue-50'
|
bg: 'bg-blue-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Video,
|
icon: Video,
|
||||||
name: 'Zoom',
|
name: 'Zoom',
|
||||||
description: 'Join Zoom meeting',
|
description: 'Join Zoom meeting',
|
||||||
href: '/tools/zoom-qr-code',
|
href: '/tools/zoom-qr-code',
|
||||||
color: 'text-sky-500',
|
color: 'text-sky-500',
|
||||||
bg: 'bg-sky-50'
|
bg: 'bg-sky-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Users,
|
icon: Users,
|
||||||
name: 'Teams',
|
name: 'Teams',
|
||||||
description: 'Join Teams meeting',
|
description: 'Join Teams meeting',
|
||||||
href: '/tools/teams-qr-code',
|
href: '/tools/teams-qr-code',
|
||||||
color: 'text-violet-500',
|
color: 'text-violet-500',
|
||||||
bg: 'bg-violet-50'
|
bg: 'bg-violet-50'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Animation variants
|
// Animation variants
|
||||||
const containerVariants: Variants = {
|
const containerVariants: Variants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: {
|
transition: {
|
||||||
staggerChildren: 0.05
|
staggerChildren: 0.05
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemVariants: Variants = {
|
const itemVariants: Variants = {
|
||||||
hidden: { opacity: 0, y: 20 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
transition: {
|
transition: {
|
||||||
duration: 0.4
|
duration: 0.4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FreeToolsGrid() {
|
export function FreeToolsGrid() {
|
||||||
return (
|
return (
|
||||||
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
|
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="text-center mb-16"
|
className="text-center mb-16"
|
||||||
>
|
>
|
||||||
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900 mb-4">
|
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900 mb-4">
|
||||||
More Free QR Code Tools
|
More Free QR Code Tools
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
||||||
Create specialized QR codes for every need. Completely free and no signup required.
|
Create specialized QR codes for every need. Completely free and no signup required.
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
whileInView="visible"
|
whileInView="visible"
|
||||||
viewport={{ once: true, margin: "-50px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-4 gap-4 md:gap-6"
|
className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-4 gap-4 md:gap-6"
|
||||||
>
|
>
|
||||||
{TOOLS.map((tool) => (
|
{TOOLS.map((tool) => (
|
||||||
<motion.div key={tool.name} variants={itemVariants}>
|
<motion.div key={tool.name} variants={itemVariants}>
|
||||||
<Link
|
<Link
|
||||||
href={tool.href}
|
href={tool.href}
|
||||||
className="group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-slate-200/80 bg-white hover:border-primary-200 hover:shadow-xl hover:shadow-primary-500/10 transition-all duration-300"
|
className="group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-slate-200/80 bg-white hover:border-primary-200 hover:shadow-xl hover:shadow-primary-500/10 transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div className={`w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4 group-hover:scale-110 transition-transform duration-300`}>
|
<div className={`w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4 group-hover:scale-110 transition-transform duration-300`}>
|
||||||
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} />
|
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-base md:text-lg font-semibold text-slate-900 mb-0.5">
|
<h3 className="text-base md:text-lg font-semibold text-slate-900 mb-0.5">
|
||||||
{tool.name}
|
{tool.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs md:text-sm text-slate-500 text-center">
|
<p className="text-xs md:text-sm text-slate-500 text-center">
|
||||||
{tool.description}
|
{tool.description}
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,56 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Hero } from '@/components/marketing/Hero';
|
import { Hero } from '@/components/marketing/Hero';
|
||||||
import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner';
|
import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner';
|
||||||
import { StatsStrip } from '@/components/marketing/StatsStrip';
|
import { StatsStrip } from '@/components/marketing/StatsStrip';
|
||||||
import { TemplateCards } from '@/components/marketing/TemplateCards';
|
import { TemplateCards } from '@/components/marketing/TemplateCards';
|
||||||
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
||||||
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
||||||
import { Features } from '@/components/marketing/Features';
|
import { Features } from '@/components/marketing/Features';
|
||||||
import { Pricing } from '@/components/marketing/Pricing';
|
import { Pricing } from '@/components/marketing/Pricing';
|
||||||
import { FAQ } from '@/components/marketing/FAQ';
|
import { FAQ } from '@/components/marketing/FAQ';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||||
import en from '@/i18n/en.json';
|
import en from '@/i18n/en.json';
|
||||||
|
|
||||||
export default function HomePageClient() {
|
export default function HomePageClient() {
|
||||||
// Always use English for marketing pages
|
// Always use English for marketing pages
|
||||||
const t = en;
|
const t = en;
|
||||||
|
|
||||||
const industries = [
|
const industries = [
|
||||||
'Restaurant Chain',
|
'Restaurant Chain',
|
||||||
'Tech Startup',
|
'Tech Startup',
|
||||||
'Real Estate',
|
'Real Estate',
|
||||||
'Event Agency',
|
'Event Agency',
|
||||||
'Retail Store',
|
'Retail Store',
|
||||||
'Healthcare',
|
'Healthcare',
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero t={t} />
|
<Hero t={t} />
|
||||||
|
|
||||||
{/* Main Interaction: Generator */}
|
{/* Main Interaction: Generator */}
|
||||||
<InstantGenerator t={t} />
|
<InstantGenerator t={t} />
|
||||||
|
|
||||||
<AIComingSoonBanner />
|
<AIComingSoonBanner />
|
||||||
|
|
||||||
{/* Free Tools Grid */}
|
{/* Free Tools Grid */}
|
||||||
<FreeToolsGrid />
|
<FreeToolsGrid />
|
||||||
|
|
||||||
<StaticVsDynamic t={t} />
|
<StaticVsDynamic t={t} />
|
||||||
<Features t={t} />
|
<Features t={t} />
|
||||||
|
|
||||||
{/* Pricing Section */}
|
{/* Pricing Section */}
|
||||||
<Pricing t={t} />
|
<Pricing t={t} />
|
||||||
|
|
||||||
{/* FAQ Section */}
|
{/* FAQ Section */}
|
||||||
<FAQ t={t} />
|
<FAQ t={t} />
|
||||||
|
|
||||||
{/* Scroll to Top Button */}
|
{/* Scroll to Top Button */}
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,81 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ChevronRight, Home } from 'lucide-react';
|
import { ChevronRight, Home } from 'lucide-react';
|
||||||
|
|
||||||
interface BreadcrumbItem {
|
interface BreadcrumbItem {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BreadcrumbSchemaProps {
|
interface BreadcrumbSchemaProps {
|
||||||
items: BreadcrumbItem[];
|
items: BreadcrumbItem[];
|
||||||
showUI?: boolean;
|
showUI?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates JSON-LD BreadcrumbList schema for SEO
|
* Generates JSON-LD BreadcrumbList schema for SEO
|
||||||
* Optionally renders visible breadcrumb navigation
|
* Optionally renders visible breadcrumb navigation
|
||||||
*/
|
*/
|
||||||
export function BreadcrumbSchema({ items, showUI = false }: BreadcrumbSchemaProps) {
|
export function BreadcrumbSchema({ items, showUI = false }: BreadcrumbSchemaProps) {
|
||||||
const breadcrumbSchema = {
|
const breadcrumbSchema = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'BreadcrumbList',
|
'@type': 'BreadcrumbList',
|
||||||
itemListElement: items.map((item, index) => ({
|
itemListElement: items.map((item, index) => ({
|
||||||
'@type': 'ListItem',
|
'@type': 'ListItem',
|
||||||
position: index + 1,
|
position: index + 1,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
item: item.url,
|
item: item.url,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
||||||
/>
|
/>
|
||||||
{showUI && (
|
{showUI && (
|
||||||
<nav aria-label="Breadcrumb" className="bg-white/95 backdrop-blur-sm border-b border-slate-200">
|
<nav aria-label="Breadcrumb" className="bg-white/95 backdrop-blur-sm border-b border-slate-200">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<ol className="flex items-center gap-1 py-3 text-sm">
|
<ol className="flex items-center gap-1 py-3 text-sm">
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<li key={item.url} className="flex items-center">
|
<li key={item.url} className="flex items-center">
|
||||||
{index > 0 && (
|
{index > 0 && (
|
||||||
<ChevronRight className="w-4 h-4 text-slate-400 mx-1" />
|
<ChevronRight className="w-4 h-4 text-slate-400 mx-1" />
|
||||||
)}
|
)}
|
||||||
{index === items.length - 1 ? (
|
{index === items.length - 1 ? (
|
||||||
<span className="text-slate-600 font-medium truncate max-w-[200px]">
|
<span className="text-slate-600 font-medium truncate max-w-[200px]">
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
href={item.url.replace('https://qrmaster.io', '')}
|
href={item.url.replace('https://qrmaster.io', '')}
|
||||||
className="text-slate-500 hover:text-indigo-600 transition-colors flex items-center gap-1"
|
className="text-slate-500 hover:text-indigo-600 transition-colors flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{index === 0 && <Home className="w-4 h-4" />}
|
{index === 0 && <Home className="w-4 h-4" />}
|
||||||
<span className="hidden sm:inline">{item.name}</span>
|
<span className="hidden sm:inline">{item.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-configured breadcrumb for tool pages
|
* Pre-configured breadcrumb for tool pages
|
||||||
* Renders: Home > Free QR Code Tools > [Tool Name]
|
* Renders: Home > Free QR Code Tools > [Tool Name]
|
||||||
*/
|
*/
|
||||||
export function ToolBreadcrumb({ toolName, toolSlug }: { toolName: string; toolSlug: string }) {
|
export function ToolBreadcrumb({ toolName, toolSlug }: { toolName: string; toolSlug: string }) {
|
||||||
const items: BreadcrumbItem[] = [
|
const items: BreadcrumbItem[] = [
|
||||||
{ name: 'Home', url: 'https://qrmaster.io/' },
|
{ name: 'Home', url: 'https://qrmaster.io/' },
|
||||||
{ name: 'Free QR Code Tools', url: 'https://qrmaster.io/#tools' },
|
{ name: 'Free QR Code Tools', url: 'https://qrmaster.io/#tools' },
|
||||||
{ name: toolName, url: `https://qrmaster.io/tools/${toolSlug}` },
|
{ name: toolName, url: `https://qrmaster.io/tools/${toolSlug}` },
|
||||||
];
|
];
|
||||||
|
|
||||||
return <BreadcrumbSchema items={items} showUI={true} />;
|
return <BreadcrumbSchema items={items} showUI={true} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface TextareaProps
|
export interface TextareaProps
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
|
||||||
|
|
||||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Textarea.displayName = "Textarea"
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
export { Textarea }
|
export { Textarea }
|
||||||
|
|
|
||||||
756
src/i18n/en.json
756
src/i18n/en.json
|
|
@ -1,379 +1,379 @@
|
||||||
{
|
{
|
||||||
"nav": {
|
"nav": {
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"pricing": "Pricing",
|
"pricing": "Pricing",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"create_qr": "Create QR",
|
"create_qr": "Create QR",
|
||||||
"bulk_creation": "Bulk Creation",
|
"bulk_creation": "Bulk Creation",
|
||||||
"analytics": "Analytics",
|
"analytics": "Analytics",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"cta": "Get Started Free"
|
"cta": "Get Started Free"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"badge": "Free QR Code Generator",
|
"badge": "Free QR Code Generator",
|
||||||
"title": "Create QR Codes That Work Everywhere",
|
"title": "Create QR Codes That Work Everywhere",
|
||||||
"subtitle": "Generate static and dynamic QR codes with tracking, custom branding, and bulk generation. Free forever.",
|
"subtitle": "Generate static and dynamic QR codes with tracking, custom branding, and bulk generation. Free forever.",
|
||||||
"features": [
|
"features": [
|
||||||
"No credit card required to start",
|
"No credit card required to start",
|
||||||
"Create QR codes free forever",
|
"Create QR codes free forever",
|
||||||
"Advanced tracking and analytics",
|
"Advanced tracking and analytics",
|
||||||
"Custom colors and styles"
|
"Custom colors and styles"
|
||||||
],
|
],
|
||||||
"cta_primary": "Make a QR Code Free",
|
"cta_primary": "Make a QR Code Free",
|
||||||
"cta_secondary": "View Pricing",
|
"cta_secondary": "View Pricing",
|
||||||
"engagement_badge": "Free Forever"
|
"engagement_badge": "Free Forever"
|
||||||
},
|
},
|
||||||
"trust": {
|
"trust": {
|
||||||
"users": "Happy Users",
|
"users": "Happy Users",
|
||||||
"codes": "Active QR Codes",
|
"codes": "Active QR Codes",
|
||||||
"scans": "Total Scans",
|
"scans": "Total Scans",
|
||||||
"countries": "Countries"
|
"countries": "Countries"
|
||||||
},
|
},
|
||||||
"industries": {
|
"industries": {
|
||||||
"restaurant": "Restaurant Chain",
|
"restaurant": "Restaurant Chain",
|
||||||
"tech": "Tech Startup",
|
"tech": "Tech Startup",
|
||||||
"realestate": "Real Estate",
|
"realestate": "Real Estate",
|
||||||
"events": "Event Agency",
|
"events": "Event Agency",
|
||||||
"retail": "Retail Store",
|
"retail": "Retail Store",
|
||||||
"healthcare": "Healthcare"
|
"healthcare": "Healthcare"
|
||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"title": "Start with a Template",
|
"title": "Start with a Template",
|
||||||
"restaurant": "Restaurant Menu",
|
"restaurant": "Restaurant Menu",
|
||||||
"business": "Business Card",
|
"business": "Business Card",
|
||||||
"vcard": "Contact Card",
|
"vcard": "Contact Card",
|
||||||
"event": "Event Ticket",
|
"event": "Event Ticket",
|
||||||
"use_template": "Use template →"
|
"use_template": "Use template →"
|
||||||
},
|
},
|
||||||
"generator": {
|
"generator": {
|
||||||
"title": "Instant QR Code Generator",
|
"title": "Instant QR Code Generator",
|
||||||
"url_placeholder": "Enter your URL here...",
|
"url_placeholder": "Enter your URL here...",
|
||||||
"foreground": "Foreground",
|
"foreground": "Foreground",
|
||||||
"background": "Background",
|
"background": "Background",
|
||||||
"corners": "Corners",
|
"corners": "Corners",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"contrast_good": "Good contrast",
|
"contrast_good": "Good contrast",
|
||||||
"download_svg": "Download SVG",
|
"download_svg": "Download SVG",
|
||||||
"download_png": "Download PNG",
|
"download_png": "Download PNG",
|
||||||
"save_track": "Save & Track",
|
"save_track": "Save & Track",
|
||||||
"live_preview": "Live Preview",
|
"live_preview": "Live Preview",
|
||||||
"demo_note": "This is a demo QR code"
|
"demo_note": "This is a demo QR code"
|
||||||
},
|
},
|
||||||
"static_vs_dynamic": {
|
"static_vs_dynamic": {
|
||||||
"title": "Why Dynamic QR Codes Save You Money",
|
"title": "Why Dynamic QR Codes Save You Money",
|
||||||
"description": "Stop re-printing materials. Switch destinations instantly and track every scan.",
|
"description": "Stop re-printing materials. Switch destinations instantly and track every scan.",
|
||||||
"static": {
|
"static": {
|
||||||
"title": "Static QR Codes",
|
"title": "Static QR Codes",
|
||||||
"subtitle": "Always Free",
|
"subtitle": "Always Free",
|
||||||
"description": "Perfect for permanent content that never changes",
|
"description": "Perfect for permanent content that never changes",
|
||||||
"features": [
|
"features": [
|
||||||
"Content cannot be edited",
|
"Content cannot be edited",
|
||||||
"No scan tracking",
|
"No scan tracking",
|
||||||
"Works forever",
|
"Works forever",
|
||||||
"No account required"
|
"No account required"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
"title": "Dynamic QR Codes",
|
"title": "Dynamic QR Codes",
|
||||||
"subtitle": "Recommended",
|
"subtitle": "Recommended",
|
||||||
"description": "Full control with tracking and editing capabilities",
|
"description": "Full control with tracking and editing capabilities",
|
||||||
"features": [
|
"features": [
|
||||||
"Edit content anytime",
|
"Edit content anytime",
|
||||||
"Advanced analytics",
|
"Advanced analytics",
|
||||||
"Custom branding",
|
"Custom branding",
|
||||||
"Bulk operations"
|
"Bulk operations"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"title": "Everything you need to create professional QR codes",
|
"title": "Everything you need to create professional QR codes",
|
||||||
"analytics": {
|
"analytics": {
|
||||||
"title": "Advanced Analytics",
|
"title": "Advanced Analytics",
|
||||||
"description": "Track scans, locations, devices, and user behavior with detailed insights."
|
"description": "Track scans, locations, devices, and user behavior with detailed insights."
|
||||||
},
|
},
|
||||||
"customization": {
|
"customization": {
|
||||||
"title": "Full Customization",
|
"title": "Full Customization",
|
||||||
"description": "Brand your QR codes with custom colors and styling options."
|
"description": "Brand your QR codes with custom colors and styling options."
|
||||||
},
|
},
|
||||||
"unlimited": {
|
"unlimited": {
|
||||||
"title": "Unlimited Static QR Codes",
|
"title": "Unlimited Static QR Codes",
|
||||||
"description": "Create as many static QR codes as you need. Free forever, no limits."
|
"description": "Create as many static QR codes as you need. Free forever, no limits."
|
||||||
},
|
},
|
||||||
"bulk": {
|
"bulk": {
|
||||||
"title": "Bulk Operations",
|
"title": "Bulk Operations",
|
||||||
"description": "Create hundreds of QR codes at once with CSV import and batch processing."
|
"description": "Create hundreds of QR codes at once with CSV import and batch processing."
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"title": "Integrations",
|
"title": "Integrations",
|
||||||
"description": "Connect with Zapier, Airtable, Google Sheets, and more popular tools."
|
"description": "Connect with Zapier, Airtable, Google Sheets, and more popular tools."
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"title": "Developer API",
|
"title": "Developer API",
|
||||||
"description": "Integrate QR code generation into your applications with our REST API."
|
"description": "Integrate QR code generation into your applications with our REST API."
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
"title": "24/7 Support",
|
"title": "24/7 Support",
|
||||||
"description": "Get help when you need it with our dedicated customer support team."
|
"description": "Get help when you need it with our dedicated customer support team."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
"title": "Choose Your Plan",
|
"title": "Choose Your Plan",
|
||||||
"subtitle": "Select the perfect plan for your QR code needs",
|
"subtitle": "Select the perfect plan for your QR code needs",
|
||||||
"choose_plan": "Choose Your Plan",
|
"choose_plan": "Choose Your Plan",
|
||||||
"select_plan": "Select the perfect plan for your QR code needs",
|
"select_plan": "Select the perfect plan for your QR code needs",
|
||||||
"current_plan": "Current Plan",
|
"current_plan": "Current Plan",
|
||||||
"upgrade_to": "Upgrade to",
|
"upgrade_to": "Upgrade to",
|
||||||
"downgrade_to_free": "Downgrade to Free",
|
"downgrade_to_free": "Downgrade to Free",
|
||||||
"most_popular": "Most Popular",
|
"most_popular": "Most Popular",
|
||||||
"all_plans_note": "All plans include unlimited static QR codes and basic customization.",
|
"all_plans_note": "All plans include unlimited static QR codes and basic customization.",
|
||||||
"free": {
|
"free": {
|
||||||
"title": "Free",
|
"title": "Free",
|
||||||
"name": "Free",
|
"name": "Free",
|
||||||
"price": "€0",
|
"price": "€0",
|
||||||
"period": "forever",
|
"period": "forever",
|
||||||
"features": [
|
"features": [
|
||||||
"3 dynamic QR codes",
|
"3 dynamic QR codes",
|
||||||
"Unlimited static QR codes",
|
"Unlimited static QR codes",
|
||||||
"Basic scan tracking",
|
"Basic scan tracking",
|
||||||
"Standard QR design templates",
|
"Standard QR design templates",
|
||||||
"Download as SVG/PNG"
|
"Download as SVG/PNG"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pro": {
|
"pro": {
|
||||||
"title": "Pro",
|
"title": "Pro",
|
||||||
"name": "Pro",
|
"name": "Pro",
|
||||||
"price": "€9",
|
"price": "€9",
|
||||||
"period": "per month",
|
"period": "per month",
|
||||||
"badge": "Most Popular",
|
"badge": "Most Popular",
|
||||||
"features": [
|
"features": [
|
||||||
"50 dynamic QR codes",
|
"50 dynamic QR codes",
|
||||||
"Unlimited static QR codes",
|
"Unlimited static QR codes",
|
||||||
"Advanced analytics (scans, devices, locations)",
|
"Advanced analytics (scans, devices, locations)",
|
||||||
"Custom branding (colors & logos)"
|
"Custom branding (colors & logos)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"business": {
|
"business": {
|
||||||
"title": "Business",
|
"title": "Business",
|
||||||
"name": "Business",
|
"name": "Business",
|
||||||
"price": "€29",
|
"price": "€29",
|
||||||
"period": "per month",
|
"period": "per month",
|
||||||
"features": [
|
"features": [
|
||||||
"500 dynamic QR codes",
|
"500 dynamic QR codes",
|
||||||
"Unlimited static QR codes",
|
"Unlimited static QR codes",
|
||||||
"Everything from Pro",
|
"Everything from Pro",
|
||||||
"Bulk QR Creation (up to 1,000)",
|
"Bulk QR Creation (up to 1,000)",
|
||||||
"Priority email support",
|
"Priority email support",
|
||||||
"Advanced tracking & insights"
|
"Advanced tracking & insights"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Frequently Asked Questions",
|
"title": "Frequently Asked Questions",
|
||||||
"questions": {
|
"questions": {
|
||||||
"account": {
|
"account": {
|
||||||
"question": "Do I need an account to create QR codes?",
|
"question": "Do I need an account to create QR codes?",
|
||||||
"answer": "No account is required for static QR codes. However, dynamic QR codes with tracking and editing capabilities require a free account."
|
"answer": "No account is required for static QR codes. However, dynamic QR codes with tracking and editing capabilities require a free account."
|
||||||
},
|
},
|
||||||
"static_vs_dynamic": {
|
"static_vs_dynamic": {
|
||||||
"question": "What's the difference between static and dynamic QR codes?",
|
"question": "What's the difference between static and dynamic QR codes?",
|
||||||
"answer": "Static QR codes contain fixed content that cannot be changed. Dynamic QR codes can be edited anytime and provide detailed analytics."
|
"answer": "Static QR codes contain fixed content that cannot be changed. Dynamic QR codes can be edited anytime and provide detailed analytics."
|
||||||
},
|
},
|
||||||
"forever": {
|
"forever": {
|
||||||
"question": "Will my QR codes work forever?",
|
"question": "Will my QR codes work forever?",
|
||||||
"answer": "Static QR codes work forever as the content is embedded directly. Dynamic QR codes work as long as your account is active."
|
"answer": "Static QR codes work forever as the content is embedded directly. Dynamic QR codes work as long as your account is active."
|
||||||
},
|
},
|
||||||
"file_type": {
|
"file_type": {
|
||||||
"question": "What file type should I use for printing?",
|
"question": "What file type should I use for printing?",
|
||||||
"answer": "For print materials, we recommend SVG format for scalability or high-resolution PNG (300+ DPI) for best quality."
|
"answer": "For print materials, we recommend SVG format for scalability or high-resolution PNG (300+ DPI) for best quality."
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"question": "Can I password-protect a QR code?",
|
"question": "Can I password-protect a QR code?",
|
||||||
"answer": "Yes, Pro and Business plans include password protection and access control features for your QR codes."
|
"answer": "Yes, Pro and Business plans include password protection and access control features for your QR codes."
|
||||||
},
|
},
|
||||||
"analytics": {
|
"analytics": {
|
||||||
"question": "How do analytics work?",
|
"question": "How do analytics work?",
|
||||||
"answer": "We track scans, locations, devices, and referrers while respecting user privacy. No personal data is stored."
|
"answer": "We track scans, locations, devices, and referrers while respecting user privacy. No personal data is stored."
|
||||||
},
|
},
|
||||||
"privacy": {
|
"privacy": {
|
||||||
"question": "Do you track personal data?",
|
"question": "Do you track personal data?",
|
||||||
"answer": "We respect privacy and only collect anonymous usage data. IP addresses are hashed and we honor Do Not Track headers."
|
"answer": "We respect privacy and only collect anonymous usage data. IP addresses are hashed and we honor Do Not Track headers."
|
||||||
},
|
},
|
||||||
"bulk": {
|
"bulk": {
|
||||||
"question": "Can I bulk-create codes with my own data?",
|
"question": "Can I bulk-create codes with my own data?",
|
||||||
"answer": "Yes, you can upload CSV or Excel files to create multiple QR codes at once with custom data mapping."
|
"answer": "Yes, you can upload CSV or Excel files to create multiple QR codes at once with custom data mapping."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
"subtitle": "Manage your QR codes and track performance",
|
"subtitle": "Manage your QR codes and track performance",
|
||||||
"stats": {
|
"stats": {
|
||||||
"total_scans": "Total Scans",
|
"total_scans": "Total Scans",
|
||||||
"active_codes": "Active QR Codes",
|
"active_codes": "Active QR Codes",
|
||||||
"conversion_rate": "Conversion Rate"
|
"conversion_rate": "Conversion Rate"
|
||||||
},
|
},
|
||||||
"recent_codes": "Recent QR Codes",
|
"recent_codes": "Recent QR Codes",
|
||||||
"blog_resources": "Blog & Resources",
|
"blog_resources": "Blog & Resources",
|
||||||
"menu": {
|
"menu": {
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Create QR Code",
|
"title": "Create QR Code",
|
||||||
"subtitle": "Generate dynamic and static QR codes with custom branding",
|
"subtitle": "Generate dynamic and static QR codes with custom branding",
|
||||||
"content": "Content",
|
"content": "Content",
|
||||||
"type": "QR Code Type",
|
"type": "QR Code Type",
|
||||||
"style": "Style & Branding",
|
"style": "Style & Branding",
|
||||||
"preview": "Live Preview",
|
"preview": "Live Preview",
|
||||||
"title_label": "Title",
|
"title_label": "Title",
|
||||||
"title_placeholder": "My QR Code",
|
"title_placeholder": "My QR Code",
|
||||||
"content_type": "Content Type",
|
"content_type": "Content Type",
|
||||||
"url_label": "URL",
|
"url_label": "URL",
|
||||||
"url_placeholder": "https://example.com",
|
"url_placeholder": "https://example.com",
|
||||||
"tags_label": "Tags (comma-separated)",
|
"tags_label": "Tags (comma-separated)",
|
||||||
"tags_placeholder": "marketing, campaign, 2025",
|
"tags_placeholder": "marketing, campaign, 2025",
|
||||||
"qr_code_type": "QR Code Type",
|
"qr_code_type": "QR Code Type",
|
||||||
"dynamic": "Dynamic",
|
"dynamic": "Dynamic",
|
||||||
"static": "Static",
|
"static": "Static",
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
"dynamic_description": "Dynamic: Track scans, edit URL later, view analytics. QR contains tracking link.",
|
"dynamic_description": "Dynamic: Track scans, edit URL later, view analytics. QR contains tracking link.",
|
||||||
"static_description": "Static: Direct to content, no tracking, cannot edit. QR contains actual content.",
|
"static_description": "Static: Direct to content, no tracking, cannot edit. QR contains actual content.",
|
||||||
"foreground_color": "Foreground Color",
|
"foreground_color": "Foreground Color",
|
||||||
"background_color": "Background Color",
|
"background_color": "Background Color",
|
||||||
"corner_style": "Corner Style",
|
"corner_style": "Corner Style",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"good_contrast": "Good contrast",
|
"good_contrast": "Good contrast",
|
||||||
"contrast_ratio": "Contrast ratio",
|
"contrast_ratio": "Contrast ratio",
|
||||||
"download_svg": "Download SVG",
|
"download_svg": "Download SVG",
|
||||||
"download_png": "Download PNG",
|
"download_png": "Download PNG",
|
||||||
"save_qr_code": "Save QR Code"
|
"save_qr_code": "Save QR Code"
|
||||||
},
|
},
|
||||||
"analytics": {
|
"analytics": {
|
||||||
"title": "Analytics",
|
"title": "Analytics",
|
||||||
"subtitle": "Track and analyze your QR code performance",
|
"subtitle": "Track and analyze your QR code performance",
|
||||||
"export_report": "Export Report",
|
"export_report": "Export Report",
|
||||||
"from_last_period": "from last period",
|
"from_last_period": "from last period",
|
||||||
"no_mobile_scans": "No mobile scans",
|
"no_mobile_scans": "No mobile scans",
|
||||||
"of_total": "of total",
|
"of_total": "of total",
|
||||||
"ranges": {
|
"ranges": {
|
||||||
"7d": "7 Days",
|
"7d": "7 Days",
|
||||||
"30d": "30 Days",
|
"30d": "30 Days",
|
||||||
"90d": "90 Days"
|
"90d": "90 Days"
|
||||||
},
|
},
|
||||||
"kpis": {
|
"kpis": {
|
||||||
"total_scans": "Total Scans",
|
"total_scans": "Total Scans",
|
||||||
"avg_scans": "Avg Scans/QR",
|
"avg_scans": "Avg Scans/QR",
|
||||||
"mobile_usage": "Mobile Usage",
|
"mobile_usage": "Mobile Usage",
|
||||||
"top_country": "Top Country"
|
"top_country": "Top Country"
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"scans_over_time": "Scans Over Time",
|
"scans_over_time": "Scans Over Time",
|
||||||
"device_types": "Device Types",
|
"device_types": "Device Types",
|
||||||
"top_countries": "Top Countries"
|
"top_countries": "Top Countries"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"qr_code": "QR Code",
|
"qr_code": "QR Code",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"total_scans": "Total Scans",
|
"total_scans": "Total Scans",
|
||||||
"unique_scans": "Unique Scans",
|
"unique_scans": "Unique Scans",
|
||||||
"conversion": "Conversion",
|
"conversion": "Conversion",
|
||||||
"trend": "Trend",
|
"trend": "Trend",
|
||||||
"scans": "Scans",
|
"scans": "Scans",
|
||||||
"percentage": "Percentage",
|
"percentage": "Percentage",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
"performance": "Performance",
|
"performance": "Performance",
|
||||||
"created": "Created",
|
"created": "Created",
|
||||||
"status": "Status"
|
"status": "Status"
|
||||||
},
|
},
|
||||||
"performance_title": "QR Code Performance"
|
"performance_title": "QR Code Performance"
|
||||||
},
|
},
|
||||||
"bulk": {
|
"bulk": {
|
||||||
"title": "Bulk Creation",
|
"title": "Bulk Creation",
|
||||||
"subtitle": "Create multiple QR codes at once from CSV or Excel files",
|
"subtitle": "Create multiple QR codes at once from CSV or Excel files",
|
||||||
"template_warning_title": "Please Follow the Template Format",
|
"template_warning_title": "Please Follow the Template Format",
|
||||||
"template_warning_text": "Download the template below and follow the format exactly. Your CSV must include columns for title and content (URL).",
|
"template_warning_text": "Download the template below and follow the format exactly. Your CSV must include columns for title and content (URL).",
|
||||||
"static_only_title": "Static QR Codes Only",
|
"static_only_title": "Static QR Codes Only",
|
||||||
"static_only_text": "Bulk creation generates static QR codes that cannot be edited after creation. These QR codes do not include tracking or analytics. Perfect for print materials and offline use.",
|
"static_only_text": "Bulk creation generates static QR codes that cannot be edited after creation. These QR codes do not include tracking or analytics. Perfect for print materials and offline use.",
|
||||||
"download_template": "Download Template",
|
"download_template": "Download Template",
|
||||||
"no_file_selected": "No file selected",
|
"no_file_selected": "No file selected",
|
||||||
"simple_format": "Simple Format",
|
"simple_format": "Simple Format",
|
||||||
"just_title_url": "Just title & URL",
|
"just_title_url": "Just title & URL",
|
||||||
"static_qr_codes": "Static QR Codes",
|
"static_qr_codes": "Static QR Codes",
|
||||||
"no_tracking": "No tracking included",
|
"no_tracking": "No tracking included",
|
||||||
"instant_download": "Instant Download",
|
"instant_download": "Instant Download",
|
||||||
"get_zip": "Get ZIP with all SVGs",
|
"get_zip": "Get ZIP with all SVGs",
|
||||||
"max_rows": "max 1,000 rows",
|
"max_rows": "max 1,000 rows",
|
||||||
"steps": {
|
"steps": {
|
||||||
"upload": "Upload File",
|
"upload": "Upload File",
|
||||||
"preview": "Preview & Map",
|
"preview": "Preview & Map",
|
||||||
"download": "Download"
|
"download": "Download"
|
||||||
},
|
},
|
||||||
"drag_drop": "Drag & drop your file here",
|
"drag_drop": "Drag & drop your file here",
|
||||||
"or_click": "or click to browse",
|
"or_click": "or click to browse",
|
||||||
"supported_formats": "Supports CSV, XLS, XLSX (max 1,000 rows)"
|
"supported_formats": "Supports CSV, XLS, XLSX (max 1,000 rows)"
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"title": "Integrations",
|
"title": "Integrations",
|
||||||
"metrics": {
|
"metrics": {
|
||||||
"total_codes": "QR Codes Total",
|
"total_codes": "QR Codes Total",
|
||||||
"active_integrations": "Active Integrations",
|
"active_integrations": "Active Integrations",
|
||||||
"sync_status": "Sync Status",
|
"sync_status": "Sync Status",
|
||||||
"available_services": "Available Services"
|
"available_services": "Available Services"
|
||||||
},
|
},
|
||||||
"zapier": {
|
"zapier": {
|
||||||
"title": "Zapier",
|
"title": "Zapier",
|
||||||
"description": "Automate QR code creation with 5000+ apps",
|
"description": "Automate QR code creation with 5000+ apps",
|
||||||
"features": [
|
"features": [
|
||||||
"Trigger on new QR codes",
|
"Trigger on new QR codes",
|
||||||
"Create codes from other apps",
|
"Create codes from other apps",
|
||||||
"Sync scan data"
|
"Sync scan data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"airtable": {
|
"airtable": {
|
||||||
"title": "Airtable",
|
"title": "Airtable",
|
||||||
"description": "Sync QR codes with your Airtable bases",
|
"description": "Sync QR codes with your Airtable bases",
|
||||||
"features": [
|
"features": [
|
||||||
"Two-way sync",
|
"Two-way sync",
|
||||||
"Custom field mapping",
|
"Custom field mapping",
|
||||||
"Real-time updates"
|
"Real-time updates"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"sheets": {
|
"sheets": {
|
||||||
"title": "Google Sheets",
|
"title": "Google Sheets",
|
||||||
"description": "Export data to Google Sheets automatically",
|
"description": "Export data to Google Sheets automatically",
|
||||||
"features": [
|
"features": [
|
||||||
"Automated exports",
|
"Automated exports",
|
||||||
"Custom templates",
|
"Custom templates",
|
||||||
"Scheduled updates"
|
"Scheduled updates"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"activate": "Activate & Configure"
|
"activate": "Activate & Configure"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"subtitle": "Manage your account settings and preferences",
|
"subtitle": "Manage your account settings and preferences",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"team": "Team & Roles",
|
"team": "Team & Roles",
|
||||||
"api": "API Keys",
|
"api": "API Keys",
|
||||||
"workspace": "Workspace"
|
"workspace": "Workspace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"error": "An error occurred",
|
"error": "An error occurred",
|
||||||
"success": "Success!"
|
"success": "Success!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,67 +1,67 @@
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
export function middleware(req: NextRequest) {
|
||||||
const path = req.nextUrl.pathname;
|
const path = req.nextUrl.pathname;
|
||||||
|
|
||||||
// Public routes that don't require authentication
|
// Public routes that don't require authentication
|
||||||
const publicPaths = [
|
const publicPaths = [
|
||||||
'/',
|
'/',
|
||||||
'/pricing',
|
'/pricing',
|
||||||
'/faq',
|
'/faq',
|
||||||
'/blog',
|
'/blog',
|
||||||
'/login',
|
'/login',
|
||||||
'/signup',
|
'/signup',
|
||||||
'/privacy',
|
'/privacy',
|
||||||
'/newsletter',
|
'/newsletter',
|
||||||
'/tools',
|
'/tools',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if path is public
|
// Check if path is public
|
||||||
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
||||||
|
|
||||||
// Allow API routes
|
// Allow API routes
|
||||||
if (path.startsWith('/api/')) {
|
if (path.startsWith('/api/')) {
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow redirect routes (QR code redirects)
|
// Allow redirect routes (QR code redirects)
|
||||||
if (path.startsWith('/r/')) {
|
if (path.startsWith('/r/')) {
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow static files
|
// Allow static files
|
||||||
if (path.includes('.') || path.startsWith('/_next')) {
|
if (path.includes('.') || path.startsWith('/_next')) {
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow public paths
|
// Allow public paths
|
||||||
if (isPublicPath) {
|
if (isPublicPath) {
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For protected routes, check for userId cookie
|
// For protected routes, check for userId cookie
|
||||||
const userId = req.cookies.get('userId')?.value;
|
const userId = req.cookies.get('userId')?.value;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
// Not authenticated - redirect to signup
|
// Not authenticated - redirect to signup
|
||||||
const signupUrl = new URL('/signup', req.url);
|
const signupUrl = new URL('/signup', req.url);
|
||||||
return NextResponse.redirect(signupUrl);
|
return NextResponse.redirect(signupUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticated - allow access
|
// Authenticated - allow access
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
/*
|
/*
|
||||||
* Match all request paths except for the ones starting with:
|
* Match all request paths except for the ones starting with:
|
||||||
* - _next/static (static files)
|
* - _next/static (static files)
|
||||||
* - _next/image (image optimization files)
|
* - _next/image (image optimization files)
|
||||||
* - favicon.ico (favicon file)
|
* - favicon.ico (favicon file)
|
||||||
* - public folder
|
* - public folder
|
||||||
*/
|
*/
|
||||||
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue