'use client'; import React, { useRef, useState } from 'react'; import { parseHuggingFaceUrl } from '../lib/huggingface'; import QRCodeWithLogo from './QRCodeWithLogo'; import { saveAs } from 'file-saver'; import * as htmlToImage from 'html-to-image'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Download, ChevronLeft, Copy, Palette } from 'lucide-react'; import { FaFacebook, FaLinkedin } from 'react-icons/fa'; import { FaSquareXTwitter } from 'react-icons/fa6'; const HuggingFaceQRGenerator = () => { const [inputUrl, setInputUrl] = useState(''); const [profileData, setProfileData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [qrCodeInstance, setQrCodeInstance] = useState(null); const [showQR, setShowQR] = useState(false); const [gradientIndex, setGradientIndex] = useState(0); const [showColorPicker, setShowColorPicker] = useState(false); const [customColor, setCustomColor] = useState('#f59e0b'); const gradients = [ { name: 'Sunset', colors: ['#f59e0b', '#f43f5e'], type: 'gradient' }, // orange → rose { name: 'Aqua Blue', colors: ['#06b6d4', '#3b82f6'], type: 'gradient' }, // cyan → blue { name: 'Purple Orange', colors: ['#8b5cf6', '#f59e0b'], type: 'gradient' }, // purple → orange { name: 'Emerald Gold', colors: ['#10b981', '#fbbf24'], type: 'gradient' }, // green → amber { name: 'Indigo Pink', colors: ['#4f46e5', '#ec4899'], type: 'gradient' }, // indigo → pink { name: 'Teal Violet', colors: ['#14b8a6', '#7c3aed'], type: 'gradient' }, // teal → violet { name: 'Sky Purple', colors: ['#38bdf8', '#a78bfa'], type: 'gradient' }, // sky → purple { name: 'Slate Blue', colors: ['#64748b', '#3b82f6'], type: 'gradient' }, // slate → blue ]; const solidColors = [ { name: 'Orange', color: '#f59e0b' }, { name: 'Blue', color: '#3b82f6' }, { name: 'Green', color: '#10b981' }, { name: 'Purple', color: '#8b5cf6' }, { name: 'Pink', color: '#ec4899' }, { name: 'Red', color: '#ef4444' }, { name: 'Yellow', color: '#eab308' }, { name: 'Gray', color: '#6b7280' }, ]; const handleGenerate = async () => { setError(''); setLoading(true); setShowQR(false); try { // Parse the URL to extract username and resource info const parsed = parseHuggingFaceUrl(inputUrl); // Fetch profile data from our API const response = await fetch('/api/huggingface', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: parsed.username, resourceType: parsed.resourceType, resourceName: parsed.resourceName }), }); if (!response.ok) { throw new Error('Failed to fetch profile data'); } const data = await response.json(); // Use proxy for the avatar to avoid CORS issues const proxiedAvatarUrl = `/api/proxy-image?url=${encodeURIComponent(data.avatarUrl)}`; setProfileData({ ...data, avatarUrl: proxiedAvatarUrl, originalAvatarUrl: data.avatarUrl, qrValue: parsed.profileUrl }); setShowQR(true); } catch (err: any) { setError(err.message || 'Invalid URL or username'); setProfileData(null); setShowQR(false); } finally { setLoading(false); } }; const handleBack = () => { setShowQR(false); }; const handleCycleBackground = () => { setGradientIndex((prev) => (prev + 1) % gradients.length); setCustomColor(null); }; const handleSelectGradient = (index: number) => { setGradientIndex(index); setCustomColor(null); setShowColorPicker(false); }; const handleSelectSolidColor = (color: string) => { setCustomColor(color); setShowColorPicker(false); }; const getBackgroundStyle = () => { if (customColor) { return { background: customColor }; } const gradient = gradients[gradientIndex]; return { background: `linear-gradient(135deg, ${gradient.colors[0]}, ${gradient.colors[1]})` }; }; const phoneRef = useRef(null); const cardRef = useRef(null); const handleDownload = async (format = 'png') => { if (!profileData) return; try { const cardElement = cardRef.current || document.getElementById('qr-card'); const phoneElement = phoneRef.current; if (format === 'full' && phoneElement) { const rect = phoneElement.getBoundingClientRect(); const dataUrl = await htmlToImage.toPng(phoneElement, { quality: 1.0, pixelRatio: 2, width: Math.ceil(rect.width), height: Math.ceil(rect.height), style: { margin: '0' }, }); const response = await fetch(dataUrl); const blob = await response.blob(); saveAs(blob, `huggingface-${profileData.username}-phone.png`); } else if (format === 'png' || format === 'card') { // Download just the inner card const rect = cardElement!.getBoundingClientRect(); const dataUrl = await htmlToImage.toPng(cardElement!, { quality: 1.0, backgroundColor: '#ffffff', pixelRatio: 2, width: Math.ceil(rect.width), height: Math.ceil(rect.height), style: { margin: '0', borderRadius: '12px' } }); // Convert dataURL to blob const response = await fetch(dataUrl); const blob = await response.blob(); saveAs(blob, `huggingface-${profileData.username}-card.png`); } else if (format === 'qr-only' && qrCodeInstance) { // Download just the QR code const blob = await qrCodeInstance.download({ name: `huggingface-${profileData.username}`, extension: 'png' }); saveAs(blob, `huggingface-${profileData.username}-qr.png`); } } catch (err) { console.error('Download error:', err); } }; const handleShare = (platform: string) => { const shareText = `Check out my Hugging Face profile!`; const shareUrl = profileData?.qrValue || ''; const shareLinks: { [key: string]: string } = { twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}`, facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`, linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}` }; if (shareLinks[platform]) { window.open(shareLinks[platform], '_blank', 'width=600,height=400'); } }; const handleShareSms = () => { const text = encodeURIComponent(`Check out my Hugging Face profile: ${profileData?.qrValue || ''}`); window.open(`sms:?&body=${text}`, '_blank'); }; const handleShareEmail = () => { const subject = encodeURIComponent('My Hugging Face Profile'); const body = encodeURIComponent(`Hey! Check out my Hugging Face profile: ${profileData?.qrValue || ''}`); window.open(`mailto:?subject=${subject}&body=${body}`, '_self'); }; const handleShareNative = async () => { try { if (navigator.share) { await navigator.share({ title: 'Hugging Face Profile', text: 'Check out my Hugging Face profile!', url: profileData?.qrValue || '' }); } else { await navigator.clipboard.writeText(profileData?.qrValue || ''); alert('Link copied to clipboard'); } } catch (e) { // ignore cancel } }; const handleCopyImage = async () => { if (!profileData) return; try { const phoneElement = phoneRef.current; if (!phoneElement) return; const rect = phoneElement.getBoundingClientRect(); const dataUrl = await htmlToImage.toPng(phoneElement, { quality: 1.0, pixelRatio: 2, width: Math.ceil(rect.width), height: Math.ceil(rect.height), style: { margin: '0' }, }); // Convert data URL to blob const response = await fetch(dataUrl); const blob = await response.blob(); // Copy to clipboard await navigator.clipboard.write([ new ClipboardItem({ 'image/png': blob }) ]); alert('Image copied to clipboard!'); } catch (err) { console.error('Copy error:', err); alert('Failed to copy image. Your browser may not support this feature.'); } }; const getResourceIcon = (type: string) => { switch(type) { case 'model': return '🤖'; case 'dataset': return '📊'; case 'space': return '🚀'; default: return '👤'; } }; const isValid = inputUrl.trim().length > 0 // Validate HuggingFace username format const validateInput = (input: string): boolean => { if (!input.trim()) return true; // empty is neutral // Check if it's a valid username (alphanumeric, hyphens, underscores) // or a valid HuggingFace URL const usernamePattern = /^[a-zA-Z0-9_-]+$/; const urlPattern = /huggingface\.co/i; return usernamePattern.test(input) || urlPattern.test(input); }; const inputIsInvalid = inputUrl.trim().length > 0 && !validateInput(inputUrl); return (
{/* Input form - hidden when QR is shown */} {!showQR && (
{/* Input card */}
Hugging Face Generate a clean QR code for any Hugging Face profile or resource.
https://huggingface.co/ setInputUrl(e.target.value)} placeholder="Reubencf" className="h-10 sm:h-12 border-0 rounded-none focus-visible:ring-0 focus-visible:ring-offset-0 text-sm sm:text-base" style={{ paddingLeft: '12px', paddingRight: '12px', fontFamily: 'var(--font-inter)' }} onKeyPress={(e) => e.key === 'Enter' && handleGenerate()} disabled={loading} aria-invalid={inputIsInvalid} aria-describedby="hf-input-help" autoFocus />

Paste a full URL or just the username, e.g. reubencf.

{error && (
{error}
)}
)} {/* Full screen QR preview - shown after successful generation */} {showQR && profileData && (
{/* Back button moved outside of the phone so it won't appear in exports */}
e.stopPropagation()}>
{profileData.fullName}
{profileData.fullName}

{(() => { const t = profileData?.resourceType || profileData?.type; if (t === 'model') return 'Scan to open this model on Hugging Face'; if (t === 'dataset') return 'Scan to open this dataset on Hugging Face'; if (t === 'space') return 'Scan to open this Space on Hugging Face'; if (profileData?.fullName) return `Scan to open ${profileData.fullName} on Hugging Face`; return 'Scan to open on Hugging Face'; })()}

Hugging Face Hugging Face
{/* Share sheet placed below the phone (not part of exported element) */}
e.stopPropagation()}>
Actions
Download
Copy
Color
Share to
{/* Color Picker Popup */} {showColorPicker && (
e.stopPropagation()}>

Choose Background

Gradients

{gradients.map((gradient, index) => (

Solid Colors

{solidColors.map((color, index) => (
)}
)}
); }; export default HuggingFaceQRGenerator;