Compare commits
40 Commits
feature/mo
...
7b2788da7a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b2788da7a | ||
|
|
2771faf3ba | ||
| 9e37136efd | |||
| 4569d89ab2 | |||
| 916ea51d68 | |||
| 8ade0ef92b | |||
| 7e894bf65e | |||
| 54c3652c99 | |||
|
|
702e2710de | ||
|
|
9ae05d48a4 | ||
|
|
324526cb64 | ||
|
|
9040e50837 | ||
|
|
30c1e57eab | ||
|
|
eef4855c1b | ||
| f3637fc2fe | |||
| 53dcfb686e | |||
|
|
74d0b5e7f2 | ||
|
|
ce4c6ab985 | ||
|
|
c2b32b794b | ||
|
|
7f4de4bd1e | ||
|
|
738f9766e1 | ||
|
|
cbafffdf1b | ||
|
|
f872fb64b2 | ||
|
|
9c5f7beb91 | ||
|
|
cb521f2aee | ||
|
|
b00697bc37 | ||
| 0409fe2afd | |||
| f67945d8e7 | |||
| 59131a54f0 | |||
|
|
e44dc1c6bb | ||
| 3682673852 | |||
|
|
1251584b13 | ||
|
|
dd93ca560a | ||
|
|
efb1654370 | ||
|
|
896c9b1a07 | ||
|
|
cca1374c9e | ||
|
|
c1471830f3 | ||
| 373e19a515 | |||
|
|
99acb37c83 | ||
|
|
05531cda3f |
@@ -52,4 +52,4 @@ logs
|
||||
*.log
|
||||
|
||||
# Prisma
|
||||
prisma/migrations
|
||||
# prisma/migrations # Now included in Docker image for deployment
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Database credentials (used by both db and web services in docker-compose.yml)
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=qrmaster
|
||||
|
||||
# Note: DATABASE_URL and DIRECT_URL are auto-generated from POSTGRES_* vars in docker-compose.yml
|
||||
# You don't need to set them here when using Docker Compose
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||
DIRECT_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_SECRET=CHANGE_ME
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
2
.gitignore
vendored
@@ -36,7 +36,7 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
# prisma
|
||||
|
||||
# /prisma/migrations/ # Now tracked in Git for deployment
|
||||
|
||||
# docker
|
||||
docker-compose.override.yml
|
||||
|
||||
@@ -26,7 +26,6 @@ COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# Add build-time environment variables with defaults
|
||||
ENV DATABASE_URL="postgresql://postgres:postgres@db:5432/qrmaster?schema=public"
|
||||
ENV NEXTAUTH_URL="https://www.qrmaster.net"
|
||||
ENV NEXTAUTH_SECRET="build-time-secret"
|
||||
ENV IP_SALT="build-time-salt"
|
||||
@@ -37,6 +36,7 @@ ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net"
|
||||
ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ"
|
||||
ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
|
||||
ENV NEXT_PUBLIC_INDEXABLE="true"
|
||||
ENV NEXT_PUBLIC_FACEBOOK_PIXEL_ID="1601718491252690"
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
|
||||
87
INDEXING_GUIDE.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Indexing Setup & Usage Guide
|
||||
|
||||
This guide explains how to fast-track your content indexing on **Google** and **Bing/Yandex** using the provided scripts.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **WAIT UNTIL LIVE:** Do not run these scripts until your new URLs are live and returning a `200 OK` status. If you submit a `404` URL, it may negatively impact your crawling budget or cause errors.
|
||||
|
||||
---
|
||||
|
||||
## 1. Google Indexing API
|
||||
|
||||
The Google Indexing API allows you to notify Google when pages are added or removed. It is faster than waiting for the Googlebot to crawl your sitemap.
|
||||
|
||||
### Prerequisites: `service_account.json`
|
||||
|
||||
To use the script `scripts/trigger-indexing.js`, you need a **Service Account Key** from Google Cloud.
|
||||
|
||||
1. **Go to Google Cloud Console:** [https://console.cloud.google.com/](https://console.cloud.google.com/)
|
||||
2. **Create a Project:** (e.g., "QR Master Indexing").
|
||||
3. **Enable API:** Search for "Web Search Indexing API" and enable it.
|
||||
4. **Create Service Account:**
|
||||
* Go to "IAM & Admin" > "Service Accounts".
|
||||
* Click "Create Service Account".
|
||||
* Name it (e.g., "indexer").
|
||||
* Grant it the "Owner" role (simplest for this) or a custom role with Indexing permissions.
|
||||
5. **Create Key:**
|
||||
* Click on the newly created service account email.
|
||||
* Go to "Keys" tab -> "Add Key" -> "Create new key" -> **JSON**.
|
||||
* This will download a JSON file.
|
||||
6. **Save Key:**
|
||||
* Rename the file to `service_account.json`.
|
||||
* Place it in the **root** of your project (same folder as `package.json`).
|
||||
* **NOTE:** This file is ignored by git for security (`.gitignore`), so you must copy it manually if you switch laptops.
|
||||
7. **Authorize in Search Console:**
|
||||
* Open the JSON file and copy the `client_email` address.
|
||||
* Go to **Google Search Console** property for `qrmaster.net`.
|
||||
* Go to "Settings" > "Users and permissions".
|
||||
* **Add User:** Paste the service account email and give it **"Owner"** permission. (This is required for the API to work).
|
||||
|
||||
### How to Run
|
||||
|
||||
1. **Run the script:**
|
||||
```bash
|
||||
npm run trigger:indexing
|
||||
```
|
||||
*(Or manually: `npx tsx scripts/trigger-indexing.ts`)*
|
||||
|
||||
2. The script will automatically fetch ALL active URLs from the project (including tools and blog posts) and submit them to Google. You should see a "Success" message for each URL.
|
||||
|
||||
---
|
||||
|
||||
## 2. IndexNow (Bing, Yandex, etc.)
|
||||
|
||||
IndexNow is a protocol used by Bing and others. It's much simpler than Google's API.
|
||||
|
||||
### Prerequisites: API Key
|
||||
|
||||
1. **Get Key:** Go to [Bing Webmaster Tools](https://www.bing.com/webmasters) or generate one at [indexnow.org](https://www.indexnow.org/).
|
||||
2. **Verify Setup:**
|
||||
* The key is typically a long random string (e.g., `abc123...`).
|
||||
* Ensure you have a text file named after the key (e.g., `abc123....txt`) containing the key itself inside your `public/` folder so it's accessible at `https://www.qrmaster.net/abc123....txt`.
|
||||
* Alternatively, set the environment variable in your `.env` file:
|
||||
```
|
||||
INDEXNOW_KEY=your_key_here
|
||||
```
|
||||
|
||||
### How to Run
|
||||
|
||||
This script (`scripts/submit-indexnow.ts`) automatically gathers all meaningful URLs from your project (tools, blog posts, main pages) and submits them.
|
||||
|
||||
1. Run the script:
|
||||
```bash
|
||||
npm run submit:indexnow
|
||||
```
|
||||
*(Or manually: `npx tsx scripts/submit-indexnow.ts`)*
|
||||
|
||||
2. It will output which URLs were submitted.
|
||||
|
||||
---
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] New page is published and live.
|
||||
- [ ] `service_account.json` is in the project root.
|
||||
- [ ] Service Account email is added as Owner in Google Search Console.
|
||||
- [ ] Run `npm run trigger:indexing` (for Google).
|
||||
- [ ] Run `npm run submit:indexnow` (for Bing/Yandex).
|
||||
11
README.md
@@ -284,9 +284,9 @@ qr-master/
|
||||
|
||||
| Variable | Description | Required | Default |
|
||||
| ------------------------------------ | ----------------------------- | -------- | ---------------------------------------------------------------------- |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Yes | `postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public` |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Yes | - |
|
||||
| `NEXTAUTH_URL` | Application URL | Yes | `http://localhost:3050` |
|
||||
| `NEXTAUTH_SECRET` | Secret for JWT encryption | Yes | Generate with `openssl rand -base64 32` |
|
||||
| `NEXTAUTH_SECRET` | Secret for JWT encryption | Yes | - (Generate with `openssl rand -base64 32`) |
|
||||
| `IP_SALT` | Salt for IP hashing (privacy) | Yes | Generate with `openssl rand -base64 32` |
|
||||
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | No | - |
|
||||
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | No | - |
|
||||
@@ -473,3 +473,10 @@ For support, email support@qrmaster.net or open an issue on GitHub.
|
||||
---
|
||||
|
||||
Built with ❤️ by QR Master Team
|
||||
|
||||
|
||||
|
||||
Führe diese im Terminal aus:
|
||||
|
||||
IndexNow (Bing/Yandex + Partner): npm run submit:indexnow
|
||||
Google Indexing API: npm run trigger:indexing
|
||||
83
blog_roadmap.md
Normal file
@@ -0,0 +1,83 @@
|
||||
📅 Blog Content Roadmap (Q1 2026)
|
||||
Goal: Publish 20 high-quality SEO posts over 60 days (Jan 29 - Mar 27). Cadence: Every 3 days. Strategy: "Strict 404 Gate" (Future posts are invisible/404 until publish date).
|
||||
|
||||
✅ Completed (Ready to Ship)
|
||||
Jan 29:
|
||||
Free Barcode Generator (Online)
|
||||
Status: 🟢 Ready (Content Complete + SEO Optimized + Hero Image Generated).
|
||||
Key Feature: Quick Answer Box, SVG/PNG Comparison, FAQ.
|
||||
🚀 Next Priority: Feb 01
|
||||
🎵 Spotify Code Generator: Share Music Instantly
|
||||
Target Audience: Artists, bands, podcasters, playlist curators. SEO Focus: spotify code generator, create spotify code, music marketing qr, spotify uri to code.
|
||||
|
||||
Drafting Blueprint:
|
||||
|
||||
H1: Spotify Code Generator: Share Songs, Albums & Playlists
|
||||
Quick Answer: How to get a code (3-step process).
|
||||
Visual Guide: Where to find the "Spotify URI".
|
||||
Use Cases:
|
||||
Merch: T-shirts with album link.
|
||||
Posters: Gig promotion.
|
||||
Socials: Instagram Stories overlay.
|
||||
Critical Comparison (Pro Tip):
|
||||
Spotify Codes = Cool look, but NO analytics.
|
||||
Dynamic QR Codes = Less "native" look, but FULL tracking (scans, location, etc.).
|
||||
Recommendation: Use QR for marketing campaigns where ROI matters; use Spotify Codes for pure branding on merch.
|
||||
FAQ: Vector download? Do they expire? High-res printing?
|
||||
CTA: "Generate Music QR Code" (Link to main generator).
|
||||
Image Concept:
|
||||
|
||||
Style: Neon, vibrant, "Spotify Green" accents, dark mode aesthetic.
|
||||
Subject: A stylized soundwave transforming into a scannable code, or a vinyl record with a code center.
|
||||
📋 Upcoming Schedule (Backlog)
|
||||
Publish Date Topic / Slug Category Status
|
||||
Feb 04 WhatsApp QR Code (Direct Chat Link) Social ⚪ Pending
|
||||
Feb 07 Instagram QR Code (Grow Following) Social ⚪ Pending
|
||||
Feb 10 vCard QR Code (Digital Business Card) Business ⚪ Pending
|
||||
Feb 13 QR Code Analytics Guide (Deep Dive) Analytics ⚪ Pending
|
||||
Feb 16 Trackable QR Codes (How-to) Tracking ⚪ Pending
|
||||
Feb 19 Dynamic vs Static QR (Ultimate Guide) Basics ⚪ Pending
|
||||
Feb 22 UTM Tracking with QR Codes Marketing ⚪ Pending
|
||||
Feb 25 QR Code Statistics 2026 Trends ⚪ Pending
|
||||
Feb 28 Restaurant Menu QR Codes Hospitality ⚪ Pending
|
||||
Mar 03 QR Codes for Events Events ⚪ Pending
|
||||
Mar 06 Business Card QR Codes Business ⚪ Pending
|
||||
Mar 09 Marketing Strategy Examples Marketing ⚪ Pending
|
||||
Mar 12 Bulk QR Code Generator (Excel/CSV) Bulk ⚪ Pending
|
||||
Mar 15 Google QR Alternative Comparison ⚪ Pending
|
||||
Mar 18 Security & Quishing Security ⚪ Pending
|
||||
Mar 21 Best QR Generator 2026 Review Reviews ⚪ Pending
|
||||
Mar 24 QR Code API Documentation Developer ⚪ Pending
|
||||
Mar 27 Free vs Paid Generator Comparison ⚪ Pending
|
||||
🛠️ Execution Workflow (Repeat for each post)
|
||||
Select Topic: Take next item from list.
|
||||
SEO & Outline: Define title, keywords, and H2 structure (use User/Expert persona).
|
||||
Implement: Replace placeholder content in
|
||||
src/lib/blog-data.ts
|
||||
.
|
||||
Asset: Generate hero image (public/blog/[slug].png) via DALL-E.
|
||||
Verify: Ensure no build errors and correct 404 behavior if date > now.
|
||||
✅ SEO Validation Checklist (Target Score: 80+)
|
||||
1. Page Title
|
||||
|
||||
Focus keyword used at the beginning.
|
||||
Length: ~60 characters (0 characters available is perfect).
|
||||
2. Meta Description
|
||||
|
||||
Focus keyword included.
|
||||
Length: ~160 characters.
|
||||
3. Content Structure
|
||||
|
||||
H1 contains focus keyword.
|
||||
First Paragraph contains focus keyword.
|
||||
Word count: > 600 words (Aim for comprehensive coverage).
|
||||
Keyword Density: ~2% (e.g., used 4-5 times in 600 words).
|
||||
4. Assets (Images)
|
||||
|
||||
Image Filename contains focus keyword (e.g.,
|
||||
free-barcode-generator-guide.png
|
||||
).
|
||||
Image Alt Tag contains focus keyword.
|
||||
5. Links
|
||||
|
||||
Add relevant internal links to tools/pricing/other posts.
|
||||
464
claude_plan_restaurant.md
Normal file
@@ -0,0 +1,464 @@
|
||||
Here is Claude's plan:
|
||||
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
|
||||
Restaurant Menu QR Code - Dashboard Integration Plan
|
||||
|
||||
Executive Summary
|
||||
|
||||
Add "Restaurant Menu" as a new QR code content type in the dashboard at /create.
|
||||
Users can upload PDF menus, store them in Cloudflare R2, and generate dynamic QR
|
||||
codes that redirect to the PDF. This integrates with the existing QRCode model and
|
||||
tracking system.
|
||||
|
||||
Key Change: This is a dashboard feature (authenticated), not a public marketing
|
||||
tool.
|
||||
|
||||
Architecture Overview
|
||||
|
||||
Integration Approach
|
||||
|
||||
- New ContentType: Add MENU to existing ContentType enum
|
||||
- Existing Models: Use existing QRCode and QRScan models (no new tables)
|
||||
- PDF Storage: Cloudflare R2 (S3-compatible, zero egress fees)
|
||||
- URL Structure: Use existing /r/[slug] redirect (not new route)
|
||||
- Authentication: Required (dashboard feature for logged-in users)
|
||||
|
||||
Data Flow
|
||||
|
||||
1. User logs in → Goes to /create → Selects "Restaurant Menu" type
|
||||
2. Uploads PDF → Validate → Upload to R2 → Get public URL
|
||||
3. Creates QR code with content: { pdfUrl: "...", restaurantName: "...", menuTitle:
|
||||
"..." }
|
||||
4. QR code redirects to: /r/[slug] → Redirect to PDF URL
|
||||
5. Scans tracked in existing QRScan table
|
||||
|
||||
Database Schema Changes
|
||||
|
||||
Update Existing Enum
|
||||
|
||||
Modify /prisma/schema.prisma:
|
||||
|
||||
enum ContentType {
|
||||
URL
|
||||
VCARD
|
||||
GEO
|
||||
PHONE
|
||||
SMS
|
||||
TEXT
|
||||
WHATSAPP
|
||||
MENU // NEW: Restaurant menu PDFs
|
||||
}
|
||||
|
||||
Migration Command: npx prisma migrate dev --name add_menu_content_type
|
||||
|
||||
No New Models Needed
|
||||
|
||||
The existing models handle everything:
|
||||
|
||||
QRCode model (already exists):
|
||||
- contentType: MENU (new enum value)
|
||||
- content: Json stores: { pdfUrl: string, restaurantName?: string, menuTitle?:
|
||||
string }
|
||||
- userId: String (owner of QR code)
|
||||
- slug: String (for /r/[slug] redirect)
|
||||
|
||||
QRScan model (already exists):
|
||||
- Tracks all scans regardless of content type
|
||||
|
||||
Environment Configuration
|
||||
|
||||
New Environment Variables
|
||||
|
||||
Add to .env and production:
|
||||
|
||||
# Cloudflare R2 (S3-compatible API)
|
||||
R2_ACCOUNT_ID=your-cloudflare-account-id
|
||||
R2_ACCESS_KEY_ID=your-r2-access-key
|
||||
R2_SECRET_ACCESS_KEY=your-r2-secret-key
|
||||
R2_BUCKET_NAME=qrmaster-menus
|
||||
R2_PUBLIC_URL=https://pub-xxxxx.r2.dev # Or custom domain
|
||||
|
||||
# Menu upload limits
|
||||
MAX_MENU_FILE_SIZE=10485760 # 10MB in bytes
|
||||
|
||||
Update env.ts
|
||||
|
||||
Add to /src/lib/env.ts schema:
|
||||
|
||||
const envSchema = z.object({
|
||||
// ... existing fields ...
|
||||
R2_ACCOUNT_ID: z.string().optional(),
|
||||
R2_ACCESS_KEY_ID: z.string().optional(),
|
||||
R2_SECRET_ACCESS_KEY: z.string().optional(),
|
||||
R2_BUCKET_NAME: z.string().default('qrmaster-menus'),
|
||||
R2_PUBLIC_URL: z.string().optional(),
|
||||
MAX_MENU_FILE_SIZE: z.string().default('10485760'),
|
||||
});
|
||||
|
||||
Critical Files to Modify/Create
|
||||
|
||||
1. R2 Client Library
|
||||
|
||||
File: /src/lib/r2.ts (NEW)
|
||||
|
||||
Purpose: Handle PDF uploads to Cloudflare R2
|
||||
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from
|
||||
'@aws-sdk/client-s3';
|
||||
import { env } from './env';
|
||||
|
||||
const r2Client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: env.R2_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
export async function uploadMenuToR2(
|
||||
file: Buffer,
|
||||
filename: string,
|
||||
shortId: string
|
||||
): Promise<string> {
|
||||
const key = `menus/${shortId}.pdf`;
|
||||
|
||||
await r2Client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: env.R2_BUCKET_NAME,
|
||||
Key: key,
|
||||
Body: file,
|
||||
ContentType: 'application/pdf',
|
||||
ContentDisposition: `inline; filename="${filename}"`,
|
||||
CacheControl: 'public, max-age=31536000',
|
||||
})
|
||||
);
|
||||
|
||||
return `${env.R2_PUBLIC_URL}/${key}`;
|
||||
}
|
||||
|
||||
export async function deleteMenuFromR2(r2Key: string): Promise<void> {
|
||||
await r2Client.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: env.R2_BUCKET_NAME,
|
||||
Key: r2Key,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function generateUniqueFilename(originalFilename: string): string {
|
||||
const timestamp = Date.now();
|
||||
const random = crypto.randomBytes(4).toString('hex');
|
||||
const ext = originalFilename.split('.').pop();
|
||||
return `menu_${timestamp}_${random}.${ext}`;
|
||||
}
|
||||
|
||||
2. Upload API Endpoint
|
||||
|
||||
File: /src/app/api/menu/upload/route.ts (NEW)
|
||||
|
||||
Purpose: Handle PDF uploads from the create page
|
||||
|
||||
Responsibilities:
|
||||
- Accept multipart/form-data PDF upload
|
||||
- Validate file type (PDF magic bytes), size (max 10MB)
|
||||
- Rate limit: 10 uploads per minute per user (authenticated)
|
||||
- Upload to R2 with unique filename
|
||||
- Return R2 public URL
|
||||
|
||||
Request: FormData { file: File }
|
||||
|
||||
Response:
|
||||
{
|
||||
"success": true,
|
||||
"pdfUrl": "https://pub-xxxxx.r2.dev/menus/menu_1234567890_abcd.pdf"
|
||||
}
|
||||
|
||||
Key Implementation Details:
|
||||
- Use request.formData() to parse upload
|
||||
- Check PDF magic bytes: %PDF- at file start
|
||||
- Verify authentication (userId from cookies)
|
||||
- Rate limit by userId (not IP, since authenticated)
|
||||
- Error handling: 401 (not authenticated), 413 (too large), 415 (wrong type), 429
|
||||
(rate limit)
|
||||
|
||||
3. Update Redirect Route
|
||||
|
||||
File: /src/app/r/[slug]/route.ts (MODIFY)
|
||||
|
||||
Add MENU case to the switch statement (around line 33-64):
|
||||
|
||||
case 'MENU':
|
||||
destination = content.pdfUrl || 'https://example.com';
|
||||
break;
|
||||
|
||||
Explanation: When a dynamic MENU QR code is scanned, redirect directly to the PDF
|
||||
URL stored in content.pdfUrl
|
||||
|
||||
4. Update Validation Schema
|
||||
|
||||
File: /src/lib/validationSchemas.ts (MODIFY)
|
||||
|
||||
Line 28: Update contentType enum to include MENU:
|
||||
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT',
|
||||
'MENU'], {
|
||||
errorMap: () => ({ message: 'Invalid content type' })
|
||||
}),
|
||||
|
||||
Line 63: Update bulk QR schema as well:
|
||||
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT',
|
||||
'MENU']),
|
||||
|
||||
5. Update Create Page - Add MENU Type
|
||||
|
||||
File: /src/app/(app)/create/page.tsx (MODIFY)
|
||||
|
||||
Multiple changes needed:
|
||||
|
||||
A. Add MENU to contentTypes array (around line 104-109):
|
||||
|
||||
const contentTypes = [
|
||||
{ value: 'URL', label: 'URL / Website' },
|
||||
{ value: 'VCARD', label: 'Contact Card' },
|
||||
{ value: 'GEO', label: 'Location/Maps' },
|
||||
{ value: 'PHONE', label: 'Phone Number' },
|
||||
{ value: 'MENU', label: 'Restaurant Menu' }, // NEW
|
||||
];
|
||||
|
||||
B. Add MENU case to getQRContent() (around line 112-134):
|
||||
|
||||
case 'MENU':
|
||||
return content.pdfUrl || 'https://example.com/menu.pdf';
|
||||
|
||||
C. Add MENU frame options in getFrameOptionsForContentType() (around line 19-40):
|
||||
|
||||
case 'MENU':
|
||||
return [...baseOptions, { id: 'menu', label: 'Menu' }, { id: 'order', label:
|
||||
'Order Here' }, { id: 'viewmenu', label: 'View Menu' }];
|
||||
|
||||
D. Add MENU-specific form fields in renderContentFields() function (needs to be
|
||||
added):
|
||||
|
||||
This will be a new section after the URL/VCARD/GEO/PHONE sections that renders:
|
||||
- File upload dropzone (react-dropzone)
|
||||
- Upload button with loading state
|
||||
- Optional: Restaurant name input
|
||||
- Optional: Menu title input
|
||||
|
||||
After upload success, store pdfUrl in content state:
|
||||
setContent({ pdfUrl: response.pdfUrl, restaurantName: '', menuTitle: '' });
|
||||
|
||||
6. Update Rate Limiting
|
||||
|
||||
File: /src/lib/rateLimit.ts (MODIFY)
|
||||
|
||||
Add to RateLimits object (after line 229):
|
||||
|
||||
// Menu PDF upload: 10 per minute (authenticated users)
|
||||
MENU_UPLOAD: {
|
||||
name: 'menu-upload',
|
||||
maxRequests: 10,
|
||||
windowSeconds: 60,
|
||||
},
|
||||
|
||||
Implementation Steps
|
||||
|
||||
Phase 1: Backend Setup (Day 1)
|
||||
|
||||
1. Install Dependencies
|
||||
npm install @aws-sdk/client-s3 react-dropzone
|
||||
2. Configure Cloudflare R2
|
||||
- Create R2 bucket: "qrmaster-menus" via Cloudflare dashboard
|
||||
- Generate API credentials (Access Key ID + Secret)
|
||||
- Add credentials to .env and production environment
|
||||
- Set bucket to public (for PDF access)
|
||||
3. Database Migration
|
||||
- Add MENU to ContentType enum in prisma/schema.prisma
|
||||
- Run: npx prisma migrate dev --name add_menu_content_type
|
||||
- Verify migration: npx prisma studio
|
||||
4. Environment Configuration
|
||||
- Update src/lib/env.ts with R2 variables
|
||||
- Update src/lib/rateLimit.ts with MENU_UPLOAD config
|
||||
5. Create R2 Client
|
||||
- Create src/lib/r2.ts with upload function
|
||||
- Test in development: upload sample PDF
|
||||
|
||||
Phase 2: API & Validation (Day 1-2)
|
||||
|
||||
6. Update Validation Schema (/src/lib/validationSchemas.ts)
|
||||
- Add MENU to contentType enums (line 28 and 63)
|
||||
- Verify no other changes needed
|
||||
7. Create Upload API (/src/app/api/menu/upload/route.ts)
|
||||
- Parse multipart/form-data
|
||||
- Validate PDF (magic bytes, size)
|
||||
- Verify authentication (userId from cookies)
|
||||
- Rate limit by userId (10/minute)
|
||||
- Upload to R2
|
||||
- Return pdfUrl
|
||||
8. Update Redirect Route (/src/app/r/[slug]/route.ts)
|
||||
- Add MENU case to switch statement (line 33-64)
|
||||
- Redirect to content.pdfUrl
|
||||
|
||||
Phase 3: Dashboard Integration (Day 2-3)
|
||||
|
||||
9. Update Create Page (/src/app/(app)/create/page.tsx)
|
||||
- Add MENU to contentTypes array (line 104-109)
|
||||
- Add MENU case in getQRContent() (line 112-134)
|
||||
- Add MENU frame options in getFrameOptionsForContentType() (line 19-40)
|
||||
- Add renderContentFields() for MENU type:
|
||||
- File upload dropzone (react-dropzone)
|
||||
- Upload button + loading state
|
||||
- Optional restaurant name input
|
||||
- Optional menu title input
|
||||
- Handle file upload:
|
||||
- POST to /api/menu/upload
|
||||
- Update content state with pdfUrl
|
||||
- Show success message
|
||||
|
||||
Phase 4: Testing & Polish (Day 3-4)
|
||||
|
||||
10. Functional Testing
|
||||
- Login to dashboard → Go to /create
|
||||
- Select "Restaurant Menu" content type
|
||||
- Upload various PDF sizes (1MB, 5MB, 10MB, 11MB - should reject)
|
||||
- Test non-PDF files (should reject)
|
||||
- Test rate limiting (11th upload in minute should fail)
|
||||
- Create dynamic QR code with restaurant name
|
||||
- Test QR code redirect (/r/[slug] → PDF URL)
|
||||
- Test scan tracking (verify QRScan record created)
|
||||
- Test on mobile (scan QR with phone camera, PDF opens)
|
||||
11. Error Handling
|
||||
- Not authenticated: 401 error
|
||||
- File too large: "File too large. Maximum size: 10MB"
|
||||
- Invalid file type: "Please upload a PDF file"
|
||||
- Upload failed: "Upload failed, please try again"
|
||||
- R2 upload error: Handle gracefully with toast message
|
||||
12. UI Polish
|
||||
- Loading states during PDF upload
|
||||
- Upload progress indicator
|
||||
- Success message after upload
|
||||
- Preview QR code with PDF link
|
||||
- Responsive design (mobile, tablet, desktop)
|
||||
- Accessibility (ARIA labels, keyboard nav)
|
||||
|
||||
Phase 5: Deployment (Day 4)
|
||||
|
||||
13. Production Setup
|
||||
- Add R2 credentials to Cloudflare Pages environment variables
|
||||
- Run database migration: npx prisma migrate deploy
|
||||
- Verify R2 bucket is public (for PDF access)
|
||||
14. Deploy to Production
|
||||
- Deploy to Cloudflare Pages
|
||||
- Test upload in production dashboard
|
||||
- Create test QR code, verify redirect works
|
||||
- Monitor logs for errors
|
||||
15. Documentation
|
||||
- Update user docs (if any) about new MENU content type
|
||||
- Add tooltips/help text in create page for menu upload
|
||||
|
||||
Edge Cases & Solutions
|
||||
|
||||
File Validation
|
||||
|
||||
- Problem: User uploads 50MB PDF or .exe file
|
||||
- Solution:
|
||||
- Client-side validation (check file.size and file.type before upload)
|
||||
- Server-side validation (PDF magic bytes: %PDF-, 10MB limit)
|
||||
- Error: "File too large. Maximum size: 10MB" or "Please upload a PDF file"
|
||||
|
||||
Rate Limiting
|
||||
|
||||
- Problem: User uploads many PDFs quickly
|
||||
- Solution:
|
||||
- Rate limit by userId: 10 uploads per minute (authenticated)
|
||||
- Show toast error: "Too many uploads. Please wait a moment."
|
||||
- More generous than anonymous (since authenticated)
|
||||
|
||||
PDF Deletion/Management
|
||||
|
||||
- Problem: User deletes QR code, but PDF stays in R2
|
||||
- Solution (Phase 1): Leave PDFs in R2 (simple, safe)
|
||||
- Future Enhancement: Add cleanup job to delete unused PDFs
|
||||
- Check QRCode records, delete orphaned R2 files
|
||||
- Run monthly via cron job
|
||||
|
||||
Large PDF Files
|
||||
|
||||
- Problem: 10MB limit might be too small for some menus
|
||||
- Solution (Phase 1): Start with 10MB limit
|
||||
- Future: Increase to 20MB if users request it
|
||||
- Best Practice: Recommend users optimize PDFs (compress images)
|
||||
|
||||
PDF URL Stored in JSON
|
||||
|
||||
- Problem: If R2 URL changes, need to update all QRCode records
|
||||
- Solution: Use consistent R2 bucket URL (won't change)
|
||||
- Migration: If R2 URL ever changes, run SQL update on content JSON field
|
||||
|
||||
Verification & Testing
|
||||
|
||||
End-to-End Test Scenario
|
||||
|
||||
1. Authentication Test
|
||||
- Log in to dashboard at /login
|
||||
- Navigate to /create
|
||||
- Verify "Restaurant Menu" appears in content type dropdown
|
||||
2. Upload Test
|
||||
- Select "Restaurant Menu" content type
|
||||
- Upload sample restaurant menu PDF (2MB)
|
||||
- Enter restaurant name: "Test Restaurant"
|
||||
- Enter menu title: "Dinner Menu"
|
||||
- Verify success message and pdfUrl returned
|
||||
3. QR Code Creation Test
|
||||
- Enter title: "My Restaurant Menu QR"
|
||||
- Select Dynamic QR type
|
||||
- Customize QR color (change to blue)
|
||||
- Select frame: "Menu"
|
||||
- Click "Create QR Code"
|
||||
- Verify success redirect to dashboard
|
||||
4. Scan Test
|
||||
- From dashboard, copy QR code URL: qrmaster.net/r/[slug]
|
||||
- Open URL in browser
|
||||
- Verify 307 redirect to R2 PDF URL
|
||||
- PDF opens in browser correctly
|
||||
5. Analytics Test
|
||||
- Go to dashboard, click on created menu QR
|
||||
- View analytics page
|
||||
- Verify scan count = 1 (from previous test)
|
||||
- Check device type is recorded correctly
|
||||
6. Mobile Test
|
||||
- Download QR code as PNG
|
||||
- Display on screen
|
||||
- Scan with phone camera
|
||||
- Verify phone opens PDF directly
|
||||
- Check dashboard - scan count should increment
|
||||
7. Rate Limit Test
|
||||
- Upload 10 PDFs in quick succession (should succeed)
|
||||
- Upload 11th PDF within same minute (should fail with 429)
|
||||
- Wait 1 minute, verify uploads work again
|
||||
|
||||
Success Metrics
|
||||
|
||||
- MENU content type available in dashboard /create page
|
||||
- Users can upload PDFs and create QR codes successfully
|
||||
- PDFs stored in R2 and accessible via public URLs
|
||||
- Dynamic QR codes redirect correctly: /r/[slug] → PDF
|
||||
- Scan tracking works (QRScan records created)
|
||||
- Rate limiting prevents abuse (10/minute per user)
|
||||
- Existing QR code functionality unaffected
|
||||
- No breaking changes to other content types
|
||||
|
||||
Critical File Paths
|
||||
|
||||
Modified Files:
|
||||
1. /prisma/schema.prisma - Add MENU to ContentType enum
|
||||
2. /src/lib/validationSchemas.ts - Add MENU to contentType enums (lines 28, 63)
|
||||
3. /src/app/(app)/create/page.tsx - Add MENU UI and logic
|
||||
4. /src/app/r/[slug]/route.ts - Add MENU redirect case
|
||||
5. /src/lib/env.ts - Add R2 environment variables
|
||||
6. /src/lib/rateLimit.ts - Add MENU_UPLOAD rate limit
|
||||
|
||||
New Files:
|
||||
7. /src/lib/r2.ts - R2 client library for PDF uploads
|
||||
8. /src/app/api/menu/upload/route.ts - PDF upload API endpoint
|
||||
@@ -5,9 +5,9 @@ services:
|
||||
container_name: qrmaster-db-dev
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: qrmaster
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
||||
ports:
|
||||
- "5435:5432"
|
||||
|
||||
@@ -5,9 +5,9 @@ services:
|
||||
container_name: qrmaster-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: qrmaster
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
||||
ports:
|
||||
- "5435:5432"
|
||||
@@ -51,11 +51,11 @@ services:
|
||||
- "3050:3000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATABASE_URL: postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||
DIRECT_URL: postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
|
||||
DIRECT_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
|
||||
REDIS_URL: redis://redis:6379
|
||||
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3050}
|
||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-your-secret-key-change-in-production}
|
||||
NEXTAUTH_URL: ${NEXTAUTH_URL}
|
||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3050}
|
||||
IP_SALT: ${IP_SALT:-your-salt-change-in-production}
|
||||
ENABLE_DEMO: ${ENABLE_DEMO:-false}
|
||||
@@ -75,6 +75,12 @@ services:
|
||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||
NEXT_PUBLIC_POSTHOG_KEY: ${NEXT_PUBLIC_POSTHOG_KEY:-}
|
||||
NEXT_PUBLIC_POSTHOG_HOST: ${NEXT_PUBLIC_POSTHOG_HOST:-https://us.i.posthog.com}
|
||||
# Cloudflare R2 Storage
|
||||
R2_ACCOUNT_ID: ${R2_ACCOUNT_ID:-}
|
||||
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
|
||||
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
|
||||
R2_BUCKET_NAME: ${R2_BUCKET_NAME:-qrmaster-menus}
|
||||
R2_PUBLIC_URL: ${R2_PUBLIC_URL:-}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -3,6 +3,10 @@ NODE_ENV=development
|
||||
PORT=3000
|
||||
|
||||
# Database Configuration (PostgreSQL)
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=qrmaster
|
||||
|
||||
# For local development (without Docker):
|
||||
# DATABASE_URL=postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public
|
||||
# For Docker Compose (internal Docker network):
|
||||
|
||||
24
features_overview_de.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Neue Features und Updates für QR Master (DE)
|
||||
|
||||
## Übersicht
|
||||
Wir haben unser Angebot aktualisiert, um noch mehr Wert für unsere Nutzer zu bieten. Hier sind die neuesten Ergänzungen und Verbesserungen:
|
||||
|
||||
### 1. Erweiterte QR Code Typen
|
||||
Wir haben spezifische QR Code Lösungen für verschiedene Anwendungsfälle hinzugefügt:
|
||||
|
||||
- **Feedback QR Code**: Sammeln Sie direkt Kundenfeedback. Scans führen zu einem anpassbaren Feedback-Formular.
|
||||
- **PDF QR Code**: Teilen Sie Dokumente, Speisekarten oder Broschüren als PDF. Ideal für Restaurants und Unternehmen.
|
||||
- **Coupon QR Code**: Bieten Sie Rabatte und Gutscheine via QR Code an. Perfekt für Marketingkampagnen im Einzelhandel.
|
||||
- **App Store QR Code**: Ein intelligenter QR Code, der Nutzer basierend auf ihrem Gerät (iOS oder Android) automatisch zum richtigen App Store leitet.
|
||||
|
||||
### 2. Mehr Dynamik im Kostenlosen Plan
|
||||
Um den Einstieg zu erleichtern, haben wir das Limit für den kostenlosen Plan erhöht:
|
||||
- **Neu**: 8 Dynamische QR Codes kostenlos (statt bisher 3).
|
||||
- **Vorteil**: Mehr Flexibilität für kleine Unternehmen und Startups, um verschiedene Kampagnen gleichzeitig zu testen.
|
||||
|
||||
### 3. SEO Optimierung
|
||||
Alle neuen QR Code Typen sind jetzt vollständig in unsere Plattform integriert und für Suchmaschinen optimiert, damit Nutzer die richtige Lösung für ihr Problem finden.
|
||||
|
||||
---
|
||||
|
||||
*Erstellt am 22.01.2026*
|
||||
827
keyword_planer_google.md
Normal file
@@ -0,0 +1,827 @@
|
||||
Keyword Stats 2026-01-22 at 10_55_20
|
||||
1. Januar 2025 - 31. Dezember 2025
|
||||
Keyword Currency Avg. monthly searches Änderung über drei Monate Änderung im Jahresvergleich Competition Competition (indexed value) Top of page bid (low range) Top of page bid (high range) Ad impression share Organic impression share Organic average position In account? In plan? Searches: Jan 2025 Searches: Feb 2025 Searches: Mar 2025 Searches: Apr 2025 Searches: May 2025 Searches: Jun 2025 Searches: Jul 2025 Searches: Aug 2025 Searches: Sep 2025 Searches: Oct 2025 Searches: Nov 2025 Searches: Dec 2025
|
||||
dynamic qr code generator EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
||||
dynamic qr codes EUR 5000 -90% 0% Mittel 38 "2,58" "17,08"
|
||||
dynamic qr EUR 500 0% 0% Mittel 34 "1,85" "11,67"
|
||||
generate qr EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
create qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
dynamic qr code generator free EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
||||
qr code generator free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
code generator EUR 5000 0% 0% Niedrig 32 "1,29" "3,97"
|
||||
free qr generator EUR 5000 0% 0% Hoch 76 "1,21" "3,62"
|
||||
create qr code free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
custom qr code generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
qr code free EUR 5000 0% 0% Hoch 84 "0,94" "3,33"
|
||||
make qr code EUR 50000 -90% 0% Hoch 82 "0,89" "3,63"
|
||||
create dynamic qr code EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
||||
make qr code free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
static qr code generator EUR 5000 0% 900% Hoch 71 "1,06" "3,50"
|
||||
my qr code generator EUR 500 0% 0% Hoch 81 "1,45" "3,51"
|
||||
qr code generator with analytics EUR 500 0% -90% Mittel 65 "3,63" "27,08"
|
||||
dynamic qr codes free EUR 500 0% 0% Hoch 79 "1,27" "4,58"
|
||||
scan code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
url qr code generator EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
dynamic qr generator EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
||||
create a dynamic qr code EUR 50 0% 0% Hoch 83 "4,10" "13,65"
|
||||
get a qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
create qr code generator EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
||||
qr code generator with tracking EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
||||
bulk qr code generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
create custom qr code EUR 500 0% 0% Mittel 51 "2,44" "7,89"
|
||||
qr codes for business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
||||
custom qr code EUR 5000 0% 0% Mittel 57 "1,83" "6,10"
|
||||
make qr EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
qr code tracking EUR 5000 -90% -90% Mittel 64 "1,62" "14,63"
|
||||
free qr code generator for business cards EUR 500 0% 0% Hoch 85 "2,04" "4,44"
|
||||
qr creation EUR 50000 0% 0% Hoch 72 "0,71" "3,34"
|
||||
qr code generator for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
||||
create qr code for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
||||
create qr code for business EUR 50 0% 0% Hoch 96 "2,97" "5,82"
|
||||
qr code generator pricing EUR 500 -90% -90% Mittel 49 "2,33" "17,90"
|
||||
static qr code generator free EUR 50 0% 900% Hoch 87 "0,91" "3,24"
|
||||
qr codes on business cards EUR 50000 -90% -90% Hoch 100 "2,19" "6,72"
|
||||
static qr codes EUR 500 -90% 0% Mittel 62 "0,90" "3,64"
|
||||
free qr code for business card EUR 500 0% 0% Hoch 100 "2,40" "4,89"
|
||||
professional qr code generator EUR 50000 0% 0% Mittel 53 "0,91" "10,80"
|
||||
dynamic qr code tracking EUR 50 0% 0% Mittel 55 "1,52" "18,26"
|
||||
qr barcode generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
free qr code generator with logo EUR 500 0% 0% Hoch 80 "1,31" "4,47"
|
||||
qr code generator for business EUR 500 0% 900% Niedrig 17 "1,81" "7,00"
|
||||
vcard qr code generator EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
||||
free qr code generator with tracking EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
||||
qr code with analytics EUR 500 0% 0% Mittel 56 "4,85" "22,14"
|
||||
my qr code EUR 5000 0% 0% Mittel 59 "0,97" "3,79"
|
||||
qr code designer EUR 500 0% 0% Mittel 64 "0,91" "3,63"
|
||||
create dynamic qr code free EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
||||
qr code for business card free EUR 500 -90% -90% Hoch 96 "2,28" "4,93"
|
||||
free custom qr code generator EUR 500 0% 0% Hoch 91 "1,69" "4,83"
|
||||
completely free qr code generator EUR 500 0% 0% Hoch 90 "0,92" "3,04"
|
||||
vcard qr code EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
||||
custom qr code free EUR 500 0% 0% Hoch 88 "1,86" "4,97"
|
||||
get a free qr code EUR 500 0% 900% Hoch 86 "1,10" "4,78"
|
||||
qr gen EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
create qr free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
||||
trackable qr code generator EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
||||
free code generator EUR 500 0% 0% Mittel 40 "1,00" "3,98"
|
||||
custom qr EUR 500 0% 0% Hoch 87 "1,51" "4,43"
|
||||
qr code for location EUR 500 -90% -90% Mittel 37 "0,79" "3,42"
|
||||
create qr code for business card free EUR 50 0% 0% Hoch 100 "0,98" "4,46"
|
||||
free create a qr code EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
||||
qr code generator with logo EUR 5000 0% 0% Mittel 64 "1,51" "5,12"
|
||||
free qr code for business EUR 50 0% 0% Hoch 88 "1,95" "4,50"
|
||||
qr maker free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
facebook qr code generator EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
||||
create free qr code for business card EUR 50 0% 0% Hoch 98 "3,34" "4,97"
|
||||
qr code com EUR 500 0% 0% Mittel 62 "0,82" "3,73"
|
||||
qr code for restaurant menu EUR 500 900% 900% Hoch 73 "1,89" "8,11"
|
||||
trackable qr codes EUR 500 0% 0% Hoch 70 "5,57" "34,95"
|
||||
create website qr code EUR 50 0% 0% Hoch 88 "2,22" "6,88"
|
||||
qr business card generator EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
||||
qr code generator and tracking EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
||||
qr code restaurant menu EUR 500 -90% -90% Hoch 82 "2,81" "11,90"
|
||||
qr tracking EUR 500 0% 0% Niedrig 11 "1,66" "12,51"
|
||||
custom qr generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
v card qr code EUR 50 0% 0% Hoch 79 "1,10" "4,16"
|
||||
cool qr code generator EUR 500 0% 0% Mittel 63 "1,19" "3,87"
|
||||
make a qr code for business card EUR 50 0% 0% Hoch 85 "3,17" "7,50"
|
||||
qr generator for business cards EUR 50 0% 0% Hoch 84 "3,18" "5,18"
|
||||
branded qr code generator EUR 50 0% 0% Hoch 83 "2,63" "10,80"
|
||||
website to qr code EUR 5000 0% 0% Mittel 49 "0,68" "3,48"
|
||||
create qr code with tracking EUR 50 0% 0% Niedrig 18 "3,65" "7,01"
|
||||
vcard qr code free EUR 500 -90% 0% Hoch 80 "0,83" "3,58"
|
||||
vcard qr EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
||||
make custom qr code EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
create static qr code EUR 50 0% 0% Hoch 90 "1,31" "4,10"
|
||||
qr code for your business EUR 50 0% 0% Hoch 99 "2,16" "5,79"
|
||||
get a qr code for my business EUR 50 0% 0% Hoch 95 "2,89" "8,41"
|
||||
generate dynamic qr codes EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
||||
restaurant qr code EUR 500 0% 0% Hoch 80 "1,61" "11,55"
|
||||
vcard qr code generator free EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
||||
my qr EUR 5000 0% 0% Mittel 59 "0,97" "3,79"
|
||||
qr code generator and analytics EUR 50 ∞ 0% Hoch 88 "6,49" "27,63"
|
||||
unique qr code generator EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
||||
url qr generator EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
free qr code generator free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
free qr code tracking EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
||||
menu qr code generator EUR 50 0% 0% Hoch 84 "2,58" "5,01"
|
||||
generate static qr code EUR 50 0% 0% Hoch 90 "1,22" "3,11"
|
||||
unlimited qr code generator EUR 500 -90% -90% Hoch 82 "1,33" "3,70"
|
||||
vcard qr generator EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
||||
qr code generator phone number EUR 500 -90% 0% Hoch 75 "0,74" "3,88"
|
||||
create qr code from url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
business qr code generator free EUR 50 0% 0% Hoch 93 "1,96" "4,72"
|
||||
make qr code for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
||||
create a website qr code EUR 50 0% 0% Hoch 88 "2,61" "7,92"
|
||||
qr card EUR 5000 -90% 0% Hoch 100 "1,35" "4,74"
|
||||
dynamic qr code pricing EUR 50 0% -90% Mittel 62 "1,89" "11,48"
|
||||
ai qr code generator EUR 500 0% 0% Mittel 39 "0,87" "3,54"
|
||||
qr code de EUR 50 -90% 0% Mittel 55 "0,59" "5,28"
|
||||
qr code create free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
make a dynamic qr code EUR 50 0% 0% Hoch 88 "1,80" "7,05"
|
||||
create my qr code EUR 500 -90% -90% Hoch 88 "1,08" "4,05"
|
||||
qr code menu free EUR 50 0% -90% Mittel 56 "1,77" "5,39"
|
||||
creating a qr code for business EUR 50 0% 0% Hoch 86 "2,30" "8,64"
|
||||
create qr barcode EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
qr card generator EUR 50 0% 0% Hoch 75 "1,26" "3,70"
|
||||
dynamic qr free EUR 50 0% 0% Hoch 86 "0,46" "3,73"
|
||||
free barcode EUR 5000 0% 0% Niedrig 6 "0,64" "3,16"
|
||||
generate qr codes for free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
create a qr code for my business EUR 50 0% 0% Hoch 89 "4,10" "9,04"
|
||||
vcard qr code business card EUR 50 0% 0% Mittel 53 "3,35" "7,32"
|
||||
create a qr code for your business EUR 50 0% 0% Hoch 93 "2,54" "5,08"
|
||||
edit qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
||||
url code generator EUR 500 0% 0% Mittel 66 "1,19" "3,61"
|
||||
qr qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
free unlimited qr code generator EUR 500 0% 0% Hoch 89 "0,93" "3,06"
|
||||
event qr code EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
||||
qr code generator contact card EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
||||
free code qr generator EUR 50 0% 0% Hoch 73 "0,89" "3,63"
|
||||
make a qr code for business EUR 50 0% 0% Hoch 88 "1,76" "5,26"
|
||||
create a static qr code EUR 50 0% 0% Hoch 85 "1,51" "4,36"
|
||||
create trackable qr code EUR 50 0% 0% Hoch 74 "5,22" "22,03"
|
||||
produce qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
make dynamic qr code EUR 50 0% 0% Hoch 86 "3,06" "6,45"
|
||||
qr code generator from url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
qr code for my business EUR 50 0% 0% Hoch 97 "2,46" "7,58"
|
||||
my qr generator EUR 50 0% 0% Hoch 93 "0,85" "3,31"
|
||||
free qr code generator with analytics EUR 50 0% 0% Hoch 93 "2,73" "7,89"
|
||||
make your own qr code free EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
||||
static qr codes generator EUR 50 0% 0% Mittel 51 "1,10" "3,70"
|
||||
create your own qr code free EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
||||
design qr code generator EUR 500 0% 0% Niedrig 32 "0,95" "3,36"
|
||||
static qr generator EUR 50 0% 0% Hoch 87 "0,91" "4,14"
|
||||
qr codes pro EUR 500 0% 0% Niedrig 28 "1,23" "8,93"
|
||||
qr code with tracking free EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
||||
website to qr code generator EUR 50 -90% 0% Hoch 77 "0,95" "4,10"
|
||||
facebook qr code generator free EUR 500 0% 0% Niedrig 15 "1,27" "3,63"
|
||||
make your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
||||
smart qr code generator EUR 50 0% 0% Mittel 44 "1,09" "5,84"
|
||||
creating qr code business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
||||
qr barcode generator free EUR 5000 0% 0% Hoch 76 "1,21" "3,62"
|
||||
create qr code from website EUR 50 0% 0% Hoch 93 "1,13" "4,59"
|
||||
branded qr codes EUR 500 0% 0% Mittel 54 "1,84" "10,35"
|
||||
create free dynamic qr code EUR 50 0% 0% Hoch 90 "1,84" "5,87"
|
||||
free url qr code generator EUR 50 0% 0% Hoch 87 "1,18" "3,63"
|
||||
generate website qr code EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
||||
create your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
||||
create the qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
make my qr code EUR 500 -90% -90% Hoch 88 "1,08" "4,05"
|
||||
event qr code generator EUR 50 0% 0% Mittel 57 "1,38" "4,25"
|
||||
paypal qr code EUR 5000 0% 0% Niedrig 9 "1,03" "6,79"
|
||||
unique qr codes EUR 500 0% 0% Niedrig 30 "1,21" "5,02"
|
||||
generate qr code for url free EUR 50 0% 0% Hoch 86 "1,27" "3,63"
|
||||
create business qr code EUR 50 0% 0% Hoch 96 "2,97" "5,82"
|
||||
generate the qr code EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
qr code generator free business card EUR 50 0% 0% Mittel 60 "1,72" "4,10"
|
||||
qr code generator business EUR 500 0% 0% Hoch 75 "3,51" "7,51"
|
||||
qr code generator for business card free EUR 50 0% 0% Hoch 92 "2,60" "4,10"
|
||||
cool qr codes EUR 500 0% 0% Niedrig 25 "1,16" "3,82"
|
||||
create custom qr code free EUR 50 0% 0% Hoch 89 "2,02" "5,06"
|
||||
qr code for EUR 500 0% 0% Niedrig 12 "1,04" "3,52"
|
||||
qr code generator ai EUR 500 0% 900% Mittel 53 "0,87" "3,63"
|
||||
barcode create EUR 5000 0% 0% Mittel 35 "0,78" "3,15"
|
||||
create a qr code for a url free EUR 50 0% 0% Hoch 90 "1,51" "4,86"
|
||||
url to qr code generator free EUR 50 0% 0% Hoch 81 "1,22" "3,11"
|
||||
create a qr code for business card free EUR 50 0% 0% Hoch 93 "3,63" "5,66"
|
||||
generate a qr EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
||||
free qr code designer EUR 50 0% 0% Hoch 90 "1,27" "3,34"
|
||||
dynamic qr code cost EUR 50 0% 0% Mittel 60 "1,62" "10,52"
|
||||
custom logo qr code EUR 500 0% 0% Hoch 98 "2,27" "7,67"
|
||||
instant qr code generator EUR 50 0% 0% Hoch 78 "1,30" "3,63"
|
||||
create menu qr code free EUR 50 0% 0% Hoch 74 "4,10" "6,74"
|
||||
qr code generator qr code generator qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
create vcard qr code EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
||||
get qr code for business EUR 50 0% 0% Hoch 100 "3,61" "4,62"
|
||||
qr code sign up EUR 50 0% 0% Mittel 52 "1,44" "9,17"
|
||||
with qr code EUR 50 0% 0% Niedrig 22 "1,00" "3,92"
|
||||
create a business qr code EUR 50 0% 0% Hoch 96 "4,50" "11,66"
|
||||
create qr for free EUR 50 0% 0% Hoch 89 "1,31" "3,63"
|
||||
free make a qr code EUR 50 0% 0% Hoch 83 "1,28" "3,32"
|
||||
qr code for menu free EUR 50 0% 0% Hoch 80 "2,31" "5,12"
|
||||
qr code pricing EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
||||
qr code generator permanent EUR 500 0% 900% Hoch 78 "1,22" "4,10"
|
||||
free trackable qr code EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
||||
create free qr code business card EUR 500 0% 0% Hoch 85 "2,04" "4,44"
|
||||
create free qr code generator EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
instagram qr code generator free EUR 50 0% 0% Hoch 91 "1,09" "3,63"
|
||||
develop a qr code EUR 50 0% 0% Hoch 83 "1,60" "5,88"
|
||||
free business card qr code EUR 500 -90% 0% Hoch 97 "1,92" "4,84"
|
||||
free qr code generator contact card EUR 50 0% 0% Hoch 88 "1,37" "4,04"
|
||||
qr code qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
create static qr code free EUR 50 0% 0% Hoch 81 "0,93" "3,36"
|
||||
qr code from website EUR 50 0% 0% Hoch 85 "0,82" "3,20"
|
||||
qr code generator for phone number EUR 500 -90% 0% Mittel 56 "0,79" "3,27"
|
||||
create a qr code for an event EUR 50 0% 0% Hoch 74 "1,64" "5,94"
|
||||
create qr scan code EUR 50 0% 0% Hoch 88 "1,27" "3,71"
|
||||
create business card qr code free EUR 50 0% 0% Hoch 92 "2,66" "4,59"
|
||||
advanced qr code generator EUR 50 0% 0% Mittel 42 "1,29" "6,91"
|
||||
to create a qr code EUR 50 0% 0% Hoch 89 "2,93" "6,04"
|
||||
make a qr code generator EUR 50 0% 0% Hoch 68 "1,27" "3,44"
|
||||
qr restaurant menu EUR 50 0% 0% Mittel 35 "2,52" "28,99"
|
||||
qr code vcard free EUR 50 0% 0% Hoch 84 "0,86" "4,96"
|
||||
create barcode free EUR 500 0% 0% Mittel 39 "0,93" "3,34"
|
||||
free business qr code EUR 50 0% 0% Hoch 95 "2,29" "4,10"
|
||||
produce a qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
||||
create qr code for event EUR 50 0% 0% Hoch 90 "1,12" "9,73"
|
||||
website to qr code free EUR 50 0% 0% Hoch 85 "0,86" "3,61"
|
||||
free qr code vcard EUR 50 0% 0% Hoch 99 "1,87" "3,63"
|
||||
create qr generator EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
||||
qr design EUR 5000 -90% -90% Hoch 93 "0,93" "3,20"
|
||||
free qr code menu EUR 50 0% 0% Niedrig 29 "1,57" "4,96"
|
||||
custom free qr code EUR 50 0% 0% Hoch 95 "1,79" "4,64"
|
||||
difference between static and dynamic qr code EUR 50 0% 0% Niedrig 24 "0,32" "10,47"
|
||||
bulk qr generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
create cool qr codes EUR 50 0% 0% Hoch 94 "1,19" "5,18"
|
||||
create unique qr code EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
||||
produce qr code free EUR 50 0% 0% Hoch 100 "2,55" "4,54"
|
||||
free contact qr code generator EUR 50 0% 0% Hoch 92 "1,31" "3,63"
|
||||
professional qr code EUR 50 0% 0% Mittel 38 "1,75" "6,26"
|
||||
menu qr codes EUR 500 0% 0% Hoch 71 "1,63" "5,60"
|
||||
free qr code contact card EUR 50 0% 0% Hoch 86 "1,85" "3,80"
|
||||
bulk create qr codes EUR 50 0% 0% Hoch 83 "3,25" "7,78"
|
||||
generate qr code from website EUR 50 0% 0% Hoch 98 "1,47" "3,63"
|
||||
create qr code from url free EUR 500 -90% 0% Hoch 92 "1,42" "3,70"
|
||||
sign in qr code generator EUR 50 0% 0% Hoch 71 "2,16" "4,34"
|
||||
custom design qr codes EUR 500 0% 0% Hoch 74 "1,38" "5,00"
|
||||
tracking qr code scans EUR 500 0% -90% Mittel 52 "3,03" "16,93"
|
||||
unlimited qr codes EUR 50 0% 0% Hoch 86 "1,04" "4,16"
|
||||
create branded qr code EUR 50 0% 0% Hoch 79 "2,53" "17,43"
|
||||
qr code generator business card free EUR 50 0% ∞ Hoch 95 "2,55" "5,69"
|
||||
tracking barcode EUR 5000 0% -90% Mittel 47 "1,35" "4,34"
|
||||
static qr EUR 500 -90% 0% Mittel 62 "0,90" "3,64"
|
||||
generate qr code for business EUR 50 0% 0% Hoch 95 "4,11" "11,03"
|
||||
free url to qr code EUR 50 0% 0% Hoch 87 "1,01" "3,63"
|
||||
qr business code EUR 50 0% 0% Hoch 91 "3,30" "9,11"
|
||||
v card qr code generator EUR 50 0% 0% Hoch 82 "1,28" "4,10"
|
||||
free dynamic qr EUR 500 0% 0% Hoch 79 "1,27" "4,58"
|
||||
create facebook qr code free EUR 50 0% 0% Hoch 94 "1,04" "4,43"
|
||||
create my qr code free EUR 50 0% 0% Hoch 89 "2,43" "11,68"
|
||||
basic qr code generator EUR 50 0% 0% Hoch 87 "1,08" "3,12"
|
||||
need a qr code EUR 50 0% 0% Hoch 79 "1,18" "5,62"
|
||||
purchase qr codes EUR 500 0% 0% Hoch 78 "2,78" "11,52"
|
||||
qr location EUR 500 -90% -90% Mittel 37 "0,79" "3,42"
|
||||
create free static qr code EUR 50 0% 0% Hoch 80 "1,06" "3,13"
|
||||
ai qr code EUR 500 0% 0% Niedrig 14 "0,76" "4,66"
|
||||
qr code generator with analytics free EUR 50 0% 0% Hoch 90 "3,15" "5,12"
|
||||
need qr code EUR 50 0% 0% Hoch 76 "1,33" "4,97"
|
||||
buy dynamic qr code EUR 50 ∞ 0% Niedrig 25 "1,87" "12,81"
|
||||
to make a qr code EUR 50 0% 0% Mittel 65 "1,58" "3,63"
|
||||
qr tracking code EUR 50 0% 0% Hoch 82 "1,10" "3,60"
|
||||
sms qr code EUR 500 0% 0% Niedrig 17 "0,60" "3,35"
|
||||
qr code tools EUR 500 900% 900% Mittel 34 "0,71" "4,11"
|
||||
dynamic code generator EUR 50 0% 0% Niedrig 17 "3,82" "15,61"
|
||||
qr code for business free EUR 50 0% 0% Hoch 91 "1,46" "3,63"
|
||||
free qr code contact generator EUR 50 0% 0% Hoch 84 "1,06" "3,34"
|
||||
free qr code generator no subscription EUR 50 0% 0% Hoch 90 "0,79" "3,09"
|
||||
create your qr code free EUR 50 0% 0% Hoch 79 "1,79" "4,10"
|
||||
free dynamic qr code generator with logo EUR 50 0% 0% Mittel 43 "0,48" "3,97"
|
||||
sms qr code generator EUR 500 0% 0% Niedrig 18 "0,66" "3,11"
|
||||
create a qr free EUR 50 0% 0% Hoch 94 "2,12" "3,63"
|
||||
qr code generator generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
qr code generator account EUR 50 0% 0% Mittel 44 "1,51" "7,59"
|
||||
make your own qr codes EUR 5000 0% 0% Hoch 79 "1,38" "4,11"
|
||||
qr tag generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
||||
free trackable qr code generator EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
||||
dynamic qr code free generator EUR 50 0% 0% Hoch 95 "2,50" "4,88"
|
||||
create a unique qr code EUR 50 0% 0% Hoch 96 "2,58" "4,89"
|
||||
free menu qr code generator EUR 50 0% 0% Hoch 95 "2,35" "3,38"
|
||||
trackable qr code generator free EUR 50 0% 0% Hoch 95 "4,38" "8,26"
|
||||
free code qr EUR 50000 0% 0% Hoch 86 "1,25" "3,63"
|
||||
create qr code with url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
difference between static and dynamic qr codes EUR 50 0% 0% Hoch 89 "0,61" "18,51"
|
||||
free dynamic qr generator EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
||||
my qr code generator free EUR 50 0% 0% Hoch 83 "1,51" "4,92"
|
||||
qr code provider EUR 50 0% 0% Mittel 40 "1,85" "7,42"
|
||||
qr analytics EUR 500 0% 0% Mittel 56 "4,85" "22,14"
|
||||
qr code generator with custom logo EUR 50 0% 0% Hoch 88 "3,95" "10,07"
|
||||
to make qr code EUR 50 0% 0% Mittel 52 "1,46" "4,59"
|
||||
static and dynamic qr codes EUR 50 0% 0% Mittel 64 "0,42" "5,07"
|
||||
get qr code generator EUR 50 0% 0% Hoch 80 "1,02" "3,86"
|
||||
qr code generator with url EUR 50 0% 0% Hoch 90 "2,65" "5,00"
|
||||
create code qr free EUR 50 0% 0% Hoch 93 "1,96" "3,63"
|
||||
create qr code of website EUR 50 0% 0% Hoch 97 "2,15" "4,93"
|
||||
qr code generator free unlimited EUR 50 0% 0% Hoch 94 "1,18" "3,63"
|
||||
create qr code for EUR 50 0% 0% Hoch 68 "1,14" "3,63"
|
||||
the qr generator EUR 50 0% 0% Mittel 60 "0,98" "3,99"
|
||||
bulk qr code generator from excel EUR 50 0% 0% Mittel 42 "1,22" "4,36"
|
||||
make url qr code EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
qr code and tracking EUR 5000 -90% -90% Mittel 64 "1,62" "14,63"
|
||||
make qr code for location EUR 50 0% 0% Hoch 86 "1,37" "4,01"
|
||||
sign up qr code EUR 50 0% 0% Niedrig 23 "1,81" "5,80"
|
||||
custom branded qr codes EUR 50 0% 0% Hoch 100 "2,33" "13,48"
|
||||
build qr code generator EUR 50 0% 0% Hoch 80 "1,02" "3,86"
|
||||
free qr code business card generator EUR 50 0% 0% Mittel 60 "1,72" "4,10"
|
||||
generate qr code with url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
qr create free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
||||
make qr code from website EUR 50 0% 0% Hoch 92 "1,48" "3,53"
|
||||
qr bulk generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
qr code s EUR 500 0% 0% Niedrig 27 "0,57" "4,92"
|
||||
create your own qr codes EUR 5000 0% 0% Hoch 79 "1,38" "4,11"
|
||||
restaurant menu qr code free EUR 50 0% 0% Hoch 95 "1,78" "8,90"
|
||||
event qr code generator free EUR 50 0% 0% Hoch 73 "1,55" "4,53"
|
||||
qr signs EUR 5000 -90% -90% Hoch 100 "0,65" "4,49"
|
||||
log in qr code generator EUR 5000 0% 0% Niedrig 15 "0,67" "3,07"
|
||||
instant qr code EUR 50 0% 0% Hoch 77 "1,86" "4,80"
|
||||
custom qr code designer EUR 50 0% 0% Hoch 90 "3,13" "4,88"
|
||||
create a url qr code EUR 50 0% 0% Hoch 89 "2,80" "5,01"
|
||||
the qr code generator free EUR 50 0% 0% Hoch 82 "1,06" "3,63"
|
||||
new qr code generator EUR 50 0% 0% Hoch 79 "1,51" "4,58"
|
||||
trackable qr code free EUR 50 0% 0% Hoch 88 "3,62" "12,45"
|
||||
free personalized qr code generator EUR 50 0% 0% Hoch 93 "0,81" "3,63"
|
||||
qr code generator from website EUR 50 0% 0% Hoch 98 "1,28" "4,12"
|
||||
create qr code for instagram free EUR 50 0% 0% Hoch 95 "1,51" "4,46"
|
||||
digital qr code generator EUR 50 0% 0% Mittel 45 "1,84" "4,17"
|
||||
qr png generator EUR 50 0% 0% Hoch 74 "1,28" "3,33"
|
||||
create custom qr EUR 500 0% 0% Mittel 51 "2,44" "7,89"
|
||||
make qr code for business EUR 50 0% 0% Hoch 100 "2,49" "3,36"
|
||||
contact card qr code generator EUR 50 0% 0% Hoch 89 "1,68" "4,94"
|
||||
qr printing EUR 500 0% 0% Mittel 46 "0,74" "7,53"
|
||||
qr for business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
||||
permanent qr code generator free EUR 500 0% 900% Hoch 85 "1,03" "3,57"
|
||||
free menu qr code EUR 50 0% 0% Hoch 97 "1,94" "4,53"
|
||||
generate qr code for a url EUR 50 0% 0% Hoch 94 "2,17" "5,95"
|
||||
manage qr codes EUR 500 0% -90% Niedrig 30 "2,11" "18,77"
|
||||
qr generator custom EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
qr code to phone number EUR 50 0% 0% Hoch 75 "0,74" "3,16"
|
||||
customer qr code EUR 50 0% 0% Mittel 53 "1,80" "3,72"
|
||||
website url qr code generator EUR 50 0% 0% Hoch 77 "1,65" "4,23"
|
||||
square barcode generator EUR 500 0% 0% Mittel 45 "0,84" "8,47"
|
||||
website to create qr code EUR 50 0% 0% Hoch 87 "1,89" "4,62"
|
||||
scan barcode generator EUR 500 0% 0% Niedrig 27 "0,84" "5,11"
|
||||
obtain a qr code EUR 50000 -90% 0% Hoch 82 "0,89" "3,63"
|
||||
qr code generator for my website EUR 500 0% 0% Hoch 87 "4,10" "10,77"
|
||||
generate your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
||||
generate qr code from url free EUR 50 0% 0% Hoch 84 "0,38" "3,93"
|
||||
create qr code for menu free EUR 50 0% ∞ Hoch 86 "2,03" "5,10"
|
||||
qr business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
||||
qr code generator site EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
||||
code create EUR 50 -90% 0% Niedrig 3 "0,62" "6,98"
|
||||
dynamic codes EUR 500 0% 0% Niedrig 12 "0,78" "4,21"
|
||||
qr code analytics free EUR 50 0% 0% Hoch 86 "1,32" "5,57"
|
||||
free url to qr code generator EUR 50 0% 0% Hoch 91 "0,94" "3,65"
|
||||
personalized qr code generator EUR 50 0% 0% Hoch 90 "1,75" "6,74"
|
||||
custom logo qr code generator EUR 500 0% 0% Hoch 68 "2,73" "7,71"
|
||||
restaurant qr EUR 500 0% 0% Hoch 80 "1,61" "11,55"
|
||||
generate facebook qr code EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
||||
qr contact card generator EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
||||
free qr generator code EUR 50 0% 0% Hoch 90 "1,69" "4,10"
|
||||
dynamic url qr code EUR 50 0% 0% Hoch 82 "5,45" "18,45"
|
||||
free qr code analytics EUR 50 0% 0% Hoch 86 "1,32" "5,57"
|
||||
generate qr code for phone number EUR 50 0% 0% Hoch 79 "0,76" "3,37"
|
||||
free qr code generator and tracking EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
||||
generate unique qr code EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
||||
youtube qr code generator free EUR 50 0% 0% Hoch 96 "1,21" "3,63"
|
||||
customize qr code generator EUR 50 0% 0% Hoch 83 "1,16" "6,79"
|
||||
make a qr scan code EUR 50 0% 0% Hoch 88 "1,27" "3,71"
|
||||
create a qr code for location EUR 50 0% 0% Hoch 100 "1,91" "3,60"
|
||||
url to qr generator EUR 50 0% 0% Hoch 89 "0,20" "9,36"
|
||||
business qr EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
||||
edit a qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
||||
generate qr codes in bulk EUR 50 0% 0% Hoch 100 "2,07" "8,02"
|
||||
qr code generator freeware EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
||||
automated qr code generator EUR 50 0% 0% Hoch 86 "0,93" "3,18"
|
||||
create qr code to email EUR 50 0% 0% Hoch 82 "0,86" "4,06"
|
||||
free png qr code generator EUR 50 0% 0% Hoch 88 "1,55" "5,87"
|
||||
free qr code generator for url EUR 50 0% 0% Hoch 89 "1,51" "4,10"
|
||||
create a qr code from website EUR 50 0% 0% Hoch 77 "2,58" "6,51"
|
||||
qr code qr EUR 5000 0% 0% Niedrig 0 "1,03" "3,01"
|
||||
make qr code generator free EUR 500 0% 0% Hoch 83 "1,51" "3,63"
|
||||
qr code generator custom logo EUR 500 0% 0% Hoch 68 "2,73" "7,71"
|
||||
make your qr code free EUR 50 0% 0% Hoch 79 "1,79" "4,10"
|
||||
free qr business cards EUR 500 -90% 0% Hoch 97 "1,92" "4,84"
|
||||
create qr barcode free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
||||
qr code generator barcode EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
||||
free sms qr code generator EUR 50 0% 0% Niedrig 32 "0,81" "3,03"
|
||||
difference between dynamic and static qr code EUR 50 0% 0% Hoch 70 "0,23" "5,71"
|
||||
qr code generator free for phone number EUR 50 0% ∞ Hoch 100 "0,37" "3,63"
|
||||
creating scan codes EUR 50000 0% 0% Hoch 84 "1,68" "4,83"
|
||||
create scan code free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
||||
free qr code generator for vcard EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
||||
qr code generator for contact card EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
||||
qr contact code generator EUR 500 0% 0% Hoch 75 "1,32" "4,73"
|
||||
create own qr code free EUR 50 0% 0% Hoch 85 "0,87" "4,15"
|
||||
qr to code EUR 50 0% 0% Niedrig 21 "1,00" "3,78"
|
||||
free branded qr code generator EUR 50 0% 0% Hoch 100 "1,81" "3,52"
|
||||
qr code free tracking EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
||||
free qr code generator unlimited EUR 50 0% 0% Hoch 85 "1,08" "3,27"
|
||||
create your own free qr code EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
||||
customize a qr code EUR 5000 0% 0% Mittel 57 "1,83" "6,10"
|
||||
modify qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
||||
create youtube qr code EUR 50 0% 0% Hoch 71 "0,86" "3,17"
|
||||
vcard qr free EUR 50 0% 0% Hoch 95 "1,56" "3,63"
|
||||
build qr codes EUR 50 -90% 0% Hoch 89 "0,95" "4,10"
|
||||
create qr code free from url EUR 500 0% 900% Hoch 89 "1,74" "4,85"
|
||||
barcode and qr code generator EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
||||
make free qr EUR 50 0% 0% Hoch 89 "0,85" "4,10"
|
||||
digital restaurant menu qr code EUR 50 0% 0% Hoch 95 "1,68" "5,15"
|
||||
qr code generator for EUR 50 0% 0% Hoch 96 "0,93" "3,00"
|
||||
make qr barcode EUR 50000 0% 0% Hoch 72 "0,71" "3,34"
|
||||
generate a qr code from a url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
generator free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
||||
qr campaign EUR 50 0% -90% Niedrig 19 "1,45" "10,66"
|
||||
free qr code editor EUR 50 0% 0% Hoch 75 "1,32" "3,18"
|
||||
square codes EUR 5000 0% 0% Niedrig 5 "0,83" "3,42"
|
||||
free qr code for my business EUR 50 0% 0% Hoch 90 "2,15" "4,10"
|
||||
generate qr code in bulk EUR 50 0% 0% Hoch 100 "2,07" "8,02"
|
||||
create custom qr codes free EUR 50 0% 0% Hoch 100 "2,23" "6,61"
|
||||
smart qr codes EUR 500 900% 900% Mittel 36 "1,13" "6,17"
|
||||
create qr code for url free EUR 50 0% 0% Hoch 90 "1,51" "4,86"
|
||||
qr code with brand logo EUR 50 0% 0% Hoch 88 "2,39" "4,41"
|
||||
barcode to qr code generator EUR 50 0% 0% Niedrig 27 "1,89" "8,80"
|
||||
i qr code EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
||||
qr code generator with logo for free EUR 500 0% 0% Hoch 80 "1,31" "4,47"
|
||||
qr code generator no tracking EUR 50 0% 0% Hoch 76 "0,96" "3,93"
|
||||
qr code unlimited EUR 500 0% 0% Mittel 66 "1,00" "6,13"
|
||||
csv qr code generator EUR 50 0% 0% Mittel 65 "1,02" "5,95"
|
||||
design qr code free EUR 50 0% 0% Hoch 97 "1,45" "4,05"
|
||||
qr code generator card EUR 50 0% 0% Hoch 75 "1,26" "3,70"
|
||||
qr design generator EUR 500 0% 0% Niedrig 32 "0,95" "3,36"
|
||||
qr code permanent generator EUR 50 0% 0% Hoch 74 "0,56" "5,09"
|
||||
free generate qr code for url EUR 50 0% 0% Mittel 48 "1,27" "3,19"
|
||||
qr code i EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
||||
every qr code EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
view qr code EUR 50 0% 0% Niedrig 13 "0,65" "3,23"
|
||||
code creation EUR 50 -90% 0% Niedrig 12 "2,14" "5,68"
|
||||
create own qr code generator EUR 50 0% -90% Hoch 79 "1,21" "3,71"
|
||||
credit card qr code EUR 50 0% 0% Niedrig 7 "2,64" "9,81"
|
||||
digital qr codes EUR 500 -90% 0% Mittel 63 "1,15" "4,49"
|
||||
qr generator for url EUR 50 0% 0% Hoch 91 "1,10" "7,17"
|
||||
qr code bulk generator free EUR 50 0% 0% Hoch 95 "0,74" "3,18"
|
||||
dynamic barcode EUR 50 0% 0% Niedrig 33 "1,44" "5,12"
|
||||
event qr EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
||||
custom code generator EUR 50 0% 0% Niedrig 32 "1,79" "8,74"
|
||||
qr codes marketing EUR 500 0% 0% Niedrig 33 "1,61" "10,10"
|
||||
recommended qr code generator EUR 5000 0% 0% Hoch 78 "1,63" "8,36"
|
||||
create a scan qr code EUR 50 0% 0% Hoch 96 "2,22" "4,68"
|
||||
qr code with vcard EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
||||
make a qr code for text EUR 50 0% 0% Hoch 71 "1,28" "3,34"
|
||||
logo qr generator EUR 5000 0% 0% Mittel 64 "1,51" "5,12"
|
||||
contact card qr generator EUR 50 0% 0% Hoch 82 "1,46" "3,63"
|
||||
qr barcode free EUR 50000 0% 0% Hoch 86 "1,25" "3,63"
|
||||
qr code scan device EUR 500 0% -90% Hoch 100 "0,37" "3,50"
|
||||
all qr codes EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
cost for qr code EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
||||
free qr tracking EUR 50 0% 0% Hoch 86 "1,49" "8,90"
|
||||
generator for free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
||||
create paypal qr code EUR 50 0% 0% Mittel 36 "1,79" "4,70"
|
||||
generate qr code of url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
||||
generate paypal qr code EUR 50 0% 0% Niedrig 30 "1,54" "4,76"
|
||||
email qr code free EUR 50 0% 0% Hoch 68 "0,50" "3,48"
|
||||
cost of qr codes EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
||||
the best qr code generator free EUR 50 0% 0% Hoch 82 "1,00" "3,63"
|
||||
qr code brand EUR 500 0% 0% Niedrig 4 "1,55" "8,10"
|
||||
qr all EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
create qr reader EUR 50 0% 0% Hoch 82 "1,28" "3,60"
|
||||
generate barcode qr EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
||||
create barcode url EUR 50 0% 0% Hoch 77 "0,83" "3,43"
|
||||
logos in qr codes EUR 5000 0% 0% Hoch 86 "1,20" "4,78"
|
||||
create a paypal qr code EUR 50 0% 0% Mittel 45 "0,95" "5,00"
|
||||
qr code save EUR 50 0% 0% Niedrig 19 "0,83" "3,47"
|
||||
make a qr scanner EUR 500 0% 0% Mittel 56 "0,99" "4,22"
|
||||
qr code freeware EUR 5000 0% 0% Hoch 84 "0,94" "3,33"
|
||||
barcode scan code EUR 500 0% 0% Hoch 97 "0,81" "3,80"
|
||||
qr code digital EUR 50 0% 0% Mittel 45 "0,59" "6,18"
|
||||
qr code from vcard EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
||||
qr generator vcard free EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
||||
make cool qr code EUR 50 0% 0% Hoch 83 "1,45" "3,96"
|
||||
all qr EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
qr do EUR 50 0% 0% Niedrig 15 "0,85" "4,10"
|
||||
qr events EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
||||
call qr EUR 50 0% 0% Mittel 37 "1,00" "4,12"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Keyword Stats 2026-01-22 at 11_03_27
|
||||
1. Januar 2025 - 31. Dezember 2025
|
||||
Keyword Currency Avg. monthly searches Änderung über drei Monate Änderung im Jahresvergleich Competition Competition (indexed value) Top of page bid (low range) Top of page bid (high range) Ad impression share Organic impression share Organic average position In account? In plan? Searches: Jan 2025 Searches: Feb 2025 Searches: Mar 2025 Searches: Apr 2025 Searches: May 2025 Searches: Jun 2025 Searches: Jul 2025 Searches: Aug 2025 Searches: Sep 2025 Searches: Oct 2025 Searches: Nov 2025 Searches: Dec 2025
|
||||
custom qr code generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
qr barcode EUR 50000 0% 900% Niedrig 3 "0,42" "1,93"
|
||||
bulk qr code generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
bar code generator EUR 500000 0% 0% Niedrig 3 "0,67" "2,58"
|
||||
qr code generator for business EUR 500 0% 900% Niedrig 17 "1,81" "7,01"
|
||||
this is a qr code EUR 5000 0% 0% Niedrig 9 "0,47" "1,33"
|
||||
get a qr code for business EUR 50 0% 0% Niedrig 20
|
||||
a qr code EUR 500 0% 0% Niedrig 23 "0,49" "2,01"
|
||||
qr dynamic code generator EUR 50 -100% -100% Unbekannt
|
||||
facebook qr code generator EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
||||
whatsapp qr code generator EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
||||
qr tracking EUR 500 0% 0% Niedrig 11 "1,66" "12,51"
|
||||
custom qr generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
create qr code with tracking EUR 50 0% 0% Niedrig 18 "3,65" "7,01"
|
||||
qr code generator sign in EUR 500 -90% -90% Niedrig 6
|
||||
make custom qr code EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
qr code qr code EUR 500 -90% -90% Niedrig 27 "0,26" "1,28"
|
||||
free barcode EUR 5000 0% 0% Niedrig 6 "0,64" "3,16"
|
||||
youtube qr code EUR 5000 0% 0% Niedrig 16 "0,52" "2,21"
|
||||
paypal qr code generator EUR 500 0% 0% Niedrig 19 "0,66" "2,58"
|
||||
code qr code EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
||||
qr code to text EUR 500 0% 0% Niedrig 4 "0,50" "2,20"
|
||||
code to qr code EUR 50 0% 0% Niedrig 30 "0,43" "1,51"
|
||||
qr code location generator EUR 50 0% 0% Niedrig 17
|
||||
restaurant menu qr code generator EUR 50 0% 0% Niedrig 0
|
||||
bulk qr code EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
||||
qr code instagram generator EUR 50 0% 0% Niedrig 29 "0,71" "1,75"
|
||||
qr codes pro EUR 500 0% 0% Niedrig 28 "1,23" "8,93"
|
||||
facebook qr code generator free EUR 500 0% 0% Niedrig 15 "1,27" "3,63"
|
||||
generate website qr code EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
||||
paypal qr code EUR 5000 0% 0% Niedrig 9 "1,03" "6,79"
|
||||
unique qr codes EUR 500 0% 0% Niedrig 30 "1,21" "5,02"
|
||||
the qr code EUR 500 0% 0% Niedrig 23 "0,66" "2,21"
|
||||
scan location qr code EUR 50 0% 0% Niedrig 24
|
||||
cool qr codes EUR 500 0% 0% Niedrig 25 "1,16" "3,82"
|
||||
qr code for EUR 500 0% 0% Niedrig 12 "1,04" "3,53"
|
||||
to create qr code EUR 50 -100% -100% Unbekannt
|
||||
qr code for phone number EUR 500 0% 0% Niedrig 28 "0,70" "2,56"
|
||||
qr wifi EUR 500 0% 0% Niedrig 2 "0,12" "1,00"
|
||||
with qr code EUR 50 0% 0% Niedrig 22 "1,00" "3,93"
|
||||
free bulk qr code generator EUR 500 900% 900% Niedrig 27 "0,91" "2,85"
|
||||
cost of qr code generator EUR 50 -100% -100% Unbekannt
|
||||
qr code analytics tracking EUR 50 0% 0% Niedrig 9
|
||||
free qr code for restaurant menu EUR 50 0% -100% Unbekannt
|
||||
qr code time tracking EUR 50 0% 0% Niedrig 0
|
||||
qr code with text EUR 500 0% 0% Niedrig 15 "0,13" "1,40"
|
||||
free qr code menu EUR 50 0% 0% Niedrig 29 "1,57" "4,96"
|
||||
qr code generator for restaurant menu EUR 500 0% 0% Niedrig 5
|
||||
difference between static and dynamic qr code EUR 50 0% 0% Niedrig 24 "0,32" "10,47"
|
||||
bulk qr generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
barcode code generator EUR 500000 0% 0% Niedrig 3 "0,67" "2,58"
|
||||
generate dynamic qr code free EUR 50 -100% -100% Unbekannt
|
||||
qr code contact generator free EUR 50 0% -100% Unbekannt
|
||||
scan qr code for location EUR 50 0% 0% Niedrig 17
|
||||
ai qr code EUR 500 0% 0% Niedrig 14 "0,76" "4,66"
|
||||
buy dynamic qr code EUR 50 ∞ 0% Niedrig 25 "1,87" "12,81"
|
||||
sms qr code EUR 500 0% 0% Niedrig 17 "0,60" "3,35"
|
||||
dynamic code generator EUR 50 0% 0% Niedrig 17 "3,82" "15,62"
|
||||
qr code restaurant menu free EUR 50 0% -100% Unbekannt
|
||||
vcard qr code generator with logo EUR 50 -100% -100% Unbekannt
|
||||
bulk qr code generator free EUR 500 900% 900% Niedrig 27 "0,91" "2,85"
|
||||
sms qr code generator EUR 500 0% 0% Niedrig 18 "0,66" "3,11"
|
||||
phone qr code EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
||||
create your dynamic qr code EUR 50 -100% -100% Unbekannt
|
||||
qr code as a business card EUR 50 -100% -100% Unbekannt
|
||||
generate free barcode EUR 50000 0% 0% Niedrig 5 "0,67" "2,52"
|
||||
qr code barcode generator EUR 500 0% 0% Niedrig 15 "0,58" "1,51"
|
||||
bulk qr code generator with logo EUR 50 0% 0% Niedrig 0
|
||||
qr restaurant EUR 500 0% 0% Niedrig 7
|
||||
sign up qr code EUR 50 0% 0% Niedrig 23 "1,81" "5,80"
|
||||
create qr code menu free EUR 50 -100% -100% Unbekannt
|
||||
bulk qr code generator excel EUR 50 0% 0% Niedrig 21
|
||||
qr code options EUR 50 0% 900% Niedrig 15 "1,10" "2,91"
|
||||
qr bulk generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
||||
qr code s EUR 500 0% 0% Niedrig 27 "0,57" "4,93"
|
||||
qr code to qr code EUR 50 ∞ ∞ Niedrig 14
|
||||
log in qr code generator EUR 5000 0% 0% Niedrig 15 "0,67" "3,08"
|
||||
create whatsapp qr code EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
||||
qr code in EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
||||
twitter qr code generator EUR 50 0% 0% Niedrig 4
|
||||
care code EUR 500 0% 0% Niedrig 2
|
||||
create qr code gratuit EUR 50 -100% -100% Unbekannt
|
||||
restaurants with qr codes EUR 50 0% 0% Niedrig 11
|
||||
master qr EUR 50 0% ∞ Niedrig 0
|
||||
manage qr codes EUR 500 0% -90% Niedrig 30 "2,11" "18,77"
|
||||
qr generator custom EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
||||
qr code generator it EUR 50 0% 0% Niedrig 11
|
||||
bulk qr EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
||||
master qr code EUR 50 0% 0% Niedrig 2
|
||||
qr code generator in bulk EUR 50 0% 0% Niedrig 7
|
||||
scan barcode generator EUR 500 0% 0% Niedrig 27 "0,84" "5,11"
|
||||
static qr and dynamic qr EUR 50 -100% -100% Unbekannt
|
||||
barcode make EUR 5000 900% 900% Niedrig 15 "0,64" "2,78"
|
||||
qr code generator site EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
||||
code create EUR 50 -90% 0% Niedrig 3 "0,62" "6,98"
|
||||
dynamic codes EUR 500 0% 0% Niedrig 12 "0,78" "4,21"
|
||||
scan qr code for restaurant menu EUR 50 0% 0% Unbekannt
|
||||
qr code tracking analytics EUR 50 0% 0% Niedrig 0
|
||||
generate facebook qr code EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
||||
create qr code location EUR 50 -100% 0% Unbekannt
|
||||
qr code of location EUR 50 -100% -100% Unbekannt
|
||||
qr code with EUR 50 0% 0% Niedrig 7
|
||||
code to qr EUR 50 0% 0% Niedrig 30 "0,43" "1,51"
|
||||
make your own qr code for business EUR 50 0% -100% Unbekannt
|
||||
scan your qr code EUR 50 0% 0% Niedrig 27 "0,56" "2,47"
|
||||
for qr code generator EUR 50 -100% -100% Unbekannt
|
||||
qr code what is qr EUR 5000 0% 0% Niedrig 15 "0,05" "0,64"
|
||||
qr code with location EUR 50 -100% -100% Unbekannt
|
||||
qr code qr EUR 5000 0% 0% Niedrig 0 "1,03" "3,01"
|
||||
qr code generator bulk free EUR 50 0% 0% Niedrig 24
|
||||
qr code and EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
||||
qr number EUR 500 0% 900% Niedrig 5 "0,07" "0,94"
|
||||
credit card qr code generator EUR 50 0% -100% Unbekannt
|
||||
about qr code generator EUR 50 0% 0% Unbekannt
|
||||
whatsapp qr generator EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
||||
generate to qr code EUR 50 -100% -100% Unbekannt
|
||||
qr codes work EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
||||
qr to code EUR 50 0% 0% Niedrig 21 "1,00" "3,78"
|
||||
in qr code EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
||||
create qr business card free EUR 50 -100% -100% Unbekannt
|
||||
qr code it EUR 50 0% 0% Niedrig 29 "1,21" "2,25"
|
||||
qr marketing EUR 50 0% 0% Niedrig 3
|
||||
it qr code EUR 50 0% 0% Niedrig 5
|
||||
and qr code EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
||||
qr code is EUR 50 0% 0% Niedrig 17 "0,09" "1,83"
|
||||
bulk code generator EUR 50 0% 0% Niedrig 5
|
||||
qr code calculator EUR 50 0% 0% Niedrig 23
|
||||
qr bulk EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
||||
generator qr barcode EUR 50 -100% -100% Unbekannt
|
||||
generator free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
||||
code generator code EUR 50 ∞ 0% Niedrig 14
|
||||
qr number generator EUR 50 0% 0% Niedrig 14
|
||||
qr code generator bulk create EUR 50 -100% -100% Unbekannt
|
||||
qr campaign EUR 50 0% -90% Niedrig 19 "1,45" "10,66"
|
||||
square codes EUR 5000 0% 0% Niedrig 5 "0,83" "3,42"
|
||||
generator create EUR 500 0% 0% Niedrig 1
|
||||
code for qr code EUR 50 ∞ 0% Niedrig 14
|
||||
qr bulk code generator EUR 50 0% 0% Niedrig 11
|
||||
qr code generator with location EUR 50 -100% 0% Unbekannt
|
||||
qr code scanner qr code generator EUR 50 ∞ 0% Niedrig 29
|
||||
youtube qr EUR 5000 0% 0% Niedrig 16 "0,52" "2,21"
|
||||
qr code generator custom design EUR 50 -100% -100% Unbekannt
|
||||
your code generator EUR 50 0% -100% Unbekannt
|
||||
free dynamic code generator EUR 50 0% -100% Unbekannt
|
||||
qr scan code free EUR 50 0% 0% Niedrig 0
|
||||
edit qr EUR 50 0% 0% Niedrig 2
|
||||
qr code scan qr code EUR 50 0% 0% Niedrig 25
|
||||
sozdat qr code EUR 50 -100% 0% Unbekannt
|
||||
barcode to qr code generator EUR 50 0% 0% Niedrig 27 "1,89" "8,80"
|
||||
qr codes that work EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
||||
i qr code EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
||||
free barcode code generator EUR 50000 0% 0% Niedrig 5 "0,67" "2,52"
|
||||
work qr code EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
||||
qr code on device EUR 50 0% 0% Niedrig 1
|
||||
barcode code generator free EUR 50 0% -90% Niedrig 6 "0,20" "2,34"
|
||||
device qr code EUR 50 0% 0% Niedrig 22 "0,58" "2,52"
|
||||
code barcode EUR 500 0% 900% Niedrig 10 "0,19" "1,55"
|
||||
all qr code generator EUR 50 0% 0% Unbekannt
|
||||
advanced qr code EUR 50 0% 0% Niedrig 2
|
||||
qr generator bulk EUR 50 0% 0% Niedrig 14
|
||||
information qr code generator EUR 50 -100% -100% Unbekannt
|
||||
device qr EUR 50 0% -100% Unbekannt
|
||||
type qr code EUR 50 0% 0% Niedrig 9 "0,52" "1,66"
|
||||
qr edit EUR 50 0% 0% Niedrig 21
|
||||
no qr codes EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
||||
qr code na EUR 50 0% 0% Unbekannt
|
||||
qr code & EUR 50 0% ∞ Niedrig 0
|
||||
qr code a EUR 500 0% 0% Niedrig 23 "0,49" "2,01"
|
||||
qr generator location EUR 50 ∞ 0% Niedrig 0
|
||||
qr code an EUR 50 0% 0% Niedrig 19
|
||||
qr code on EUR 50 0% 0% Niedrig 12
|
||||
qr code i EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
||||
qr code of EUR 50 0% 0% Niedrig 9 "0,50" "2,07"
|
||||
qr code on this phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
||||
your qr EUR 50 0% 0% Niedrig 20
|
||||
qr code what is EUR 5000 0% 0% Niedrig 15 "0,05" "0,64"
|
||||
qr code generator vcard business card EUR 50 -100% -100% Unbekannt
|
||||
every qr code EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
at qr code EUR 50 0% 0% Niedrig 4
|
||||
qr code what is it EUR 50 0% 0% Niedrig 17 "0,05" "0,44"
|
||||
qr code where is it EUR 50 0% 0% Niedrig 0
|
||||
view qr code EUR 50 0% 0% Niedrig 13 "0,65" "3,23"
|
||||
code creation EUR 50 -90% 0% Niedrig 12 "2,14" "5,68"
|
||||
qr code from EUR 50 0% 0% Niedrig 14 "0,58" "1,45"
|
||||
write qr code EUR 50 0% 0% Niedrig 0
|
||||
credit card qr code EUR 50 0% 0% Niedrig 7 "2,64" "9,81"
|
||||
qr code about EUR 50 0% 0% Niedrig 18
|
||||
www com qr code EUR 50 0% 0% Unbekannt
|
||||
this qr code EUR 50 0% 0% Niedrig 8
|
||||
by qr code EUR 50 0% 0% Niedrig 19
|
||||
qr code to location EUR 50 -100% -100% Unbekannt
|
||||
location qr generator EUR 50 0% 0% Niedrig 0
|
||||
business card qr code generator with logo EUR 50 0% 0% Niedrig 0
|
||||
qr work EUR 50 900% 900% Niedrig 1
|
||||
generate bulk qr code from excel EUR 50 ∞ ∞ Niedrig 14
|
||||
any qr code EUR 500 0% 0% Niedrig 0
|
||||
generate qr code bulk EUR 50 -100% 0% Unbekannt
|
||||
qr code qr scanner EUR 500 0% 0% Niedrig 0
|
||||
get qr code for location EUR 50 -100% -100% Unbekannt
|
||||
scan code for EUR 50 0% 0% Niedrig 19
|
||||
generator barcode qr EUR 50 0% -100% Unbekannt
|
||||
free qr code for location EUR 50 -100% 0% Unbekannt
|
||||
open a qr code EUR 500 0% 0% Niedrig 23 "0,41" "1,51"
|
||||
phone qr EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
||||
barcode to qr generator EUR 50 -100% -100% Unbekannt
|
||||
static code generator EUR 50 0% 0% Niedrig 26
|
||||
location qr code creation EUR 50 0% 0% Unbekannt
|
||||
location scan qr code EUR 50 ∞ ∞ Niedrig 0
|
||||
create location qr code free EUR 50 -100% 0% Unbekannt
|
||||
generator qr code vcard EUR 50 0% 0% Unbekannt
|
||||
bulk generator EUR 50 0% 0% Niedrig 4 "0,27" "1,33"
|
||||
qr code of this device EUR 50 0% ∞ Niedrig 20
|
||||
qr code generator location free EUR 50 0% 0% Niedrig 0
|
||||
qr location code EUR 50 0% -100% Unbekannt
|
||||
account qr code EUR 50 0% 0% Niedrig 13
|
||||
qr code to code EUR 50 0% 0% Niedrig 0
|
||||
free qr scan generator EUR 50 0% 0% Niedrig 26 "0,89" "1,51"
|
||||
dynamic qr code generator with logo EUR 50 0% 0% Niedrig 0
|
||||
qr code to barcode generator EUR 50 0% 0% Niedrig 5
|
||||
qr code of a location EUR 50 0% -100% Unbekannt
|
||||
the code generator EUR 50 0% 0% Niedrig 27
|
||||
create qr code of location EUR 50 0% -100% Unbekannt
|
||||
bulk qr barcode generator EUR 50 0% 0% Niedrig 3
|
||||
qr hunt EUR 50 0% 0% Niedrig 4
|
||||
bulk qr generator free EUR 50 -100% -100% Unbekannt
|
||||
dynamic qr code display EUR 50 900% 900% Niedrig 6
|
||||
location qr code free EUR 50 0% -100% Unbekannt
|
||||
location qr scanner EUR 50 -100% 0% Unbekannt
|
||||
qr quotes EUR 500 0% 0% Niedrig 5 "0,61" "2,17"
|
||||
qr c9de EUR 50 -90% 0% Niedrig 2
|
||||
you are code generator EUR 50 0% -100% Unbekannt
|
||||
make a qr code for location EUR 50 -100% -100% Unbekannt
|
||||
qr code generator from csv EUR 50 0% 0% Niedrig 0
|
||||
generate qr code from barcode EUR 50 0% 0% Niedrig 29
|
||||
make generator EUR 500 0% 0% Niedrig 9 "0,46" "2,62"
|
||||
all qr codes EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
bulk qr code generator with logo free EUR 50 0% ∞ Unbekannt
|
||||
whatsapp qr code business card EUR 50 -100% 0% Unbekannt
|
||||
qr code with phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
||||
generator for free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
||||
generate url from qr code EUR 500 -90% 0% Niedrig 4 "2,05" "2,69"
|
||||
barcode and qr codes EUR 50000 0% 900% Niedrig 3 "0,42" "1,93"
|
||||
qr code menu restaurant free EUR 50 0% 0% Unbekannt
|
||||
qr code features EUR 500 0% 900% Niedrig 2 "0,33" "1,29"
|
||||
generate qr code for location free EUR 50 0% -100% Unbekannt
|
||||
location in qr code EUR 50 0% 0% Unbekannt
|
||||
generate qr code by url EUR 50 -100% -100% Unbekannt
|
||||
qr code required EUR 50 0% 0% Niedrig 11 "0,94" "2,61"
|
||||
qr code from phone EUR 50 0% 0% Niedrig 11 "0,49" "1,03"
|
||||
location qr code scan EUR 50 -100% -100% Unbekannt
|
||||
no qr EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
||||
qr code scan for location EUR 50 0% -100% Unbekannt
|
||||
generate url to qr code EUR 50 -100% -100% Unbekannt
|
||||
get code from qr code EUR 50 0% 0% Niedrig 10
|
||||
scan the qr code for location EUR 50 ∞ ∞ Niedrig 0
|
||||
kod generator EUR 50 0% 0% Niedrig 0
|
||||
qr code in text EUR 50 0% 0% Niedrig 2
|
||||
restaurant menu scan code EUR 50 0% 0% Niedrig 0
|
||||
location qr code free generator EUR 50 0% 0% Unbekannt
|
||||
url from qr code EUR 50 0% 0% Niedrig 4
|
||||
scan tracking EUR 50 900% 0% Niedrig 24 "0,31" "2,42"
|
||||
qr code of this phone EUR 50 0% 0% Niedrig 0
|
||||
qr code brand EUR 500 0% 0% Niedrig 4 "1,55" "8,10"
|
||||
qr code in phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
||||
www the qr code generator scan EUR 50 -100% 0% Unbekannt
|
||||
create qr code from barcode EUR 50 0% 0% Niedrig 29
|
||||
qr all EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
bulk qr code barcode generator EUR 50 ∞ 0% Niedrig 0
|
||||
qr scan location EUR 50 0% 0% Niedrig 0
|
||||
qr code no EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
||||
qr code that works EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
||||
qr code randomizer EUR 5000 0% 0% Niedrig 13 "0,60" "1,71"
|
||||
qr code marketing campaigns EUR 50 0% 0% Niedrig 3
|
||||
qr code generator create your qr code for free EUR 50 -100% -100% Unbekannt
|
||||
account qr EUR 50 0% ∞ Niedrig 0
|
||||
qr code for credit card EUR 50 -100% -100% Unbekannt
|
||||
qr cost EUR 50 0% 0% Niedrig 5
|
||||
qr code on the phone EUR 50 0% 0% Niedrig 13
|
||||
scan qr code scan qr code EUR 50 0% 0% Niedrig 25
|
||||
qr kodlari EUR 50 -100% -100% Unbekannt
|
||||
qr code scan code EUR 50 0% 0% Niedrig 17
|
||||
scan by qr code EUR 50 0% 0% Niedrig 0
|
||||
3 qr code EUR 50 0% 0% Niedrig 0
|
||||
scan qr code location EUR 50 0% 0% Niedrig 17
|
||||
qr scanner in EUR 50 0% 0% Niedrig 18
|
||||
create qr code to whatsapp EUR 50 -100% -100% Unbekannt
|
||||
qr code on email EUR 50 0% 0% Niedrig 25
|
||||
create qr code for whatsapp free EUR 50 0% -100% Unbekannt
|
||||
make qr code of location EUR 50 -100% 0% Unbekannt
|
||||
qr code save EUR 50 0% 0% Niedrig 19 "0,83" "3,47"
|
||||
no to qr code EUR 50 0% -100% Unbekannt
|
||||
call qr code EUR 50000 0% 0% Niedrig 0 "0,57" "2,59"
|
||||
free barcode code EUR 500 0% 0% Niedrig 18 "0,65" "2,84"
|
||||
qr code for scan EUR 50 0% 0% Niedrig 14 "0,52" "1,56"
|
||||
bar code s EUR 50 0% 0% Niedrig 24 "1,26" "1,72"
|
||||
qr code as text EUR 500 0% 0% Niedrig 15 "0,13" "1,40"
|
||||
qr view EUR 50 0% 0% Niedrig 3
|
||||
qr code generator device EUR 50 0% 0% Niedrig 8
|
||||
qr scanner for EUR 50 0% 0% Niedrig 14 "0,52" "1,56"
|
||||
qr code generator full EUR 50 0% 0% Unbekannt
|
||||
qr code generator for visiting card free EUR 50 0% 0% Unbekannt
|
||||
qr coe3 EUR 50 -100% -100% Unbekannt
|
||||
qr text code EUR 50 0% 0% Niedrig 29
|
||||
scan qr free EUR 50 0% 0% Niedrig 25
|
||||
cole generator EUR 50 0% 0% Niedrig 24
|
||||
into qr code EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
||||
qr code how does it work EUR 50 0% 0% Niedrig 25 "0,10" "0,52"
|
||||
any qr EUR 500 0% 0% Niedrig 0
|
||||
menu card qr code generator EUR 50 0% 0% Unbekannt
|
||||
url to code generator EUR 50 ∞ 0% Niedrig 14
|
||||
website qr scanner EUR 500 0% 0% Niedrig 6 "0,03" "1,77"
|
||||
all in qr code EUR 50 0% 0% Unbekannt
|
||||
all qr EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
||||
qr scanner location EUR 50 0% 0% Niedrig 0
|
||||
qr how to use EUR 50 0% 0% Niedrig 0
|
||||
qr scanner for location EUR 50 -100% 0% Unbekannt
|
||||
scan qr code for EUR 50 0% 0% Niedrig 11
|
||||
scan the qr code for EUR 50 0% 0% Niedrig 0
|
||||
scan this qr code for EUR 50 ∞ ∞ Niedrig 29
|
||||
the qr barcode generator EUR 50 -100% -100% Unbekannt
|
||||
trackable codes EUR 50 0% 0% Niedrig 0
|
||||
scan qr code in EUR 50 0% 0% Niedrig 0
|
||||
qr do EUR 50 0% 0% Niedrig 15 "0,86" "4,10"
|
||||
qr code login generator EUR 50 0% 0% Niedrig 16
|
||||
@@ -1,641 +0,0 @@
|
||||
Issues
|
||||
/
|
||||
Open Graph tags incomplete
|
||||
|
||||
Why and how to fix
|
||||
|
||||
Submit to IndexNow
|
||||
|
||||
Create new issue
|
||||
|
||||
All URLs
|
||||
|
||||
Pages
|
||||
|
||||
Resources
|
||||
|
||||
Content
|
||||
|
||||
Links
|
||||
|
||||
Redirects
|
||||
|
||||
Indexability
|
||||
|
||||
Sitemaps
|
||||
|
||||
Ahrefs metrics
|
||||
Word or phrase
|
||||
|
||||
URL
|
||||
|
||||
Advanced filter
|
||||
Crawl history
|
||||
Hide chart
|
||||
12 Jan
|
||||
13 Jan
|
||||
13 Jan
|
||||
14 Jan
|
||||
14 Jan
|
||||
15 Jan
|
||||
0
|
||||
2
|
||||
4
|
||||
6
|
||||
8
|
||||
All filter results
|
||||
|
||||
All filter results
|
||||
8
|
||||
|
||||
Lost from filter results
|
||||
0
|
||||
|
||||
Lost
|
||||
0
|
||||
|
||||
Patches
|
||||
|
||||
Changes: Don't show
|
||||
|
||||
Columns
|
||||
|
||||
Export
|
||||
PR
|
||||
URL
|
||||
Organic traffic
|
||||
Is valid Open graph
|
||||
Open graph attributes
|
||||
Open graph values
|
||||
Depth
|
||||
Is indexable page
|
||||
No. of all inlinks
|
||||
24
|
||||
html
|
||||
Free vCard QR Generator: Digital Cards | QR Master
|
||||
https://www.qrmaster.net/blog/vcard-qr-code-generator
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Professional business card with vCard QR code being scanned by smartphone
|
||||
https://www.qrmaster.net/blog/vcard-qr-code.png
|
||||
Create professional vCard QR codes for digital business cards. Share contact info instantly with a scan—includes templates and best practices.
|
||||
Free vCard QR Generator: Digital Cards
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
Restaurant Menu QR Codes: 2025 Guide | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-restaurant-menu
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Restaurant table with QR code menu card and smartphone scanning
|
||||
https://www.qrmaster.net/blog/restaurant-qr-menu.png
|
||||
Step-by-step guide to creating digital menu QR codes for your restaurant. Learn best practices for touchless menus, placement tips, and tracking.
|
||||
Restaurant Menu QR Codes: 2025 Guide
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
QR Code Analytics: The Complete Guide | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-analytics
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
QR Code Analytics dashboard displaying scan metrics and user data
|
||||
https://www.qrmaster.net/blog/qr-code-analytics-hero.webp
|
||||
Master QR Code Analytics with our complete guide. Learn how to track scans, measure ROI, and optimize your marketing campaigns using real-time data.
|
||||
QR Code Analytics: The Complete Guide
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
Dynamic vs Static QR Codes: The Ultimate Comparison | QR Master
|
||||
https://www.qrmaster.net/blog/dynamic-vs-static-qr-codes
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Comparison graphic showing features of static versus dynamic QR codes
|
||||
https://www.qrmaster.net/blog/static-vs-dynamic-qr-codes-hero.png
|
||||
Static vs Dynamic QR Codes: Which should you choose? Learn the key differences, pros and cons, and why dynamic codes are better for business.
|
||||
Dynamic vs Static QR Codes: The Ultimate Comparison
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
How to Generate Bulk QR Codes from Excel | QR Master
|
||||
https://www.qrmaster.net/blog/bulk-qr-code-generator-excel
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Excel spreadsheet being converted into multiple QR codes
|
||||
https://www.qrmaster.net/blog/building-qr-generator.png
|
||||
Generate hundreds of QR codes from Excel or CSV files in minutes. Step-by-step guide with templates, best practices, and free tools.
|
||||
How to Generate Bulk QR Codes from Excel
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
QR Code Print Size Guide: Minimum Sizes for Every Use Case | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-print-size-guide
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Various print materials showing different QR code sizes
|
||||
https://www.qrmaster.net/blog/qr-print-sizes.png
|
||||
Complete guide to QR code print sizes. Learn minimum dimensions for business cards, posters, banners, and more to ensure reliable scanning.
|
||||
QR Code Print Size Guide: Minimum Sizes for Every Use Case
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
Best QR Code Generator for Small Business 2025 | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-small-business
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
Small business owner using QR codes for customer engagement
|
||||
https://www.qrmaster.net/blog/small-business-qr.png
|
||||
Find the best QR code solution for your small business. Compare features, pricing, and use cases for marketing, payments, and operations.
|
||||
Best QR Code Generator for Small Business 2025
|
||||
0
|
||||
Yes
|
||||
8
|
||||
24
|
||||
html
|
||||
QR Code Tracking: Complete Guide 2025 | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-tracking-guide-2025
|
||||
0
|
||||
No
|
||||
og:type
|
||||
og:image:alt
|
||||
og:image
|
||||
og:description
|
||||
og:title
|
||||
article
|
||||
QR Code Tracking and analytics dashboard visualization
|
||||
https://www.qrmaster.net/blog/qr-code-tracking-guide-hero.webp
|
||||
The complete guide to QR Code Tracking in 2025. Learn how to track scans, measure ROI, and optimize your marketing campaigns.
|
||||
QR Code Tracking: Complete Guide 2025
|
||||
0
|
||||
Yes
|
||||
8
|
||||
Showing 8 of 8
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Issues
|
||||
/
|
||||
Pages to submit to IndexNow
|
||||
|
||||
Why and how to fix
|
||||
|
||||
Submit to IndexNow
|
||||
|
||||
Create new issue
|
||||
|
||||
All URLs
|
||||
|
||||
Pages
|
||||
|
||||
Resources
|
||||
|
||||
Content
|
||||
|
||||
Links
|
||||
|
||||
Redirects
|
||||
|
||||
Indexability
|
||||
|
||||
Sitemaps
|
||||
|
||||
Ahrefs metrics
|
||||
Word or phrase
|
||||
|
||||
URL
|
||||
|
||||
Advanced filter
|
||||
Crawl history
|
||||
Hide chart
|
||||
12 Jan
|
||||
13 Jan
|
||||
13 Jan
|
||||
14 Jan
|
||||
14 Jan
|
||||
15 Jan
|
||||
0
|
||||
9
|
||||
18
|
||||
27
|
||||
36
|
||||
All filter results
|
||||
|
||||
All filter results
|
||||
12
|
||||
|
||||
Lost from filter results
|
||||
|
||||
Lost
|
||||
|
||||
Patches: Show all
|
||||
|
||||
Changes: Absolute
|
||||
|
||||
Columns
|
||||
|
||||
Export
|
||||
PR
|
||||
URL
|
||||
Organic traffic
|
||||
Changes
|
||||
HTTP status code
|
||||
Content type
|
||||
Is indexable page
|
||||
Title
|
||||
Patch it
|
||||
|
||||
Batch AI
|
||||
Meta description
|
||||
Patch it
|
||||
|
||||
Batch AI
|
||||
H1
|
||||
H2
|
||||
No. of content words
|
||||
Changes
|
||||
No. of internal outlinks
|
||||
Changes
|
||||
No. of external outlinks
|
||||
Changes
|
||||
Page text
|
||||
First found at
|
||||
40
|
||||
html
|
||||
QR Master: Dynamic QR Generator
|
||||
https://www.qrmaster.net/
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Master: Dynamic QR Generator
|
||||
Enter new title
|
||||
Create professional QR codes with QR Master. Dynamic QR with tracking, bulk generation, custom branding, and real-time analytics for all your campaigns.
|
||||
Enter new meta description
|
||||
QR Master: Dynamic QR Code Generator with Analytics
|
||||
Create QR Codes That Work Everywhere
|
||||
Create QR Codes That Work Everywhere
|
||||
Instant QR Code Generator
|
||||
The Future of QR Codes is AI-Powered
|
||||
More Free QR Code Tools
|
||||
Why Dynamic QR Codes Save You Money
|
||||
All 8
|
||||
777
|
||||
29
|
||||
0
|
||||
View text
|
||||
5 KB
|
||||
38
|
||||
html
|
||||
QR Insights: Latest QR Strategies | QR Master
|
||||
https://www.qrmaster.net/blog
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Insights: Latest QR Strategies | QR Master
|
||||
Enter new title
|
||||
Expert guides on QR code analytics, dynamic vs static codes, bulk generation, and smart marketing use cases. Learn how to maximize your QR campaign ROI.
|
||||
Enter new meta description
|
||||
QR Code Insights
|
||||
481
|
||||
495
|
||||
−14
|
||||
37
|
||||
0
|
||||
View changes
|
||||
3 KB
|
||||
3 KB
|
||||
38
|
||||
html
|
||||
Pricing Plans | QR Master
|
||||
https://www.qrmaster.net/pricing
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
Pricing Plans | QR Master
|
||||
Enter new title
|
||||
Choose the perfect QR code plan for your needs. Free, Pro, and Business plans with dynamic QR codes, analytics, bulk generation, and custom branding.
|
||||
Enter new meta description
|
||||
QR Master Pricing – Choose Your QR Code Plan
|
||||
Choose Your Plan
|
||||
Compare our plans
|
||||
Choose Your Plan
|
||||
271
|
||||
29
|
||||
30
|
||||
−1
|
||||
0
|
||||
View text
|
||||
2 KB
|
||||
38
|
||||
html
|
||||
QR Code Erstellen – Kostenlos | QR Master
|
||||
https://www.qrmaster.net/qr-code-erstellen
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Code Erstellen – Kostenlos | QR Master
|
||||
Enter new title
|
||||
Erstellen Sie QR Codes kostenlos in Sekunden. Dynamische QR-Codes mit Tracking, Branding und Massen-Erstellung. Für immer kostenlos.
|
||||
Enter new meta description
|
||||
QR Code Erstellen – Kostenloser QR Code Generator mit Tracking
|
||||
Erstellen Sie QR-Codes, die überall funktionieren
|
||||
Erstellen Sie QR-Codes, die überall funktionieren
|
||||
Sofortiger QR-Code-Generator
|
||||
Warum dynamische QR-Codes Geld sparen
|
||||
Alles was Sie brauchen, um professionelle QR-Codes zu erstellen
|
||||
Wählen Sie Ihren Plan
|
||||
All 6
|
||||
554
|
||||
29
|
||||
0
|
||||
View text
|
||||
4 KB
|
||||
24
|
||||
html
|
||||
Free vCard QR Generator: Digital Cards | QR Master
|
||||
https://www.qrmaster.net/blog/vcard-qr-code-generator
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
Free vCard QR Generator: Digital Cards | QR Master
|
||||
Enter new title
|
||||
Create professional vCard QR codes for digital business cards. Share contact info instantly with a scan—includes templates and best practices.
|
||||
Enter new meta description
|
||||
Free vCard QR Generator: Digital Cards
|
||||
Quick Answer
|
||||
What is a vCard QR Code?
|
||||
Why Use a Digital Business Card QR Code?
|
||||
Information You Can Include in a vCard
|
||||
Static vs Dynamic vCard QR Codes
|
||||
All 13
|
||||
1,135
|
||||
1,149
|
||||
−14
|
||||
37
|
||||
0
|
||||
View changes
|
||||
7 KB
|
||||
7 KB
|
||||
24
|
||||
html
|
||||
Restaurant Menu QR Codes: 2025 Guide | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-restaurant-menu
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
Restaurant Menu QR Codes: 2025 Guide | QR Master
|
||||
Enter new title
|
||||
Step-by-step guide to creating digital menu QR codes for your restaurant. Learn best practices for touchless menus, placement tips, and tracking.
|
||||
Enter new meta description
|
||||
Restaurant Menu QR Codes: 2025 Guide
|
||||
Quick Answer
|
||||
Why Restaurants Need QR Code Menus in 2025
|
||||
Step 1: Prepare Your Digital Menu
|
||||
Step 2: Create Your QR Code with QR Master
|
||||
Step 3: Customize Your Restaurant QR Code
|
||||
All 13
|
||||
1,242
|
||||
1,256
|
||||
−14
|
||||
38
|
||||
0
|
||||
View changes
|
||||
8 KB
|
||||
8 KB
|
||||
24
|
||||
html
|
||||
QR Code Analytics: The Complete Guide | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-analytics
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Code Analytics: The Complete Guide | QR Master
|
||||
Enter new title
|
||||
Master QR Code Analytics with our complete guide. Learn how to track scans, measure ROI, and optimize your marketing campaigns using real-time data.
|
||||
Master QR Code Analytics with our complete guide. Learn how to track scans, measure ROI, and optimize your marketing campaigns using real-time data and insights.
|
||||
Enter new meta description
|
||||
QR Code Analytics: The Complete Guide
|
||||
Quick Answer
|
||||
What Are Scan Analytics?
|
||||
How to Set Up QR Code Analytics
|
||||
Key Metrics in QR Code Analytics
|
||||
Advanced Campaign Tracking Strategies
|
||||
All 12
|
||||
1,526
|
||||
1,538
|
||||
−12
|
||||
37
|
||||
0
|
||||
View changes
|
||||
10 KB
|
||||
10 KB
|
||||
24
|
||||
html
|
||||
Dynamic vs Static QR Codes: The Ultimate Comparison | QR Master
|
||||
https://www.qrmaster.net/blog/dynamic-vs-static-qr-codes
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
Dynamic vs Static QR Codes: The Ultimate Comparison | QR Master
|
||||
Enter new title
|
||||
Static vs Dynamic QR Codes: Which should you choose? Learn the key differences, pros and cons, and why dynamic codes are better for business.
|
||||
Static vs Dynamic QR Codes: Which one should you choose? Learn the key differences, pros and cons, and why dynamic QR codes are the better choice for business and marketing.
|
||||
Enter new meta description
|
||||
Dynamic vs Static QR Codes: The Ultimate Comparison
|
||||
Quick Answer
|
||||
What is a Static QR Code?
|
||||
What is a Dynamic QR Code?
|
||||
Direct Comparison: Static vs Dynamic
|
||||
Why Dynamic QR Codes Are Better for Business
|
||||
All 10
|
||||
1,074
|
||||
1,082
|
||||
−8
|
||||
37
|
||||
0
|
||||
View changes
|
||||
7 KB
|
||||
7 KB
|
||||
24
|
||||
html
|
||||
How to Generate Bulk QR Codes from Excel | QR Master
|
||||
https://www.qrmaster.net/blog/bulk-qr-code-generator-excel
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
How to Generate Bulk QR Codes from Excel | QR Master
|
||||
Enter new title
|
||||
Generate hundreds of QR codes from Excel or CSV files in minutes. Step-by-step guide with templates, best practices, and free tools.
|
||||
Enter new meta description
|
||||
How to Generate Bulk QR Codes from Excel
|
||||
Quick Answer
|
||||
How Bulk QR Code Generation Works
|
||||
Step-by-Step Guide: Excel to QR Codes
|
||||
Use Cases for Bulk QR Codes
|
||||
Free vs Paid Bulk QR Tools
|
||||
All 12
|
||||
1,882
|
||||
1,896
|
||||
−14
|
||||
37
|
||||
1
|
||||
View changes
|
||||
12 KB
|
||||
13 KB
|
||||
24
|
||||
html
|
||||
QR Code Print Size Guide: Minimum Sizes for Every Use Case | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-print-size-guide
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Code Print Size Guide: Minimum Sizes for Every Use Case | QR Master
|
||||
Enter new title
|
||||
Complete guide to QR code print sizes. Learn minimum dimensions for business cards, posters, banners, and more to ensure reliable scanning.
|
||||
Enter new meta description
|
||||
QR Code Print Size Guide: Minimum Sizes for Every Use Case
|
||||
Quick Answer
|
||||
Why QR Code Size Matters
|
||||
The Scanning Distance Formula
|
||||
QR Code Sizes by Application
|
||||
Factors Affecting Scanability
|
||||
All 12
|
||||
948
|
||||
962
|
||||
−14
|
||||
37
|
||||
0
|
||||
View changes
|
||||
6 KB
|
||||
6 KB
|
||||
24
|
||||
html
|
||||
Best QR Code Generator for Small Business 2025 | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-small-business
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
Best QR Code Generator for Small Business 2025 | QR Master
|
||||
Enter new title
|
||||
Find the best QR code solution for your small business. Compare features, pricing, and use cases for marketing, payments, and operations.
|
||||
Enter new meta description
|
||||
Best QR Code Generator for Small Business 2025
|
||||
Quick Answer
|
||||
Why Small Businesses Need QR Codes
|
||||
Top 10 QR Code Use Cases for Small Business
|
||||
What to Look for in a Small Business QR Solution
|
||||
QR Master for Small Business
|
||||
All 11
|
||||
1,034
|
||||
1,048
|
||||
−14
|
||||
37
|
||||
0
|
||||
View changes
|
||||
7 KB
|
||||
7 KB
|
||||
24
|
||||
html
|
||||
QR Code Tracking: Complete Guide 2025 | QR Master
|
||||
https://www.qrmaster.net/blog/qr-code-tracking-guide-2025
|
||||
0
|
||||
200
|
||||
text/html; charset=utf-8
|
||||
Yes
|
||||
QR Code Tracking: Complete Guide 2025 | QR Master
|
||||
Enter new title
|
||||
The complete guide to QR Code Tracking in 2025. Learn how to track scans, measure ROI, and optimize your marketing campaigns.
|
||||
The complete guide to QR Code Tracking in 2025. Learn how to track scans, measure ROI with analytics tools, and optimize your marketing campaigns for maximum engagement.
|
||||
Enter new meta description
|
||||
QR Code Tracking: Complete Guide 2025
|
||||
Quick Answer
|
||||
What is QR Code Tracking?
|
||||
Why Track QR Codes? Key Benefits
|
||||
How to Track QR Code Scans: 4 Methods
|
||||
QR Code Tracking Tools Comparison
|
||||
All 15
|
||||
2,959
|
||||
2,967
|
||||
−8
|
||||
38
|
||||
1
|
||||
View changes
|
||||
19 KB
|
||||
19 KB
|
||||
Showing 12 of 12
|
||||
@@ -23,11 +23,20 @@ const nextConfig = {
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/blog/bulk-qr-codes-excel',
|
||||
destination: '/blog/bulk-qr-code-generator-excel',
|
||||
source: '/tools/phone-qr-code',
|
||||
destination: '/tools/call-qr-code-generator',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/barcode-generator',
|
||||
destination: '/tools/barcode-generator',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/bar-code-generator',
|
||||
destination: '/tools/barcode-generator',
|
||||
permanent: true,
|
||||
},
|
||||
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
2660
package-lock.json
generated
15
package.json
@@ -5,7 +5,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3050",
|
||||
"build": "prisma generate && next build",
|
||||
"build": "prisma generate && cross-env NODE_OPTIONS='--max-old-space-size=4096' next build",
|
||||
"trigger:indexing": "tsx scripts/trigger-indexing.ts",
|
||||
"submit:indexnow": "tsx scripts/submit-indexnow.ts",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
@@ -28,6 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.11.1",
|
||||
"@aws-sdk/client-s3": "^3.972.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.972.0",
|
||||
"@edge-runtime/cookies": "^6.0.0",
|
||||
"@prisma/client": "^5.7.0",
|
||||
"@stripe/stripe-js": "^8.0.0",
|
||||
@@ -36,12 +39,14 @@
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chart.js": "^4.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"copy-image-clipboard": "^2.1.2",
|
||||
"d3-scale": "^4.0.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"dotenv": "^17.2.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^12.24.10",
|
||||
"googleapis": "^170.1.0",
|
||||
"html-to-image": "^1.11.13",
|
||||
"i18next": "^23.7.6",
|
||||
"ioredis": "^5.3.2",
|
||||
@@ -51,17 +56,20 @@
|
||||
"next": "^14.2.35",
|
||||
"next-auth": "^4.24.5",
|
||||
"papaparse": "^5.4.1",
|
||||
"posthog-js": "^1.276.0",
|
||||
"posthog-js": "^1.332.0",
|
||||
"qr-code-styling": "^1.9.2",
|
||||
"qrcode": "^1.5.3",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-barcode": "^1.6.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-facebook-pixel": "^1.0.4",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"resend": "^6.4.2",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"stripe": "^19.1.0",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"uuid": "^13.0.0",
|
||||
@@ -75,10 +83,11 @@
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^16.1.1",
|
||||
"next-sitemap": "^4.2.3",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.1",
|
||||
"prisma": "^5.7.0",
|
||||
|
||||
146
prisma/migrations/20251013172642_init/migration.sql
Normal file
@@ -0,0 +1,146 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "QRType" AS ENUM ('STATIC', 'DYNAMIC');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ContentType" AS ENUM ('URL', 'WIFI', 'VCARD', 'PHONE', 'EMAIL', 'SMS', 'TEXT', 'WHATSAPP');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "QRStatus" AS ENUM ('ACTIVE', 'PAUSED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"password" TEXT,
|
||||
"image" TEXT,
|
||||
"emailVerified" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
|
||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionToken" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerificationToken" (
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "QRCode" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"type" "QRType" NOT NULL DEFAULT 'DYNAMIC',
|
||||
"contentType" "ContentType" NOT NULL DEFAULT 'URL',
|
||||
"content" JSONB NOT NULL,
|
||||
"tags" TEXT[],
|
||||
"status" "QRStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"style" JSONB NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "QRCode_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "QRScan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"qrId" TEXT NOT NULL,
|
||||
"ts" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"ipHash" TEXT NOT NULL,
|
||||
"userAgent" TEXT,
|
||||
"device" TEXT,
|
||||
"os" TEXT,
|
||||
"country" TEXT,
|
||||
"referrer" TEXT,
|
||||
"utmSource" TEXT,
|
||||
"utmMedium" TEXT,
|
||||
"utmCampaign" TEXT,
|
||||
"isUnique" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "QRScan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Integration" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'inactive',
|
||||
"config" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Integration_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "QRCode_slug_key" ON "QRCode"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QRCode_userId_createdAt_idx" ON "QRCode"("userId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QRScan_qrId_ts_idx" ON "QRScan"("qrId", "ts");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QRCode" ADD CONSTRAINT "QRCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QRScan" ADD CONSTRAINT "QRScan_qrId_fkey" FOREIGN KEY ("qrId") REFERENCES "QRCode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Integration" ADD CONSTRAINT "Integration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
22
prisma/migrations/20251014195005_init/migration.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[stripeCustomerId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[stripeSubscriptionId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Plan" AS ENUM ('FREE', 'PRO', 'BUSINESS');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "plan" "Plan" NOT NULL DEFAULT 'FREE',
|
||||
ADD COLUMN "stripeCurrentPeriodEnd" TIMESTAMP(3),
|
||||
ADD COLUMN "stripeCustomerId" TEXT,
|
||||
ADD COLUMN "stripePriceId" TEXT,
|
||||
ADD COLUMN "stripeSubscriptionId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_stripeCustomerId_key" ON "User"("stripeCustomerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_stripeSubscriptionId_key" ON "User"("stripeSubscriptionId");
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The values [WIFI,EMAIL] on the enum `ContentType` will be removed. If these variants are still used in the database, this will fail.
|
||||
- A unique constraint covering the columns `[resetPasswordToken]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
BEGIN;
|
||||
CREATE TYPE "ContentType_new" AS ENUM ('URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'TEXT', 'WHATSAPP');
|
||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" DROP DEFAULT;
|
||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" TYPE "ContentType_new" USING ("contentType"::text::"ContentType_new");
|
||||
ALTER TYPE "ContentType" RENAME TO "ContentType_old";
|
||||
ALTER TYPE "ContentType_new" RENAME TO "ContentType";
|
||||
DROP TYPE "ContentType_old";
|
||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" SET DEFAULT 'URL';
|
||||
COMMIT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "resetPasswordExpires" TIMESTAMP(3),
|
||||
ADD COLUMN "resetPasswordToken" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NewsletterSubscription" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL DEFAULT 'ai-coming-soon',
|
||||
"status" TEXT NOT NULL DEFAULT 'subscribed',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "NewsletterSubscription_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Lead" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL DEFAULT 'reprint-calculator',
|
||||
"reprintCost" DOUBLE PRECISION,
|
||||
"updatesPerYear" INTEGER,
|
||||
"annualSavings" DOUBLE PRECISION,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Lead_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NewsletterSubscription_email_key" ON "NewsletterSubscription"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NewsletterSubscription_email_idx" ON "NewsletterSubscription"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NewsletterSubscription_createdAt_idx" ON "NewsletterSubscription"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lead_email_idx" ON "Lead"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lead_createdAt_idx" ON "Lead"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Lead_source_idx" ON "Lead"("source");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_resetPasswordToken_key" ON "User"("resetPasswordToken");
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `updatedAt` to the `Lead` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
-- This migration adds more than one value to an enum.
|
||||
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||
-- in a single migration. This can be worked around by creating
|
||||
-- multiple migrations, each migration adding only one value to
|
||||
-- the enum.
|
||||
|
||||
|
||||
ALTER TYPE "ContentType" ADD VALUE 'PDF';
|
||||
ALTER TYPE "ContentType" ADD VALUE 'APP';
|
||||
ALTER TYPE "ContentType" ADD VALUE 'COUPON';
|
||||
ALTER TYPE "ContentType" ADD VALUE 'FEEDBACK';
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "Lead_createdAt_idx";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "Lead_email_idx";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "Lead_source_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Lead" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
ALTER COLUMN "updatesPerYear" SET DATA TYPE DOUBLE PRECISION;
|
||||
@@ -112,6 +112,10 @@ enum ContentType {
|
||||
SMS
|
||||
TEXT
|
||||
WHATSAPP
|
||||
PDF
|
||||
APP
|
||||
COUPON
|
||||
FEEDBACK
|
||||
}
|
||||
|
||||
enum QRStatus {
|
||||
@@ -168,11 +172,8 @@ model Lead {
|
||||
email String
|
||||
source String @default("reprint-calculator")
|
||||
reprintCost Float?
|
||||
updatesPerYear Int?
|
||||
updatesPerYear Float?
|
||||
annualSavings Float?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([email])
|
||||
@@index([createdAt])
|
||||
@@index([source])
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
1
public/ads.txt
Normal file
@@ -0,0 +1 @@
|
||||
google.com, pub-2782770414424875, DIRECT, f08c47fec0942fa0
|
||||
BIN
public/barcode-generator-preview.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
public/blog/best-qr-generator-2026.png
Normal file
|
After Width: | Height: | Size: 749 KiB |
BIN
public/blog/bulk-qr-events-hero.png
Normal file
|
After Width: | Height: | Size: 804 KiB |
BIN
public/blog/dynamic-vs-static-hero-v2.png
Normal file
|
After Width: | Height: | Size: 860 KiB |
BIN
public/blog/free-barcode-generator-guide.png
Normal file
|
After Width: | Height: | Size: 623 KiB |
BIN
public/blog/free-vs-paid-qr.png
Normal file
|
After Width: | Height: | Size: 601 KiB |
BIN
public/blog/instagram-qr-code.png
Normal file
|
After Width: | Height: | Size: 577 KiB |
BIN
public/blog/qr-analytics-guide-hero.png
Normal file
|
After Width: | Height: | Size: 630 KiB |
BIN
public/blog/qr-api.png
Normal file
|
After Width: | Height: | Size: 865 KiB |
BIN
public/blog/qr-code-analytics-hero-v2.png
Normal file
|
After Width: | Height: | Size: 863 KiB |
BIN
public/blog/qr-code-events.png
Normal file
|
After Width: | Height: | Size: 896 KiB |
BIN
public/blog/qr-code-generator-guide-hero.png
Normal file
|
After Width: | Height: | Size: 454 KiB |
BIN
public/blog/qr-code-tracking-hero-v2.png
Normal file
|
After Width: | Height: | Size: 646 KiB |
BIN
public/blog/qr-marketing-best-practices-hero.png
Normal file
|
After Width: | Height: | Size: 699 KiB |
BIN
public/blog/qr-marketing-strategy.png
Normal file
|
After Width: | Height: | Size: 672 KiB |
BIN
public/blog/qr-security.png
Normal file
|
After Width: | Height: | Size: 966 KiB |
BIN
public/blog/qr-statistics-2026.png
Normal file
|
After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 545 KiB |
|
Before Width: | Height: | Size: 440 KiB |
BIN
public/blog/spotify-qr-code.png
Normal file
|
After Width: | Height: | Size: 577 KiB |
BIN
public/blog/trackable-qr-codes.png
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
public/blog/utm-qr-codes.png
Normal file
|
After Width: | Height: | Size: 679 KiB |
BIN
public/blog/whatsapp-qr-code.png
Normal file
|
After Width: | Height: | Size: 584 KiB |
BIN
public/images/examples/qr_example_business_1769163647196.png
Normal file
|
After Width: | Height: | Size: 657 KiB |
BIN
public/images/examples/qr_example_ecommerce_1769163606650.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
public/images/examples/qr_example_event_1769163635077.png
Normal file
|
After Width: | Height: | Size: 423 KiB |
BIN
public/images/examples/qr_example_nonprofit_1769163705055.png
Normal file
|
After Width: | Height: | Size: 650 KiB |
BIN
public/images/examples/qr_example_packaging_1769163674382.png
Normal file
|
After Width: | Height: | Size: 646 KiB |
BIN
public/images/examples/qr_example_realestate_1769163689433.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
BIN
public/images/examples/qr_example_restaurant_1769163621640.png
Normal file
|
After Width: | Height: | Size: 700 KiB |
BIN
public/images/examples/qr_example_social_1769163660794.png
Normal file
|
After Width: | Height: | Size: 422 KiB |
32
scripts/debug-hang.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
console.log('1. Starting debug script...');
|
||||
|
||||
try {
|
||||
console.log('2. Importing fs...');
|
||||
const fs = require('fs');
|
||||
console.log(' fs imported.');
|
||||
|
||||
console.log('3. Importing path...');
|
||||
const path = require('path');
|
||||
console.log(' path imported.');
|
||||
|
||||
console.log('4. Importing googleapis...');
|
||||
const { google } = require('googleapis');
|
||||
console.log(' googleapis imported.');
|
||||
|
||||
console.log('5. Importing ../src/lib/indexnow...');
|
||||
const indexnow = require('../src/lib/indexnow');
|
||||
console.log(' indexnow imported.');
|
||||
console.log(' Keys:', Object.keys(indexnow));
|
||||
|
||||
if (indexnow.getAllIndexableUrls) {
|
||||
console.log('6. Calling getAllIndexableUrls...');
|
||||
const urls = indexnow.getAllIndexableUrls();
|
||||
console.log(` Got ${urls.length} URLs.`);
|
||||
}
|
||||
|
||||
console.log('7. Done!');
|
||||
|
||||
} catch (err) {
|
||||
console.error('ERROR during import:', err);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ switch ($choice) {
|
||||
Write-Host "Access points:"
|
||||
Write-Host " - App: http://localhost:3050"
|
||||
Write-Host " - Database UI: http://localhost:8080"
|
||||
Write-Host " - Database: localhost:5432"
|
||||
Write-Host " - Database: localhost:5435"
|
||||
Write-Host " - Redis: localhost:6379"
|
||||
}
|
||||
"2" {
|
||||
@@ -115,7 +115,7 @@ switch ($choice) {
|
||||
Write-Host ""
|
||||
Write-Host "Access points:"
|
||||
Write-Host " - App: http://localhost:3050"
|
||||
Write-Host " - Database: localhost:5432"
|
||||
Write-Host " - Database: localhost:5435"
|
||||
Write-Host " - Redis: localhost:6379"
|
||||
Write-Host ""
|
||||
Write-Host "To view logs:"
|
||||
|
||||
@@ -98,7 +98,7 @@ case $choice in
|
||||
echo "Access points:"
|
||||
echo " - App: http://localhost:3050"
|
||||
echo " - Database UI: http://localhost:8080"
|
||||
echo " - Database: localhost:5432"
|
||||
echo " - Database: localhost:5435"
|
||||
echo " - Redis: localhost:6379"
|
||||
;;
|
||||
2)
|
||||
@@ -126,7 +126,7 @@ case $choice in
|
||||
echo ""
|
||||
echo "Access points:"
|
||||
echo " - App: http://localhost:3050"
|
||||
echo " - Database: localhost:5432"
|
||||
echo " - Database: localhost:5435"
|
||||
echo " - Redis: localhost:6379"
|
||||
echo ""
|
||||
echo "To view logs:"
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
|
||||
// Helper script to run IndexNow submission
|
||||
// Run with: npx tsx scripts/submit-indexnow.ts
|
||||
// Run with: npm run submit:indexnow
|
||||
|
||||
import { getAllIndexableUrls, submitToIndexNow } from '../src/lib/indexnow';
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Starting IndexNow Submission Script...');
|
||||
|
||||
console.log(' Gathering URLs for IndexNow submission...');
|
||||
const urls = getAllIndexableUrls();
|
||||
console.log(` Found ${urls.length} indexable URLs.`);
|
||||
|
||||
// Basic validation of key presence (logic can be improved)
|
||||
if (!process.env.INDEXNOW_KEY) {
|
||||
console.warn('⚠️ WARNING: INDEXNOW_KEY environment variable is not set. Using placeholder.');
|
||||
// In production, you'd fail here. For dev/demo, we proceed but expect failure from API.
|
||||
console.warn('⚠️ WARNING: INDEXNOW_KEY environment variable is not set.');
|
||||
console.warn(' The submission might fail if the key is not hardcoded in src/lib/indexnow.ts');
|
||||
}
|
||||
|
||||
await submitToIndexNow(urls);
|
||||
console.log('\n✨ IndexNow submission process completed.');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
81
scripts/trigger-indexing.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
|
||||
import { google } from 'googleapis';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { getAllIndexableUrls } from '../src/lib/indexnow';
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURATION
|
||||
// ==========================================
|
||||
|
||||
// Path to your Service Account Key (JSON file)
|
||||
const KEY_FILE = path.join(__dirname, '../service_account.json');
|
||||
|
||||
// Urls are now fetched dynamically from src/lib/indexnow.ts
|
||||
// ==========================================
|
||||
|
||||
async function runUsingServiceAccount() {
|
||||
console.log('🚀 Starting Google Indexing Script (All Pages)...');
|
||||
|
||||
if (!fs.existsSync(KEY_FILE)) {
|
||||
console.error('\n❌ ERROR: Service Account Key not found!');
|
||||
console.error(` Expected path: ${KEY_FILE}`);
|
||||
console.error(' Please follow the instructions in INDEXING_GUIDE.md to create and save the key.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔑 Authenticating with key file: ${path.basename(KEY_FILE)}...`);
|
||||
|
||||
const auth = new google.auth.GoogleAuth({
|
||||
keyFile: KEY_FILE,
|
||||
scopes: ['https://www.googleapis.com/auth/indexing'],
|
||||
});
|
||||
|
||||
try {
|
||||
const client = await auth.getClient();
|
||||
console.log('✅ Authentication successful.');
|
||||
|
||||
console.log(' Gathering URLs to index...');
|
||||
const allUrls = getAllIndexableUrls();
|
||||
console.log(` Found ${allUrls.length} URLs to index.`);
|
||||
|
||||
for (const url of allUrls) {
|
||||
console.log(`\n📄 Processing: ${url}`);
|
||||
|
||||
try {
|
||||
const result = await google.indexing('v3').urlNotifications.publish({
|
||||
auth: auth,
|
||||
requestBody: {
|
||||
url: url,
|
||||
type: 'URL_UPDATED'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(` 👉 Status: ${result.status} ${result.statusText}`);
|
||||
// Optional: Log more details from result.data if needed
|
||||
|
||||
} catch (innerError: any) {
|
||||
console.error(` ❌ Failed to index ${url}`);
|
||||
if (innerError.response) {
|
||||
console.error(` Reason: ${innerError.response.status} - ${JSON.stringify(innerError.response.data)}`);
|
||||
// 429 = Quota exceeded
|
||||
// 403 = Permission denied (check service account owner status)
|
||||
} else {
|
||||
console.error(` Reason: ${innerError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Add a small delay to avoid hitting rate limits too fast if you have hundreds of URLs
|
||||
// await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
console.log('\n✨ Done! All requests processed.');
|
||||
console.log(' Note: Check Google Search Console for actual indexing status over time.');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('\n❌ Fatal error occurred:');
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
runUsingServiceAccount();
|
||||
742
searchvolume.md
Normal file
@@ -0,0 +1,742 @@
|
||||
Overview: qr code generator
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
370,000
|
||||
Monthly Volume
|
||||
337,000
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
91%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Not Enough Data
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
12%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
73
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$0.51
|
||||
Monthly Cost
|
||||
$3,072
|
||||
Search Volume
|
||||
90,500
|
||||
Advertisers
|
||||
15
|
||||
Homepages
|
||||
6
|
||||
Fresh SV
|
||||
918,000
|
||||
Universal search in SERP
|
||||
8,191
|
||||
Similar keywords
|
||||
qr code generator
|
||||
370,000
|
||||
qr code generator free
|
||||
43,300
|
||||
free qr code generator
|
||||
34,400
|
||||
generate qr code
|
||||
10,800
|
||||
google qr code generator
|
||||
8,000
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
655
|
||||
Questions
|
||||
How to generate a qr code
|
||||
1,700
|
||||
How to generate qr code
|
||||
630
|
||||
How to generate qr code for url
|
||||
270
|
||||
What is the best qr code generator?
|
||||
220
|
||||
How to generate bank qr code without edge
|
||||
200
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
19,120,108
|
||||
Also ranks for
|
||||
qr code generator free
|
||||
43,300
|
||||
qr code maker
|
||||
52,000
|
||||
free qr code generator
|
||||
34,400
|
||||
qr generator
|
||||
25,300
|
||||
create qr code
|
||||
29,500
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: barcode generator
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
58,300
|
||||
Monthly Volume
|
||||
51,000
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
87%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Low Mobile
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
1%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
22
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$1.68
|
||||
Monthly Cost
|
||||
$5,316
|
||||
Search Volume
|
||||
110,000
|
||||
Advertisers
|
||||
5
|
||||
Homepages
|
||||
21
|
||||
Fresh SV
|
||||
72,800
|
||||
Universal search in SERP
|
||||
5,381
|
||||
Similar keywords
|
||||
barcode generator
|
||||
58,300
|
||||
free barcode generator
|
||||
4,000
|
||||
upc barcode generator
|
||||
3,200
|
||||
2d barcode generator
|
||||
1,300
|
||||
generate barcode
|
||||
1,300
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
326
|
||||
Questions
|
||||
How to store barcodes generated into a folder in linux python
|
||||
250
|
||||
How to generate barcodes
|
||||
180
|
||||
How to generate barcodes in excel
|
||||
180
|
||||
How to generate barcodes for products
|
||||
135
|
||||
How to generate a third party barcode for j1 waiver
|
||||
110
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
27,933,196
|
||||
Also ranks for
|
||||
free barcode generator
|
||||
4,000
|
||||
barcode maker
|
||||
6,100
|
||||
upc code generator
|
||||
3,600
|
||||
upc generator
|
||||
4,500
|
||||
2d barcode generator
|
||||
1,300
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: qr code maker
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
52,000
|
||||
Monthly Volume
|
||||
48,200
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
93%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Not Enough Data
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
12%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
47
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$0.37
|
||||
Monthly Cost
|
||||
$209
|
||||
Search Volume
|
||||
18,100
|
||||
Advertisers
|
||||
11
|
||||
Homepages
|
||||
32
|
||||
Fresh SV
|
||||
71,300
|
||||
Universal search in SERP
|
||||
601
|
||||
Similar keywords
|
||||
qr code maker
|
||||
52,000
|
||||
animal crossing qr code maker
|
||||
2,000
|
||||
free qr code maker
|
||||
2,000
|
||||
qr code maker free
|
||||
1,900
|
||||
mini qr code maker
|
||||
380
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
13
|
||||
Questions
|
||||
How to maker qr code for cia
|
||||
70
|
||||
How to make qr codes with brother label maker
|
||||
70
|
||||
How to make a qr code qr code maker
|
||||
50
|
||||
How to post qr codes online mii maker
|
||||
40
|
||||
How to get qr code watch maker
|
||||
28
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
84,638,180
|
||||
Also ranks for
|
||||
qr code generator free
|
||||
43,300
|
||||
free qr code generator
|
||||
34,400
|
||||
create a qr code
|
||||
17,100
|
||||
create qr code
|
||||
29,500
|
||||
qr generator
|
||||
25,300
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: google qr code generator
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
8,000
|
||||
Monthly Volume
|
||||
5,100
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
64%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Low Mobile
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
2%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
52
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$3.53
|
||||
Monthly Cost
|
||||
$0.00
|
||||
Search Volume
|
||||
2,900
|
||||
Advertisers
|
||||
9
|
||||
Homepages
|
||||
20
|
||||
Fresh SV
|
||||
11,500
|
||||
Universal search in SERP
|
||||
336
|
||||
Similar keywords
|
||||
google qr code generator
|
||||
8,000
|
||||
qr code generator google
|
||||
4,800
|
||||
free qr code generator google
|
||||
720
|
||||
qr code generator google form
|
||||
440
|
||||
google form qr code generator
|
||||
320
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
59
|
||||
Questions
|
||||
Does google have a qr code generator?
|
||||
135
|
||||
How to generate qr code for google authenticator
|
||||
100
|
||||
Does google have a qr code generator for contact info?
|
||||
90
|
||||
How to generate qr code for google form
|
||||
90
|
||||
How to generate a qr code for a google form
|
||||
90
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
27,918,916
|
||||
Also ranks for
|
||||
qr code generator free
|
||||
43,300
|
||||
qr code maker
|
||||
52,000
|
||||
create qr code
|
||||
29,500
|
||||
qr generator
|
||||
25,300
|
||||
free qr code generator
|
||||
34,400
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: create qr code
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
29,500
|
||||
Monthly Volume
|
||||
26,400
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
89%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Not Enough Data
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
16%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
52
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$3.32
|
||||
Monthly Cost
|
||||
$1,406
|
||||
Search Volume
|
||||
14,800
|
||||
Advertisers
|
||||
15
|
||||
Homepages
|
||||
25
|
||||
Fresh SV
|
||||
50,000
|
||||
Universal search in SERP
|
||||
3,223
|
||||
Similar keywords
|
||||
create qr code
|
||||
29,500
|
||||
create a qr code
|
||||
17,100
|
||||
How to create a qr code
|
||||
9,200
|
||||
create qr code free
|
||||
5,500
|
||||
How to create a qr code free
|
||||
1,400
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
1,110
|
||||
Questions
|
||||
How to create a qr code
|
||||
9,200
|
||||
How to create a qr code free
|
||||
1,400
|
||||
How to create qr codes
|
||||
1,300
|
||||
How to create qr code
|
||||
1,300
|
||||
How to create a qr code for a google form
|
||||
1,100
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
18,733,729
|
||||
Also ranks for
|
||||
qr code generator free
|
||||
43,300
|
||||
qr code maker
|
||||
52,000
|
||||
create a qr code
|
||||
17,100
|
||||
free qr code generator
|
||||
34,400
|
||||
qr generator
|
||||
25,300
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: qr code with logo
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
1,600
|
||||
Monthly Volume
|
||||
1,300
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
81%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Low Mobile
|
||||
Paid clicks
|
||||
Low
|
||||
0‑3%
|
||||
8%
|
||||
High
|
||||
13%+
|
||||
Difficulty
|
||||
48
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$0.00
|
||||
Monthly Cost
|
||||
$0.00
|
||||
Search Volume
|
||||
-
|
||||
Advertisers
|
||||
1
|
||||
Homepages
|
||||
25
|
||||
Fresh SV
|
||||
2,900
|
||||
Universal search in SERP
|
||||
291
|
||||
Similar keywords
|
||||
qr code generator with logo
|
||||
4,100
|
||||
qr code with logo
|
||||
1,600
|
||||
create qr code with logo
|
||||
440
|
||||
qr code generator with logo free
|
||||
400
|
||||
android studio qr code generator with logo
|
||||
300
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
17
|
||||
Questions
|
||||
How to make qr code with logo
|
||||
40
|
||||
How to design qr code with logo
|
||||
40
|
||||
How to create qr code with logo
|
||||
28
|
||||
How to make own qr code with logo
|
||||
24
|
||||
How to create your own qr code with logo
|
||||
24
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
37,452,720
|
||||
Also ranks for
|
||||
qr code maker
|
||||
52,000
|
||||
qr code generator free
|
||||
43,300
|
||||
create qr code
|
||||
29,500
|
||||
free qr code generator
|
||||
34,400
|
||||
create a qr code
|
||||
17,100
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Overview: spotify code generator
|
||||
View Cached Page
|
||||
|
||||
Create Report
|
||||
840
|
||||
Monthly Volume
|
||||
630
|
||||
Estimated Clicks
|
||||
Clicked any result
|
||||
Low
|
||||
76%
|
||||
High
|
||||
Mobile vs Desktop
|
||||
Mobile
|
||||
Desktop
|
||||
Not Enough Data
|
||||
Paid clicks
|
||||
Not Enough Data
|
||||
Difficulty
|
||||
21
|
||||
Google Provided Data
|
||||
|
||||
Expand
|
||||
Cost Per Click
|
||||
$0.00
|
||||
Monthly Cost
|
||||
$0.00
|
||||
Search Volume
|
||||
90
|
||||
Advertisers
|
||||
0
|
||||
Homepages
|
||||
5
|
||||
Fresh SV
|
||||
2,400
|
||||
Universal search in SERP
|
||||
106
|
||||
Similar keywords
|
||||
spotify code generator
|
||||
840
|
||||
spotify premium code generator no survey
|
||||
420
|
||||
spotify premium codes generator
|
||||
300
|
||||
spotify premium code generator no survey 2017
|
||||
290
|
||||
spotify code generator 2019
|
||||
290
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
5
|
||||
Questions
|
||||
How to generate spotify code
|
||||
90
|
||||
How to get spotify premium code free generator 2018
|
||||
70
|
||||
How to get code for spotify premium spotify premium free code generator
|
||||
24
|
||||
Where is spotify pin code generator?
|
||||
12
|
||||
How to generate a spotify code
|
||||
-
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
Log in to see all results
|
||||
--
|
||||
View All
|
||||
82,799,750
|
||||
Also ranks for
|
||||
qr code maker
|
||||
52,000
|
||||
spotify codes
|
||||
7,100
|
||||
spotify code
|
||||
5,700
|
||||
qrcode
|
||||
11,900
|
||||
create qr code
|
||||
29,500
|
||||
506
seo-strategy.md
Normal file
@@ -0,0 +1,506 @@
|
||||
## A) Executive summary (max 12 bullets)
|
||||
|
||||
* **Win fast (0–60 days)** by launching a *“wedge” set* of low-KD, high-intent tool pages (WhatsApp / Instagram / vCard / Bulk / PDF) + one differentiated feature hub (**QR Code Analytics + Tracking**) that every tool page upsells into.
|
||||
* **Build an intent ladder**: *Free generator → Dynamic QR → Tracking/Analytics → Bulk/API/Teams → Custom domains + integrations* (this mirrors how category leaders gate value). ([qr-code-generator.com][1])
|
||||
* **Exploit SERP splits**: head terms (“qr code generator”) are crowded with generalist tools (Canva/Adobe) + legacy generators, while **dynamic/tracking** queries skew toward SaaS platforms—your product sweet spot. ([qr-code-generator.com][1])
|
||||
* **Turn “Google QR Code Generator” into a capture page**: Google/Chrome already generates a basic QR for a URL; your angle is *“Chrome is static-only → here’s dynamic + analytics + UTM + campaign dashboards.”* ([Google Hilfe][2])
|
||||
* **Programmatic SEO (pSEO) is mandatory** in this space: competitors scale with templated “solutions” pages by QR type (vCard, WiFi, Spotify, Instagram, etc.). ([qr-code-generator.com][3])
|
||||
* **Avoid pSEO index bloat** with strict canonical + noindex rules and *minimum content thresholds* per template (examples below).
|
||||
* **Differentiate on trust**: QR scams (“quishing”) are rising; bake “safe redirect + link preview + scan security” into product messaging and content. ([Der Guardian][4])
|
||||
* **Make “Barcode Generator” a top-of-funnel traffic engine** (58k SV / KD 22 in your data) but route conversions toward QR analytics + dynamic capabilities; barcode SERPs are full of embed-only utilities and hardware vendors. ([Free Online Barcode Generator by TEC-IT][5])
|
||||
* **Ship IA early**: a scalable sitemap with `/tools/`, `/features/`, `/integrations/`, `/compare/`, `/learn/`, and `/templates/` prevents cannibalization and makes internal linking deterministic.
|
||||
* **Measure leading indicators**: indexation coverage, impressions, tool-page CVR to signup, activation (QR created), and upgrades (dynamic/tracking enabled).
|
||||
* **Link acquisition**: win with embed widgets, UTM/GA4 tracking guides, open-source SDKs, and directory placements (10 angles below).
|
||||
* **Assumptions used** (adjustable): **EN**, **Global/US focus**, **Freemium SaaS → subscription**, primary conversion **signup → generate → enable tracking**.
|
||||
|
||||
---
|
||||
|
||||
## B) Competitor landscape (top competitors + what they do best + weaknesses)
|
||||
|
||||
Below is a **SERP-driven** view of recurring domains across “QR code generator”, “dynamic QR”, “tracking/analytics”, and “type” queries (vCard/Instagram/Spotify/etc.):
|
||||
|
||||
### 1) QR Code Generator (Bitly) — `qr-code-generator.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Clear **feature ladder + gating** (static free → dynamic/analytics → bulk/API/teams). ([qr-code-generator.com][1])
|
||||
* Massive **“solutions” library** (SEO scale by QR type). ([qr-code-generator.com][3])
|
||||
**Weaknesses to exploit**
|
||||
* Heavy gating/upsell can frustrate “free” intent.
|
||||
* Many “solution” pages trend toward **marketing copy**—opportunity for deeper “how-to + templates + examples + tracking instrumentation”.
|
||||
|
||||
### 2) QRCode Monkey — `qrcode-monkey.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* “Free + design/customization” positioning; vectors/print talk resonates. ([QRCode Monkey][6])
|
||||
* Has an **API pitch** (some scaling). ([QRCode Monkey][7])
|
||||
**Weaknesses**
|
||||
* Less credible on analytics-first workflows; your advantage is *campaign measurement + dashboards*.
|
||||
|
||||
### 3) The QR Code Generator (TQRCG) — `the-qrcode-generator.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Trust messaging: “free means free” + warns about expiring codes. ([the-qrcode-generator.com][8])
|
||||
**Weaknesses**
|
||||
* Content often “how-to guide” oriented; you can outrank with **better tools + richer templates + integrations**.
|
||||
|
||||
### 4) Hovercode — `hovercode.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Product-led pages (“create now”) + “trackable QR codes” positioning. ([Hovercode][9])
|
||||
* pSEO via many generator variants (logo, circle, etc.). ([Hovercode][10])
|
||||
**Weaknesses**
|
||||
* Opportunity to beat them with **comparison pages + GA4 instrumentation + bulk workflows**.
|
||||
|
||||
### 5) Scanova — `scanova.io`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Strong **feature pages**: dynamic, tracking, security, landing pages (good enterprise pitch). ([Scanova][11])
|
||||
**Weaknesses**
|
||||
* Many blogs are long; you can win snippets with **structured templates + FAQs + exact steps + schema**.
|
||||
|
||||
### 6) Flowcode — `flowcode.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Owns “offline conversions + analytics” narrative (enterprise). ([flowcode.com][12])
|
||||
**Weaknesses**
|
||||
* Often skewed to demos; you can capture SMB/free intent and upgrade later.
|
||||
|
||||
### 7) QRCodeChimp — `qrcodechimp.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Huge template catalog (menus, forms, cards, etc.) + GA integration content. ([QR Code Chimp][13])
|
||||
**Weaknesses**
|
||||
* Template sprawl risks thin pages—beat them on **quality thresholds + tighter topical clusters**.
|
||||
|
||||
### 8) ME-QR — `me-qr.com`
|
||||
|
||||
**Best at**
|
||||
|
||||
* Aggressive pSEO for types (PDF/Instagram/WhatsApp/Spotify). ([me-qr.com][14])
|
||||
**Weaknesses**
|
||||
* Many pages feel commodity; you can differentiate with **better UX + security + analytics clarity**.
|
||||
|
||||
### 9) Canva / Adobe Express (generalists)
|
||||
|
||||
* Canva and Adobe rank on “free QR code generator” intent via ecosystem pull. ([Canva][15])
|
||||
**Your play**: don’t “out-brand” them—**out-specialize** on dynamic/tracking/bulk/API and win long-tail + mid-tail.
|
||||
|
||||
### 10) Barcode generators (for your “Barcode Generator” gold mine)
|
||||
|
||||
* TEC-IT (embed + backlink requirement) and Barcodes Inc (hardware upsell). ([Free Online Barcode Generator by TEC-IT][5])
|
||||
**Your play**: best-in-class UX + formats + bulk + API docs + “barcode vs QR” education to route users into QR analytics.
|
||||
|
||||
---
|
||||
|
||||
## C) Keyword clusters + priority order (explain why)
|
||||
|
||||
### Intent model (how to cluster)
|
||||
|
||||
* **Do / Generate (tool intent)**: “X QR code generator”, “bulk”, “PDF to QR”, “WiFi QR”, “Instagram QR”, “WhatsApp QR”.
|
||||
* **Decide (commercial investigation)**: “dynamic vs static”, “trackable QR codes”, “best QR code generator”, “QR code analytics”.
|
||||
* **Implement (technical)**: “QR code API”, “track QR codes in GA4”, “UTM QR code”, “bulk QR from CSV / Sheets”.
|
||||
* **Navigate (platform-native)**: “Google QR code generator”, “Spotify code generator”, “Instagram QR code”.
|
||||
|
||||
### Priority ladder (P0 → P2)
|
||||
|
||||
**P0 (launch first; fastest to rank + high upsell value)**
|
||||
|
||||
1. **WhatsApp QR Code Generator** (SV 180 / KD 17 in your list) → high intent + low KD + SMB conversion path.
|
||||
2. **Instagram QR Code Generator** (SV 440 / KD 23) → same logic + add “IG has native QR; here’s branded + tracked campaigns”. ([Instagram Hilfe][16])
|
||||
3. **vCard QR Code Generator** (SV 180 / KD 24) → business use case; great signup driver.
|
||||
4. **QR Code Analytics** (SV 135 / KD 24) → *your core differentiator*; becomes the internal-link destination from every tool page.
|
||||
5. **Trackable QR Codes** (SV 135 / KD 0) → perfect wedge term; map to a commercial page that demonstrates tracking dashboard and “dynamic”.
|
||||
6. **Barcode Generator** (58k / KD 22) → big traffic engine; route to QR features + analytics.
|
||||
|
||||
**P1 (build authority + revenue features)**
|
||||
|
||||
* **Bulk QR Code Generator** (SV 360 / KD 33)
|
||||
* **QR Code Tracking** (SV 320 / KD 37) (map carefully vs “analytics”)
|
||||
* **WiFi QR Code Generator** (SV 1,400 / KD 34)
|
||||
* **PDF to QR Code Generator** (SV ~260 / KD 36, CPC high)
|
||||
* **Google QR Code Generator** (SV 8k) (capture via “Chrome static QR” + upsell). ([Google Hilfe][2])
|
||||
|
||||
**P2 (long-term mid/high competition)**
|
||||
|
||||
* **Dynamic QR Code Generator** (SV 1,200 / KD 43)
|
||||
* **Free QR Code Generator** (SV 34,400 / KD 34)
|
||||
* **QR code maker** (SV 52k / KD 47)
|
||||
* **QR Code Generator** (SV 370k) — pillar target supported by everything above.
|
||||
|
||||
### Cannibalization rule (critical)
|
||||
|
||||
* **One primary intent per page.** Example mapping:
|
||||
|
||||
* `/features/qr-code-analytics/` = “qr code analytics” (feature/commercial)
|
||||
* `/learn/qr-code-tracking/` = “qr code tracking” (educational/how it works + GA4)
|
||||
* `/tools/trackable-qr-code-generator/` = “trackable qr codes” (tool + demo dashboard)
|
||||
|
||||
---
|
||||
|
||||
## D) Recommended sitemap / IA (with URL examples)
|
||||
|
||||
### Core structure (scalable + pSEO-safe)
|
||||
|
||||
**1) Tools (transactional)**
|
||||
|
||||
* `/qr-code-generator/` (core tool hub, not a blog post)
|
||||
* `/tools/vcard-qr-code-generator/`
|
||||
* `/tools/whatsapp-qr-code-generator/`
|
||||
* `/tools/instagram-qr-code-generator/`
|
||||
* `/tools/wifi-qr-code-generator/`
|
||||
* `/tools/pdf-to-qr-code-generator/`
|
||||
* `/tools/bulk-qr-code-generator/`
|
||||
* `/barcode-generator/` (separate category; include QR/2D + 1D)
|
||||
|
||||
**2) Features (commercial)**
|
||||
|
||||
* `/features/dynamic-qr-codes/`
|
||||
* `/features/qr-code-analytics/`
|
||||
* `/features/qr-code-campaigns/` (folders, tags, exports)
|
||||
* `/features/custom-domain/`
|
||||
* `/features/teams-roles/`
|
||||
* `/features/security-anti-phishing/` (trust wedge; see “quishing”). ([Der Guardian][4])
|
||||
|
||||
**3) Integrations (high-intent + linkable)**
|
||||
|
||||
* `/integrations/google-analytics-4/`
|
||||
* `/integrations/hubspot/`
|
||||
* `/integrations/zapier/`
|
||||
* `/integrations/shopify/`
|
||||
(Ship GA4 first; it supports your “tracking” narrative.)
|
||||
|
||||
**4) Learn Hub (educational; supports rankings + conversions)**
|
||||
|
||||
* `/learn/dynamic-vs-static-qr-codes/`
|
||||
* `/learn/how-to-track-qr-codes-in-ga4/`
|
||||
* `/learn/qr-code-size-guide/`
|
||||
* `/learn/qr-code-error-correction/`
|
||||
* `/learn/google-qr-code-generator/` (Chrome’s built-in QR + limitations). ([Google Hilfe][2])
|
||||
* `/learn/spotify-code-generator/` (Spotify Codes explainer + CTA to your tool). ([SpotifyCodes][17])
|
||||
|
||||
**5) Templates / Use cases (pSEO with guardrails)**
|
||||
|
||||
* `/templates/restaurant-menu-qr/`
|
||||
* `/templates/business-card-qr/`
|
||||
* `/templates/event-check-in-qr/`
|
||||
Each template must include: examples, copy/paste CTAs, recommended QR type, tracking setup, and links to the tool.
|
||||
|
||||
### Breadcrumb + internal linking rules (hub-and-spoke)
|
||||
|
||||
* **Tool pages** link up to:
|
||||
|
||||
* `/features/qr-code-analytics/`
|
||||
* `/features/dynamic-qr-codes/`
|
||||
* `/learn/dynamic-vs-static-qr-codes/`
|
||||
* the **closest** templates + GA4 integration (where relevant)
|
||||
* **Learn pages** link down to:
|
||||
|
||||
* the *single best-matching tool page* (primary CTA)
|
||||
* 2–4 related learn pages (cluster reinforcement)
|
||||
* **Integrations** link to:
|
||||
|
||||
* analytics feature + tracking learn guide + relevant tool pages
|
||||
|
||||
---
|
||||
|
||||
## E) “Wedge” plan: what to launch first to rank within 30–60 days
|
||||
|
||||
### Launch set (minimum viable topical authority)
|
||||
|
||||
**Week 1–3 shipping goal: 8 pages that create a ranking flywheel**
|
||||
|
||||
**Tool pages (P0)**
|
||||
|
||||
1. `/tools/whatsapp-qr-code-generator/` (KD 17)
|
||||
2. `/tools/instagram-qr-code-generator/` (KD 23)
|
||||
3. `/tools/vcard-qr-code-generator/` (KD 24)
|
||||
4. `/tools/trackable-qr-code-generator/` (KD 0 term → commercial wedge)
|
||||
5. `/barcode-generator/` (traffic engine)
|
||||
|
||||
**Feature + Learn pages (conversion + trust)**
|
||||
6) `/features/qr-code-analytics/` (your core differentiator)
|
||||
7) `/learn/dynamic-vs-static-qr-codes/` (decision content)
|
||||
8) `/learn/google-qr-code-generator/` (steal “Google/Chrome” demand; Chrome is static URL sharing). ([Google Hilfe][2])
|
||||
|
||||
### Why this ranks fast on a new domain
|
||||
|
||||
* Low-KD type terms are less “brand dominated” than head terms.
|
||||
* Every tool page naturally links to analytics + dynamic, so **internal PageRank concentrates** on your money features.
|
||||
* “Google QR code generator” content can win featured snippets because it’s step-based and grounded in official Chrome documentation. ([Google Hilfe][2])
|
||||
|
||||
---
|
||||
|
||||
## F) 90-day execution roadmap (week-by-week)
|
||||
|
||||
### Weeks 1–2: Foundations (technical + tracking + SEO hygiene)
|
||||
|
||||
* **Tech SEO**
|
||||
|
||||
* Set up GSC + GA4 (or PostHog) + server-side event pipeline for “QR created / downloaded / scan events”.
|
||||
* Define **indexation policy**: which templates get indexed, which are noindex.
|
||||
* Implement: XML sitemaps by type (`/sitemap-tools.xml`, `/sitemap-learn.xml`), robots, canonicals, hreflang plan (even if EN-only now).
|
||||
* **Schema baseline**
|
||||
|
||||
* Organization, WebSite, BreadcrumbList sitewide.
|
||||
* SoftwareApplication/WebApplication on core tool hub.
|
||||
* **Information architecture**
|
||||
|
||||
* Ship nav for Tools / Features / Learn / Pricing / API.
|
||||
|
||||
### Week 3: Ship the wedge tool pages (P0)
|
||||
|
||||
* Publish WhatsApp / Instagram / vCard / Trackable tool pages.
|
||||
* Each ships with: FAQ, examples, “Static vs Dynamic” block, “Enable analytics” CTA, and internal links to `/features/qr-code-analytics/`.
|
||||
|
||||
### Week 4: Ship the analytics feature hub + dynamic feature hub
|
||||
|
||||
* `/features/qr-code-analytics/` + `/features/dynamic-qr-codes/`
|
||||
* Add product screenshots/GIFs and a simple “How tracking works” diagram (dynamic redirect → logging → dashboard).
|
||||
|
||||
### Week 5: Learn cluster for decision + “Google QR”
|
||||
|
||||
* `/learn/dynamic-vs-static-qr-codes/`
|
||||
* `/learn/google-qr-code-generator/` (include “Chrome creates QR for a page” and limitations). ([Google Hilfe][2])
|
||||
|
||||
### Week 6: Barcode Generator tool + “Barcode vs QR” guide
|
||||
|
||||
* Launch `/barcode-generator/` + `/learn/barcode-vs-qr-code/` to route barcode traffic into QR use cases.
|
||||
* Add bulk export formats and “print quality” section to compete with incumbents. ([Free Online Barcode Generator by TEC-IT][5])
|
||||
|
||||
### Week 7: Bulk + PDF tools (P1)
|
||||
|
||||
* `/tools/bulk-qr-code-generator/` (CSV upload; align with SERP expectations like “download ZIP”). ([quickchart.io][18])
|
||||
* `/tools/pdf-to-qr-code-generator/` (CPC-heavy query → strong conversion)
|
||||
|
||||
### Week 8: GA4 integration page (linkable asset)
|
||||
|
||||
* `/integrations/google-analytics-4/`
|
||||
* Companion guide: `/learn/how-to-track-qr-codes-in-ga4/` (UTMs, events, attribution).
|
||||
|
||||
### Week 9: Authority pieces (start the pillar support)
|
||||
|
||||
Publish 2 of these 5 (see section below):
|
||||
|
||||
* “QR Code Size Guide”
|
||||
* “QR Code Error Correction Explained”
|
||||
* “UTM Builder for QR Campaigns”
|
||||
* “QR Code Security / Quishing Prevention”
|
||||
* “QR Code Analytics Benchmarks”
|
||||
|
||||
### Week 10: pSEO expansion (controlled)
|
||||
|
||||
* Add 10–20 additional `/tools/{type}/` pages (WiFi, email, SMS, etc.) only if they meet your thin-content threshold.
|
||||
* Add 10–20 `/templates/` pages tied to real use cases.
|
||||
|
||||
### Week 11: Comparisons (conversion-focused)
|
||||
|
||||
* `/compare/qr-code-generator-vs-canva/`
|
||||
* `/compare/qr-code-generator-vs-qrcode-monkey/`
|
||||
* `/compare/dynamic-qr-code-generators/` (listicle with your wedge terms)
|
||||
|
||||
### Week 12–13: Iterate based on GSC data
|
||||
|
||||
* Optimize pages with impressions but low CTR (titles/meta).
|
||||
* Expand FAQs to match PAA.
|
||||
* Strengthen internal links from high-impression pages to money pages.
|
||||
|
||||
---
|
||||
|
||||
## G) Page briefs for the top 5 money pages (H1, sections, schema, CTA, internal links)
|
||||
|
||||
### 1) Dynamic QR Code Generator
|
||||
|
||||
**URL:** `/features/dynamic-qr-codes/` (feature) + optional `/tools/dynamic-qr-code-generator/` (tool demo)
|
||||
**Primary keyword:** dynamic qr code generator
|
||||
**H1:** Dynamic QR Code Generator (Editable + Trackable)
|
||||
**Sections (order matters)**
|
||||
|
||||
* What is a dynamic QR code? (vs static)
|
||||
* Edit destination after printing (URL, file, page)
|
||||
* Tracking/analytics overview (scans, time, location, device)
|
||||
* Use cases (menus, flyers, events, packaging)
|
||||
* How it works (redirect + logging)
|
||||
* Pricing preview + free tier
|
||||
* FAQ (Do they expire? Can I change the URL? Can I export data?)
|
||||
**Schema**
|
||||
* FAQPage
|
||||
* SoftwareApplication (or WebApplication)
|
||||
* BreadcrumbList
|
||||
**Primary CTA**
|
||||
* “Create a dynamic QR code” (signup)
|
||||
**Internal links**
|
||||
* To `/features/qr-code-analytics/`, `/learn/dynamic-vs-static-qr-codes/`, `/integrations/google-analytics-4/`
|
||||
|
||||
> Competitor pattern to beat: strong gating + feature ladder is common. ([qr-code-generator.com][1])
|
||||
|
||||
---
|
||||
|
||||
### 2) QR Code Analytics
|
||||
|
||||
**URL:** `/features/qr-code-analytics/`
|
||||
**Primary keyword:** qr code analytics
|
||||
**H1:** QR Code Analytics: Track Scans, Measure Campaign ROI
|
||||
**Sections**
|
||||
|
||||
* What you can measure (total/unique scans, geo, device, time)
|
||||
* Campaign organization (folders/tags, UTM conventions)
|
||||
* Export + integrations (GA4 first)
|
||||
* Dashboards (examples: restaurant menu, event check-in, retail)
|
||||
* Data accuracy & privacy notes
|
||||
* FAQ (“Can I track a static QR?” → explain dynamic requirement)
|
||||
**Schema**
|
||||
* FAQPage
|
||||
* SoftwareApplication
|
||||
* BreadcrumbList
|
||||
**CTA**
|
||||
* “Enable analytics on your QR code” (upgrade nudges)
|
||||
**Internal links**
|
||||
* From **every tool page** (sticky sidebar “Track scans with Analytics”)
|
||||
* To `/learn/how-to-track-qr-codes-in-ga4/`
|
||||
|
||||
> This is exactly what SaaS competitors highlight for upsell. ([flowcode.com][12])
|
||||
|
||||
---
|
||||
|
||||
### 3) Bulk QR Code Generator
|
||||
|
||||
**URL:** `/tools/bulk-qr-code-generator/`
|
||||
**Primary keyword:** bulk qr code generator
|
||||
**H1:** Bulk QR Code Generator (CSV Upload → Download ZIP)
|
||||
**Sections**
|
||||
|
||||
* Upload CSV / paste data / Google Sheets import (later)
|
||||
* Output formats (PNG/SVG/PDF), naming conventions
|
||||
* Dynamic vs static toggle per row (upsell!)
|
||||
* Common workflows: inventory labels, invites, coupons
|
||||
* QA: scan testing, error correction, print sizing
|
||||
* FAQ
|
||||
**Schema**
|
||||
* FAQPage
|
||||
* HowTo (only if you include step-by-step with images)
|
||||
**CTA**
|
||||
* “Generate bulk QR codes” + secondary “Enable tracking for all”
|
||||
**Internal links**
|
||||
* To `/features/qr-code-analytics/` + `/features/dynamic-qr-codes/`
|
||||
|
||||
> SERPs often expect “free bulk + zip”; match that intent. ([QR Explore][19])
|
||||
|
||||
---
|
||||
|
||||
### 4) vCard QR Code Generator
|
||||
|
||||
**URL:** `/tools/vcard-qr-code-generator/`
|
||||
**Primary keyword:** vCard qr code generator
|
||||
**H1:** vCard QR Code Generator (Digital Business Card)
|
||||
**Sections**
|
||||
|
||||
* vCard fields + preview (VCF standard)
|
||||
* iOS/Android compatibility + best practices
|
||||
* Static vs dynamic vCard (edit contact later)
|
||||
* Examples: sales reps, events, storefront QR
|
||||
* CTA: “Add scan tracking to your business cards”
|
||||
* FAQ (works on Android/iOS; does it expire; can I add photo; etc.)
|
||||
**Schema**
|
||||
* FAQPage
|
||||
* SoftwareApplication
|
||||
**CTA**
|
||||
* “Create vCard QR” + upsell “Track scans / update later”
|
||||
**Internal links**
|
||||
* To `/learn/dynamic-vs-static-qr-codes/` + analytics feature
|
||||
|
||||
---
|
||||
|
||||
### 5) QR Code API (developer money page)
|
||||
|
||||
**URL:** `/features/qr-code-api/` + `/docs/api/`
|
||||
**Primary keyword:** qr code api, qr code generator api
|
||||
**H1:** QR Code API (Generate QR Codes at Scale)
|
||||
**Sections**
|
||||
|
||||
* Authentication, endpoints, rate limits
|
||||
* Generate static/dynamic, bulk endpoints, webhooks (scan events)
|
||||
* Code samples (JS/Python/cURL)
|
||||
* Compliance + uptime
|
||||
* Pricing tiers
|
||||
**Schema**
|
||||
* SoftwareApplication (feature page)
|
||||
* TechArticle (docs pages)
|
||||
**CTA**
|
||||
* “Get API key” / “Start trial”
|
||||
**Internal links**
|
||||
* From bulk generator + analytics pages
|
||||
|
||||
---
|
||||
|
||||
## H) Risks + mitigation (cannibalization, programmatic pitfalls, E-E-A-T, index bloat)
|
||||
|
||||
### 1) Keyword cannibalization (very likely in this niche)
|
||||
|
||||
**Risk:** “qr code tracking”, “trackable qr codes”, “qr code analytics” collapse into the same intent.
|
||||
**Mitigation:** hard-map intents:
|
||||
|
||||
* Analytics = feature/commercial
|
||||
* Tracking = learn/how-to + GA4
|
||||
* Trackable QR = tool landing with demo dashboard
|
||||
|
||||
### 2) Programmatic SEO thin pages / index bloat
|
||||
|
||||
**Risk:** hundreds of near-identical “{type} QR generator” pages get ignored/deindexed.
|
||||
**Mitigation (hard rules)**
|
||||
|
||||
* Index only pages that include **unique elements**:
|
||||
|
||||
* type-specific fields + validation (real tool)
|
||||
* 2–3 examples
|
||||
* type-specific FAQs
|
||||
* type-specific tracking use case
|
||||
* **Noindex**: parameter pages, empty states, duplicate locale stubs, search/filter pages.
|
||||
|
||||
### 3) Trust & QR scam concerns (reputation risk, but also opportunity)
|
||||
|
||||
**Risk:** Users fear scanning QR codes; Google may reward safety content.
|
||||
**Mitigation:** ship “Security” feature page + learn content about safe scanning and link previews, referencing real-world scam patterns. ([Der Guardian][4])
|
||||
|
||||
### 4) Over-reliance on “Google QR Code Generator” traffic
|
||||
|
||||
**Risk:** users only want Chrome’s built-in static QR and bounce.
|
||||
**Mitigation:** page structure: “How to do it in Chrome” (satisfy intent) → “When you need dynamic + analytics” (convert). ([Google Hilfe][2])
|
||||
|
||||
### 5) E-E-A-T gap vs incumbents
|
||||
|
||||
**Risk:** new domain lacks credibility.
|
||||
**Mitigation**
|
||||
|
||||
* Publish 2–3 “benchmarks / research” assets with original data (even small): scan-rate benchmarks, print-size testing, or campaign case studies.
|
||||
* Add transparent pricing, uptime, privacy policy, and author/editor pages for Learn content.
|
||||
|
||||
---
|
||||
|
||||
If you tell me your **target market (US vs DACH vs global), language (EN/DE), and monetization (freemium vs trials)**, I can *tighten the sitemap + 90-day calendar* so it perfectly matches your rollout (especially internationalization + URL strategy).
|
||||
|
||||
[1]: https://www.qr-code-generator.com/?utm_source=chatgpt.com "QR Code Generator | Create Your Free QR Codes"
|
||||
[2]: https://support.google.com/chrome/answer/10051760?co=GENIE.Platform%3DDesktop&hl=en&utm_source=chatgpt.com "Share pages in Chrome - Computer"
|
||||
[3]: https://www.qr-code-generator.com/solutions/?utm_source=chatgpt.com "QR Code Solution for Every Purpose"
|
||||
[4]: https://www.theguardian.com/money/2025/may/25/qr-code-scam-what-is-quishing-drivers-app-phone-parking-payment?utm_source=chatgpt.com "'Pay here': the QR code 'quishing' scam targeting drivers"
|
||||
[5]: https://barcode.tec-it.com/en?utm_source=chatgpt.com "Free Online Barcode Generator: Create Barcodes for Free!"
|
||||
[6]: https://www.qrcode-monkey.com/?utm_source=chatgpt.com "QRCode Monkey - The free QR Code Generator to create ..."
|
||||
[7]: https://www.qrcode-monkey.com/de/qr-code-service/?utm_source=chatgpt.com "QR Code API for Static Codes"
|
||||
[8]: https://www.the-qrcode-generator.com/?utm_source=chatgpt.com "The QR Code Generator (TQRCG): Create Free QR Codes"
|
||||
[9]: https://hovercode.com/?utm_source=chatgpt.com "QR Code Generator | Create Free Dynamic QR Codes"
|
||||
[10]: https://hovercode.com/circle-qr-code-generator/?utm_source=chatgpt.com "Generate circle QR codes (no sign up required)"
|
||||
[11]: https://scanova.io/features/?utm_source=chatgpt.com "Powerful features for all QR Code use cases"
|
||||
[12]: https://www.flowcode.com/product/analytics?utm_source=chatgpt.com "Gain insight into your offline marketing with in-depth Analytics"
|
||||
[13]: https://www.qrcodechimp.com/qr-code-analytics-guide/?utm_source=chatgpt.com "QR Code Analytics: Track, Analyze & Optimize Your ..."
|
||||
[14]: https://me-qr.com/qr-code-generator/pdf?srsltid=AfmBOooK1o7kkjaSizlEOWcEcYcDWfKhZuuM3XvrJGQlm2xdiTbw1exS&utm_source=chatgpt.com "Create QR Code For PDF FREE"
|
||||
[15]: https://www.canva.com/qr-code-generator/?utm_source=chatgpt.com "Free QR Code Generator - Create QR codes with ease"
|
||||
[16]: https://help.instagram.com/925529167647849/?utm_source=chatgpt.com "Find and customize the QR code of your Instagram profile"
|
||||
[17]: https://www.spotifycodes.com/?utm_source=chatgpt.com "Spotify Codes"
|
||||
[18]: https://quickchart.io/bulk-qr-code-generator/?utm_source=chatgpt.com "Bulk QR Code Generator | Custom colors and logo, free"
|
||||
[19]: https://qrexplore.com/generate/?utm_source=chatgpt.com "Bulk QR Code Generator"
|
||||
156
seo_2026_jan.md
Normal file
@@ -0,0 +1,156 @@
|
||||
SEO Opportunity Report & Implementation Plan (Jan 2026)
|
||||
1. Executive Summary
|
||||
An analysis of the provided Google Keyword Planner data (Jan 22, 2026) reveals significant low-competition, high-volume traffic opportunities that were previously untapped. We have immediately capitalized on the Barcode opportunity and have a clear path to capture Custom QR intent.
|
||||
|
||||
2. Key Data Findings ("Hidden Gems")
|
||||
We identified three specific clusters where search volume is high but competition is exceptionally low.
|
||||
|
||||
A. The "QR Barcode" Anomaly (Gold Mine) 🏆
|
||||
Users are confused about the terminology, searching for "qr barcode" or "bar code generator" instead of just "barcode".
|
||||
|
||||
Keywords: qr barcode, bar code generator, scan code generator
|
||||
Volume: 10k – 100k (High)
|
||||
Competition: Low / Medium
|
||||
Opportunity: Most competitors optimize for "Barcode Generator". By targeting the "wrong" terms users actually type, we can win easy traffic.
|
||||
B. The "Free" Intent
|
||||
High volume, but users are specifically looking for "free" and "no signup".
|
||||
|
||||
Keyword: free qr code generator (100k – 1M)
|
||||
Keyword: qr code generator free (100k – 1M)
|
||||
Opportunity: Aggressive targeting of these exact match phrases on the homepage metadata.
|
||||
C. The "Custom" Gap
|
||||
Users want customization but don't always use the term "design".
|
||||
|
||||
Keyword: custom qr code generator
|
||||
Volume: 1k – 10k
|
||||
Competition: Low
|
||||
Current Status: MISSING. We do not have a dedicated landing page for this high-intent cluster.
|
||||
3. Actions Already Implemented ✅
|
||||
We have immediately updated the metadata to capture the traffic identified in findings A and B.
|
||||
|
||||
1. Barcode Generator Optimization
|
||||
File:
|
||||
src/app/(marketing)/tools/barcode-generator/page.tsx
|
||||
|
||||
Action: Updated <title> and meta description.
|
||||
New Target: "QR Barcode" and "Bar Code Generator".
|
||||
Why: To capture the 100k+ users searching for these specific variants.
|
||||
2. Homepage Optimization
|
||||
File:
|
||||
src/app/(marketing)/page.tsx
|
||||
|
||||
Action: Injected high-volume keyword tags.
|
||||
New Target: qr generator, free qr code generator, custom qr code generator.
|
||||
Why: To signal relevance to Google for the broadest "head terms".
|
||||
4. Implementation Plan: "Custom QR Code" Landing Page 🚀
|
||||
To capture the 1k–10k/month users searching for "custom qr code generator" (Finding C), we need a dedicated landing page. This page will focus on design features (colors, logos, frames) rather than just "generating" a code.
|
||||
|
||||
Phase 1: Page Structure (New File)
|
||||
Path: src/app/(marketing)/tools/custom-qr-code-generator/page.tsx
|
||||
|
||||
Content Strategy:
|
||||
|
||||
H1: "Free Custom QR Code Generator with Logo & Colors"
|
||||
Hero: Visual emphasis on beautiful codes, not black-and-white ones.
|
||||
Live Editor Demo: (Reuse existing QRCodeGeneratorClient but pre-opened "Design" tab).
|
||||
Features Section:
|
||||
"Add Your Logo"
|
||||
"Custom Colors & Gradients"
|
||||
"Unique Frames & Shapes"
|
||||
FAQ: Targeting specific "custom" questions (vector formats, scanning reliability).
|
||||
Phase 2: Internal Linking
|
||||
Add link to /tools/custom-qr-code-generator in the Footer and Main Navigation under "Tools".
|
||||
Link from Homepage "Features" section.
|
||||
Phase 3: Metadata Strategy
|
||||
title: 'Custom QR Code Generator – Add Logo, Colors & Frames',
|
||||
description: 'Create unique custom QR codes with your logo, colors, and frames. Free online customizer with high-quality vector download (SVG/PNG).',
|
||||
keywords: ['custom qr code generator', 'qr code with logo', 'qr code design', 'creative qr code', 'branded qr code']
|
||||
5. Next Steps
|
||||
Approval: Confirm this plan to build the custom-qr-code-generator page.
|
||||
Execution: I will create the directory and page file.
|
||||
Verify: Check that the new page renders and is linked correctly.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
new findings
|
||||
|
||||
Das ist super, dass du die Dateien hochgeladen hast! Denn: Du hast die Daten bereits.
|
||||
|
||||
Ich habe deine Excel-Listen analysiert und das Problem gefunden: In deiner Ansicht im Tool hieß die Spalte vielleicht anders oder war versteckt, aber in der Datei heißt sie Avg. monthly searches. Das ist exakt das Suchvolumen, das du gesucht hast.
|
||||
|
||||
Hier sind die echten "Schätze" direkt aus deiner Datei, gefiltert nach meiner Strategie:
|
||||
|
||||
1. Die "Low Hanging Fruits" (Viel Traffic, fast 0 Konkurrenz)
|
||||
Das sind die Keywords, bei denen du SOFORT ranken kannst, weil die großen Player schlafen.
|
||||
|
||||
call qr code
|
||||
|
||||
Suchvolumen: 50.000 (!)
|
||||
|
||||
Wettbewerb-Index: 0 (Das ist extrem selten! Absolut gar keine Konkurrenz auf Google Ads).
|
||||
|
||||
Idee: Mach eine Seite "Telefon-QR-Code erstellen". Leute wollen, dass beim Scannen direkt eine Nummer gewählt wird. Das ist eine riesige Nische.
|
||||
|
||||
generate website qr code
|
||||
|
||||
Suchvolumen: 50.000
|
||||
|
||||
Wettbewerb-Index: 17 (Sehr gering).
|
||||
|
||||
CPC: 6,16 € (Das ist der Jackpot: Viel Traffic, wenig Konkurrenz UND hoher Wert).
|
||||
|
||||
bar code generator
|
||||
|
||||
Suchvolumen: 500.000
|
||||
|
||||
Wettbewerb-Index: 3 (Fast nichts).
|
||||
|
||||
Bestätigt meine frühere Analyse: Das ist dein Traffic-Bringer.
|
||||
|
||||
2. Die "Goldminen" (Hoher Klickpreis / High Ticket)
|
||||
Hier zahlen Firmen richtig Geld. Wenn du hier rankst, sind die Nutzer sehr wertvoll.
|
||||
|
||||
manage qr codes
|
||||
|
||||
CPC (Gebot): 18,77 €
|
||||
|
||||
Das suchen Firmen, die viele Codes verwalten müssen (Agenturen, Großkunden). Perfekt für dein "Pro"-Abo.
|
||||
|
||||
dynamic code generator
|
||||
|
||||
CPC: 15,62 €
|
||||
|
||||
Das Kern-Keyword für dein Business-Modell.
|
||||
|
||||
qr tracking
|
||||
|
||||
CPC: 12,51 €
|
||||
|
||||
Wettbewerb ist hier höher, aber der Umsatz pro Kunde auch.
|
||||
|
||||
3. Der Trend-Tipp (+900% Wachstum)
|
||||
free bulk qr code generator (Massen-Erstellung)
|
||||
|
||||
Trend: +900%
|
||||
|
||||
Viele Leute suchen nach einer Möglichkeit, 100 Codes auf einmal zu erstellen (z.B. für Mitarbeiterausweise oder Inventar). Wenn du so ein Feature ("Bulk Upload") hast oder baust, bist du Marktführer.
|
||||
|
||||
Was du jetzt tun musst (in Excel oder Google Sheets):
|
||||
Du brauchst das Tool gar nicht mehr zwingend. Du kannst einfach in deiner Datei filtern:
|
||||
|
||||
Such die Spalte Avg. monthly searches -> Das ist dein Suchvolumen.
|
||||
|
||||
Such die Spalte Competition (indexed value) -> Das ist die Härte (0-100).
|
||||
|
||||
Filter: Avg. monthly searches > 1000 UND Competition (indexed value) < 20.
|
||||
|
||||
Damit findest du genau die oben genannten Chancen. Soll ich dir für eines dieser Keywords (z.B. den "Call QR Code" mit Index 0) eine Gliederung schreiben?
|
||||
13
service_account.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "gen-lang-client-0595806638",
|
||||
"private_key_id": "e44bc1717f1cf413521149de272bf13bfa89a336",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0xJkozHODpcpD\nu3dTHPfprZk6eKiOT05h+uG8Clm8i8LLaS/eHT+B02qxFYMBX0VH9O2GvPp/VnfC\nB/Clc7bofN5VDpQMjVUiPDqMbUVEAiQHNOTp9pkfJltaHAl/J5Cc/DccCaOn89xT\nFD5b7dTn29suuBZHTqsaFDlydnU2xJAwcrWBm7/A0JZM85d76yhY0Jxcg9w8XlpE\n+TWN8OxSUIfubaac0mfI40RH2EfugmA7M45t7B3sEbmHk5tVQSItvncz2ls9fUE4\neB6u4foMFp4Z9k5Ejs7y4N3Yft0JWS+RjI0bcvvvQ/wcnDfcwCdDFFn2Y+hflKMm\nS9+ZRnmBAgMBAAECggEAAztAeo3JifZD3nzEUcDte9cHgN7AMtlJ3Wvc7va5Sw50\nizkCmSlwPoc4/0MvoMo0+701JVxbenXveMpEb3fZMoszkdU9U9iPZCfzB4wQErOa\nppuprbbOXtO9JzZVinWzflPSIUVK16lUVvYVrmfpHYou1G/dIMIXQkVsD7NR9t/B\nafD0w/q1nwwyPB08BjSemKXDQo6NF0cE/TIvaMj8vtxuouAL+fea0n/XxMQNoIoJ\nF+pJtPQ1hkQrpayzuj3smQ11PFpYuvsZHuS3dG9j4gPjGClezK3Sflt7vwNywIRc\ntJ0Qx58on0dy0YnppMWrHh/nykraVLusvMI04joqwQKBgQDlE1Mbi8dpeKn7zkV9\nLS/O6S5Ql2k2G6KxI8GHn3qxB5yfU8G2xqk64r04YB6SMCXscIQu1Tmro8kDMTZk\n5b/issH3+7uqGcJMYhZczWsjax3S1ugepXt29dF26VnbyfvD7h9qleKLhIq32z9P\nxzZGhptTCa0swypi7prNE0MhZwKBgQDKA75g8UhVULA6q3hFEG+24ICd3Gekdz1y\nmaDrPjSJmeMSUlDl4QhGRbZBSJcAfcFKk4+Nme3sTYvjMMz6per4a5TC/+IlSufm\nOSL+CSVijvVYwCMyLyiAcm5Pqcjw16S6enHIidnOYP8e8OM0H2aNKfFTKq30B3ww\nAF8ipa+01wKBgQC24JaYhx7LtOj/fc08AbcJGF9BN59m8ukPQdxeyZLJgaooCFW9\n9RtlR16IgzPkwUuFVs4wFUnVHQx83+zs3/4wnUT9FJrdUXMsR6JStCu0Ou+0Qp1M\n2g+XCOgQZnq2XKoB4ThzfvU9LLMR1JbWudM6unuF71OxSJ2uHY636YjOQQKBgBs6\n+fSTUY6+e6LM7j9RAd4C0RN2XDodIJlMABb1oZtStPsJQYJbHQRr7S9Lm58jVGS7\nE0ShFSMfKNYNA/RdXRjzV3AZkeA5Ap1T4lWf4fwxDP1TmOrw1GLMCfaPClj8mGXS\nj3farRNWm80N53JlMSuiFbeCL0SPpbvKsQg4kUCtAoGAUORyhW70nhZJ1BbmvyRf\n17fcwenK/3GmWgqsrzN7/ucPwjqIzLGVoAXd2euxpE49/VW2xYpJjyHJHuoXDc66\n+AUog0bsxcKpM5tL3VelQl3SkUlCG7jYe20rMm01y35uM2REvQv3/r9F7Bbaq/9n\nSCwu/45QobgLCUx0B7wDqWA=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "indexer@gen-lang-client-0595806638.iam.gserviceaccount.com",
|
||||
"client_id": "111279247752160222047",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/indexer%40gen-lang-client-0595806638.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
|
||||
export default function EditQRPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const qrId = params.id as string;
|
||||
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [qrCode, setQrCode] = useState<any>(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchQRCode = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/qrs/${qrId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setQrCode(data);
|
||||
setTitle(data.title);
|
||||
setContent(data.content || {});
|
||||
} else {
|
||||
showToast('Failed to load QR code', 'error');
|
||||
router.push('/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching QR code:', error);
|
||||
showToast('Failed to load QR code', 'error');
|
||||
router.push('/dashboard');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchQRCode();
|
||||
}, [qrId, router]);
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
|
||||
try {
|
||||
const response = await fetchWithCsrf(`/api/qrs/${qrId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
content,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast('QR code updated successfully!', 'success');
|
||||
router.push('/dashboard');
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showToast(error.error || 'Failed to update QR code', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating QR code:', error);
|
||||
showToast('Failed to update QR code', 'error');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading QR code...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!qrCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Static QR codes cannot be edited
|
||||
if (qrCode.type === 'STATIC') {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto mt-12">
|
||||
<Card>
|
||||
<CardContent className="p-12 text-center">
|
||||
<div className="w-20 h-20 bg-warning-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg className="w-10 h-10 text-warning-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Static QR Code</h2>
|
||||
<p className="text-gray-600 mb-8">
|
||||
Static QR codes cannot be edited because their content is embedded directly in the QR code image.
|
||||
</p>
|
||||
<Button onClick={() => router.push('/dashboard')}>
|
||||
Back to Dashboard
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Edit QR Code</h1>
|
||||
<p className="text-gray-600 mt-2">Update your dynamic QR code content</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>QR Code Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<Input
|
||||
label="Title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Enter QR code title"
|
||||
required
|
||||
/>
|
||||
|
||||
{qrCode.contentType === 'URL' && (
|
||||
<Input
|
||||
label="URL"
|
||||
type="url"
|
||||
value={content.url || ''}
|
||||
onChange={(e) => setContent({ ...content, url: e.target.value })}
|
||||
placeholder="https://example.com"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'PHONE' && (
|
||||
<Input
|
||||
label="Phone Number"
|
||||
type="tel"
|
||||
value={content.phone || ''}
|
||||
onChange={(e) => setContent({ ...content, phone: e.target.value })}
|
||||
placeholder="+1234567890"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'VCARD' && (
|
||||
<>
|
||||
<Input
|
||||
label="First Name"
|
||||
value={content.firstName || ''}
|
||||
onChange={(e) => setContent({ ...content, firstName: e.target.value })}
|
||||
placeholder="John"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Last Name"
|
||||
value={content.lastName || ''}
|
||||
onChange={(e) => setContent({ ...content, lastName: e.target.value })}
|
||||
placeholder="Doe"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
value={content.email || ''}
|
||||
onChange={(e) => setContent({ ...content, email: e.target.value })}
|
||||
placeholder="john@example.com"
|
||||
/>
|
||||
<Input
|
||||
label="Phone"
|
||||
value={content.phone || ''}
|
||||
onChange={(e) => setContent({ ...content, phone: e.target.value })}
|
||||
placeholder="+1234567890"
|
||||
/>
|
||||
<Input
|
||||
label="Organization"
|
||||
value={content.organization || ''}
|
||||
onChange={(e) => setContent({ ...content, organization: e.target.value })}
|
||||
placeholder="Company Name"
|
||||
/>
|
||||
<Input
|
||||
label="Job Title"
|
||||
value={content.title || ''}
|
||||
onChange={(e) => setContent({ ...content, title: e.target.value })}
|
||||
placeholder="CEO"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'GEO' && (
|
||||
<>
|
||||
<Input
|
||||
label="Latitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={content.latitude || ''}
|
||||
onChange={(e) => setContent({ ...content, latitude: parseFloat(e.target.value) || 0 })}
|
||||
placeholder="37.7749"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Longitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={content.longitude || ''}
|
||||
onChange={(e) => setContent({ ...content, longitude: parseFloat(e.target.value) || 0 })}
|
||||
placeholder="-122.4194"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Location Label (Optional)"
|
||||
value={content.label || ''}
|
||||
onChange={(e) => setContent({ ...content, label: e.target.value })}
|
||||
placeholder="Golden Gate Bridge"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'TEXT' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Text Content
|
||||
</label>
|
||||
<textarea
|
||||
value={content.text || ''}
|
||||
onChange={(e) => setContent({ ...content, text: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
rows={4}
|
||||
placeholder="Enter your text content"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-4 pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.push('/dashboard')}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={csrfLoading || saving}
|
||||
>
|
||||
{csrfLoading ? 'Loading...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Authentication | QR Master',
|
||||
description: 'Securely login or sign up to QR Master to manage your dynamic QR codes, track analytics, and access premium features. Your gateway to professional QR management.',
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/favicon.svg', type: 'image/svg+xml' },
|
||||
{ url: '/logo.svg', type: 'image/svg+xml' },
|
||||
],
|
||||
apple: '/logo.svg',
|
||||
},
|
||||
};
|
||||
|
||||
export default function AuthRootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="font-sans">
|
||||
<Providers>
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white">
|
||||
{children}
|
||||
<div className="py-6 text-center text-sm text-slate-500 space-x-4">
|
||||
<a href="/" className="hover:text-primary-600 transition-colors">Home</a>
|
||||
<a href="/privacy" className="hover:text-primary-600 transition-colors">Privacy</a>
|
||||
<a href="/faq" className="hover:text-primary-600 transition-colors">FAQ</a>
|
||||
</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import LoginClientPage from './ClientPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Login to QR Master | Access Your Dashboard'
|
||||
},
|
||||
description: 'Sign in to QR Master to create, manage, and track your QR codes. Access your dashboard and view analytics.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/login',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Login to QR Master | Access Your Dashboard',
|
||||
description: 'Sign in to QR Master to create, manage, and track your QR codes.',
|
||||
url: 'https://www.qrmaster.net/login',
|
||||
type: 'website',
|
||||
images: [{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master Login',
|
||||
}],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Login to QR Master | Access Your Dashboard',
|
||||
description: 'Sign in to QR Master to create, manage, and track your QR codes.',
|
||||
images: ['https://www.qrmaster.net/og-image.png'],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
|
||||
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
|
||||
<span className="text-2xl font-bold text-gray-900">QR Master</span>
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
|
||||
<p className="text-gray-600 mt-2">Sign in to your account</p>
|
||||
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8 flex items-center justify-center min-h-[400px]">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
}>
|
||||
<LoginClientPage />
|
||||
</Suspense>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
By signing in, you agree to our{' '}
|
||||
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import SignupClientPage from './ClientPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Create Free Account | QR Master'
|
||||
},
|
||||
description: 'Sign up for QR Master to create free QR codes. Start with tracking, customization, and bulk generation features.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Create Free Account | QR Master',
|
||||
description: 'Sign up for QR Master to create free QR codes with tracking and customization.',
|
||||
url: 'https://www.qrmaster.net/signup',
|
||||
type: 'website',
|
||||
images: [{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master Sign Up',
|
||||
}],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Create Free Account | QR Master',
|
||||
description: 'Sign up for QR Master to create free QR codes with tracking and customization.',
|
||||
images: ['https://www.qrmaster.net/og-image.png'],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default function SignupPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
|
||||
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
|
||||
<span className="text-2xl font-bold text-gray-900">QR Master</span>
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Create Account</h1>
|
||||
<p className="text-gray-600 mt-2">Start creating QR codes in seconds</p>
|
||||
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8 flex items-center justify-center min-h-[500px]">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
}>
|
||||
<SignupClientPage />
|
||||
</Suspense>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
By signing up, you agree to our{' '}
|
||||
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,20 @@ import { calculateContrast, cn } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import {
|
||||
Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload
|
||||
} from 'lucide-react';
|
||||
|
||||
// Tooltip component for form field help
|
||||
const Tooltip = ({ text }: { text: string }) => (
|
||||
<div className="group relative inline-block ml-1">
|
||||
<HelpCircle className="w-4 h-4 text-gray-400 cursor-help" />
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-gray-900 text-white text-xs rounded-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 w-48 text-center">
|
||||
{text}
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Content-type specific frame options
|
||||
const getFrameOptionsForContentType = (contentType: string) => {
|
||||
@@ -34,6 +48,14 @@ const getFrameOptionsForContentType = (contentType: string) => {
|
||||
return [...baseOptions, { id: 'chatme', label: 'Chat Me' }, { id: 'whatsapp', label: 'WhatsApp' }];
|
||||
case 'TEXT':
|
||||
return [...baseOptions, { id: 'read', label: 'Read' }, { id: 'info', label: 'Info' }];
|
||||
case 'PDF':
|
||||
return [...baseOptions, { id: 'download', label: 'Download' }, { id: 'view', label: 'View PDF' }];
|
||||
case 'APP':
|
||||
return [...baseOptions, { id: 'getapp', label: 'Get App' }, { id: 'download', label: 'Download' }];
|
||||
case 'COUPON':
|
||||
return [...baseOptions, { id: 'redeem', label: 'Redeem' }, { id: 'save', label: 'Save Offer' }];
|
||||
case 'FEEDBACK':
|
||||
return [...baseOptions, { id: 'review', label: 'Review' }, { id: 'feedback', label: 'Feedback' }];
|
||||
default:
|
||||
return [...baseOptions, { id: 'website', label: 'Website' }, { id: 'visit', label: 'Visit' }];
|
||||
}
|
||||
@@ -44,6 +66,7 @@ export default function CreatePage() {
|
||||
const { t } = useTranslation();
|
||||
const { fetchWithCsrf } = useCsrf();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [userPlan, setUserPlan] = useState<string>('FREE');
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -102,10 +125,14 @@ export default function CreatePage() {
|
||||
const hasGoodContrast = contrast >= 4.5;
|
||||
|
||||
const contentTypes = [
|
||||
{ value: 'URL', label: 'URL / Website' },
|
||||
{ value: 'VCARD', label: 'Contact Card' },
|
||||
{ value: 'GEO', label: 'Location/Maps' },
|
||||
{ value: 'PHONE', label: 'Phone Number' },
|
||||
{ value: 'URL', label: 'URL / Website', icon: Globe },
|
||||
{ value: 'VCARD', label: 'Contact Card', icon: User },
|
||||
{ value: 'GEO', label: 'Location / Maps', icon: MapPin },
|
||||
{ value: 'PHONE', label: 'Phone Number', icon: Phone },
|
||||
{ value: 'PDF', label: 'PDF / File', icon: FileText },
|
||||
{ value: 'APP', label: 'App Download', icon: Smartphone },
|
||||
{ value: 'COUPON', label: 'Coupon / Discount', icon: Ticket },
|
||||
{ value: 'FEEDBACK', label: 'Feedback / Review', icon: Star },
|
||||
];
|
||||
|
||||
// Get QR content based on content type
|
||||
@@ -128,6 +155,14 @@ export default function CreatePage() {
|
||||
return content.text || 'Sample text';
|
||||
case 'WHATSAPP':
|
||||
return `https://wa.me/${content.phone || '+1234567890'}${content.message ? `?text=${encodeURIComponent(content.message)}` : ''}`;
|
||||
case 'PDF':
|
||||
return content.fileUrl || 'https://example.com/file.pdf';
|
||||
case 'APP':
|
||||
return content.fallbackUrl || content.iosUrl || content.androidUrl || 'https://example.com/app';
|
||||
case 'COUPON':
|
||||
return `Coupon: ${content.code || 'SAVE20'} - ${content.discount || '20% OFF'}`;
|
||||
case 'FEEDBACK':
|
||||
return content.feedbackUrl || 'https://example.com/feedback';
|
||||
default:
|
||||
return 'https://example.com';
|
||||
}
|
||||
@@ -398,6 +433,208 @@ export default function CreatePage() {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'PDF':
|
||||
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// 10MB limit
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
showToast('File size too large (max 10MB)', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setContent({ ...content, fileUrl: data.url, fileName: data.filename });
|
||||
showToast('File uploaded successfully!', 'success');
|
||||
} else {
|
||||
showToast(data.error || 'Upload failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
showToast('Error uploading file', 'error');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Upload Menu / PDF</label>
|
||||
<Tooltip text="Upload your menu PDF (Max 10MB). Hosted securely." />
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-lg hover:bg-gray-50 transition-colors relative">
|
||||
<div className="space-y-1 text-center">
|
||||
{uploading ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500 mb-2"></div>
|
||||
<p className="text-sm text-gray-500">Uploading...</p>
|
||||
</div>
|
||||
) : content.fileUrl ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mx-auto h-12 w-12 text-primary-500 bg-primary-50 rounded-full flex items-center justify-center mb-2">
|
||||
<FileText className="h-6 w-6" />
|
||||
</div>
|
||||
<p className="text-sm text-green-600 font-medium mb-1">Upload Complete!</p>
|
||||
<a href={content.fileUrl} target="_blank" rel="noopener noreferrer" className="text-xs text-primary-500 hover:underline break-all max-w-xs mb-3 block">
|
||||
{content.fileName || 'View File'}
|
||||
</a>
|
||||
<label htmlFor="file-upload" className="cursor-pointer bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<span>Replace File</span>
|
||||
<input id="file-upload" name="file-upload" type="file" className="sr-only" accept=".pdf,image/*" onChange={handleFileUpload} />
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<div className="flex text-sm text-gray-600 justify-center">
|
||||
<label htmlFor="file-upload" className="relative cursor-pointer bg-white rounded-md font-medium text-primary-600 hover:text-primary-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary-500">
|
||||
<span>Upload a file</span>
|
||||
<input id="file-upload" name="file-upload" type="file" className="sr-only" accept=".pdf,image/*" onChange={handleFileUpload} />
|
||||
</label>
|
||||
<p className="pl-1">or drag and drop</p>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">PDF, PNG, JPG up to 10MB</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{content.fileUrl && (
|
||||
<Input
|
||||
label="File Name / Menu Title"
|
||||
value={content.fileName || ''}
|
||||
onChange={(e) => setContent({ ...content, fileName: e.target.value })}
|
||||
placeholder="Product Catalog 2026"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
case 'APP':
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">iOS App Store URL</label>
|
||||
<Tooltip text="Link to your app in the Apple App Store" />
|
||||
</div>
|
||||
<Input
|
||||
value={content.iosUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, iosUrl: e.target.value })}
|
||||
placeholder="https://apps.apple.com/app/..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Android Play Store URL</label>
|
||||
<Tooltip text="Link to your app in the Google Play Store" />
|
||||
</div>
|
||||
<Input
|
||||
value={content.androidUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, androidUrl: e.target.value })}
|
||||
placeholder="https://play.google.com/store/apps/..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Fallback URL</label>
|
||||
<Tooltip text="Where desktop users go (e.g., your website). QR detects device automatically!" />
|
||||
</div>
|
||||
<Input
|
||||
value={content.fallbackUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, fallbackUrl: e.target.value })}
|
||||
placeholder="https://yourapp.com"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
case 'COUPON':
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
label="Coupon Code"
|
||||
value={content.code || ''}
|
||||
onChange={(e) => setContent({ ...content, code: e.target.value })}
|
||||
placeholder="SUMMER20"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Discount"
|
||||
value={content.discount || ''}
|
||||
onChange={(e) => setContent({ ...content, discount: e.target.value })}
|
||||
placeholder="20% OFF"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Title"
|
||||
value={content.title || ''}
|
||||
onChange={(e) => setContent({ ...content, title: e.target.value })}
|
||||
placeholder="Summer Sale 2026"
|
||||
/>
|
||||
<Input
|
||||
label="Description (optional)"
|
||||
value={content.description || ''}
|
||||
onChange={(e) => setContent({ ...content, description: e.target.value })}
|
||||
placeholder="Valid on all products"
|
||||
/>
|
||||
<Input
|
||||
label="Expiry Date (optional)"
|
||||
type="date"
|
||||
value={content.expiryDate || ''}
|
||||
onChange={(e) => setContent({ ...content, expiryDate: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
label="Redeem URL (optional)"
|
||||
value={content.redeemUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, redeemUrl: e.target.value })}
|
||||
placeholder="https://shop.example.com?coupon=SUMMER20"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
case 'FEEDBACK':
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
label="Business Name"
|
||||
value={content.businessName || ''}
|
||||
onChange={(e) => setContent({ ...content, businessName: e.target.value })}
|
||||
placeholder="Your Restaurant Name"
|
||||
required
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Google Review URL</label>
|
||||
<Tooltip text="Redirect satisfied customers to leave a Google review." />
|
||||
</div>
|
||||
<Input
|
||||
value={content.googleReviewUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, googleReviewUrl: e.target.value })}
|
||||
placeholder="https://search.google.com/local/writereview?placeid=..."
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label="Thank You Message"
|
||||
value={content.thankYouMessage || ''}
|
||||
onChange={(e) => setContent({ ...content, thankYouMessage: e.target.value })}
|
||||
placeholder="Thanks for your feedback!"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -428,12 +665,31 @@ export default function CreatePage() {
|
||||
required
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Content Type"
|
||||
value={contentType}
|
||||
onChange={(e) => setContentType(e.target.value)}
|
||||
options={contentTypes}
|
||||
/>
|
||||
{/* Custom Content Type Selector with Icons */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Content Type</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{contentTypes.map((type) => {
|
||||
const Icon = type.icon;
|
||||
return (
|
||||
<button
|
||||
key={type.value}
|
||||
type="button"
|
||||
onClick={() => setContentType(type.value)}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-2 p-3 rounded-lg border-2 transition-all text-sm",
|
||||
contentType === type.value
|
||||
? "border-primary-500 bg-primary-50 text-primary-700"
|
||||
: "border-gray-200 hover:border-gray-300 text-gray-600"
|
||||
)}
|
||||
>
|
||||
<Icon className="w-5 h-5" />
|
||||
<span className="text-xs font-medium text-center">{type.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderContentFields()}
|
||||
</CardContent>
|
||||
@@ -17,22 +17,16 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootAppLayout({
|
||||
export default function AppGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="font-sans">
|
||||
<Providers>
|
||||
<Suspense fallback={null}>
|
||||
<AppLayout>
|
||||
{children}
|
||||
</AppLayout>
|
||||
</Suspense>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
459
src/app/(main)/(app)/qr/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,459 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
import { Upload, FileText, HelpCircle } from 'lucide-react';
|
||||
|
||||
// Tooltip component for form field help
|
||||
const Tooltip = ({ text }: { text: string }) => (
|
||||
<div className="group relative inline-block ml-1">
|
||||
<HelpCircle className="w-4 h-4 text-gray-400 cursor-help" />
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-gray-900 text-white text-xs rounded-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 w-48 text-center">
|
||||
{text}
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function EditQRPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const qrId = params.id as string;
|
||||
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [qrCode, setQrCode] = useState<any>(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchQRCode = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/qrs/${qrId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setQrCode(data);
|
||||
setTitle(data.title);
|
||||
setContent(data.content || {});
|
||||
} else {
|
||||
showToast('Failed to load QR code', 'error');
|
||||
router.push('/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching QR code:', error);
|
||||
showToast('Failed to load QR code', 'error');
|
||||
router.push('/dashboard');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchQRCode();
|
||||
}, [qrId, router]);
|
||||
|
||||
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// 10MB limit
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
showToast('File size too large (max 10MB)', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setContent({ ...content, fileUrl: data.url, fileName: data.filename });
|
||||
showToast('File uploaded successfully!', 'success');
|
||||
} else {
|
||||
showToast(data.error || 'Upload failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
showToast('Error uploading file', 'error');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
|
||||
try {
|
||||
const response = await fetchWithCsrf(`/api/qrs/${qrId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
content,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast('QR code updated successfully!', 'success');
|
||||
router.push('/dashboard');
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showToast(error.error || 'Failed to update QR code', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating QR code:', error);
|
||||
showToast('Failed to update QR code', 'error');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading QR code...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!qrCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Static QR codes cannot be edited
|
||||
if (qrCode.type === 'STATIC') {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto mt-12">
|
||||
<Card>
|
||||
<CardContent className="p-12 text-center">
|
||||
<div className="w-20 h-20 bg-warning-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg className="w-10 h-10 text-warning-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Static QR Code</h2>
|
||||
<p className="text-gray-600 mb-8">
|
||||
Static QR codes cannot be edited because their content is embedded directly in the QR code image.
|
||||
</p>
|
||||
<Button onClick={() => router.push('/dashboard')}>
|
||||
Back to Dashboard
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Edit QR Code</h1>
|
||||
<p className="text-gray-600 mt-2">Update your dynamic QR code content</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>QR Code Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<Input
|
||||
label="Title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Enter QR code title"
|
||||
required
|
||||
/>
|
||||
|
||||
{qrCode.contentType === 'URL' && (
|
||||
<Input
|
||||
label="URL"
|
||||
type="url"
|
||||
value={content.url || ''}
|
||||
onChange={(e) => setContent({ ...content, url: e.target.value })}
|
||||
placeholder="https://example.com"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'PHONE' && (
|
||||
<Input
|
||||
label="Phone Number"
|
||||
type="tel"
|
||||
value={content.phone || ''}
|
||||
onChange={(e) => setContent({ ...content, phone: e.target.value })}
|
||||
placeholder="+1234567890"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'VCARD' && (
|
||||
<>
|
||||
<Input
|
||||
label="First Name"
|
||||
value={content.firstName || ''}
|
||||
onChange={(e) => setContent({ ...content, firstName: e.target.value })}
|
||||
placeholder="John"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Last Name"
|
||||
value={content.lastName || ''}
|
||||
onChange={(e) => setContent({ ...content, lastName: e.target.value })}
|
||||
placeholder="Doe"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
value={content.email || ''}
|
||||
onChange={(e) => setContent({ ...content, email: e.target.value })}
|
||||
placeholder="john@example.com"
|
||||
/>
|
||||
<Input
|
||||
label="Phone"
|
||||
value={content.phone || ''}
|
||||
onChange={(e) => setContent({ ...content, phone: e.target.value })}
|
||||
placeholder="+1234567890"
|
||||
/>
|
||||
<Input
|
||||
label="Organization"
|
||||
value={content.organization || ''}
|
||||
onChange={(e) => setContent({ ...content, organization: e.target.value })}
|
||||
placeholder="Company Name"
|
||||
/>
|
||||
<Input
|
||||
label="Job Title"
|
||||
value={content.title || ''}
|
||||
onChange={(e) => setContent({ ...content, title: e.target.value })}
|
||||
placeholder="CEO"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'GEO' && (
|
||||
<>
|
||||
<Input
|
||||
label="Latitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={content.latitude || ''}
|
||||
onChange={(e) => setContent({ ...content, latitude: parseFloat(e.target.value) || 0 })}
|
||||
placeholder="37.7749"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Longitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={content.longitude || ''}
|
||||
onChange={(e) => setContent({ ...content, longitude: parseFloat(e.target.value) || 0 })}
|
||||
placeholder="-122.4194"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Location Label (Optional)"
|
||||
value={content.label || ''}
|
||||
onChange={(e) => setContent({ ...content, label: e.target.value })}
|
||||
placeholder="Golden Gate Bridge"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'TEXT' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Text Content
|
||||
</label>
|
||||
<textarea
|
||||
value={content.text || ''}
|
||||
onChange={(e) => setContent({ ...content, text: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
rows={4}
|
||||
placeholder="Enter your text content"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'PDF' && (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Upload Menu / PDF</label>
|
||||
<Tooltip text="Upload your menu PDF (Max 10MB). Hosted securely." />
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-lg hover:bg-gray-50 transition-colors relative">
|
||||
<div className="space-y-1 text-center">
|
||||
{uploading ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500 mb-2"></div>
|
||||
<p className="text-sm text-gray-500">Uploading...</p>
|
||||
</div>
|
||||
) : content.fileUrl ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mx-auto h-12 w-12 text-primary-500 bg-primary-50 rounded-full flex items-center justify-center mb-2">
|
||||
<FileText className="h-6 w-6" />
|
||||
</div>
|
||||
<p className="text-sm text-green-600 font-medium mb-1">Upload Complete!</p>
|
||||
<a href={content.fileUrl} target="_blank" rel="noopener noreferrer" className="text-xs text-primary-500 hover:underline break-all max-w-xs mb-3 block">
|
||||
{content.fileName || 'View File'}
|
||||
</a>
|
||||
<label htmlFor="file-upload" className="cursor-pointer bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<span>Replace File</span>
|
||||
<input id="file-upload" name="file-upload" type="file" className="sr-only" accept=".pdf,image/*" onChange={handleFileUpload} />
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<div className="flex text-sm text-gray-600 justify-center">
|
||||
<label htmlFor="file-upload" className="relative cursor-pointer bg-white rounded-md font-medium text-primary-600 hover:text-primary-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary-500">
|
||||
<span>Upload a file</span>
|
||||
<input id="file-upload" name="file-upload" type="file" className="sr-only" accept=".pdf,image/*" onChange={handleFileUpload} />
|
||||
</label>
|
||||
<p className="pl-1">or drag and drop</p>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">PDF, PNG, JPG up to 10MB</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{content.fileUrl && (
|
||||
<Input
|
||||
label="File Name / Menu Title"
|
||||
value={content.fileName || ''}
|
||||
onChange={(e) => setContent({ ...content, fileName: e.target.value })}
|
||||
placeholder="Product Catalog 2026"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'APP' && (
|
||||
<>
|
||||
<Input
|
||||
label="iOS App Store URL"
|
||||
value={content.iosUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, iosUrl: e.target.value })}
|
||||
placeholder="https://apps.apple.com/app/..."
|
||||
/>
|
||||
<Input
|
||||
label="Android Play Store URL"
|
||||
value={content.androidUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, androidUrl: e.target.value })}
|
||||
placeholder="https://play.google.com/store/apps/..."
|
||||
/>
|
||||
<Input
|
||||
label="Fallback URL (Desktop)"
|
||||
value={content.fallbackUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, fallbackUrl: e.target.value })}
|
||||
placeholder="https://yourapp.com"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'COUPON' && (
|
||||
<>
|
||||
<Input
|
||||
label="Coupon Code"
|
||||
value={content.code || ''}
|
||||
onChange={(e) => setContent({ ...content, code: e.target.value })}
|
||||
placeholder="SUMMER20"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Discount"
|
||||
value={content.discount || ''}
|
||||
onChange={(e) => setContent({ ...content, discount: e.target.value })}
|
||||
placeholder="20% OFF"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Title"
|
||||
value={content.title || ''}
|
||||
onChange={(e) => setContent({ ...content, title: e.target.value })}
|
||||
placeholder="Summer Sale 2026"
|
||||
/>
|
||||
<Input
|
||||
label="Description (optional)"
|
||||
value={content.description || ''}
|
||||
onChange={(e) => setContent({ ...content, description: e.target.value })}
|
||||
placeholder="Valid on all products"
|
||||
/>
|
||||
<Input
|
||||
label="Expiry Date (optional)"
|
||||
type="date"
|
||||
value={content.expiryDate || ''}
|
||||
onChange={(e) => setContent({ ...content, expiryDate: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
label="Redeem URL (optional)"
|
||||
value={content.redeemUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, redeemUrl: e.target.value })}
|
||||
placeholder="https://shop.example.com"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{qrCode.contentType === 'FEEDBACK' && (
|
||||
<>
|
||||
<Input
|
||||
label="Business Name"
|
||||
value={content.businessName || ''}
|
||||
onChange={(e) => setContent({ ...content, businessName: e.target.value })}
|
||||
placeholder="Your Restaurant Name"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Google Review URL (optional)"
|
||||
value={content.googleReviewUrl || ''}
|
||||
onChange={(e) => setContent({ ...content, googleReviewUrl: e.target.value })}
|
||||
placeholder="https://search.google.com/local/writereview?placeid=..."
|
||||
/>
|
||||
<Input
|
||||
label="Thank You Message"
|
||||
value={content.thankYouMessage || ''}
|
||||
onChange={(e) => setContent({ ...content, thankYouMessage: e.target.value })}
|
||||
placeholder="Thanks for your feedback!"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-4 pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.push('/dashboard')}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={csrfLoading || saving}
|
||||
>
|
||||
{csrfLoading ? 'Loading...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
196
src/app/(main)/(app)/qr/[id]/feedback/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Star, ArrowLeft, ChevronLeft, ChevronRight, MessageSquare } from 'lucide-react';
|
||||
|
||||
interface Feedback {
|
||||
id: string;
|
||||
rating: number;
|
||||
comment: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface FeedbackStats {
|
||||
total: number;
|
||||
avgRating: number;
|
||||
distribution: { [key: number]: number };
|
||||
}
|
||||
|
||||
interface Pagination {
|
||||
page: number;
|
||||
totalPages: number;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export default function FeedbackListPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const qrId = params.id as string;
|
||||
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
const [stats, setStats] = useState<FeedbackStats | null>(null);
|
||||
const [pagination, setPagination] = useState<Pagination>({ page: 1, totalPages: 1, hasMore: false });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFeedback(currentPage);
|
||||
}, [qrId, currentPage]);
|
||||
|
||||
const fetchFeedback = async (page: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/qrs/${qrId}/feedback?page=${page}&limit=20`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setFeedbacks(data.feedbacks);
|
||||
setStats(data.stats);
|
||||
setPagination(data.pagination);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching feedback:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderStars = (rating: number) => (
|
||||
<div className="flex gap-0.5">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
className={`w-4 h-4 ${star <= rating ? 'text-amber-400 fill-amber-400' : 'text-gray-200'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading && !stats) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Link href={`/qr/${qrId}`} className="inline-flex items-center text-gray-500 hover:text-gray-700 mb-4">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back to QR Code
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Customer Feedback</h1>
|
||||
<p className="text-gray-600 mt-1">{stats?.total || 0} total responses</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Overview */}
|
||||
{stats && (
|
||||
<Card className="mb-8">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-8">
|
||||
{/* Average Rating */}
|
||||
<div className="text-center md:text-left">
|
||||
<div className="text-5xl font-bold text-gray-900 mb-1">{stats.avgRating}</div>
|
||||
<div className="flex justify-center md:justify-start mb-1">
|
||||
{renderStars(Math.round(stats.avgRating))}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">{stats.total} reviews</p>
|
||||
</div>
|
||||
|
||||
{/* Distribution */}
|
||||
<div className="flex-1 space-y-2">
|
||||
{[5, 4, 3, 2, 1].map((rating) => {
|
||||
const count = stats.distribution[rating] || 0;
|
||||
const percentage = stats.total > 0 ? (count / stats.total) * 100 : 0;
|
||||
return (
|
||||
<div key={rating} className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-600 w-12">{rating} stars</span>
|
||||
<div className="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-amber-400 rounded-full transition-all"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-gray-500 w-12 text-right">{count}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Feedback List */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5" />
|
||||
All Reviews
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{feedbacks.length === 0 ? (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<Star className="w-12 h-12 mx-auto mb-4 text-gray-300" />
|
||||
<p>No feedback received yet</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-gray-100">
|
||||
{feedbacks.map((feedback) => (
|
||||
<div key={feedback.id} className="py-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
{renderStars(feedback.rating)}
|
||||
<span className="text-sm text-gray-400">
|
||||
{new Date(feedback.date).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{feedback.comment && (
|
||||
<p className="text-gray-700">{feedback.comment}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{pagination.totalPages > 1 && (
|
||||
<div className="flex items-center justify-between mt-6 pt-6 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4 mr-1" />
|
||||
Previous
|
||||
</Button>
|
||||
<span className="text-sm text-gray-500">
|
||||
Page {currentPage} of {pagination.totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage((p) => p + 1)}
|
||||
disabled={!pagination.hasMore}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="w-4 h-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
287
src/app/(main)/(app)/qr/[id]/page.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import {
|
||||
ArrowLeft, Edit, ExternalLink, Star, MessageSquare,
|
||||
BarChart3, Copy, Check, Pause, Play
|
||||
} from 'lucide-react';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
|
||||
interface QRCode {
|
||||
id: string;
|
||||
title: string;
|
||||
type: 'STATIC' | 'DYNAMIC';
|
||||
contentType: string;
|
||||
content: any;
|
||||
slug: string;
|
||||
status: 'ACTIVE' | 'PAUSED';
|
||||
style: any;
|
||||
createdAt: string;
|
||||
_count?: { scans: number };
|
||||
}
|
||||
|
||||
interface FeedbackStats {
|
||||
total: number;
|
||||
avgRating: number;
|
||||
distribution: { [key: number]: number };
|
||||
}
|
||||
|
||||
export default function QRDetailPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const qrId = params.id as string;
|
||||
const { fetchWithCsrf } = useCsrf();
|
||||
|
||||
const [qrCode, setQrCode] = useState<QRCode | null>(null);
|
||||
const [feedbackStats, setFeedbackStats] = useState<FeedbackStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchQRCode();
|
||||
}, [qrId]);
|
||||
|
||||
const fetchQRCode = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/qrs/${qrId}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setQrCode(data);
|
||||
|
||||
// Fetch feedback stats if it's a feedback QR
|
||||
if (data.contentType === 'FEEDBACK') {
|
||||
const feedbackRes = await fetch(`/api/qrs/${qrId}/feedback?limit=1`);
|
||||
if (feedbackRes.ok) {
|
||||
const feedbackData = await feedbackRes.json();
|
||||
setFeedbackStats(feedbackData.stats);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showToast('QR code not found', 'error');
|
||||
router.push('/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching QR code:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyLink = async () => {
|
||||
const url = `${window.location.origin}/r/${qrCode?.slug}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
showToast('Link copied!', 'success');
|
||||
};
|
||||
|
||||
const toggleStatus = async () => {
|
||||
if (!qrCode) return;
|
||||
const newStatus = qrCode.status === 'ACTIVE' ? 'PAUSED' : 'ACTIVE';
|
||||
|
||||
try {
|
||||
const res = await fetchWithCsrf(`/api/qrs/${qrId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status: newStatus }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setQrCode({ ...qrCode, status: newStatus });
|
||||
showToast(`QR code ${newStatus === 'ACTIVE' ? 'activated' : 'paused'}`, 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Failed to update status', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const renderStars = (rating: number) => (
|
||||
<div className="flex gap-0.5">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
className={`w-4 h-4 ${star <= rating ? 'text-amber-400 fill-amber-400' : 'text-gray-200'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!qrCode) return null;
|
||||
|
||||
const qrUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/r/${qrCode.slug}`;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Link href="/dashboard" className="inline-flex items-center text-gray-500 hover:text-gray-700 mb-4">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back to Dashboard
|
||||
</Link>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">{qrCode.title}</h1>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<Badge variant={qrCode.type === 'DYNAMIC' ? 'info' : 'default'}>
|
||||
{qrCode.type}
|
||||
</Badge>
|
||||
<Badge variant={qrCode.status === 'ACTIVE' ? 'success' : 'warning'}>
|
||||
{qrCode.status}
|
||||
</Badge>
|
||||
<Badge>{qrCode.contentType}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{qrCode.type === 'DYNAMIC' && (
|
||||
<>
|
||||
<Button variant="outline" size="sm" onClick={toggleStatus}>
|
||||
{qrCode.status === 'ACTIVE' ? <Pause className="w-4 h-4 mr-1" /> : <Play className="w-4 h-4 mr-1" />}
|
||||
{qrCode.status === 'ACTIVE' ? 'Pause' : 'Activate'}
|
||||
</Button>
|
||||
<Link href={`/qr/${qrId}/edit`}>
|
||||
<Button variant="outline" size="sm">
|
||||
<Edit className="w-4 h-4 mr-1" /> Edit
|
||||
</Button>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-8">
|
||||
{/* Left: QR Code */}
|
||||
<div>
|
||||
<Card>
|
||||
<CardContent className="p-6 flex flex-col items-center">
|
||||
<div className="bg-white p-4 rounded-xl shadow-sm mb-4">
|
||||
<QRCodeSVG
|
||||
value={qrUrl}
|
||||
size={200}
|
||||
fgColor={qrCode.style?.foregroundColor || '#000000'}
|
||||
bgColor={qrCode.style?.backgroundColor || '#FFFFFF'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-2">
|
||||
<Button variant="outline" className="w-full" onClick={copyLink}>
|
||||
{copied ? <Check className="w-4 h-4 mr-2" /> : <Copy className="w-4 h-4 mr-2" />}
|
||||
{copied ? 'Copied!' : 'Copy Link'}
|
||||
</Button>
|
||||
<a href={qrUrl} target="_blank" rel="noopener noreferrer" className="block">
|
||||
<Button variant="outline" className="w-full">
|
||||
<ExternalLink className="w-4 h-4 mr-2" /> Open Link
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Right: Stats & Info */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardContent className="p-4 text-center">
|
||||
<BarChart3 className="w-6 h-6 mx-auto mb-2 text-indigo-500" />
|
||||
<p className="text-2xl font-bold text-gray-900">{qrCode._count?.scans || 0}</p>
|
||||
<p className="text-sm text-gray-500">Total Scans</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="p-4 text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">{qrCode.type}</p>
|
||||
<p className="text-sm text-gray-500">QR Type</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="p-4 text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">
|
||||
{new Date(qrCode.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">Created</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Feedback Summary (only for FEEDBACK type) */}
|
||||
{qrCode.contentType === 'FEEDBACK' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Star className="w-5 h-5 text-amber-400" />
|
||||
Customer Feedback
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{feedbackStats && feedbackStats.total > 0 ? (
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-6 mb-4">
|
||||
{/* Average */}
|
||||
<div className="text-center sm:text-left">
|
||||
<div className="text-4xl font-bold text-gray-900">{feedbackStats.avgRating}</div>
|
||||
{renderStars(Math.round(feedbackStats.avgRating))}
|
||||
<p className="text-sm text-gray-500 mt-1">{feedbackStats.total} reviews</p>
|
||||
</div>
|
||||
|
||||
{/* Distribution */}
|
||||
<div className="flex-1 space-y-1">
|
||||
{[5, 4, 3, 2, 1].map((rating) => {
|
||||
const count = feedbackStats.distribution[rating] || 0;
|
||||
const pct = feedbackStats.total > 0 ? (count / feedbackStats.total) * 100 : 0;
|
||||
return (
|
||||
<div key={rating} className="flex items-center gap-2 text-sm">
|
||||
<span className="w-8 text-gray-500">{rating}★</span>
|
||||
<div className="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-amber-400 rounded-full" style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className="w-8 text-gray-400 text-right">{count}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 mb-4">No feedback received yet. Share your QR code to collect reviews!</p>
|
||||
)}
|
||||
|
||||
<Link href={`/qr/${qrId}/feedback`} className="block">
|
||||
<Button variant="outline" className="w-full">
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
View All Feedback
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Content Info */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Content Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<pre className="bg-gray-50 p-4 rounded-lg text-sm overflow-auto">
|
||||
{JSON.stringify(qrCode.content, null, 2)}
|
||||
</pre>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
src/app/(main)/(auth)/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function AuthLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex flex-col">
|
||||
<div className="flex-grow">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<footer className="py-6 text-center text-sm text-gray-500 bg-transparent">
|
||||
<div className="space-x-6 mb-2">
|
||||
<Link href="/" className="hover:text-gray-900 transition-colors">Home</Link>
|
||||
<Link href="/pricing" className="hover:text-gray-900 transition-colors">Pricing</Link>
|
||||
<Link href="/privacy" className="hover:text-gray-900 transition-colors">Privacy</Link>
|
||||
<Link href="/faq" className="hover:text-gray-900 transition-colors">FAQ</Link>
|
||||
</div>
|
||||
<p>© {new Date().getFullYear()} QR Master</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
187
src/app/(main)/(auth)/login/LoginClient.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
|
||||
export default function LoginClient() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { t } = useTranslation();
|
||||
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetchWithCsrf('/api/auth/simple-login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
// Store user in localStorage for client-side
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
|
||||
// Track successful login with PostHog
|
||||
try {
|
||||
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
|
||||
identifyUser(data.user.id, {
|
||||
email: data.user.email,
|
||||
name: data.user.name,
|
||||
plan: data.user.plan || 'FREE',
|
||||
});
|
||||
trackEvent('user_login', {
|
||||
method: 'email',
|
||||
email: data.user.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('PostHog tracking error:', error);
|
||||
}
|
||||
|
||||
// Check for redirect parameter
|
||||
const redirectUrl = searchParams.get('redirect') || '/dashboard';
|
||||
router.push(redirectUrl);
|
||||
router.refresh();
|
||||
} else {
|
||||
setError(data.error || 'Invalid email or password');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('An error occurred. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = () => {
|
||||
// Redirect to Google OAuth API route
|
||||
window.location.href = '/api/auth/google';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
|
||||
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
|
||||
<span className="text-2xl font-bold text-gray-900">QR Master</span>
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
|
||||
<p className="text-gray-600 mt-2">Sign in to your account</p>
|
||||
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{error && (
|
||||
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="mr-2" />
|
||||
<span className="text-sm text-gray-600">Remember me</span>
|
||||
</label>
|
||||
<Link href="/forgot-password" className="text-sm text-primary-600 hover:text-primary-700">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" loading={loading} disabled={csrfLoading || loading}>
|
||||
{csrfLoading ? 'Loading...' : 'Sign In'}
|
||||
</Button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleGoogleSignIn}
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="#4285F4"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="#34A853"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="#FBBC05"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA4335"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Sign in with Google
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
<Link href="/signup" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
By signing in, you agree to our{' '}
|
||||
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
src/app/(main)/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Metadata } from 'next';
|
||||
import LoginClient from './LoginClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'QR Master – Smart QR Generator & Analytics',
|
||||
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default function LoginPage() {
|
||||
return <LoginClient />;
|
||||
}
|
||||
@@ -8,9 +8,7 @@ import { Input } from '@/components/ui/Input';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
|
||||
import { Suspense } from 'react';
|
||||
|
||||
function ResetPasswordContent() {
|
||||
export default function ResetPasswordPage() {
|
||||
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
@@ -208,11 +206,3 @@ function ResetPasswordContent() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="min-h-screen flex items-center justify-center">Loading...</div>}>
|
||||
<ResetPasswordContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
208
src/app/(main)/(auth)/signup/SignupClient.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useCsrf } from '@/hooks/useCsrf';
|
||||
|
||||
export default function SignupClient() {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { fetchWithCsrf } = useCsrf();
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError('Passwords do not match');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
setError('Password must be at least 8 characters');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchWithCsrf('/api/auth/signup', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
// Store user in localStorage for client-side
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
|
||||
// Track successful signup with PostHog
|
||||
try {
|
||||
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
|
||||
identifyUser(data.user.id, {
|
||||
email: data.user.email,
|
||||
name: data.user.name,
|
||||
plan: data.user.plan || 'FREE',
|
||||
signupMethod: 'email',
|
||||
});
|
||||
trackEvent('user_signup', {
|
||||
method: 'email',
|
||||
email: data.user.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('PostHog tracking error:', error);
|
||||
}
|
||||
|
||||
// Redirect to dashboard
|
||||
router.push('/dashboard');
|
||||
router.refresh();
|
||||
} else {
|
||||
setError(data.error || 'Failed to create account');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('An error occurred. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = () => {
|
||||
// Redirect to Google OAuth API route
|
||||
window.location.href = '/api/auth/google';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
|
||||
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
|
||||
<span className="text-2xl font-bold text-gray-900">QR Master</span>
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Create Account</h1>
|
||||
<p className="text-gray-600 mt-2">Start creating QR codes in seconds</p>
|
||||
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{error && (
|
||||
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Full Name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="John Doe"
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Confirm Password"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full" loading={loading}>
|
||||
Create Account
|
||||
</Button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleGoogleSignIn}
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="#4285F4"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="#34A853"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="#FBBC05"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA4335"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Sign up with Google
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-600">
|
||||
Already have an account?{' '}
|
||||
<Link href="/login" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
By signing up, you agree to our{' '}
|
||||
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/app/(main)/(auth)/signup/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Metadata } from 'next';
|
||||
import SignupClient from './SignupClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Account | QR Master',
|
||||
description: 'Start creating dynamic QR codes in seconds. Join thousands of businesses using QR Master.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: true,
|
||||
},
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
openGraph: {
|
||||
url: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
};
|
||||
|
||||
export default function SignupPage() {
|
||||
return <SignupClient />;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
import en from '@/i18n/en.json';
|
||||
import { ChevronDown, Wifi, Contact, MessageCircle, QrCode, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users } from 'lucide-react';
|
||||
import { ChevronDown, Wifi, Contact, MessageCircle, QrCode, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Barcode as BarcodeIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function MarketingLayout({
|
||||
{ name: 'WhatsApp', description: 'Start a chat', href: '/tools/whatsapp-qr-code', icon: MessageCircle, color: 'text-green-500', bgColor: 'bg-green-50' },
|
||||
{ name: 'Email', description: 'Compose an email', href: '/tools/email-qr-code', icon: Mail, color: 'text-amber-500', bgColor: 'bg-amber-50' },
|
||||
{ name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', icon: MessageSquare, color: 'text-cyan-500', bgColor: 'bg-cyan-50' },
|
||||
{ name: 'Phone', description: 'Start a call', href: '/tools/phone-qr-code', icon: Phone, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Call', description: 'Start a call', href: '/tools/call-qr-code-generator', icon: Phone, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Event', description: 'Add calendar event', href: '/tools/event-qr-code', icon: Calendar, color: 'text-red-500', bgColor: 'bg-red-50' },
|
||||
{ name: 'Location', description: 'Share a place', href: '/tools/geolocation-qr-code', icon: MapPin, color: 'text-emerald-500', bgColor: 'bg-emerald-50' },
|
||||
{ name: 'Facebook', description: 'Facebook profile/page', href: '/tools/facebook-qr-code', icon: Facebook, color: 'text-blue-600', bgColor: 'bg-blue-50' },
|
||||
@@ -62,6 +62,7 @@ export default function MarketingLayout({
|
||||
{ name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Barcode', description: 'Generate barcodes', href: '/tools/barcode-generator', icon: BarcodeIcon, color: 'text-slate-800', bgColor: 'bg-slate-100' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -71,11 +72,14 @@ export default function MarketingLayout({
|
||||
<nav aria-label="Site Map">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/pricing">Pricing</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
<li><a href="/signup">Sign Up</a></li>
|
||||
<li><Link href="/pricing">{t.nav.pricing}</Link></li>
|
||||
<li><Link href="/blog">{t.nav.blog}</Link></li>
|
||||
<li><Link href="/learn">{t.nav.learn}</Link></li>
|
||||
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
||||
<li><Link href="/about">{t.nav.about}</Link></li>
|
||||
<li><Link href="/contact">{t.nav.contact}</Link></li>
|
||||
<li><Link href="/login">{t.nav.login}</Link></li>
|
||||
<li><Link href="/signup">{t.nav.signup || "Sign Up"}</Link></li>
|
||||
{/* Tools */}
|
||||
<li><a href="/tools/url-qr-code">URL QR Code</a></li>
|
||||
<li><a href="/tools/text-qr-code">Text QR Code</a></li>
|
||||
@@ -84,7 +88,7 @@ export default function MarketingLayout({
|
||||
<li><a href="/tools/whatsapp-qr-code">WhatsApp QR Code</a></li>
|
||||
<li><a href="/tools/email-qr-code">Email QR Code</a></li>
|
||||
<li><a href="/tools/sms-qr-code">SMS QR Code</a></li>
|
||||
<li><a href="/tools/phone-qr-code">Phone QR Code</a></li>
|
||||
<li><a href="/tools/call-qr-code-generator">Call QR Code</a></li>
|
||||
<li><a href="/tools/event-qr-code">Event QR Code</a></li>
|
||||
<li><a href="/tools/geolocation-qr-code">Location QR Code</a></li>
|
||||
<li><a href="/tools/facebook-qr-code">Facebook QR Code</a></li>
|
||||
@@ -96,6 +100,7 @@ export default function MarketingLayout({
|
||||
<li><a href="/tools/paypal-qr-code">PayPal QR Code</a></li>
|
||||
<li><a href="/tools/zoom-qr-code">Zoom QR Code</a></li>
|
||||
<li><a href="/tools/teams-qr-code">Teams QR Code</a></li>
|
||||
<li><a href="/tools/barcode-generator">Barcode Generator</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -169,9 +174,15 @@ export default function MarketingLayout({
|
||||
<Link href="/#pricing" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.pricing}
|
||||
</Link>
|
||||
<Link href="/about" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.about}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.learn}
|
||||
</Link>
|
||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.faq}
|
||||
</Link>
|
||||
@@ -256,7 +267,9 @@ export default function MarketingLayout({
|
||||
|
||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||
<Link href="/about" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.about}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/learn" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.learn}</Link>
|
||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
285
src/app/(main)/(marketing)/about/page.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Metadata } from 'next';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema } from '@/lib/schema';
|
||||
import { CheckCircle2, Shield, Users, BarChart3, Globe, Lock } from 'lucide-react';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'About QR Master | The Team & Mission Behind the Platform',
|
||||
description: 'QR Master is built for measurable campaigns and secure QR code operations. Learn about our mission, values, and why businesses trust us.',
|
||||
openGraph: {
|
||||
title: 'About QR Master | Dynamic QR Codes & Analytics',
|
||||
description: 'We help businesses create, manage, and track QR codes at scale. Transparent pricing, privacy-first, and built for reliability.',
|
||||
url: 'https://www.qrmaster.net/about',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={organizationSchema()} />
|
||||
|
||||
<div className="bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20 sm:py-24">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-gray-900 leading-tight mb-6">
|
||||
QR codes should be <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600">flexible, measurable, and reliable</span>.
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
QR Master helps teams create dynamic QR codes that can be updated after printing—so you can stop reprinting materials every time something changes. Whether you’re running a menu, an event, or a multi-channel campaign, QR Master turns QR codes into a tool you can manage and measure.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-6 shadow-lg shadow-blue-500/25">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Mission Statement */}
|
||||
<section className="py-16 border-y border-gray-100 bg-white">
|
||||
<div className="container mx-auto px-4 max-w-4xl text-center">
|
||||
<div className="inline-flex items-center justify-center p-3 sm:p-4 mb-6 rounded-full bg-blue-50 text-blue-700 font-medium text-sm sm:text-base">
|
||||
<Globe className="w-5 h-5 mr-2" />
|
||||
Our Mission
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||||
Create QR codes that work everywhere—and make campaigns measurable.
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* What We Do */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">What we do</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
QR Master is a QR code platform built for real-world workflows: print, campaigns, and operations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{/* Feature 1 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-6">
|
||||
<Globe className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-3">Dynamic QR Codes</h3>
|
||||
<p className="text-gray-600 leading-relaxed mb-4">
|
||||
Change the destination of a QR code after it’s already printed. Keep your printed materials valid—while you update your content anytime.
|
||||
</p>
|
||||
<Link href="/dynamic-qr-code-generator" className="text-blue-600 font-medium hover:underline">
|
||||
Learn about Dynamic QR →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Feature 2 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow">
|
||||
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-6">
|
||||
<Users className="w-6 h-6 text-purple-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-3">Bulk QR Creation</h3>
|
||||
<p className="text-gray-600 leading-relaxed mb-4">
|
||||
Generate hundreds of QR codes at once by uploading a CSV/Excel file (up to 1,000 rows per upload). Ideal for packaging, retail labels, inventory, or multi-location marketing.
|
||||
</p>
|
||||
<Link href="/bulk-qr-code-generator" className="text-blue-600 font-medium hover:underline">
|
||||
Explore Bulk Creation →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-6">
|
||||
<BarChart3 className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-3">Advanced Analytics</h3>
|
||||
<p className="text-gray-600 leading-relaxed mb-4">
|
||||
Understand QR performance with scan analytics—so you can improve placements and campaigns based on real usage over time.
|
||||
</p>
|
||||
<Link href="/qr-code-tracking" className="text-blue-600 font-medium hover:underline">
|
||||
See Analytics Features →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center">
|
||||
<Link href="/create-qr">
|
||||
<Button size="lg">Create QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Who It's For / Not For */}
|
||||
<section className="py-20 bg-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="grid md:grid-cols-2 gap-16">
|
||||
{/* Ideal For */}
|
||||
<div>
|
||||
<div className="flex items-start mb-6">
|
||||
<CheckCircle2 className="w-6 h-6 text-green-500 mr-3 mt-1 flex-shrink-0" />
|
||||
<h3 className="text-2xl font-bold text-gray-900">
|
||||
Who QR Master is for
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-600 mb-6">
|
||||
QR Master is built for people who rely on QR codes in the real world:
|
||||
</p>
|
||||
<ul className="space-y-4">
|
||||
{[
|
||||
"Restaurants using QR codes for menus and table experiences",
|
||||
"Marketing teams & agencies running measurable print campaigns",
|
||||
"Event organizers handling tickets, check-ins, and engagement",
|
||||
"Retail & packaging workflows where scale and tracking matter"
|
||||
].map((item, i) => (
|
||||
<li key={i} className="flex items-start">
|
||||
<div className="flex-shrink-0 w-1.5 h-1.5 rounded-full bg-green-500 mt-2 mr-3" />
|
||||
<span className="text-gray-700">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Not Ideal For */}
|
||||
<div className="bg-gray-50 p-8 rounded-2xl border border-gray-100">
|
||||
<div className="flex items-start mb-6">
|
||||
<Shield className="w-6 h-6 text-gray-400 mr-3 mt-1 flex-shrink-0" />
|
||||
<h3 className="text-2xl font-bold text-gray-900">
|
||||
When QR Master may <span className="italic">not</span> be the best fit
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-600 leading-relaxed mb-6">
|
||||
If you require enterprise SSO, on-premise hosting, or deeply customized enterprise integrations, QR Master may not match those requirements today.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 italic">
|
||||
(We prefer being transparent so you can choose the right tool.)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trust & Transparency */}
|
||||
<section className="py-20 bg-gray-900 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">Trust & Transparency</h2>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
We know QR codes often sit at the intersection of marketing, security, and privacy. That’s why we prioritize clear product behavior and straightforward policies.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
<div>
|
||||
<div className="flex items-center mb-4">
|
||||
<Lock className="w-6 h-6 text-blue-400 mr-3" />
|
||||
<h3 className="text-xl font-bold">Security basics</h3>
|
||||
</div>
|
||||
<p className="text-gray-400 leading-relaxed mb-4">
|
||||
QR Master runs over HTTPS/TLS as standard. We focus on protecting the redirect and management experience with secure access patterns and responsible data handling.
|
||||
</p>
|
||||
<p className="text-gray-500 text-sm">
|
||||
If you need formal security documentation (controls, subprocessors, incident process), we recommend publishing a dedicated Security page and linking it here.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center mb-4">
|
||||
<Shield className="w-6 h-6 text-purple-400 mr-3" />
|
||||
<h3 className="text-xl font-bold">Privacy & data handling</h3>
|
||||
</div>
|
||||
<p className="text-gray-400 leading-relaxed mb-6">
|
||||
We aim for a privacy-conscious approach and provide a transparent Privacy Policy describing how data is handled and what tools/subprocessors are involved.
|
||||
</p>
|
||||
<div className="flex space-x-6 text-sm">
|
||||
<Link href="/privacy" className="text-blue-400 hover:text-blue-300 transition-colors">
|
||||
Read our Privacy Policy →
|
||||
</Link>
|
||||
<Link href="/faq" className="text-blue-400 hover:text-blue-300 transition-colors">
|
||||
Explore FAQs →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Support & Contact */}
|
||||
<section className="py-20 bg-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
{/* Support */}
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Support</h3>
|
||||
<p className="text-gray-600 mb-6">We keep support simple and responsive.</p>
|
||||
|
||||
<div className="space-y-4 text-gray-700">
|
||||
<div className="flex items-start">
|
||||
<span className="font-semibold w-32">Email:</span>
|
||||
<ObfuscatedMailto email="support@qrmaster.net" className="text-blue-600 hover:underline" />
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="font-semibold w-32">Support hours:</span>
|
||||
<span>Monday–Friday, 9:00–17:00 CET</span>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="font-semibold w-32">Languages:</span>
|
||||
<span>English (EN) and German (DE)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 bg-blue-50 p-6 rounded-xl text-sm text-blue-800">
|
||||
<p className="font-semibold mb-2">Reaching out with a production issue?</p>
|
||||
<p>Please include:</p>
|
||||
<ul className="list-disc ml-5 mt-1 space-y-1 text-blue-700">
|
||||
<li>The QR code type (static/dynamic)</li>
|
||||
<li>Destination URL</li>
|
||||
<li>Any relevant campaign context</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Contact</h3>
|
||||
<div className="bg-gray-50 p-8 rounded-2xl border border-gray-100">
|
||||
<div className="space-y-4">
|
||||
<p className="font-bold text-gray-900 text-lg">QR Master</p>
|
||||
<p className="text-gray-600">
|
||||
1001 Blucher Street<br />
|
||||
Corpus Christi, Texas, USA
|
||||
</p>
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<p className="text-gray-600">
|
||||
<span className="font-medium text-gray-900">For general questions:</span><br />
|
||||
<ObfuscatedMailto email="support@qrmaster.net" className="text-blue-600 hover:underline" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-20 text-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-6">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
93
src/app/(main)/(marketing)/authors/[slug]/page.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import Script from "next/script";
|
||||
import { getAuthorBySlug, getPostsByAuthor } from "@/lib/content";
|
||||
import { authors } from "@/lib/author-data";
|
||||
import { authorPageSchema } from "@/lib/schema";
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const author = getAuthorBySlug(params.slug);
|
||||
if (!author) return {};
|
||||
return {
|
||||
title: `${author.name} - ${author.role} | QR Master`,
|
||||
description: author.bio
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return authors.map((author) => ({
|
||||
slug: author.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function AuthorPage({ params }: { params: { slug: string } }) {
|
||||
const author = getAuthorBySlug(params.slug);
|
||||
if (!author) return notFound();
|
||||
|
||||
const posts = getPostsByAuthor(params.slug);
|
||||
const jsonLd = authorPageSchema(author, posts);
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-3xl py-12 px-4 space-y-10">
|
||||
<Script id="ld-author" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
<header className="flex flex-col md:flex-row gap-8 items-center md:items-start text-center md:text-left">
|
||||
{author.image ? (
|
||||
<div className="relative w-32 h-32 flex-shrink-0">
|
||||
<Image
|
||||
src={author.image}
|
||||
alt={author.name}
|
||||
fill
|
||||
className="rounded-full object-cover border-4 border-white shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-32 h-32 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold text-4xl shadow-lg">
|
||||
{author.name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-extrabold text-gray-900">{author.name}</h1>
|
||||
<Image src="/logo.svg" alt="QR Master" width={24} height={24} className="opacity-80" />
|
||||
</div>
|
||||
<p className="text-lg text-blue-600 font-medium">{author.role}</p>
|
||||
<p className="text-gray-600 max-w-xl">{author.bio}</p>
|
||||
{!!author.sameAs?.length && (
|
||||
<div className="flex gap-4 justify-center md:justify-start pt-2">
|
||||
{author.sameAs.map((url) => {
|
||||
let label = "Profile";
|
||||
if (url.includes('linkedin')) label = "LinkedIn";
|
||||
if (url.includes('github')) label = "GitHub";
|
||||
if (url.includes('instagram')) label = "Instagram";
|
||||
if (url.includes('twitter') || url.includes('x.com')) label = "Twitter";
|
||||
return (
|
||||
<a key={url} href={url} target="_blank" rel="noreferrer" className="text-sm font-semibold text-gray-500 hover:text-black border-b border-transparent hover:border-black transition-colors">
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="border-t border-gray-100"></div>
|
||||
|
||||
<section className="space-y-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900">Latest Articles</h2>
|
||||
<div className="space-y-4">
|
||||
{posts.map(p => (
|
||||
<Link key={p.slug} href={`/blog/${p.slug}`} className="block group p-6 rounded-xl border border-gray-200 bg-white hover:border-blue-200 hover:shadow-sm transition-all">
|
||||
<div className="text-sm text-gray-400 mb-1">{p.date}</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 group-hover:text-blue-700 transition-colors mb-2">{p.title}</h3>
|
||||
<p className="text-gray-600">{p.description}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
110
src/app/(main)/(marketing)/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Script from "next/script";
|
||||
import { getPublishedPostBySlug, getAuthorBySlug, getRelatedPosts, getPublishedPosts } from "@/lib/content";
|
||||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||
import { StepList } from "@/components/aeo/StepList";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import { AuthorCard } from "@/components/author/AuthorCard";
|
||||
import { RelatedPosts } from "@/components/blog/RelatedPosts";
|
||||
import { blogPostingSchema, howToSchema, faqPageSchema } from "@/lib/schema";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return {};
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
// Only generate static params for published posts
|
||||
return getPublishedPosts().map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return notFound(); // STRICT 404 GATE
|
||||
|
||||
const author = getAuthorBySlug(post.authorSlug);
|
||||
const related = getRelatedPosts(post);
|
||||
|
||||
const jsonLd = blogPostingSchema(post, author);
|
||||
const howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
|
||||
const faqLd = post.faq ? faqPageSchema(post.faq) : null;
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-4xl py-12 px-4">
|
||||
<Script id="ld-blogposting" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
{howtoLd && (
|
||||
<Script id="ld-howto" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(howtoLd) }} />
|
||||
)}
|
||||
{faqLd && (
|
||||
<Script id="ld-faq" type="application/ld+json" strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
|
||||
)}
|
||||
|
||||
<header className="space-y-6 text-center max-w-3xl mx-auto mb-10">
|
||||
<div className="flex justify-center gap-2 text-sm text-gray-500 font-medium">
|
||||
<Link href="/learn" className="hover:text-blue-600">Hub</Link>
|
||||
<span>/</span>
|
||||
<Link href={`/learn/${post.pillar}`} className="hover:text-blue-600 capitalize">{post.pillar}</Link>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-extrabold text-gray-900 tracking-tight leading-tight">{post.title}</h1>
|
||||
<p className="text-xl text-gray-600 relaxed">{post.description}</p>
|
||||
|
||||
{author && (
|
||||
<div className="flex items-center justify-center gap-3 pt-4">
|
||||
{author.image ? (
|
||||
<Image src={author.image} alt={author.name} width={40} height={40} className="rounded-full" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold">
|
||||
{author.name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left text-sm">
|
||||
<div className="font-bold text-gray-900">{author.name}</div>
|
||||
<div className="text-gray-500">Updated {post.updatedAt || post.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{post.heroImage && (
|
||||
<div className="relative aspect-video w-full rounded-2xl overflow-hidden shadow-lg mb-10">
|
||||
<Image src={post.heroImage} alt={post.imageAlt || post.title} fill className="object-cover" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AEO BLOCK: ANSWER */}
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{post.quickAnswer && <AnswerBox html={post.quickAnswer} />}
|
||||
|
||||
{/* AEO BLOCK: STEPS (If defined separate from body) */}
|
||||
{!!post.keySteps?.length && <StepList steps={post.keySteps} title="Step-by-Step Guide" />}
|
||||
|
||||
|
||||
{/* MAIN CONTENT */}
|
||||
<article className="prose prose-lg prose-blue max-w-none text-gray-800" dangerouslySetInnerHTML={{ __html: post.content }} />
|
||||
|
||||
{/* AEO BLOCK: FAQ */}
|
||||
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
|
||||
|
||||
<div className="border-t border-gray-100 my-12"></div>
|
||||
|
||||
{author && <AuthorCard author={author} />}
|
||||
|
||||
<div className="mt-12">
|
||||
<RelatedPosts posts={related} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,11 @@ import { websiteSchema, breadcrumbSchema } from '@/lib/schema';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { blogPostList } from '@/lib/blog-data';
|
||||
import { blogPosts } from '@/lib/blog-data';
|
||||
|
||||
// Enable Incremental Static Regeneration (ISR)
|
||||
// Revalidate every hour
|
||||
export const revalidate = 3600;
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
@@ -19,7 +23,7 @@ function truncateAtWord(text: string, maxLength: number): string {
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Insights: Latest QR Strategies', 60);
|
||||
const description = truncateAtWord(
|
||||
'Expert guides on QR code analytics, dynamic vs static codes, bulk generation, and smart marketing use cases. Learn how to maximize your QR campaign ROI.',
|
||||
'Expert guides on QR code strategies, marketing campaigns, tracking analytics, and best practices for small businesses and enterprises.',
|
||||
160
|
||||
);
|
||||
|
||||
@@ -38,14 +42,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/blog',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Insights - QR Code Marketing & Analytics Blog',
|
||||
},
|
||||
],
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
@@ -54,14 +51,27 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function BlogPage() {
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Blog', url: '/blog' },
|
||||
];
|
||||
|
||||
// Filter posts to only show those published in the past or today
|
||||
// sort by date descending (newest first)
|
||||
const currentDate = new Date();
|
||||
|
||||
const publishedPosts = blogPosts
|
||||
.filter(post => {
|
||||
const publishDate = post.datePublished ? new Date(post.datePublished) : new Date(post.date);
|
||||
return publishDate <= currentDate;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const dateA = a.datePublished ? new Date(a.datePublished) : new Date(a.date);
|
||||
const dateB = b.datePublished ? new Date(b.datePublished) : new Date(b.date);
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[websiteSchema(), breadcrumbSchema(breadcrumbItems)]} />
|
||||
@@ -79,13 +89,13 @@ export default function BlogPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
{blogPostList.map((post: any) => (
|
||||
<Link key={post.slug} href={post.link || `/blog/${post.slug}`}>
|
||||
{publishedPosts.map((post) => (
|
||||
<Link key={post.slug} href={`/blog/${post.slug}`}>
|
||||
<Card hover className="h-full overflow-hidden shadow-md hover:shadow-xl transition-all duration-300">
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={`${post.title} - QR code guide showing ${post.category.toLowerCase()} strategies`}
|
||||
alt={post.imageAlt || `${post.title} - QR code guide`}
|
||||
width={800}
|
||||
height={600}
|
||||
className="w-full h-full object-cover transition-transform duration-500 hover:scale-110"
|
||||
@@ -102,9 +112,7 @@ export default function BlogPage() {
|
||||
<p className="text-gray-600 mb-4 leading-relaxed">{post.excerpt}</p>
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||
<p className="text-sm text-gray-500">{post.date}</p>
|
||||
<span className="text-primary-600 text-sm font-medium">
|
||||
{post.link ? 'Try Now →' : 'Read Article →'}
|
||||
</span>
|
||||
<span className="text-primary-600 text-sm font-medium">Read more →</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -8,8 +8,10 @@ import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bulk QR Code Generator | Create from Excel | QR Master',
|
||||
description: 'Generate hundreds of QR codes instantly from Excel/CSV. Create URLs, vCards, and text codes in bulk. Perfect for inventory, events, and product tagging.',
|
||||
title: {
|
||||
absolute: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
},
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding. Perfect for products and events.',
|
||||
keywords: 'bulk qr code generator, batch qr code, qr code from excel, csv qr code generator, mass qr code generation, bulk vcard qr code, bulk qr codes free',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
@@ -19,22 +21,15 @@ export const metadata: Metadata = {
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s of QR Codes from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, and inventory.',
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Bulk QR Code Generator - QR Master',
|
||||
},
|
||||
],
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s of QR Codes from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, and inventory.',
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,7 +49,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
title: 'Contact Cards',
|
||||
description: 'Create vCard QR codes with contact information',
|
||||
format: 'FirstName,LastName,Email,Phone,Organization,Title',
|
||||
example: 'John Doe,VCARD,John,Doe,john' + '@' + 'example.com,+1234567890,Company Inc,CEO',
|
||||
example: 'John Doe,VCARD,John,Doe,john[at]example.com,+1234567890,Company Inc,CEO',
|
||||
},
|
||||
{
|
||||
type: 'GEO',
|
||||
@@ -189,11 +184,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.8',
|
||||
ratingCount: '980',
|
||||
},
|
||||
|
||||
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, inventory management with custom branding.',
|
||||
featureList: [
|
||||
'Excel and CSV file import',
|
||||
@@ -448,7 +439,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="py-2 px-3">John Doe</td>
|
||||
<td className="py-2 px-3">VCARD</td>
|
||||
<td className="py-2 px-3">John,Doe,john{'@'}example.com,+1234567890,Company,CEO</td>
|
||||
<td className="py-2 px-3">John,Doe,john<span>@</span>example.com,+1234567890,Company,CEO</td>
|
||||
<td className="py-2 px-3">contact</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-200">
|
||||
@@ -504,6 +495,9 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
@@ -641,35 +635,26 @@ Product C,https://example.com/product-c,Budget Widget,electronics,sale`}
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
{/* CTA Section */}
|
||||
<section className="py-24 bg-slate-900 relative overflow-hidden">
|
||||
{/* Background Decorations */}
|
||||
<div className="absolute top-0 right-0 -mr-20 -mt-20 w-96 h-96 bg-blue-500/20 rounded-full blur-3xl opacity-50" />
|
||||
<div className="absolute bottom-0 left-0 -ml-20 -mb-20 w-80 h-80 bg-green-500/20 rounded-full blur-3xl opacity-50" />
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center relative z-10">
|
||||
<h2 className="text-4xl lg:text-5xl font-bold mb-6 text-white tracking-tight">
|
||||
Ready to Generate <span className="text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-400">1000s of Codes?</span>
|
||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Generate 1000s of QR Codes in Minutes
|
||||
</h2>
|
||||
<p className="text-xl mb-10 text-slate-300 leading-relaxed max-w-2xl mx-auto">
|
||||
Stop doing it manually. Upload your Excel file and get your QR codes in seconds. Professional, branded, and trackable.
|
||||
<p className="text-xl mb-8 text-green-100">
|
||||
Save hours of manual work. Upload your file and get all QR codes ready instantly.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-5 justify-center">
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-6 h-auto w-full sm:w-auto bg-white text-slate-900 hover:bg-slate-50 font-bold shadow-xl shadow-blue-900/20 transition-all hover:-translate-y-1">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-green-600 hover:bg-gray-100">
|
||||
Start Bulk Generation
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-6 h-auto w-full sm:w-auto border-slate-700 text-white hover:bg-slate-800 hover:border-slate-600 transition-all">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<p className="mt-8 text-sm text-slate-500">
|
||||
No credit card required for free trial.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
91
src/app/(main)/(marketing)/contact/page.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Contact Us | QR Master Support & Inquiries',
|
||||
description: 'Get in touch with QR Master support for technical help, billing questions, or partnership inquiries. We are here to help you scale your QR campaigns.',
|
||||
openGraph: {
|
||||
title: 'Contact QR Master | Support & Team',
|
||||
description: 'Need help with your QR codes? Reach out to our support team for assistance with dynamic codes, analytics, and more.',
|
||||
url: 'https://www.qrmaster.net/contact',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div className="px-4 py-5 sm:px-6">
|
||||
<h1 className="text-3xl font-bold leading-6 text-gray-900">
|
||||
Contact Us
|
||||
</h1>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
We are here to help.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-t border-gray-200">
|
||||
<div className="px-4 py-5 sm:p-6 space-y-6">
|
||||
|
||||
{/* Email Section */}
|
||||
<div>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Email Support
|
||||
</h3>
|
||||
<div className="mt-2 text-base text-gray-500">
|
||||
<p>For any inquiries, please email us at:</p>
|
||||
<div className="mt-1 text-primary-600 font-medium">
|
||||
<ObfuscatedMailto email="support@qrmaster.net" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address Section */}
|
||||
<div>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Business Address
|
||||
</h3>
|
||||
<div className="mt-2 text-base text-gray-500">
|
||||
<p>1001 Blucher Street</p>
|
||||
<p>Corpus Christi, Texas</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hours Section */}
|
||||
<div>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Support Hours
|
||||
</h3>
|
||||
<p className="mt-2 text-base text-gray-500">
|
||||
Monday - Friday, 9:00 AM - 5:00 PM CET
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Useful Links */}
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">
|
||||
Useful Links
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href="/faq" className="text-primary-600 hover:text-primary-500">
|
||||
Check our FAQ first →
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/" className="text-primary-600 hover:text-primary-500">
|
||||
Back to Home →
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
54
src/app/(main)/(marketing)/cookie-policy/page.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Cookie Policy | QR Master',
|
||||
description: 'Information about how QR Master uses cookies.',
|
||||
openGraph: {
|
||||
title: 'Cookie Policy | QR Master',
|
||||
url: 'https://www.qrmaster.net/cookie-policy',
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
|
||||
export default function CookiePolicyPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white py-12">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
|
||||
<div className="mb-8">
|
||||
<Link href="/" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Cookie Policy</h1>
|
||||
<p className="text-gray-600 mb-8">Last updated: January 2025</p>
|
||||
|
||||
<div className="prose prose-lg max-w-none text-gray-700">
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">What Are Cookies</h2>
|
||||
<p>
|
||||
As is common practice with almost all professional websites, this site uses cookies, which are tiny files that are downloaded to your computer, to improve your experience.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">How We Use Cookies</h2>
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li><strong>Essential Cookies:</strong> Required for the website to function (e.g., login, session management).</li>
|
||||
<li><strong>Analytics Cookies (Optional):</strong> We use tools like PostHog to understand how you use the site and improve it. These are only set with your consent.</li>
|
||||
<li><strong>Functionality Cookies:</strong> To remember your preferences.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Disabling Cookies</h2>
|
||||
<p>
|
||||
You can prevent the setting of cookies by adjusting the settings on your browser. Be aware that disabling cookies will affect the functionality of this and many other websites that you visit. Disabling essential cookies will result in disabling certain functionality and features of this site (like logging in).
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
661
src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
Normal file
@@ -0,0 +1,661 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import {
|
||||
Palette,
|
||||
Upload,
|
||||
Frame,
|
||||
Download,
|
||||
CheckCircle2,
|
||||
Smartphone,
|
||||
FileType,
|
||||
Zap,
|
||||
Shield,
|
||||
Eye
|
||||
} from 'lucide-react';
|
||||
import { MiniGenerator } from '@/components/marketing/MiniGenerator';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Custom QR Code Generator with Logo & Colors | QR Master',
|
||||
description: 'Create custom QR codes with your logo, brand colors, and unique frames. Free designer with instant preview. Download PNG/SVG. No signup needed to try.',
|
||||
keywords: [
|
||||
'custom qr code generator',
|
||||
'qr code with logo',
|
||||
'qr code generator with logo',
|
||||
'branded qr code generator',
|
||||
'custom qr code design',
|
||||
'qr code maker with logo',
|
||||
'personalized qr code',
|
||||
'design qr code online'
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
en: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Custom QR Code Generator with Logo & Brand Colors',
|
||||
description: 'Design unique QR codes with your logo, colors, and frames. Free, high-quality exports.',
|
||||
url: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
type: 'website',
|
||||
images: [{
|
||||
url: '/images/og/og-custom-qr-generator.png',
|
||||
width: 1200,
|
||||
height: 630
|
||||
}]
|
||||
},
|
||||
twitter: {
|
||||
title: 'Custom QR Code Generator with Logo & Brand Colors',
|
||||
description: 'Design unique QR codes with your logo, colors, and frames. Free, high-quality exports.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function CustomQRCodeGeneratorPage() {
|
||||
const features = [
|
||||
{
|
||||
icon: Upload,
|
||||
title: 'Logo Integration',
|
||||
description: 'Upload your logo or icon. Auto-resize and safe positioning. Transparent backgrounds supported.',
|
||||
},
|
||||
{
|
||||
icon: Palette,
|
||||
title: 'Custom Colors & Gradients',
|
||||
description: 'Match your brand palette. Solid colors or smooth gradients. Separate eye, pattern, frame colors.',
|
||||
},
|
||||
{
|
||||
icon: Frame,
|
||||
title: 'Custom Frames',
|
||||
description: '"Scan Me", "Learn More", "Follow Us". 20+ pre-designed frames. Custom text frames.',
|
||||
},
|
||||
{
|
||||
icon: FileType,
|
||||
title: 'Multiple Export Formats',
|
||||
description: 'PNG (high-res, up to 4000px). SVG (vector, infinitely scalable). PDF (print-ready).',
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: 'Scan-Safe Design Engine',
|
||||
description: 'Automatic error correction (Level H - 30%). Logo placement tested for scannability. Works on all devices.',
|
||||
},
|
||||
{
|
||||
icon: Eye,
|
||||
title: 'Instant Preview',
|
||||
description: 'See changes in real-time. Test scan with your phone. What you see is what you get.',
|
||||
},
|
||||
];
|
||||
|
||||
const designExamples = [
|
||||
{
|
||||
name: 'E-Commerce',
|
||||
description: 'Logo center, gradient purple-blue',
|
||||
image: '/images/examples/qr_example_ecommerce_1769163606650.png',
|
||||
category: 'Retail',
|
||||
},
|
||||
{
|
||||
name: 'Restaurant Menu',
|
||||
description: 'Food icon, warm colors, "Menu" frame',
|
||||
image: '/images/examples/qr_example_restaurant_1769163621640.png',
|
||||
category: 'Hospitality',
|
||||
},
|
||||
{
|
||||
name: 'Event Ticket',
|
||||
description: 'Gradient, geometric pattern, "Scan to Enter"',
|
||||
image: '/images/examples/qr_example_event_1769163635077.png',
|
||||
category: 'Events',
|
||||
},
|
||||
{
|
||||
name: 'Business Card',
|
||||
description: 'Minimalist, corporate blue/gray',
|
||||
image: '/images/examples/qr_example_business_1769163647196.png',
|
||||
category: 'Professional',
|
||||
},
|
||||
{
|
||||
name: 'Social Media',
|
||||
description: 'Instagram-style gradient, "Follow Us"',
|
||||
image: '/images/examples/qr_example_social_1769163660794.png',
|
||||
category: 'Social',
|
||||
},
|
||||
{
|
||||
name: 'Product Packaging',
|
||||
description: 'Premium black & gold, brand logo',
|
||||
image: '/images/examples/qr_example_packaging_1769163674382.png',
|
||||
category: 'Luxury',
|
||||
},
|
||||
{
|
||||
name: 'Real Estate',
|
||||
description: 'House icon, professional blue, "View Listing"',
|
||||
image: '/images/examples/qr_example_realestate_1769163689433.png',
|
||||
category: 'Real Estate',
|
||||
},
|
||||
{
|
||||
name: 'Non-Profit',
|
||||
description: 'Heart icon, warm gradient, "Donate Now"',
|
||||
image: '/images/examples/qr_example_nonprofit_1769163705055.png',
|
||||
category: 'Charity',
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
icon: '📢',
|
||||
title: 'Marketing Campaigns',
|
||||
description: 'Print ads, flyers, billboards. Match campaign branding. Track by design/location.',
|
||||
},
|
||||
{
|
||||
icon: '💼',
|
||||
title: 'Business Cards',
|
||||
description: 'vCard QR with your logo. Professional first impression. Easy contact sharing.',
|
||||
},
|
||||
{
|
||||
icon: '📦',
|
||||
title: 'Product Packaging',
|
||||
description: 'QR codes on labels and boxes. Link to manuals, videos, support. Premium branded look.',
|
||||
},
|
||||
{
|
||||
icon: '🎉',
|
||||
title: 'Events & Conferences',
|
||||
description: 'Branded event QR codes. Registration, schedules, networking. Consistent event identity.',
|
||||
},
|
||||
];
|
||||
|
||||
const comparison = [
|
||||
{ feature: 'Colors', generic: 'Black & white only', branded: 'Your brand colors' },
|
||||
{ feature: 'Logo', generic: 'No logo', branded: 'Your logo integrated' },
|
||||
{ feature: 'Design', generic: 'Generic look', branded: 'Professional design' },
|
||||
{ feature: 'Brand Recognition', generic: 'Low', branded: 'Instant brand trust' },
|
||||
{ feature: 'Campaign Tracking', generic: 'Harder to track', branded: 'Campaign-specific branding' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Can I add my company logo to a QR code?',
|
||||
answer: 'Yes! Upload your logo (PNG, SVG, JPG) and we\'ll automatically position it safely in the center. The logo is sized to maintain scannability using error correction.',
|
||||
},
|
||||
{
|
||||
question: 'Will my QR code still work with a logo and custom colors?',
|
||||
answer: 'Absolutely. Our generator uses advanced error correction (Level H - 30% redundancy) to ensure your QR code remains scannable even with logos and colors.',
|
||||
},
|
||||
{
|
||||
question: 'What file formats can I download?',
|
||||
answer: 'High-resolution PNG (up to 4000x4000px), scalable SVG (vector), or print-ready PDF.',
|
||||
},
|
||||
{
|
||||
question: 'Can I use gradients instead of solid colors?',
|
||||
answer: 'Yes! Choose from preset gradients or create custom linear/radial gradients.',
|
||||
},
|
||||
{
|
||||
question: 'How do I know if my custom QR code works?',
|
||||
answer: 'Use the built-in preview and test scan with your phone camera before downloading.',
|
||||
},
|
||||
{
|
||||
question: 'Is there a limit on logo size?',
|
||||
answer: 'Logos up to 5MB. We automatically resize for optimal scanning. Transparent backgrounds (PNG) work best.',
|
||||
},
|
||||
{
|
||||
question: 'Can I customize the QR code frame?',
|
||||
answer: 'Yes! Choose from 20+ frames ("Scan Me", "Learn More") or create custom text frames.',
|
||||
},
|
||||
{
|
||||
question: 'Are custom QR codes free?',
|
||||
answer: 'Static custom QR codes (URL, text, vCard, WiFi) are completely free forever. Dynamic QR codes (editable, trackable) require a paid plan.',
|
||||
},
|
||||
{
|
||||
question: 'Can I change the corner style?',
|
||||
answer: 'Yes! Customize corner dots, eyes, and patterns. Choose square, rounded, or circular styles.',
|
||||
},
|
||||
{
|
||||
question: 'What\'s the difference between static and dynamic custom QR codes?',
|
||||
answer: 'Both can be customized with logos/colors. Static = data encoded directly (free, permanent). Dynamic = redirect URL (editable, trackable, requires account).',
|
||||
},
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#software',
|
||||
name: 'QR Master - Custom QR Code Generator with Logo',
|
||||
applicationCategory: 'DesignApplication',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'EUR',
|
||||
},
|
||||
featureList: [
|
||||
'Upload custom logo',
|
||||
'Custom brand colors and gradients',
|
||||
'20+ frame designs',
|
||||
'High-resolution PNG downloads',
|
||||
'Scalable SVG exports',
|
||||
'Print-ready PDF output',
|
||||
'Real-time preview',
|
||||
'Scan-safe design validation',
|
||||
],
|
||||
};
|
||||
|
||||
const howToSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#howto',
|
||||
name: 'How to Create a Custom QR Code with Logo',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Choose QR Code Type',
|
||||
text: 'Select URL, vCard, WiFi, or other QR code type.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Upload Your Logo',
|
||||
text: 'Upload your company logo (PNG, SVG, JPG). Auto-positioned safely.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize Colors & Frame',
|
||||
text: 'Choose brand colors, gradients, and frame style.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Download as high-res PNG, SVG, or PDF. Ready to print or share.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#faq',
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center mt-8">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-purple-100 text-purple-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>✨</span>
|
||||
<span>Free Static QR Codes Forever</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
Custom QR Code Generator with Logo
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Add your logo, choose custom colors, and design unique frames. Professional QR codes in minutes – try it free, no signup required.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
'No signup required to test',
|
||||
'High-resolution downloads',
|
||||
'Tested for scannability',
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-green-500 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Try Designer Now
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="#examples"
|
||||
className="inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500 px-6 py-3 text-lg w-full sm:w-auto"
|
||||
>
|
||||
See Examples
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview Card */}
|
||||
<div className="relative">
|
||||
<Card className="p-8 shadow-2xl h-full min-h-[500px]">
|
||||
<MiniGenerator />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Scan-Safe Technology Section */}
|
||||
<section className="py-16 bg-blue-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Your Logo Won't Break the QR Code – Here's Why
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
QR codes have built-in error correction. Our generator uses the highest level (H = 30% redundancy), which means up to 30% of the code can be covered or damaged and still scan perfectly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-8">
|
||||
<div className="prose max-w-none">
|
||||
<p className="text-gray-700 mb-6">
|
||||
When you add a logo, we automatically:
|
||||
</p>
|
||||
<ul className="space-y-3 mb-6">
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Calculate safe placement zones</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Size your logo to stay within error correction limits</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Test scannability before download</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg p-6 mt-6">
|
||||
<p className="text-sm text-gray-600 mb-2 font-semibold">Technical Details:</p>
|
||||
<p className="text-sm text-gray-700">
|
||||
Based on DENSO WAVE QR Code specification (ISO/IEC 18004). Error correction levels: L (7%), M (15%), Q (25%), H (30%). We use Level H for maximum reliability with custom designs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Everything You Need for Branded QR Codes
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<feature.icon className="w-10 h-10 text-primary-600 mb-4" />
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">{feature.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Design Gallery */}
|
||||
<section id="examples" className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Get Inspired: Custom QR Code Examples
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Real examples of custom QR codes for different industries and use cases
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{designExamples.map((example, index) => (
|
||||
<Card key={index} className="p-4 hover:shadow-xl transition-shadow group">
|
||||
<div className="relative aspect-square mb-4 bg-white rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={example.image}
|
||||
alt={`${example.name} - ${example.description}`}
|
||||
fill
|
||||
className="object-contain p-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-semibold text-primary-600 uppercase">
|
||||
{example.category}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900">{example.name}</h3>
|
||||
<p className="text-sm text-gray-600">{example.description}</p>
|
||||
<Link href="/signup">
|
||||
<Button variant="outline" size="sm" className="w-full mt-3">
|
||||
Use This Style
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Create Your Custom QR Code in 4 Steps
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{[
|
||||
{
|
||||
step: 1,
|
||||
title: 'Choose QR Type & Enter Data',
|
||||
description: 'URL, vCard, WiFi, Text, Email, SMS. Enter your destination.',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Upload Your Logo',
|
||||
description: 'Drag & drop PNG/SVG/JPG. Auto-positioned and sized. Or choose from icon library.',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Customize Design',
|
||||
description: 'Pick brand colors or gradients. Choose frame style ("Scan Me", custom text). Adjust corner shapes.',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: 'Download & Use',
|
||||
description: 'High-res PNG or vector SVG. Print-ready quality. Scan-tested and verified.',
|
||||
},
|
||||
].map((step, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0 w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xl font-bold text-primary-600">{step.step}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">{step.title}</h3>
|
||||
<p className="text-gray-600">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Comparison Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Why Custom QR Codes Perform Better
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Feature</th>
|
||||
<th className="px-6 py-4 text-center text-gray-900 font-semibold">Generic QR Code</th>
|
||||
<th className="px-6 py-4 text-center text-primary-600 font-semibold">Custom Branded QR Code</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{comparison.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4 text-gray-900 font-medium">{row.feature}</td>
|
||||
<td className="px-6 py-4 text-center text-gray-600">❌ {row.generic}</td>
|
||||
<td className="px-6 py-4 text-center text-primary-600 font-semibold">✅ {row.branded}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
|
||||
<div className="mt-8 bg-blue-50 border border-blue-200 rounded-lg p-6">
|
||||
<p className="text-sm text-gray-700">
|
||||
<strong>Note:</strong> Branded QR codes can improve brand recognition and recall, user trust and engagement, and campaign tracking and attribution.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Perfect For Every Industry
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-6 text-center hover:shadow-lg transition-shadow">
|
||||
<div className="text-5xl mb-4">{useCase.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">{useCase.title}</h3>
|
||||
<p className="text-gray-600">{useCase.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{faqs.map((faq, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="text-gray-600">{faq.answer}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trust Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-8">
|
||||
Secure and Reliable
|
||||
</h2>
|
||||
<div className="flex flex-wrap justify-center gap-8">
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Your data is never stored</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Works on all modern devices and QR scanners</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Used by businesses and creators worldwide</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Branded QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-purple-100">
|
||||
Free static QR codes with logo, colors, and frames. No signup required to try. Upgrade for tracking and bulk generation.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100"
|
||||
>
|
||||
Try Designer Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10"
|
||||
>
|
||||
See Pricing for Dynamic Codes
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,10 +6,13 @@ import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Dynamic QR Code Generator | Edit & Track QR | QR Master',
|
||||
description: 'Create editable dynamic QR codes. Update destination URLs, track scans, and manage content anytime without reprinting. Free generator with analytics.',
|
||||
title: {
|
||||
absolute: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
},
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change destination URLs and track scans without reprinting. Free generator with advanced features.',
|
||||
keywords: 'dynamic qr code generator, editable qr code, dynamic qr code, free dynamic qr code, qr code generator dynamic, best dynamic qr code generator',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
@@ -19,21 +22,14 @@ export const metadata: Metadata = {
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Dynamic QR Code Generator - Edit QR Codes Anytime | QR Master',
|
||||
title: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change URLs, track scans, and update content anytime.',
|
||||
url: 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Dynamic QR Code Generator - QR Master',
|
||||
},
|
||||
],
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'Dynamic QR Code Generator - Edit QR Codes Anytime | QR Master',
|
||||
title: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change URLs, track scans, and update content anytime.',
|
||||
},
|
||||
};
|
||||
@@ -150,11 +146,7 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.9',
|
||||
ratingCount: '2150',
|
||||
},
|
||||
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change destination URLs, track scans, and update content without reprinting.',
|
||||
featureList: [
|
||||
'Edit QR codes after printing',
|
||||
@@ -335,6 +327,34 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Answer First Block (SEO/AEO) */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<AnswerFirstBlock
|
||||
whatIsIt="A dynamic QR code points to a redirect URL, allowing you to change the final destination anytime without reprinting. Unlike static codes, it enables real-time scan tracking (location, device, time) and advanced campaign management."
|
||||
whenToUse={[
|
||||
"Update destination URLs instantly (e.g., for menus, promotions, or out-of-stock items)",
|
||||
"Track precise scan metrics to measure campaign ROI (location, device type, unique scans)",
|
||||
"Keep the QR pattern simple and scannable, even for long, complex URLs"
|
||||
]}
|
||||
comparison={{
|
||||
leftTitle: "Static QR",
|
||||
rightTitle: "Dynamic QR",
|
||||
items: [
|
||||
{ label: "Target URL", value: true, text: "Fixed" },
|
||||
{ label: "Scan Tracking", value: true, text: "None" },
|
||||
{ label: "Printing", value: true, text: "One-time" }
|
||||
]
|
||||
}}
|
||||
howTo={{
|
||||
steps: [
|
||||
"Sign up for a free QR Master account (required for management)",
|
||||
"Select 'Dynamic URL' and enter your destination link",
|
||||
"Download the QR code and print it. Log in to dashboard to update the target URL anytime."
|
||||
]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Static vs Dynamic */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||