Compare commits
6 Commits
65fe18a718
...
231a85ffa4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
231a85ffa4 | ||
|
|
673eaf7fd3 | ||
|
|
30b1b12e74 | ||
|
|
139b87fe93 | ||
|
|
8257866138 | ||
|
|
8de1411e34 |
@@ -11,6 +11,7 @@ import { showToast } from '@/components/ui/Toast';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { toPng, toSvg, toBlob } from 'html-to-image';
|
import { toPng, toSvg, toBlob } from 'html-to-image';
|
||||||
import { trackEvent } from '@/components/PostHogProvider';
|
import { trackEvent } from '@/components/PostHogProvider';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -54,6 +55,7 @@ export default function BarcodeGeneratorClient() {
|
|||||||
const [lineColor, setLineColor] = useState('#000000');
|
const [lineColor, setLineColor] = useState('#000000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const barcodeRef = useRef<HTMLDivElement>(null);
|
const barcodeRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -109,6 +111,7 @@ export default function BarcodeGeneratorClient() {
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
|
|
||||||
showToast(`Barcode downloaded as ${extension.toUpperCase()}`, 'success');
|
showToast(`Barcode downloaded as ${extension.toUpperCase()}`, 'success');
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
trackEvent('barcode_downloaded', {
|
trackEvent('barcode_downloaded', {
|
||||||
format: format,
|
format: format,
|
||||||
extension: extension,
|
extension: extension,
|
||||||
@@ -118,7 +121,6 @@ export default function BarcodeGeneratorClient() {
|
|||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
showToast('Download failed', 'error');
|
showToast('Download failed', 'error');
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const copyBarcode = async () => {
|
const copyBarcode = async () => {
|
||||||
if (!barcodeRef.current) return;
|
if (!barcodeRef.current) return;
|
||||||
@@ -160,6 +162,8 @@ export default function BarcodeGeneratorClient() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -453,5 +457,6 @@ export default function BarcodeGeneratorClient() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -45,6 +46,7 @@ export default function PhoneGenerator() {
|
|||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ export default function PhoneGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -82,6 +85,8 @@ export default function PhoneGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -243,5 +248,6 @@ export default function PhoneGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import AdBanner from '@/components/ads/AdBanner';
|
import AdBanner from '@/components/ads/AdBanner';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -58,6 +59,7 @@ export default function CryptoGenerator() {
|
|||||||
const [qrMode, setQrMode] = useState<'universal' | 'wallet'>('universal');
|
const [qrMode, setQrMode] = useState<'universal' | 'wallet'>('universal');
|
||||||
const [qrColor, setQrColor] = useState('#F7931A');
|
const [qrColor, setQrColor] = useState('#F7931A');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -123,6 +125,7 @@ export default function CryptoGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -131,6 +134,8 @@ export default function CryptoGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -370,5 +375,6 @@ export default function CryptoGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -43,6 +44,7 @@ const FRAME_OPTIONS = [
|
|||||||
|
|
||||||
export default function EmailGenerator() {
|
export default function EmailGenerator() {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
email: '',
|
email: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
body: ''
|
body: ''
|
||||||
@@ -88,6 +90,7 @@ export default function EmailGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -100,6 +103,8 @@ export default function EmailGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -292,5 +297,6 @@ export default function EmailGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -51,6 +52,7 @@ export default function EventGenerator() {
|
|||||||
|
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -102,6 +104,7 @@ export default function EventGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -110,6 +113,8 @@ export default function EventGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -326,5 +331,6 @@ export default function EventGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -46,6 +47,7 @@ export default function FacebookGenerator() {
|
|||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#1877F2'); // Default to FB Blue
|
const [qrColor, setQrColor] = useState('#1877F2'); // Default to FB Blue
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ export default function FacebookGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -81,6 +84,8 @@ export default function FacebookGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -243,5 +248,6 @@ export default function FacebookGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -46,6 +47,7 @@ export default function GeolocationGenerator() {
|
|||||||
const [longitude, setLongitude] = useState('');
|
const [longitude, setLongitude] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ export default function GeolocationGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -101,6 +104,8 @@ export default function GeolocationGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -288,5 +293,6 @@ export default function GeolocationGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { toPng } from 'html-to-image';
|
|||||||
import { Star, Download, AlertCircle } from 'lucide-react';
|
import { Star, Download, AlertCircle } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
const QR_COLORS = [
|
const QR_COLORS = [
|
||||||
{ name: 'Google Blue', value: '#1A73E8' },
|
{ name: 'Google Blue', value: '#1A73E8' },
|
||||||
@@ -41,6 +42,7 @@ export default function GoogleReviewGenerator() {
|
|||||||
const [qrColor, setQrColor] = useState('#1A73E8');
|
const [qrColor, setQrColor] = useState('#1A73E8');
|
||||||
const [frameType, setFrameType] = useState('review');
|
const [frameType, setFrameType] = useState('review');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -76,12 +78,15 @@ export default function GoogleReviewGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const frameLabel = FRAME_OPTIONS.find(f => f.id === frameType && f.id !== 'none')?.label ?? null;
|
const frameLabel = FRAME_OPTIONS.find(f => f.id === frameType && f.id !== 'none')?.label ?? null;
|
||||||
const isReady = reviewUrl && !error && isValidGoogleReviewLink(reviewUrl);
|
const isReady = reviewUrl && !error && isValidGoogleReviewLink(reviewUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||||
<div className="grid lg:grid-cols-2">
|
<div className="grid lg:grid-cols-2">
|
||||||
@@ -209,5 +214,6 @@ export default function GoogleReviewGenerator() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -45,6 +46,7 @@ export default function InstagramGenerator() {
|
|||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#E1306C');
|
const [qrColor, setQrColor] = useState('#E1306C');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export default function InstagramGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -86,6 +89,8 @@ export default function InstagramGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -248,5 +253,6 @@ export default function InstagramGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors - PayPal Blue
|
// Brand Colors - PayPal Blue
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -64,6 +65,7 @@ export default function PayPalGenerator() {
|
|||||||
const [currency, setCurrency] = useState('EUR');
|
const [currency, setCurrency] = useState('EUR');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -114,6 +116,7 @@ export default function PayPalGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -122,6 +125,8 @@ export default function PayPalGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -338,5 +343,6 @@ export default function PayPalGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -46,6 +47,7 @@ export default function SMSGenerator() {
|
|||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ export default function SMSGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -84,6 +87,8 @@ export default function SMSGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -262,5 +267,6 @@ export default function SMSGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors - Microsoft Teams Purple
|
// Brand Colors - Microsoft Teams Purple
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -53,6 +54,7 @@ export default function TeamsGenerator() {
|
|||||||
const [userEmail, setUserEmail] = useState('');
|
const [userEmail, setUserEmail] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ export default function TeamsGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -104,6 +107,8 @@ export default function TeamsGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -314,5 +319,6 @@ export default function TeamsGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -45,6 +46,7 @@ export default function TextGenerator() {
|
|||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ export default function TextGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -80,6 +83,8 @@ export default function TextGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -241,5 +246,6 @@ export default function TextGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -45,6 +46,7 @@ export default function TiktokGenerator() {
|
|||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#000000');
|
const [qrColor, setQrColor] = useState('#000000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export default function TiktokGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -86,6 +89,8 @@ export default function TiktokGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -248,5 +253,6 @@ export default function TiktokGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -45,6 +46,7 @@ export default function TwitterGenerator() {
|
|||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#000000');
|
const [qrColor, setQrColor] = useState('#000000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export default function TwitterGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -86,6 +89,8 @@ export default function TwitterGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -248,5 +253,6 @@ export default function TwitterGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -46,6 +47,7 @@ export default function URLGenerator() {
|
|||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ export default function URLGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -80,6 +83,8 @@ export default function URLGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -241,5 +246,6 @@ export default function URLGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -63,6 +64,7 @@ export default function VCardGenerator() {
|
|||||||
|
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -108,6 +110,7 @@ export default function VCardGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -116,6 +119,8 @@ export default function VCardGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-6xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-6xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -344,5 +349,6 @@ export default function VCardGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Textarea } from '@/components/ui/Textarea';
|
import { Textarea } from '@/components/ui/Textarea';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ export default function WhatsappGenerator() {
|
|||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#25D366');
|
const [qrColor, setQrColor] = useState('#25D366');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -83,6 +85,7 @@ export default function WhatsappGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -91,6 +94,8 @@ export default function WhatsappGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -263,5 +268,6 @@ export default function WhatsappGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Select } from '@/components/ui/Select';
|
import { Select } from '@/components/ui/Select';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -55,6 +56,7 @@ export default function WiFiGenerator() {
|
|||||||
// Customization
|
// Customization
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ export default function WiFiGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -92,6 +95,8 @@ export default function WiFiGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -303,5 +308,6 @@ export default function WiFiGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors
|
// Brand Colors
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -44,6 +45,7 @@ export default function YoutubeGenerator() {
|
|||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [qrColor, setQrColor] = useState('#FF0000');
|
const [qrColor, setQrColor] = useState('#FF0000');
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ export default function YoutubeGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -79,6 +82,8 @@ export default function YoutubeGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -241,5 +246,6 @@ export default function YoutubeGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||||
|
|
||||||
// Brand Colors - Zoom Blue
|
// Brand Colors - Zoom Blue
|
||||||
const BRAND = {
|
const BRAND = {
|
||||||
@@ -46,6 +47,7 @@ export default function ZoomGenerator() {
|
|||||||
const [useDirectLink, setUseDirectLink] = useState(false); // Default to web URL for compatibility
|
const [useDirectLink, setUseDirectLink] = useState(false); // Default to web URL for compatibility
|
||||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||||
const [frameType, setFrameType] = useState('none');
|
const [frameType, setFrameType] = useState('none');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
|
||||||
const qrRef = useRef<HTMLDivElement>(null);
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ export default function ZoomGenerator() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download failed', err);
|
console.error('Download failed', err);
|
||||||
}
|
}
|
||||||
|
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameLabel = () => {
|
const getFrameLabel = () => {
|
||||||
@@ -111,6 +114,8 @@ export default function ZoomGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||||
|
|
||||||
{/* Main Generator Card */}
|
{/* Main Generator Card */}
|
||||||
@@ -298,5 +303,6 @@ export default function ZoomGenerator() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
99
src/components/marketing/PostDownloadPopup.tsx
Normal file
99
src/components/marketing/PostDownloadPopup.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { X, Zap, BarChart2, RefreshCw, Palette } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
|
||||||
|
interface PostDownloadPopupProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BENEFITS = [
|
||||||
|
{ icon: RefreshCw, text: 'Edit the link anytime — QR stays the same' },
|
||||||
|
{ icon: BarChart2, text: 'See who scans, when & where' },
|
||||||
|
{ icon: Palette, text: 'Custom colors, logo & frames' },
|
||||||
|
{ icon: Zap, text: 'Free plan included — upgrade anytime for more' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LS_KEY = 'qrm_download_popup_seen';
|
||||||
|
|
||||||
|
export function shouldShowDownloadPopup(): boolean {
|
||||||
|
try { return !localStorage.getItem(LS_KEY); } catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markDownloadPopupSeen(): void {
|
||||||
|
try { localStorage.setItem(LS_KEY, '1'); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PostDownloadPopup({ open, onClose }: PostDownloadPopupProps) {
|
||||||
|
const overlayRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
markDownloadPopupSeen();
|
||||||
|
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
return () => document.removeEventListener('keydown', onKey);
|
||||||
|
}, [open, onClose]);
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={overlayRef}
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style={{ backgroundColor: 'rgba(15, 23, 42, 0.6)', backdropFilter: 'blur(4px)' }}
|
||||||
|
onClick={(e) => { if (e.target === overlayRef.current) onClose(); }}
|
||||||
|
>
|
||||||
|
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="relative bg-gradient-to-br from-[#4F46E5] to-[#7C3AED] p-6 text-white text-center">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute top-4 right-4 text-white/70 hover:text-white transition-colors"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<div className="w-12 h-12 bg-white/20 rounded-2xl flex items-center justify-center mx-auto mb-3">
|
||||||
|
<Zap className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-bold">Your QR code is downloading!</h2>
|
||||||
|
<p className="text-white/80 text-sm mt-1">
|
||||||
|
Want to make it smarter — for free?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Benefits */}
|
||||||
|
<div className="p-6 space-y-3">
|
||||||
|
{BENEFITS.map(({ icon: Icon, text }) => (
|
||||||
|
<div key={text} className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-xl bg-indigo-50 flex items-center justify-center shrink-0">
|
||||||
|
<Icon className="w-4 h-4 text-[#4F46E5]" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-slate-700">{text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTAs */}
|
||||||
|
<div className="px-6 pb-6 space-y-3">
|
||||||
|
<Link href="/signup" onClick={onClose} className="block">
|
||||||
|
<Button className="w-full bg-[#4F46E5] hover:bg-[#4338CA] text-white h-12 text-base font-semibold shadow-lg">
|
||||||
|
Create Free Account
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-full text-sm text-slate-400 hover:text-slate-600 transition-colors py-1"
|
||||||
|
>
|
||||||
|
No thanks, keep it static
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
116
tmp/leads/AGENT_INSTRUCTIONS.md
Normal file
116
tmp/leads/AGENT_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# QR Master Leads Agent — Instructions
|
||||||
|
|
||||||
|
You are a growth agent for QR Master (https://qrmaster.net), a SaaS for dynamic QR code creation, tracking, and analytics. Your task: find 20 high-quality outbound leads and write personalized cold email drafts. Save them to the repo for human review — do not send any emails.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Derive ICPs from keywords
|
||||||
|
|
||||||
|
Read `seo-keywords.csv` from the repo root. Parse all rows. Sort by `CPC_High_EUR` descending. Identify the highest-value ICPs:
|
||||||
|
|
||||||
|
- **C_Analytics** (CPC EUR 16–34): Marketing Managers, CMOs, Digital Marketing Directors at SMBs (10–200 employees) who run campaigns and need QR scan tracking and analytics
|
||||||
|
- **S3_Commercial** (CPC EUR 25): Marketing Directors, Operations Managers at retail and ecommerce businesses
|
||||||
|
- **D_Restaurant** (CPC EUR 12): Restaurant Owners, F&B Managers, Hospitality Managers at restaurants, cafes, hotels
|
||||||
|
- **B_Bulk**: Print Shop Owners, Agency Owners who generate QR codes in bulk for clients
|
||||||
|
- **A_Dynamic** (high volume): Anyone actively searching for dynamic or editable QR solutions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Find 20 leads
|
||||||
|
|
||||||
|
Use both sources below. Deduplicate by email address across both sources. Aim for 20 unique leads total.
|
||||||
|
|
||||||
|
### Geographic priority
|
||||||
|
- **Primary (60%)**: English-speaking markets — US, UK, Australia, Canada, Ireland
|
||||||
|
- **Secondary (40%)**: EU — Germany, Netherlands, France, Spain, Sweden, Denmark, Belgium
|
||||||
|
|
||||||
|
### Source A — Vibe Prospecting
|
||||||
|
Search the markets above with these filters:
|
||||||
|
- Industries: restaurants/hospitality, marketing agencies, retail, print/design, events
|
||||||
|
- Company size: 5–200 employees
|
||||||
|
- Target titles: Owner, Marketing Manager, CMO, Digital Manager, Operations Manager
|
||||||
|
|
||||||
|
### Source B — Apollo.io
|
||||||
|
Search contacts by job title + industry combinations matching each ICP above.
|
||||||
|
- Verified email addresses only
|
||||||
|
- Enrich with company website, size, and industry details
|
||||||
|
|
||||||
|
**Each lead must have:** first name, last name, email, company name, industry, job title, country, ICP segment, source (Vibe/Apollo).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Enrich for personalization
|
||||||
|
|
||||||
|
For each lead that has a company website URL, visit the homepage or about page using curl via Bash. Extract 1–2 specific details (product focus, tagline, recent launch) to use in the email opening line.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — Write personalized cold emails
|
||||||
|
|
||||||
|
For each lead, write one cold email:
|
||||||
|
|
||||||
|
- **Subject line**: specific and curiosity-driven, max 8 words, zero spam trigger words
|
||||||
|
- **Opening**: reference the scraped website detail or a specific industry fact (never generic)
|
||||||
|
- **Pain point** matched to ICP segment:
|
||||||
|
- Restaurant: customers still googling the menu instead of scanning a QR
|
||||||
|
- Marketing manager: no way to know which QR code drove conversions vs which was dead weight
|
||||||
|
- Print shop: clients calling because their QR stopped working after the reprint
|
||||||
|
- Agency/bulk: spending hours regenerating codes every time a client URL changes
|
||||||
|
- **Value prop**: QR Master = dynamic QR codes editable after printing + real-time scan analytics per device, country, and time
|
||||||
|
- **CTA**: single ask — try free at https://qrmaster.net, no credit card needed
|
||||||
|
- **Tone**: professional but human, max 150 words, no buzzwords, no "I hope this email finds you well"
|
||||||
|
- **Sign-off**: Timo from QR Master (timo@qrmaster.net)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Save the draft file
|
||||||
|
|
||||||
|
1. Get today's date: run `date +%Y-%m-%d` and use as DATE
|
||||||
|
2. Create `tmp/leads/` if it does not exist
|
||||||
|
3. Write the file `tmp/leads/DATE-leads.md` with this exact structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
# QR Master Lead Outreach — DATE
|
||||||
|
**Status: DRAFT — awaiting review**
|
||||||
|
|
||||||
|
## Lead Table
|
||||||
|
| # | Name | Company | Email | Segment | Source | Country |
|
||||||
|
|---|------|---------|-------|---------|--------|---------|
|
||||||
|
| 1 | ... | ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
|
## Email Drafts
|
||||||
|
|
||||||
|
### Lead 1: Full Name — Company Name
|
||||||
|
**To:** email@address.com
|
||||||
|
**Subject:** Subject line here
|
||||||
|
|
||||||
|
Email body...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Lead 2: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Commit: `git add tmp/leads/DATE-leads.md && git commit -m "leads: add outreach draft DATE"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6 — Send review email
|
||||||
|
|
||||||
|
Send the draft file to timo@qrmaster.net so he can review it in his inbox.
|
||||||
|
SMTP: host=smtp.qrmaster.net, port=465, secure=true, user=timo@qrmaster.net, pass=fiesta.
|
||||||
|
From and To: timo@qrmaster.net.
|
||||||
|
Subject: [QR Master Leads] DATE — 20 new drafts ready for review.
|
||||||
|
Body: plain text with the full contents of the leads file pasted in.
|
||||||
|
Use nodemailer. Create tmp/send-review.mjs, run with node, then delete it.
|
||||||
|
If SMTP fails, skip silently — the committed file is the source of truth.
|
||||||
|
|
||||||
|
Output a final summary: leads found per source, file path, whether review email was sent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical rules
|
||||||
|
|
||||||
|
- **DO NOT send cold emails to leads.** Only save the file, commit, and send the review email to timo@qrmaster.net. Approval happens in Claude Code CLI.
|
||||||
|
- If fewer than 20 leads are found, include all you found with a note at the top of the file.
|
||||||
|
- Do not fabricate leads or email addresses. Only use real contacts from Vibe Prospecting and Apollo.io.
|
||||||
74
tmp/leads/INSTRUCTIONS_competitor_pain.md
Normal file
74
tmp/leads/INSTRUCTIONS_competitor_pain.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Competitor Pain Leads Agent — Instructions
|
||||||
|
|
||||||
|
You are a lead generation agent for QR Master (https://qrmaster.net). Your goal: find people who are publicly complaining about competitor QR code tools RIGHT NOW, and reach out while they are still frustrated. These are the hottest possible leads — they already want to switch.
|
||||||
|
|
||||||
|
## Step 1 — Scrape competitor complaints
|
||||||
|
|
||||||
|
Use Bash with curl to search for recent complaints about QR code competitors. Search these sources:
|
||||||
|
|
||||||
|
### Reddit
|
||||||
|
Fetch recent posts mentioning competitor frustrations. Try these searches via curl:
|
||||||
|
- https://www.reddit.com/search.json?q=QR+Tiger+broken&sort=new&limit=25
|
||||||
|
- https://www.reddit.com/search.json?q=Bitly+QR+expensive&sort=new&limit=25
|
||||||
|
- https://www.reddit.com/search.json?q=%22QR+code+stopped+working%22&sort=new&limit=25
|
||||||
|
- https://www.reddit.com/search.json?q=QR+code+generator+expensive&sort=new&limit=25
|
||||||
|
- https://www.reddit.com/search.json?q=Beaconstac+alternative&sort=new&limit=25
|
||||||
|
- https://www.reddit.com/search.json?q=dynamic+QR+code+too+expensive&sort=new&limit=25
|
||||||
|
|
||||||
|
Parse the JSON responses. **Only include posts from the last 7 days — discard anything older.** Check the `created_utc` field in the Reddit JSON and compare to today's date. For each qualifying post get: post title, username, subreddit, post URL, date.
|
||||||
|
|
||||||
|
### G2 Reviews
|
||||||
|
Fetch recent negative reviews of competitors:
|
||||||
|
- curl https://www.g2.com/products/qr-tiger/reviews?sort=recent and similar pages for Beaconstac, Uniqode.
|
||||||
|
Extract reviewer name, company, and specific complaint. **Only include reviews posted in the last 7 days** — check the review date and discard anything older.
|
||||||
|
|
||||||
|
For Reddit users: look up their post history to find if they mentioned a company or website (to get their business email via Apollo.io enrichment).
|
||||||
|
|
||||||
|
## Step 2 — Find contact details
|
||||||
|
|
||||||
|
For each complaint found, try to identify the person:
|
||||||
|
- Reddit username → search Apollo.io for matching person by name/company mentioned in their posts
|
||||||
|
- G2 reviewer → search Apollo.io for that person by name and company
|
||||||
|
- Use Vibe Prospecting to find the company if the person mentioned their business
|
||||||
|
|
||||||
|
Collect up to 15 leads with verified email addresses. Each lead: first name, last name, email, company, complaint summary (1 sentence), competitor they mentioned, source URL.
|
||||||
|
|
||||||
|
## Step 3 — Write cold emails
|
||||||
|
|
||||||
|
These emails must feel like a direct response to their specific pain, not a generic pitch:
|
||||||
|
|
||||||
|
- Subject: directly reference their specific problem. E.g. "Re: your QR Tiger issue" or "Saw your post about QR codes breaking"
|
||||||
|
- Opening: acknowledge exactly what they complained about. Reference the platform they mentioned.
|
||||||
|
- Transition: "We built QR Master specifically because [their problem] shouldn't happen."
|
||||||
|
- Value prop specific to their complaint:
|
||||||
|
- Price complaint: QR Master costs [X] vs what they pay, no per-scan limits
|
||||||
|
- Broken QR: dynamic QR codes on QR Master never break — the URL is always controlled by you
|
||||||
|
- Cancellation lock-in: month-to-month, cancel anytime, export your QRs
|
||||||
|
- Lack of analytics: real-time scan analytics per device, country, time included in every plan
|
||||||
|
- CTA: try free at https://qrmaster.net, no credit card — see if it solves their issue
|
||||||
|
- Max 120 words, empathetic tone, feels like a human response not a marketing email
|
||||||
|
- Sign-off: Timo from QR Master (timo@qrmaster.net)
|
||||||
|
|
||||||
|
## Step 4 — Save file
|
||||||
|
|
||||||
|
Get today's date: run `date +%Y-%m-%d` as DATE.
|
||||||
|
Write tmp/leads/DATE-competitor-pain.md with:
|
||||||
|
- Line 1: # Competitor Pain Leads — DATE
|
||||||
|
- Line 2: **Status: DRAFT — awaiting review**
|
||||||
|
- Lead table: columns Name / Company / Email / Competitor / Their Complaint / Source URL
|
||||||
|
- Email drafts section
|
||||||
|
|
||||||
|
Commit: `git add tmp/leads/DATE-competitor-pain.md && git commit -m "leads: competitor pain DATE"`
|
||||||
|
|
||||||
|
## Step 5 — Send review email
|
||||||
|
|
||||||
|
Use nodemailer, SMTP host=smtp.qrmaster.net port=465 secure=true user=timo@qrmaster.net pass=fiesta.
|
||||||
|
To: timo@qrmaster.net. Subject: [QR Master] Competitor pain leads DATE — N hot leads ready.
|
||||||
|
Body: full file content as plain text.
|
||||||
|
Create tmp/send-review-competitor.mjs, run with node, delete after. Skip silently if SMTP fails.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- DO NOT send cold emails to leads. Save and review only.
|
||||||
|
- Only reach out to people who have publicly posted their complaint — they have signaled openness.
|
||||||
|
- If fewer than 10 leads found, include all found with a note. Do not lower the 7-day threshold to fill the list — quality over quantity.
|
||||||
|
- Do not fabricate. Only real people with real verified emails.
|
||||||
47
tmp/leads/INSTRUCTIONS_restaurant.md
Normal file
47
tmp/leads/INSTRUCTIONS_restaurant.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# New Restaurant Opener Agent — Instructions
|
||||||
|
|
||||||
|
You are a lead generation agent for QR Master (https://qrmaster.net). Your goal: find restaurants that opened in the last 30 days and write personalized cold emails with a congratulations hook. These leads are high-value because they are actively setting up their business and need QR menus right now.
|
||||||
|
|
||||||
|
## Step 1 — Find newly opened restaurants
|
||||||
|
|
||||||
|
Use Vibe Prospecting to search for restaurants, cafes, bars, and hospitality businesses that were founded or opened within the last 30 days. Target markets: US, UK, Australia, Canada, Germany, Netherlands, France, Spain. Company size: 1–50 employees. Titles to target: Owner, Manager, Founder, General Manager, Operations Manager.
|
||||||
|
|
||||||
|
Also use Apollo.io to search for contacts at newly founded hospitality businesses. Filter by founding date within last 30 days where possible. Verified emails only.
|
||||||
|
|
||||||
|
Additionally, use Bash with curl to scrape Yelp new business listings. Try fetching https://www.yelp.com/search?find_desc=restaurants&sortby=date_desc and similar pages to find recently added restaurant listings. Extract business name, location, and any contact info available.
|
||||||
|
|
||||||
|
Aim for 15 leads total. Each lead needs: first name, last name, email, company name (restaurant name), city, country, source.
|
||||||
|
|
||||||
|
## Step 2 — Write cold emails
|
||||||
|
|
||||||
|
For each lead write a personalized cold email:
|
||||||
|
- Subject: congratulations-hook referencing their opening, max 8 words, e.g. "Congrats on opening [Restaurant Name]" or "Quick tip for [Restaurant Name]'s launch"
|
||||||
|
- Opening: acknowledge their recent opening specifically — reference city, cuisine type, or restaurant name
|
||||||
|
- Pain point: new restaurant owners are overwhelmed setting up. Their customers will Google the menu or ask for physical menus. A QR menu solves this on day one — no printing costs, update it anytime.
|
||||||
|
- Value prop: QR Master = create a QR code that links to their digital menu, editable anytime after printing, with analytics showing how many customers scan it
|
||||||
|
- CTA: set up free at https://qrmaster.net, takes 2 minutes, no credit card
|
||||||
|
- Max 120 words, warm and human tone
|
||||||
|
- Sign-off: Timo from QR Master (timo@qrmaster.net)
|
||||||
|
|
||||||
|
## Step 3 — Save file
|
||||||
|
|
||||||
|
Get today's date: run `date +%Y-%m-%d` as DATE.
|
||||||
|
Write tmp/leads/DATE-restaurants.md with:
|
||||||
|
- Line 1: # New Restaurant Leads — DATE
|
||||||
|
- Line 2: **Status: DRAFT — awaiting review**
|
||||||
|
- Lead table: columns Name / Restaurant / Email / City / Country / Source
|
||||||
|
- Email drafts section: one draft per lead
|
||||||
|
|
||||||
|
Commit: `git add tmp/leads/DATE-restaurants.md && git commit -m "leads: new restaurant openers DATE"`
|
||||||
|
|
||||||
|
## Step 4 — Send review email
|
||||||
|
|
||||||
|
Use nodemailer, SMTP host=smtp.qrmaster.net port=465 secure=true user=timo@qrmaster.net pass=fiesta.
|
||||||
|
To: timo@qrmaster.net. Subject: [QR Master] New restaurant openers DATE — N leads ready.
|
||||||
|
Body: full file content as plain text.
|
||||||
|
Create tmp/send-review-restaurants.mjs, run with node, delete after. Skip silently if SMTP fails.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- DO NOT send cold emails to leads. Save and review only.
|
||||||
|
- Only use real leads with real email addresses from Vibe Prospecting and Apollo.io. Do not fabricate.
|
||||||
|
- If fewer than 15 found, include all you found with a note.
|
||||||
54
tmp/leads/INSTRUCTIONS_tradeshow.md
Normal file
54
tmp/leads/INSTRUCTIONS_tradeshow.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Trade Show Hunter Agent — Instructions
|
||||||
|
|
||||||
|
You are a lead generation agent for QR Master (https://qrmaster.net). Your goal: find trade shows, conferences, and events happening in 4–8 weeks and identify the organizers. Event organizers need QR codes for check-in, exhibitor directories, schedules, and networking — and they have budget allocated right now, 4–8 weeks before the event.
|
||||||
|
|
||||||
|
## Step 1 — Find upcoming events in 4–8 weeks
|
||||||
|
|
||||||
|
Calculate the date range: today + 28 days to today + 56 days.
|
||||||
|
|
||||||
|
Use Bash with curl to scrape event listings. Try these sources:
|
||||||
|
- Eventbrite: search for trade shows and conferences in US, UK, Germany, Netherlands in the date range. Fetch https://www.eventbrite.com/d/online/trade-show/ and regional variants.
|
||||||
|
- 10times.com: a trade show directory. Fetch https://10times.com/tradeshows and filter by date.
|
||||||
|
- Conference listings on LinkedIn Events if accessible via curl.
|
||||||
|
|
||||||
|
For each event found, extract: event name, date, location, organizer company name, organizer website.
|
||||||
|
|
||||||
|
Then use Apollo.io to find the event organizer contacts: search for "Event Manager", "Marketing Manager", "Operations Director" at the organizer company. Verified emails only.
|
||||||
|
|
||||||
|
Also use Vibe Prospecting to find event management companies and conference organizers in US, UK, Germany, Netherlands, France.
|
||||||
|
|
||||||
|
Aim for 15 leads total. Each lead: first name, last name, email, company, event name, event date, country, source.
|
||||||
|
|
||||||
|
## Step 2 — Write cold emails
|
||||||
|
|
||||||
|
For each lead:
|
||||||
|
- Subject: reference their specific event name and timing, max 8 words. E.g. "QR codes for [Event Name] next month?"
|
||||||
|
- Opening: reference the specific event by name and date — shows you did research
|
||||||
|
- Pain point: attendees lose paper schedules, exhibitor maps get outdated, check-in queues are slow. QR codes on badges, signage, and programs solve all three.
|
||||||
|
- Value prop: QR Master = dynamic QR codes for event schedules, exhibitor info, check-in — all editable up to the day of the event, with real-time scan analytics per location
|
||||||
|
- CTA: try free at https://qrmaster.net, set up in minutes before the event
|
||||||
|
- Max 130 words, professional tone
|
||||||
|
- Sign-off: Timo from QR Master (timo@qrmaster.net)
|
||||||
|
|
||||||
|
## Step 3 — Save file
|
||||||
|
|
||||||
|
Get today's date: run `date +%Y-%m-%d` as DATE.
|
||||||
|
Write tmp/leads/DATE-tradeshow.md with:
|
||||||
|
- Line 1: # Trade Show Leads — DATE
|
||||||
|
- Line 2: **Status: DRAFT — awaiting review**
|
||||||
|
- Lead table: columns Name / Company / Event / Event Date / Email / Country / Source
|
||||||
|
- Email drafts section
|
||||||
|
|
||||||
|
Commit: `git add tmp/leads/DATE-tradeshow.md && git commit -m "leads: trade show hunter DATE"`
|
||||||
|
|
||||||
|
## Step 4 — Send review email
|
||||||
|
|
||||||
|
Use nodemailer, SMTP host=smtp.qrmaster.net port=465 secure=true user=timo@qrmaster.net pass=fiesta.
|
||||||
|
To: timo@qrmaster.net. Subject: [QR Master] Trade show leads DATE — N leads ready.
|
||||||
|
Body: full file content as plain text.
|
||||||
|
Create tmp/send-review-tradeshow.mjs, run with node, delete after. Skip silently if SMTP fails.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- DO NOT send cold emails to leads.
|
||||||
|
- Only use real contacts. Do not fabricate.
|
||||||
|
- Focus on events 4–8 weeks out — those are the decision-making sweet spot.
|
||||||
Reference in New Issue
Block a user