{"version":1,"id":"auth","framework":"next","kind":"block","files":[{"path":"src/blocks/gfa/auth-login.tsx","content":"/* eslint-disable @next/next/no-img-element */\n\"use client\";\n\nimport Link from \"next/link\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport QRCodeStyling from \"qr-code-styling\";\nimport { Button } from \"@/components/gfa/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AuthLoginPayload = {\n  method: \"phone\" | \"email\";\n  identifier: string;\n  password: string;\n  rememberMe: boolean;\n};\n\nexport type AuthLoginProps = {\n  brandLogoSrc?: string;\n  brandName?: string;\n  signupHref?: string;\n  qrData?: string;\n  className?: string;\n  /** Gallery / embed: no full-viewport height, always stacked. */\n  compact?: boolean;\n  /** When false, QR panel is omitted (useful for tight embeds). */\n  showQr?: boolean;\n  /** Demo / wire: called instead of next-auth signIn */\n  onSubmit?: (payload: AuthLoginPayload) => void | Promise<void>;\n};\n\nfunction normalizeEmailForAuth(raw: string) {\n  const email = raw.trim().toLowerCase();\n  const at = email.indexOf(\"@\");\n  if (at === -1) return email;\n  const local = email.slice(0, at);\n  const domain = email.slice(at + 1);\n  if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n    const noPlus = (local.split(\"+\")[0] ?? local).replace(/\\./g, \"\");\n    return `${noPlus}@gmail.com`;\n  }\n  return email;\n}\n\n/** Buss login UI — phone / email + QR panel (auth wiring via onSubmit). */\nexport function AuthLogin({\n  brandLogoSrc = \"/brand/logo-icon.png\",\n  brandName = \"GFA\",\n  signupHref = \"#\",\n  qrData = \"gfa-login\",\n  className,\n  compact = false,\n  showQr = true,\n  onSubmit,\n}: AuthLoginProps) {\n  const [loginMethod, setLoginMethod] = useState<\"phone\" | \"email\">(\"phone\");\n  const [phoneDigits, setPhoneDigits] = useState(\"\");\n  const [emailValue, setEmailValue] = useState(\"\");\n  const [password, setPassword] = useState(\"\");\n  const [error, setError] = useState<string | null>(null);\n  const [submitting, setSubmitting] = useState(false);\n  const [rememberMe, setRememberMe] = useState(true);\n\n  const formattedPhone = useMemo(() => {\n    const d = phoneDigits.replace(/\\D/g, \"\").slice(0, 10);\n    const p1 = d.slice(0, 3);\n    const p2 = d.slice(3, 6);\n    const p3 = d.slice(6, 8);\n    const p4 = d.slice(8, 10);\n    return [p1, p2, p3, p4].filter(Boolean).join(\" \");\n  }, [phoneDigits]);\n\n  const emailSuggestions = useMemo(() => {\n    if (loginMethod !== \"email\") return [];\n    const raw = emailValue.trim();\n    if (!raw) return [];\n    const domains = [\n      \"gmail.com\",\n      \"hotmail.com\",\n      \"yandex.com\",\n      \"outlook.com\",\n      \"outlook.com.tr\",\n      \"icloud.com\",\n    ] as const;\n    const atIndex = raw.indexOf(\"@\");\n    if (atIndex === -1) {\n      return domains.map((d) => `${raw}@${d}`).filter((s) => s !== raw);\n    }\n    const local = raw.slice(0, atIndex);\n    const typedDomain = raw.slice(atIndex + 1);\n    if (!local) return [];\n    return domains\n      .filter((d) => d.startsWith(typedDomain.toLowerCase()))\n      .map((d) => `${local}@${d}`)\n      .filter((s) => s !== raw);\n  }, [emailValue, loginMethod]);\n\n  const emailInputRef = useRef<HTMLInputElement | null>(null);\n  const [emailOpen, setEmailOpen] = useState(false);\n  const [emailActiveIndex, setEmailActiveIndex] = useState(0);\n\n  useEffect(() => {\n    if (loginMethod !== \"email\") {\n      setEmailOpen(false);\n      return;\n    }\n    setEmailActiveIndex(0);\n    setEmailOpen(emailSuggestions.length > 0);\n  }, [emailSuggestions.length, loginMethod]);\n\n  const qrRef = useRef<HTMLDivElement | null>(null);\n  const qr = useMemo(() => {\n    if (typeof window === \"undefined\") return null;\n    return new QRCodeStyling({\n      width: 216,\n      height: 216,\n      type: \"canvas\",\n      data: qrData,\n      margin: 10,\n      qrOptions: { errorCorrectionLevel: \"H\" },\n      dotsOptions: { type: \"dots\", color: \"#ffffff\" },\n      cornersSquareOptions: { type: \"extra-rounded\", color: \"#ffffff\" },\n      cornersDotOptions: { type: \"dot\", color: \"#ffffff\" },\n      backgroundOptions: { color: \"#c8102e\" },\n      imageOptions: {\n        margin: 0,\n        hideBackgroundDots: true,\n        imageSize: 0.36,\n      },\n    });\n  }, [qrData]);\n\n  useEffect(() => {\n    if (!qrRef.current || !qr) return;\n    qrRef.current.innerHTML = \"\";\n    qr.append(qrRef.current);\n  }, [qr]);\n\n  useEffect(() => {\n    if (!qr) return;\n    let cancelled = false;\n\n    const makeBadgedLogo = async () => {\n      const img = new Image();\n      img.src = brandLogoSrc;\n      await new Promise<void>((resolve, reject) => {\n        img.onload = () => resolve();\n        img.onerror = () => reject(new Error(\"Logo load failed\"));\n      });\n\n      const size = 1024;\n      const canvas = document.createElement(\"canvas\");\n      canvas.width = size;\n      canvas.height = size;\n      const ctx = canvas.getContext(\"2d\");\n      if (!ctx) return null;\n\n      ctx.imageSmoothingEnabled = true;\n      ctx.imageSmoothingQuality = \"high\";\n\n      const r = Math.round(size * 0.17);\n      const pad = Math.round(size * 0.09);\n      const w = size - pad * 2;\n      const h = size - pad * 2;\n      ctx.fillStyle = \"#ffffff\";\n      ctx.beginPath();\n      ctx.moveTo(pad + r, pad);\n      ctx.arcTo(pad + w, pad, pad + w, pad + h, r);\n      ctx.arcTo(pad + w, pad + h, pad, pad + h, r);\n      ctx.arcTo(pad, pad + h, pad, pad, r);\n      ctx.arcTo(pad, pad, pad + w, pad, r);\n      ctx.closePath();\n      ctx.fill();\n\n      // Contain logo in the badge — preserve aspect (square mark must not stretch).\n      const maxW = Math.floor(w * 0.72);\n      const maxH = Math.floor(h * 0.72);\n      const nw = img.naturalWidth || img.width || 1;\n      const nh = img.naturalHeight || img.height || 1;\n      const scale = Math.min(maxW / nw, maxH / nh);\n      const logoTargetW = Math.max(1, Math.floor(nw * scale));\n      const logoTargetH = Math.max(1, Math.floor(nh * scale));\n      const x = Math.floor((size - logoTargetW) / 2);\n      const y = Math.floor((size - logoTargetH) / 2);\n      ctx.drawImage(img, x, y, logoTargetW, logoTargetH);\n\n      return canvas.toDataURL(\"image/png\");\n    };\n\n    makeBadgedLogo()\n      .then((dataUrl) => {\n        if (cancelled || !dataUrl) return;\n        qr.update({\n          image: dataUrl,\n          imageOptions: { hideBackgroundDots: true, imageSize: 0.36, margin: 0 },\n        });\n      })\n      .catch(() => {});\n\n    return () => {\n      cancelled = true;\n    };\n  }, [qr, brandLogoSrc]);\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (submitting) return;\n\n    if (loginMethod === \"phone\") {\n      if (phoneDigits.length !== 10) {\n        setError(\"Phone number must be 10 digits.\");\n        return;\n      }\n    }\n\n    setSubmitting(true);\n    setError(null);\n    try {\n      const payload: AuthLoginPayload = {\n        method: loginMethod,\n        identifier:\n          loginMethod === \"phone\"\n            ? phoneDigits\n            : normalizeEmailForAuth(emailValue),\n        password: loginMethod === \"phone\" ? \"\" : password,\n        rememberMe,\n      };\n      await onSubmit?.(payload);\n    } catch {\n      setError(\"Sign-in is unavailable right now. Please try again.\");\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  return (\n    <main\n      className={cn(\n        \"relative flex items-center justify-center\",\n        compact\n          ? \"min-h-0 px-4 py-4\"\n          : \"min-h-dvh px-6 py-10 sm:px-10 lg:px-14\",\n        className,\n      )}\n    >\n      <div\n        className={cn(\n          \"flex w-full items-center\",\n          compact\n            ? \"max-w-sm flex-col gap-8\"\n            : \"max-w-5xl flex-col gap-12 lg:flex-row lg:justify-between lg:gap-16\",\n        )}\n      >\n        <div className={cn(\"w-full space-y-10\", compact ? \"max-w-none\" : \"max-w-xl\")}>\n          <div className=\"flex w-full justify-end sm:justify-start\">\n            <img\n              src={brandLogoSrc}\n              alt={brandName}\n              className={cn(\n                \"w-auto object-contain\",\n                compact ? \"h-9\" : \"h-12\",\n              )}\n              loading=\"eager\"\n              draggable={false}\n            />\n          </div>\n\n          <div className=\"space-y-2\">\n            <h1\n              className={cn(\n                \"font-semibold tracking-tight text-foreground\",\n                compact ? \"text-2xl\" : \"text-4xl\",\n              )}\n            >\n              Welcome back\n            </h1>\n            <p className=\"max-w-md text-sm text-muted-foreground\">\n              Enter the phone number linked to your account to continue.\n            </p>\n          </div>\n\n          <form\n            className=\"w-full max-w-sm space-y-6\"\n            onSubmit={handleSubmit}\n          >\n                <div className=\"space-y-2\">\n                  <div className=\"flex items-center justify-between gap-3\">\n                    <label\n                      htmlFor=\"identifier\"\n                      className=\"text-sm font-medium text-foreground/80\"\n                    >\n                      {loginMethod === \"phone\" ? \"Phone\" : \"Email\"}\n                    </label>\n                    <div className=\"inline-flex rounded-xl bg-muted p-1\">\n                      <button\n                        type=\"button\"\n                        onClick={() => {\n                          setLoginMethod(\"phone\");\n                          setPhoneDigits(\"\");\n                          setError(null);\n                        }}\n                        aria-pressed={loginMethod === \"phone\"}\n                        className={cn(\n                          \"h-9 rounded-lg px-3 text-sm font-medium transition\",\n                          loginMethod === \"phone\"\n                            ? \"bg-background text-foreground shadow-sm\"\n                            : \"text-muted-foreground hover:text-foreground\",\n                        )}\n                      >\n                        Phone\n                      </button>\n                      <button\n                        type=\"button\"\n                        onClick={() => {\n                          setLoginMethod(\"email\");\n                          setError(null);\n                        }}\n                        aria-pressed={loginMethod === \"email\"}\n                        className={cn(\n                          \"h-9 rounded-lg px-3 text-sm font-medium transition\",\n                          loginMethod === \"email\"\n                            ? \"bg-background text-foreground shadow-sm\"\n                            : \"text-muted-foreground hover:text-foreground\",\n                        )}\n                      >\n                        Email\n                      </button>\n                    </div>\n                  </div>\n\n                  {loginMethod === \"phone\" ? (\n                    <div className=\"flex gap-3\">\n                      <div className=\"flex h-12 items-center gap-2 rounded-xl bg-muted px-4 text-sm text-foreground/80\">\n                        <span className=\"text-base leading-none\" aria-hidden>\n                          TR\n                        </span>\n                        <span className=\"font-medium\">+90</span>\n                      </div>\n                      <input\n                        id=\"identifier\"\n                        name=\"identifier\"\n                        type=\"text\"\n                        inputMode=\"numeric\"\n                        autoComplete=\"tel\"\n                        className=\"h-12 w-full rounded-xl bg-muted px-4 text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:ring-4 focus:ring-ring/20\"\n                        placeholder=\"Phone number\"\n                        required\n                        value={formattedPhone}\n                        onChange={(e) => {\n                          const next = e.target.value\n                            .replace(/\\D/g, \"\")\n                            .slice(0, 10);\n                          setPhoneDigits(next);\n                        }}\n                      />\n                    </div>\n                  ) : (\n                    <>\n                      <input\n                        ref={emailInputRef}\n                        id=\"identifier\"\n                        name=\"identifier\"\n                        type=\"email\"\n                        autoComplete=\"email\"\n                        inputMode=\"email\"\n                        className=\"h-12 w-full rounded-xl bg-muted px-4 text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:ring-4 focus:ring-ring/20\"\n                        placeholder=\"Email address\"\n                        required\n                        value={emailValue}\n                        onChange={(e) => setEmailValue(e.target.value)}\n                        onFocus={() => setEmailOpen(emailSuggestions.length > 0)}\n                        onBlur={() => {\n                          window.setTimeout(() => setEmailOpen(false), 120);\n                        }}\n                        onKeyDown={(e) => {\n                          if (\n                            !emailOpen &&\n                            (e.key === \"ArrowDown\" || e.key === \"ArrowUp\")\n                          ) {\n                            setEmailOpen(emailSuggestions.length > 0);\n                            return;\n                          }\n                          if (!emailOpen) return;\n                          if (e.key === \"Escape\") {\n                            setEmailOpen(false);\n                            return;\n                          }\n                          if (e.key === \"ArrowDown\") {\n                            e.preventDefault();\n                            setEmailActiveIndex((i) =>\n                              Math.min(\n                                i + 1,\n                                Math.max(emailSuggestions.length - 1, 0),\n                              ),\n                            );\n                            return;\n                          }\n                          if (e.key === \"ArrowUp\") {\n                            e.preventDefault();\n                            setEmailActiveIndex((i) => Math.max(i - 1, 0));\n                            return;\n                          }\n                          if (e.key === \"Enter\") {\n                            const chosen = emailSuggestions[emailActiveIndex];\n                            if (chosen) {\n                              e.preventDefault();\n                              setEmailValue(chosen);\n                              setEmailOpen(false);\n                              window.requestAnimationFrame(() =>\n                                emailInputRef.current?.focus(),\n                              );\n                            }\n                          }\n                        }}\n                      />\n\n                      {emailOpen ? (\n                        <div className=\"relative\">\n                          <div\n                            role=\"listbox\"\n                            aria-label=\"Email suggestions\"\n                            className={cn(\n                              \"absolute z-50 mt-2 w-full overflow-auto rounded-2xl border border-border bg-popover p-1 text-popover-foreground shadow-lg\",\n                              \"max-h-48\",\n                              \"[scrollbar-color:#c8102e_transparent]\",\n                              \"[&::-webkit-scrollbar]:w-2\",\n                              \"[&::-webkit-scrollbar-thumb]:rounded-full\",\n                              \"[&::-webkit-scrollbar-thumb]:bg-[#c8102e]/70\",\n                              \"[&::-webkit-scrollbar-track]:bg-transparent\",\n                            )}\n                          >\n                            {emailSuggestions.slice(0, 10).map((s, idx) => {\n                              const active = idx === emailActiveIndex;\n                              return (\n                                <button\n                                  key={s}\n                                  type=\"button\"\n                                  role=\"option\"\n                                  aria-selected={active}\n                                  onMouseDown={(ev) => ev.preventDefault()}\n                                  onClick={() => {\n                                    setEmailValue(s);\n                                    setEmailOpen(false);\n                                    window.requestAnimationFrame(() =>\n                                      emailInputRef.current?.focus(),\n                                    );\n                                  }}\n                                  className={cn(\n                                    \"flex w-full items-center justify-between rounded-xl px-3 py-2 text-left text-sm transition\",\n                                    active\n                                      ? \"bg-[#c8102e]/10 text-foreground dark:bg-[#c8102e]/20\"\n                                      : \"text-foreground/85 hover:bg-muted\",\n                                  )}\n                                >\n                                  <span className=\"truncate\">{s}</span>\n                                </button>\n                              );\n                            })}\n                          </div>\n                        </div>\n                      ) : null}\n                    </>\n                  )}\n                </div>\n\n                {loginMethod === \"email\" ? (\n                  <div className=\"space-y-2\">\n                    <label\n                      htmlFor=\"password\"\n                      className=\"text-sm font-medium text-foreground/80\"\n                    >\n                      Password\n                    </label>\n                    <input\n                      id=\"password\"\n                      name=\"password\"\n                      type=\"password\"\n                      autoComplete=\"current-password\"\n                      className=\"h-12 w-full rounded-xl bg-muted px-4 text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:ring-4 focus:ring-ring/20\"\n                      placeholder=\"Password\"\n                      required\n                      value={password}\n                      onChange={(e) => setPassword(e.target.value)}\n                    />\n                  </div>\n                ) : null}\n\n                <label className=\"flex items-center justify-between gap-3 text-sm\">\n                  <span className=\"text-muted-foreground\">Keep me signed in</span>\n                  <input\n                    type=\"checkbox\"\n                    checked={rememberMe}\n                    onChange={(e) => setRememberMe(e.target.checked)}\n                    className=\"h-4 w-4 rounded border-border bg-background accent-foreground\"\n                  />\n                </label>\n\n                {error ? (\n                  <div className=\"text-sm font-medium text-[#c8102e]\">{error}</div>\n                ) : null}\n\n                <Button\n                  type=\"submit\"\n                  disabled={submitting}\n                  className=\"h-11 w-full rounded-xl bg-[#c8102e] text-sm font-semibold text-white transition hover:bg-[#aa0e27]\"\n                >\n                  {submitting ? \"Continuing…\" : \"Continue\"}\n                </Button>\n\n                <div className=\"pt-2 text-sm text-muted-foreground\">\n                  Don&apos;t have an account?{\" \"}\n                  <Link\n                    href={signupHref}\n                    className=\"font-medium text-foreground transition hover:opacity-80\"\n                  >\n                    Create account\n                  </Link>\n                </div>\n              </form>\n        </div>\n\n        {showQr ? (\n          <div\n            className={cn(\n              \"w-full max-w-sm shrink-0\",\n              compact ? \"block\" : \"hidden lg:block\",\n            )}\n          >\n            <div className=\"flex justify-center\">\n              <div className=\"rounded-2xl bg-[#c8102e] p-3 shadow-sm\">\n                <div ref={qrRef} aria-label=\"QR code\" />\n              </div>\n            </div>\n            <div className=\"mt-5 space-y-2 text-center\">\n              <p className=\"text-sm font-semibold text-foreground\">\n                Sign in with QR code\n              </p>\n              <p className=\"text-sm text-muted-foreground\">\n                Scan with your phone camera to sign in instantly.\n              </p>\n            </div>\n          </div>\n        ) : null}\n      </div>\n    </main>\n  );\n}\n\nexport default AuthLogin;\n"}],"meta":{"studioName":"GFA Development & Design Studio","siteUrl":"https://ui.gkhn.dev"}}