{
  "name": "authentication-devicelinkqr",
  "type": "registry:component",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/authentication/DeviceLinkQR.tsx",
      "type": "registry:component",
      "content": "import { useCallback, useEffect, useState } from \"react\";\nimport { useAuthMessages } from \"../../lib/auth-messages.js\";\nimport { CONTAINER_WIDTHS } from \"../../lib/layout-constants.js\";\nimport { ErrorDisplay } from \"../ui-components/ErrorDisplay.js\";\nimport { LoadingButton } from \"../ui-components/LoadingButton.js\";\nimport { QRCodeRenderer } from \"../ui-components/QRCodeRenderer.js\";\nimport { Alert, AlertDescription } from \"../ui/alert.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"../ui/card.js\";\n\nexport interface DeviceLinkQRProps {\n\tonLinkGenerated?: (url: string, token: string) => void;\n\tonError?: (error: string) => void;\n\tapiUrl?: string;\n}\n\ninterface DeviceLinkResponse {\n\turl: string;\n\ttoken: string;\n\texpiresIn: number;\n}\n\nexport function DeviceLinkQR({ onLinkGenerated, onError }: DeviceLinkQRProps) {\n\tconst messages = useAuthMessages();\n\tconst [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);\n\tconst [linkUrl, setLinkUrl] = useState<string | null>(null);\n\tconst [token, setToken] = useState<string | null>(null);\n\tconst [loading, setLoading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst [timeRemaining, setTimeRemaining] = useState(0);\n\tconst [copied, setCopied] = useState(false);\n\n\tconst formatTime = useCallback((seconds: number) => {\n\t\tconst mins = Math.floor(seconds / 60);\n\t\tconst secs = seconds % 60;\n\t\treturn `${mins}:${secs.toString().padStart(2, \"0\")}`;\n\t}, []);\n\n\tconst generateQRCode = useCallback(async () => {\n\t\tsetLoading(true);\n\t\tsetError(null);\n\t\tlet intervalId: ReturnType<typeof setInterval> | null = null;\n\n\t\ttry {\n\t\t\t// For now, simulate the API call since backend isn't implemented\n\t\t\t// In a real implementation, this would call your backend\n\t\t\tconst mockResponse: DeviceLinkResponse = {\n\t\t\t\turl: `${window.location.origin}/device-link/mock-token-${Date.now()}`,\n\t\t\t\ttoken: `token-${Date.now().toString().slice(-8)}`,\n\t\t\t\texpiresIn: 600, // 10 minutes\n\t\t\t};\n\n\t\t\tsetLinkUrl(mockResponse.url);\n\t\t\tsetToken(mockResponse.token);\n\t\t\tsetTimeRemaining(mockResponse.expiresIn);\n\t\t\tonLinkGenerated?.(mockResponse.url, mockResponse.token);\n\n\t\t\t// Generate QR code (using a simple text representation for now)\n\t\t\t// In a real implementation, you'd use a QR code library\n\t\t\tconst qrText = `QR Code would be generated here for: ${mockResponse.url}`;\n\t\t\tsetQrCodeUrl(qrText);\n\n\t\t\t// Start countdown\n\t\t\tintervalId = setInterval(() => {\n\t\t\t\tsetTimeRemaining((prev) => {\n\t\t\t\t\tif (prev <= 1) {\n\t\t\t\t\t\tif (intervalId) clearInterval(intervalId);\n\t\t\t\t\t\tsetQrCodeUrl(null);\n\t\t\t\t\t\tsetLinkUrl(null);\n\t\t\t\t\t\tsetToken(null);\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t\treturn prev - 1;\n\t\t\t\t});\n\t\t\t}, 1000);\n\t\t} catch (err) {\n\t\t\tconst errorMessage =\n\t\t\t\terr instanceof Error ? err.message : messages.errorDeviceLinkFailed;\n\t\t\tsetError(errorMessage);\n\t\t\tonError?.(errorMessage);\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\n\t\t// Return cleanup function\n\t\treturn () => {\n\t\t\tif (intervalId) clearInterval(intervalId);\n\t\t};\n\t}, [onLinkGenerated, onError, messages.errorDeviceLinkFailed]);\n\n\tconst copyToClipboard = useCallback(async () => {\n\t\tif (!linkUrl) return;\n\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(linkUrl);\n\t\t\tsetCopied(true);\n\t\t\tsetTimeout(() => setCopied(false), 2000);\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Failed to copy to clipboard:\", err);\n\t\t}\n\t}, [linkUrl]);\n\n\tconst resetQRCode = useCallback(() => {\n\t\tsetQrCodeUrl(null);\n\t\tsetLinkUrl(null);\n\t\tsetToken(null);\n\t\tsetTimeRemaining(0);\n\t\tsetError(null);\n\t}, []);\n\n\t// Cleanup on unmount\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tsetTimeRemaining(0);\n\t\t};\n\t}, []);\n\n\treturn (\n\t\t<Card>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle className=\"text-center\">\n\t\t\t\t\t{messages.deviceLinkTitle}\n\t\t\t\t</CardTitle>\n\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t{messages.deviceLinkSubtitle}\n\t\t\t\t</p>\n\t\t\t</CardHeader>\n\t\t\t<CardContent>\n\t\t\t\t<div className=\"flex flex-col gap-6\">\n\t\t\t\t\t{error && <ErrorDisplay error={error} />}\n\n\t\t\t\t\t{!qrCodeUrl ? (\n\t\t\t\t\t\t<div className=\"flex flex-col items-center gap-4\">\n\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\t\t\tGenerate a QR code to quickly sign in on another device\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\t\tonClick={generateQRCode}\n\t\t\t\t\t\t\t\tloading={loading}\n\t\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{messages.deviceLinkGenerateButton}\n\t\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-6\">\n\t\t\t\t\t\t\t{/* Instructions */}\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t{messages.deviceLinkInstructions.map((instruction, index) => (\n\t\t\t\t\t\t\t\t\t<div key={instruction} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs font-bold text-primary\">\n\t\t\t\t\t\t\t\t\t\t\t{index + 1}.\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t{instruction}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{/* QR Code */}\n\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col items-center gap-3 p-6\">\n\t\t\t\t\t\t\t\t\t{linkUrl ? (\n\t\t\t\t\t\t\t\t\t\t<QRCodeRenderer\n\t\t\t\t\t\t\t\t\t\t\tdata={linkUrl}\n\t\t\t\t\t\t\t\t\t\t\tsize={200}\n\t\t\t\t\t\t\t\t\t\t\tqrStyle=\"dots\"\n\t\t\t\t\t\t\t\t\t\t\tthemeAware={true}\n\t\t\t\t\t\t\t\t\t\t\tbgColor=\"var(--background)\"\n\t\t\t\t\t\t\t\t\t\t\tfgColor=\"var(--primary)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex items-center justify-center border-2 border-dashed border-muted-foreground rounded-lg\"\n\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\twidth: CONTAINER_WIDTHS.POPOVER_SMALL,\n\t\t\t\t\t\t\t\t\t\t\t\theight: CONTAINER_WIDTHS.POPOVER_SMALL,\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground text-center\">\n\t\t\t\t\t\t\t\t\t\t\t\tGenerating...\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t{timeRemaining > 0 && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm text-primary\">\n\t\t\t\t\t\t\t\t\t\t\t\t{messages.deviceLinkExpires}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<code className=\"px-2 py-1 bg-primary/10 text-primary rounded text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t\t{formatTime(timeRemaining)}\n\t\t\t\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t{/* Manual Link */}\n\t\t\t\t\t\t\t{linkUrl && (\n\t\t\t\t\t\t\t\t<Card className=\"bg-muted/50\">\n\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-3 p-4\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\tOr visit manually:\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<code className=\"text-xs bg-background p-2 rounded border break-all\">\n\t\t\t\t\t\t\t\t\t\t\t{linkUrl}\n\t\t\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t\t{token && (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tToken: {token.substring(0, 8)}...\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={copyToClipboard}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={copied ? \"text-green-600\" : \"\"}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{copied\n\t\t\t\t\t\t\t\t\t\t\t\t\t? messages.deviceLinkCopied\n\t\t\t\t\t\t\t\t\t\t\t\t\t: messages.deviceLinkCopyButton}\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t\t\t<div className=\"flex gap-3 justify-center\">\n\t\t\t\t\t\t\t\t<Button variant=\"ghost\" onClick={resetQRCode}>\n\t\t\t\t\t\t\t\t\t{messages.cancel}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tonClick={generateQRCode}\n\t\t\t\t\t\t\t\t\tdisabled={loading}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{messages.deviceLinkNewCode}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{/* Security Notice */}\n\t\t\t\t\t\t\t<Alert className=\"bg-amber-50 border-amber-200\">\n\t\t\t\t\t\t\t\t<AlertDescription className=\"text-amber-900\">\n\t\t\t\t\t\t\t\t\tThe QR code contains a one-time link that expires in 10\n\t\t\t\t\t\t\t\t\tminutes. You'll need to enter your encryption password on the\n\t\t\t\t\t\t\t\t\tnew device.\n\t\t\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t\t\t</Alert>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n",
      "target": "<%- config.aliases.components %>/devicelinkqr.tsx"
    }
  ]
}