feat: implement WiFi QR code generator and strategy docs
This commit is contained in:
245
src/app/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
Normal file
245
src/app/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef } from "react";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { Wifi, Lock, Eye, EyeOff, Check, AlertTriangle, Download, Printer } from "lucide-react";
|
||||
import * as htmlToImage from "html-to-image";
|
||||
|
||||
export default function WiFiGenerator() {
|
||||
const [ssid, setSsid] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [encryption, setEncryption] = useState("WPA");
|
||||
const [hidden, setHidden] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
// For QR generation string
|
||||
const qrValue = `WIFI:S:${ssid};T:${encryption};P:${password};H:${hidden};;`;
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = (format: "png" | "svg") => {
|
||||
if (!qrRef.current) return;
|
||||
|
||||
if (format === "svg") {
|
||||
const svg = qrRef.current.querySelector("svg");
|
||||
if (svg) {
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([svgData], { type: "image/svg+xml" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `wifi-qr-${ssid || "network"}.svg`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
} else {
|
||||
htmlToImage
|
||||
.toPng(qrRef.current)
|
||||
.then(function (dataUrl) {
|
||||
const link = document.createElement("a");
|
||||
link.download = `wifi-qr-${ssid || "network"}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("oops, something went wrong!", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 -mt-20 relative z-10">
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-indigo-500/10 overflow-hidden border border-slate-100/50 backdrop-blur-xl">
|
||||
<div className="grid lg:grid-cols-12 gap-0">
|
||||
|
||||
{/* LEFT COLUMN - FORM */}
|
||||
<div className="lg:col-span-7 p-8 sm:p-10 lg:p-12 space-y-8 bg-white">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-slate-800 flex items-center gap-2">
|
||||
Network Details
|
||||
</h2>
|
||||
<p className="text-slate-500 mt-1">
|
||||
Enter your WiFi credentials to create a QR code.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
|
||||
{/* SSID Input */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 ml-1">
|
||||
Network Name (SSID)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Wifi className="h-5 w-5 text-slate-400 group-focus-within:text-indigo-500 transition-colors" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={ssid}
|
||||
onChange={(e) => setSsid(e.target.value)}
|
||||
placeholder="e.g. Office_Guest_5G"
|
||||
className="block w-full pl-10 pr-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Password Input */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 ml-1">
|
||||
Password
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Lock className="h-5 w-5 text-slate-400 group-focus-within:text-indigo-500 transition-colors" />
|
||||
</div>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Your WiFi Password"
|
||||
className="block w-full pl-10 pr-12 py-3 bg-slate-50 border border-slate-200 rounded-xl text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all shadow-sm font-mono"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-5 w-5 text-slate-400 hover:text-slate-600 cursor-pointer transition-colors" />
|
||||
) : (
|
||||
<Eye className="h-5 w-5 text-slate-400 hover:text-slate-600 cursor-pointer transition-colors" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Encryption & Hidden */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 ml-1">
|
||||
Encryption
|
||||
</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={encryption}
|
||||
onChange={(e) => setEncryption(e.target.value)}
|
||||
className="block w-full pl-4 pr-10 py-3 bg-slate-50 border border-slate-200 rounded-xl text-slate-900 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all shadow-sm appearance-none"
|
||||
>
|
||||
<option value="WPA">WPA / WPA2 / WPA3</option>
|
||||
<option value="WEP">WEP</option>
|
||||
<option value="nopass">None (Open Network)</option>
|
||||
</select>
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-4 text-slate-500">
|
||||
<svg className="h-4 w-4 fill-current" viewBox="0 0 20 20"><path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 pt-6 sm:pt-0 sm:mt-8">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
id="hidden-network"
|
||||
type="checkbox"
|
||||
checked={hidden}
|
||||
onChange={(e) => setHidden(e.target.checked)}
|
||||
className="h-5 w-5 text-indigo-600 border-slate-300 rounded focus:ring-indigo-500 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<label htmlFor="hidden-network" className="text-sm font-medium text-slate-700 cursor-pointer select-none">
|
||||
Hidden Network
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<button
|
||||
onClick={() => window.scrollTo({ top: 400, behavior: 'smooth' })}
|
||||
id="generate-btn"
|
||||
className="w-full group relative flex justify-center py-4 px-4 border border-transparent text-lg font-semibold rounded-xl text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all shadow-lg shadow-indigo-600/30 hover:shadow-indigo-600/50"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<svg className="w-5 h-5 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Generate QR Code
|
||||
</span>
|
||||
</button>
|
||||
<p className="text-xs text-center text-slate-400 mt-3 flex items-center justify-center gap-1">
|
||||
<Lock className="w-3 h-3" /> All data stays on your device
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT COLUMN - PREVIEW */}
|
||||
<div className="lg:col-span-5 bg-slate-50 border-l border-slate-100 p-8 sm:p-10 flex flex-col items-center justify-center relative overflow-hidden">
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-40" style={{
|
||||
backgroundImage: 'radial-gradient(#cbd5e1 1px, transparent 1px)',
|
||||
backgroundSize: '24px 24px'
|
||||
}}></div>
|
||||
|
||||
<div className="relative z-10 w-full max-w-sm mx-auto text-center space-y-6">
|
||||
<div>
|
||||
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-widest mb-4">Live Preview</h3>
|
||||
|
||||
{/* QR Card */}
|
||||
<div className="bg-white p-6 rounded-2xl shadow-xl shadow-slate-200/50 border border-slate-100 inline-block mb-2 transform transition-transform hover:scale-105 duration-300" ref={qrRef}>
|
||||
<div className="bg-white p-2 rounded-lg">
|
||||
<QRCodeSVG
|
||||
value={qrValue}
|
||||
size={200}
|
||||
level="H"
|
||||
includeMargin={false}
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Actions */}
|
||||
<div className="grid grid-cols-3 gap-3 w-full">
|
||||
<button onClick={() => handleDownload("png")} className="flex items-center justify-center px-4 py-2 border border-slate-200 shadow-sm text-sm font-medium rounded-lg text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors">
|
||||
PNG
|
||||
</button>
|
||||
<button onClick={() => handleDownload("svg")} className="flex items-center justify-center px-4 py-2 border border-slate-200 shadow-sm text-sm font-medium rounded-lg text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors">
|
||||
SVG
|
||||
</button>
|
||||
<button onClick={() => window.print()} className="flex items-center justify-center px-4 py-2 border border-slate-200 shadow-sm text-sm font-medium rounded-lg text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors">
|
||||
Print
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button onClick={() => window.print()} className="w-full flex items-center justify-center px-4 py-3 border border-indigo-100 text-sm font-semibold rounded-xl text-indigo-700 bg-indigo-50 hover:bg-indigo-100 transition-colors gap-2">
|
||||
<Printer className="w-4 h-4" />
|
||||
Print WiFi Card Template
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* UPSELL BOX */}
|
||||
<div className="mt-8 bg-amber-50 rounded-2xl p-6 border border-amber-100 flex flex-col sm:flex-row items-center justify-between gap-4 max-w-4xl mx-auto shadow-sm">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 bg-amber-100 rounded-full shrink-0 text-amber-600">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-slate-800">Change passwords often?</h4>
|
||||
<p className="text-slate-600 text-sm mt-1 max-w-md">
|
||||
Note: This is a static QR Code. If you update your WiFi password, you'll need to reprint this code.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/dashboard" className="w-full sm:w-auto px-6 py-3 bg-amber-500 hover:bg-amber-600 text-white font-semibold rounded-xl transition-colors shadow-lg shadow-amber-500/20 whitespace-nowrap text-center">
|
||||
Try Dynamic Codes
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
src/app/(marketing)/tools/wifi-qr-code/page.tsx
Normal file
113
src/app/(marketing)/tools/wifi-qr-code/page.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import WiFiGenerator from './WiFiGenerator';
|
||||
import { Wifi, Shield, Zap } from 'lucide-react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Free WiFi QR Code Generator | Connect Instantly & Securely',
|
||||
description: 'Create a free QR code for your WiFi network. Instant connection for guests, 100% client-side security (your password never leaves your browser). No app needed.',
|
||||
alternates: {
|
||||
canonical: 'https://qrmaster.com/tools/wifi-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free WiFi QR Code Generator | Connect Instantly',
|
||||
description: 'Share your WiFi without sharing your password. Generate a secure QR code in seconds.',
|
||||
type: 'website',
|
||||
}
|
||||
};
|
||||
|
||||
export default function WiFiQRCodePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 font-sans">
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative bg-gradient-to-br from-indigo-600 to-blue-500 pt-32 pb-48 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||
{/* Background Decorative Elements */}
|
||||
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0">
|
||||
<div className="absolute top-0 left-1/4 w-96 h-96 bg-white/10 rounded-full blur-3xl -translate-y-1/2"></div>
|
||||
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-indigo-900/20 rounded-full blur-3xl translate-y-1/2"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 max-w-4xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
||||
</span>
|
||||
Free Tool
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6 drop-shadow-sm">
|
||||
Secure WiFi QR Code <br className="hidden sm:block" /> Generator
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
Connect guests instantly without typing passwords. <br />
|
||||
<span className="font-semibold text-white">100% Client-side & Private.</span>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-lg backdrop-blur-sm border border-white/5">
|
||||
<Shield className="w-4 h-4 text-emerald-400" />
|
||||
100% Private
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-lg backdrop-blur-sm border border-white/5">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Generation
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-lg backdrop-blur-sm border border-white/5">
|
||||
<Wifi className="w-4 h-4 text-blue-400" />
|
||||
Always Free
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR APP SECTION */}
|
||||
<WiFiGenerator />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="max-w-3xl mx-auto px-4 sm:px-6 py-24">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-slate-800">Frequently Asked Questions</h2>
|
||||
<p className="text-slate-500 mt-2">Everything you need to know about WiFi QR codes.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Is it safe to type my password here?"
|
||||
answer="Yes, absolutely. This tool uses 'Client-Side Encryption', meaning your WiFi credentials never leave your browser. They are processed locally on your device to generate the QR code image. We never see, store, or transmit your password."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does this work on both iPhone and Android?"
|
||||
answer="Yes! Most modern smartphones (iOS and Android) have a built-in QR code scanner in their camera app. Guests just need to point their camera at the code and tap the notification to join your network."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do WiFi QR codes expire?"
|
||||
answer="No, static WiFi QR codes never expire. However, if you change your WiFi password or network name (SSID), the old QR code will stop working and you will need to generate a new one."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I hide my WiFi network?"
|
||||
answer="Yes, key in your hidden network credentials and check the 'Hidden Network' box. The generated QR code will contain the necessary instructions for devices to find and connect to your hidden network."
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string, answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-6 cursor-pointer list-none text-slate-800 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180">
|
||||
<svg fill="none" height="24" shapeRendering="geometricPrecision" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-6 pb-6 pt-0 leading-relaxed">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user