Skip to content

Instantly share code, notes, and snippets.

@esafwan
Last active May 30, 2025 01:02
Show Gist options
  • Select an option

  • Save esafwan/7df2f80935ba27326e253c4ee8bfb34b to your computer and use it in GitHub Desktop.

Select an option

Save esafwan/7df2f80935ba27326e253c4ee8bfb34b to your computer and use it in GitHub Desktop.
Qz Tray React (Next)
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Printer, Wifi, WifiOff, RefreshCw, AlertTriangle } from "lucide-react"
declare global {
interface Window {
qz: any
}
}
export default function ReceiptPrintPage() {
const [isConnected, setIsConnected] = useState(false)
const [printers, setPrinters] = useState<string[]>([])
const [selectedPrinter, setSelectedPrinter] = useState<string>("BIXOLON SRP-350III")
const [isLoading, setIsLoading] = useState(false)
const [debugInfo, setDebugInfo] = useState<string>("")
const [hasBixolonPrinter, setHasBixolonPrinter] = useState<boolean | null>(null)
const [connectionError, setConnectionError] = useState<string | null>(null)
const [isQzLoaded, setIsQzLoaded] = useState(false)
// Fixed configuration for BIXOLON SRP-350III thermal printer
const printConfig = {
paperWidth: 80,
paperHeight: 300,
units: "mm",
marginTop: 0,
marginRight: 0,
marginBottom: 0,
marginLeft: 0,
copies: 1,
density: 1,
rasterize: false,
}
const cutType = "full" // Always use full cut
const receiptData = `Talal Online Grocery
Order No: 100237
Customer: +971 50 123 4567
--------------------------------------------------
Item Qty Price Total
--------------------------------------------------
Banana (1kg) 1 5.00 5.00
Milk 1L 2 6.50 13.00
Bread Pack 1 4.25 4.25
Eggs (12 pack) 1 10.00 10.00
Rice 5kg 1 25.00 25.00
--------------------------------------------------
TOTAL AED 57.25
--------------------------------------------------
Thank you for shopping with Talal Online!
`
useEffect(() => {
const loadQzTray = async () => {
// Try multiple CDN sources for QZ Tray
const qzSources = [
"https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/qz-tray/2.1.0/qz-tray.js",
"https://unpkg.com/qz-tray@2.2.4/qz-tray.js",
]
let scriptLoaded = false
let lastError = null
for (const src of qzSources) {
if (scriptLoaded) break
try {
console.log(`Attempting to load QZ Tray from: ${src}`)
await new Promise((resolve, reject) => {
// Remove any existing QZ script
const existingScript = document.querySelector('script[src*="qz-tray"]')
if (existingScript) {
existingScript.remove()
}
const script = document.createElement("script")
script.src = src
script.async = true
script.crossOrigin = "anonymous"
script.onload = () => {
console.log(`QZ Tray script loaded successfully from: ${src}`)
setIsQzLoaded(true)
scriptLoaded = true
resolve(true)
}
script.onerror = (error) => {
console.error(`Failed to load QZ Tray from ${src}:`, error)
document.head.removeChild(script)
reject(new Error(`Failed to load from ${src}`))
}
document.head.appendChild(script)
// Timeout after 10 seconds
setTimeout(() => {
if (!scriptLoaded) {
console.error(`Timeout loading QZ Tray from ${src}`)
if (document.head.contains(script)) {
document.head.removeChild(script)
}
reject(new Error(`Timeout loading from ${src}`))
}
}, 10000)
})
} catch (error) {
lastError = error
console.error(`Error loading QZ Tray from ${src}:`, error)
continue
}
}
if (!scriptLoaded) {
console.error("Failed to load QZ Tray from all sources:", lastError)
setConnectionError(
`Failed to load QZ Tray library from all CDN sources. Please check your internet connection or try downloading QZ Tray locally. Last error: ${lastError?.message || "Unknown error"}`,
)
return
}
// Wait a bit for the script to initialize
setTimeout(() => {
if (window.qz) {
console.log("QZ Tray object found, attempting connection...")
connectToQZ()
} else {
console.error("QZ Tray script loaded but window.qz not found")
setConnectionError("QZ Tray script loaded but library not initialized properly")
}
}, 1000)
}
loadQzTray()
return () => {
// Cleanup
const scripts = document.querySelectorAll('script[src*="qz-tray"]')
scripts.forEach((script) => {
if (document.head.contains(script)) {
document.head.removeChild(script)
}
})
}
}, [])
const connectToQZ = async () => {
if (!window.qz) {
console.error("QZ Tray library not found. Script may not have loaded properly.")
setConnectionError("QZ Tray library not found. Script may not have loaded properly.")
return
}
try {
console.log("Attempting to connect to QZ Tray...")
// Check if QZ is already active
const isActive = await window.qz.websocket.isActive()
console.log("QZ Tray active status:", isActive)
if (isActive) {
console.log("QZ Tray is already connected")
setIsConnected(true)
fetchPrinters()
return
}
await window.qz.websocket
.connect({
retries: 3,
delay: 1000,
})
.catch((err: any) => {
throw err
})
console.log("Successfully connected to QZ Tray")
setIsConnected(true)
setConnectionError(null)
fetchPrinters()
} catch (error: any) {
console.error("Failed to connect to QZ Tray:", error)
// Detailed error logging
if (error.message) console.error("Error message:", error.message)
if (error.stack) console.error("Error stack:", error.stack)
let errorMessage = "Failed to connect to QZ Tray. "
// Provide more specific error messages
if (error.message?.includes("SecurityError") || error.message?.includes("secure context")) {
errorMessage += "QZ Tray requires HTTPS in production. Try using localhost or HTTPS."
} else if (error.message?.includes("not found") || error.message?.includes("undefined")) {
errorMessage += "QZ Tray application may not be running or is not responding."
} else if (error.message?.includes("certificate")) {
errorMessage += "Certificate issue. Check QZ Tray certificates."
} else {
errorMessage += error.message || "Unknown error."
}
setConnectionError(errorMessage)
setIsConnected(false)
}
}
const fetchPrinters = async () => {
if (!window.qz || !isConnected) return
try {
console.log("Fetching available printers...")
const availablePrinters = await window.qz.printers.find()
console.log("Available printers:", availablePrinters)
setPrinters(availablePrinters)
// Debug info
const qzVersion = window.qz.version || "Unknown"
console.log("QZ Tray version:", qzVersion)
const debugText = `QZ Tray Version: ${qzVersion}
Total Printers Found: ${availablePrinters.length}
Printer List:
${availablePrinters.map((p: string, i: number) => `${i + 1}. "${p}"`).join("\n")}
Connection Info:
- Protocol: ${window.location.protocol}
- Hostname: ${window.location.hostname}
- QZ Loaded: ${isQzLoaded ? "Yes" : "No"}
- QZ Connected: ${isConnected ? "Yes" : "No"}
`
setDebugInfo(debugText)
// Check for BIXOLON printer
const bixolonExists = availablePrinters.some(
(printer: string) => printer.toLowerCase().includes("bixolon") && printer.toLowerCase().includes("srp-350iii"),
)
setHasBixolonPrinter(bixolonExists)
// Always try to use BIXOLON SRP-350III if available
const bixolonPrinter = availablePrinters.find(
(printer: string) => printer.toLowerCase().includes("bixolon") && printer.toLowerCase().includes("srp-350iii"),
)
if (bixolonPrinter) {
setSelectedPrinter(bixolonPrinter)
}
} catch (error: any) {
console.error("Failed to fetch printers:", error)
setDebugInfo(`Printer Fetch Error: ${error.message || "Unknown error"}`)
}
}
const printReceipt = async () => {
if (!window.qz || !isConnected || !selectedPrinter) {
alert("QZ Tray not connected or no printer selected")
return
}
setIsLoading(true)
try {
console.log("Creating print config for printer:", selectedPrinter)
console.log("Print configuration:", printConfig)
const config = window.qz.configs.create(selectedPrinter, {
size: {
width: printConfig.paperWidth,
height: printConfig.paperHeight,
units: printConfig.units,
},
margins: {
top: printConfig.marginTop,
right: printConfig.marginRight,
bottom: printConfig.marginBottom,
left: printConfig.marginLeft,
},
copies: printConfig.copies,
density: printConfig.density,
rasterize: printConfig.rasterize,
})
// Create data array with receipt content
const data = [
{
type: "raw",
format: "plain",
data: receiptData,
},
]
// Add full cut command
const cutCommand = "1D5601" // Full cut: GS V 1
console.log(`Adding full cut command: ${cutCommand}`)
data.push({
type: "raw",
format: "hex",
data: cutCommand,
})
console.log("Sending print job to printer with data:", data)
await window.qz.print(config, data)
console.log("Receipt printed successfully!")
alert("Receipt printed successfully!")
} catch (error: any) {
console.error("Print failed:", error)
console.error("Error details:", error.message || "Unknown error")
alert("Print failed: " + (error.message || "Unknown error"))
} finally {
setIsLoading(false)
}
}
const refreshPrinters = async () => {
if (!window.qz) {
console.error("QZ Tray library not found")
return
}
if (!isConnected) {
console.log("Not connected to QZ Tray, attempting to reconnect...")
await connectToQZ()
return
}
fetchPrinters()
}
const checkQzStatus = () => {
console.log("Checking QZ Tray status...")
console.log("QZ object exists:", !!window.qz)
if (window.qz) {
console.log("QZ version:", window.qz.version)
window.qz.websocket
.isActive()
.then((active: boolean) => {
console.log("QZ websocket active:", active)
})
.catch((err: any) => {
console.error("Error checking QZ websocket status:", err)
})
}
console.log("Protocol:", window.location.protocol)
console.log("Is secure context:", window.isSecureContext)
alert(`QZ Status Check: See browser console for details.
QZ object exists: ${!!window.qz}
Protocol: ${window.location.protocol}
Secure context: ${window.isSecureContext}`)
}
const manualLoadQzTray = () => {
console.log("Attempting manual QZ Tray load...")
// Check if QZ is already available
if (window.qz) {
console.log("QZ Tray already available")
setIsQzLoaded(true)
connectToQZ()
return
}
// Try to load from a local file or different source
const script = document.createElement("script")
script.src = "https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.min.js" // Try minified version
script.async = true
script.onload = () => {
console.log("Manual QZ Tray load successful")
setIsQzLoaded(true)
setConnectionError(null)
setTimeout(() => connectToQZ(), 500)
}
script.onerror = () => {
console.error("Manual QZ Tray load failed")
setConnectionError(
"Failed to load QZ Tray library. Please ensure you have internet access or download QZ Tray locally.",
)
}
document.head.appendChild(script)
}
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-2xl mx-auto space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Printer className="h-5 w-5" />
Talal Online Grocery - Receipt Printer
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Connection Status */}
<div className="flex items-center gap-2 p-3 rounded-lg bg-gray-100">
{isConnected ? (
<>
<Wifi className="h-4 w-4 text-green-600" />
<span className="text-green-600 font-medium">Connected to QZ Tray</span>
</>
) : (
<>
<WifiOff className="h-4 w-4 text-red-600" />
<span className="text-red-600 font-medium">Not connected to QZ Tray</span>
</>
)}
</div>
{/* Connection Error */}
{connectionError && (
<div className="flex items-start gap-2 p-3 rounded-lg bg-red-50 text-red-800">
<AlertTriangle className="h-4 w-4 mt-0.5 flex-shrink-0" />
<div>
<p className="font-medium">Connection Error</p>
<p className="text-sm">{connectionError}</p>
</div>
</div>
)}
{/* Connection Actions */}
<div className="flex gap-2">
<Button onClick={connectToQZ} variant="outline" size="sm" className="flex-1">
<RefreshCw className="h-4 w-4 mr-2" />
Reconnect to QZ
</Button>
<Button onClick={checkQzStatus} variant="outline" size="sm" className="flex-1">
Check QZ Status
</Button>
<Button onClick={manualLoadQzTray} variant="outline" size="sm" className="flex-1">
Reload QZ Script
</Button>
</div>
{/* BIXOLON Printer Status */}
<div className="space-y-2">
<div className="flex items-center gap-2 p-3 rounded-lg bg-blue-50">
<Printer className="h-4 w-4 text-blue-600" />
<span className="font-medium">BIXOLON SRP-350III Status:</span>
{hasBixolonPrinter === true && <span className="text-green-600 font-medium">✅ Found</span>}
{hasBixolonPrinter === false && <span className="text-red-600 font-medium">❌ Not Found</span>}
{hasBixolonPrinter === null && <span className="text-gray-600 font-medium">🔍 Checking...</span>}
</div>
<Button onClick={refreshPrinters} variant="outline" size="sm" className="w-full">
Refresh Printer Connection
</Button>
</div>
{/* Current Configuration Display */}
<div className="bg-green-50 p-3 rounded-lg">
<div className="text-sm text-green-800">
<div className="font-medium mb-1">Printer Configuration:</div>
<div className="text-xs font-mono">
Printer: {selectedPrinter || "BIXOLON SRP-350III"}
<br />
Paper: 80×300mm, No margins, Full cut enabled
<br />
Format: Raw text, 1 copy, Standard density
</div>
</div>
</div>
{/* Debug Information */}
<div className="space-y-2">
<label className="text-sm font-medium">Debug Information:</label>
<div className="bg-gray-900 text-green-400 p-3 rounded-lg font-mono text-xs whitespace-pre-line max-h-40 overflow-y-auto">
{debugInfo || "No debug information available yet."}
</div>
</div>
{/* Print Button */}
<Button onClick={printReceipt} disabled={!isConnected || isLoading} className="w-full" size="lg">
<Printer className="h-4 w-4 mr-2" />
{isLoading ? "Printing..." : "Print Receipt"}
</Button>
{!isConnected && (
<div className="text-sm text-gray-600 p-3 bg-yellow-50 rounded-lg">
<strong>Note:</strong> Make sure QZ Tray is installed and running on your computer.
<br />
Download it from:{" "}
<a
href="https://qz.io/download/"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
qz.io/download
</a>
</div>
)}
</CardContent>
</Card>
{/* Receipt Preview */}
<Card>
<CardHeader>
<CardTitle>Receipt Preview</CardTitle>
</CardHeader>
<CardContent>
<div className="bg-white p-4 border rounded-lg font-mono text-sm whitespace-pre-line">{receiptData}</div>
</CardContent>
</Card>
{/* Setup Instructions */}
<Card>
<CardHeader>
<CardTitle>Setup Instructions</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div>
<strong>1. Install QZ Tray:</strong>
<p className="text-gray-600">
Download and install QZ Tray from{" "}
<a
href="https://qz.io/download/"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
qz.io/download
</a>
</p>
</div>
<div>
<strong>2. Connect BIXOLON SRP-350III:</strong>
<p className="text-gray-600">
Make sure your BIXOLON SRP-350III thermal printer is connected and powered on
</p>
</div>
<div>
<strong>3. Start QZ Tray:</strong>
<p className="text-gray-600">Launch the QZ Tray application on your computer</p>
</div>
<div>
<strong>4. Print Receipt:</strong>
<p className="text-gray-600">
Once connected, click "Print Receipt" to print with automatic paper cutting
</p>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

A preview of the code in action: Screenshot 2025-05-30 at 5 00 53 AM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment