{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bank-account-connector",
  "title": "Bank Account Connector",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/bank-account-connector/BankAccountConnector.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ArrowLeft, Building2, CheckCircle2, ExternalLink, Loader2, Lock, Search, Shield } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Separator } from '@/components/ui/separator'\n\nexport interface BankInstitution {\n  id: string\n  name: string\n  fullName: string\n  tagline: string\n}\n\nexport interface BankAccount {\n  id: string\n  name: string\n  last4: string\n  type: 'Checking' | 'Savings' | 'Credit'\n  balance: string\n  balanceLabel: string\n  subtitle: string\n}\n\nexport interface ConnectPayload {\n  bankId: string\n  bankName: string\n  accountId: string\n  accountName: string\n  last4: string\n  balance: string\n}\n\nexport interface BankAccountConnectorProps {\n  initialBankId?: string\n  className?: string\n  onConnect?: (payload: ConnectPayload) => void\n  onCancel?: () => void\n  onSelectBank?: (bankId: string) => void\n}\n\nconst institutions: BankInstitution[] = [\n  { id: 'chase', name: 'Chase', fullName: 'JPMorgan Chase Bank', tagline: 'Checking, Savings & Credit' },\n  { id: 'bofa', name: 'Bank of America', fullName: 'Bank of America N.A.', tagline: 'Advantage Banking' },\n  { id: 'wellsfargo', name: 'Wells Fargo', fullName: 'Wells Fargo Bank', tagline: 'Everyday & Way2Save' },\n  { id: 'citibank', name: 'Citibank', fullName: 'Citibank N.A.', tagline: 'Citi Priority & Access' },\n  { id: 'capitalone', name: 'Capital One', fullName: 'Capital One Bank', tagline: '360 Checking & Savings' },\n  { id: 'svb', name: 'SVB', fullName: 'Silicon Valley Bank', tagline: 'Commercial & Startup Banking' },\n]\n\nconst accounts: BankAccount[] = [\n  {\n    id: 'acc-chk-4892',\n    name: 'Checking',\n    last4: '4892',\n    type: 'Checking',\n    balance: '$14,250.00',\n    balanceLabel: 'Available balance',\n    subtitle: 'Primary checking account',\n  },\n  {\n    id: 'acc-sav-9210',\n    name: 'Savings',\n    last4: '9210',\n    type: 'Savings',\n    balance: '$48,900.00',\n    balanceLabel: 'Available balance',\n    subtitle: 'High yield savings',\n  },\n  {\n    id: 'acc-crd-1049',\n    name: 'Business Credit',\n    last4: '1049',\n    type: 'Credit',\n    balance: '$5,200.00',\n    balanceLabel: 'Current balance',\n    subtitle: 'Corporate credit line',\n  },\n]\n\nfunction BankIcon({ id, className }: { id: string; className?: string }) {\n  switch (id) {\n    case 'chase':\n      return (\n        <svg\n          data-slot=\"bank-account-connector\"\n          viewBox=\"0 0 24 24\"\n          className={cn('size-5 shrink-0 text-[#117ACA]', className)}\n          fill=\"currentColor\"\n        >\n          <path d=\"M12 2.5L7.5 7h4.8L16.2 3.1 12 2.5zm9.5 9.5L17 7.5v4.8l3.9 3.9.6-4.2zm-9.5 9.5L16.5 17h-4.8L7.8 20.9l4.2.6zm-9.5-9.5L7 16.5v-4.8L3.1 7.8 2.5 12z\" />\n        </svg>\n      )\n    case 'bofa':\n      return (\n        <svg viewBox=\"0 0 24 24\" className={cn('size-5 shrink-0', className)} fill=\"none\">\n          <rect x=\"2\" y=\"5\" width=\"8\" height=\"4\" rx=\"1\" fill=\"#E31837\" />\n          <rect x=\"2\" y=\"10.5\" width=\"8\" height=\"4\" rx=\"1\" fill=\"#002D62\" />\n          <rect x=\"2\" y=\"16\" width=\"8\" height=\"3\" rx=\"0.5\" fill=\"#E31837\" />\n          <rect x=\"14\" y=\"5\" width=\"8\" height=\"4\" rx=\"1\" fill=\"#002D62\" />\n          <rect x=\"14\" y=\"10.5\" width=\"8\" height=\"4\" rx=\"1\" fill=\"#E31837\" />\n          <rect x=\"14\" y=\"16\" width=\"8\" height=\"3\" rx=\"0.5\" fill=\"#002D62\" />\n        </svg>\n      )\n    case 'wellsfargo':\n      return (\n        <svg viewBox=\"0 0 24 24\" className={cn('size-5 shrink-0', className)} fill=\"none\">\n          <rect width=\"24\" height=\"24\" rx=\"4\" fill=\"#D71E28\" />\n          <path d=\"M4 7h2.2l1.6 7 1.8-7h2.1l1.8 7 1.6-7H17.5l-2.6 10h-2.2l-1.8-7-1.8 7H7.1L4 7z\" fill=\"#FFCC00\" />\n        </svg>\n      )\n    case 'citibank':\n      return (\n        <svg viewBox=\"0 0 24 24\" className={cn('size-5 shrink-0', className)} fill=\"none\">\n          <path\n            d=\"M12 4.5C8 4.5 4.5 6.8 4 9.5h2.5c.5-1.5 3-2.5 5.5-2.5s5 1 5.5 2.5H20c-.5-2.7-4-5-8-5z\"\n            fill=\"#EC111A\"\n          />\n          <rect x=\"4\" y=\"11\" width=\"3\" height=\"8\" rx=\"0.5\" fill=\"#003B70\" />\n          <circle cx=\"12\" cy=\"15\" r=\"4\" fill=\"#003B70\" />\n          <rect x=\"17\" y=\"11\" width=\"3\" height=\"8\" rx=\"0.5\" fill=\"#003B70\" />\n        </svg>\n      )\n    case 'capitalone':\n      return (\n        <svg viewBox=\"0 0 24 24\" className={cn('size-5 shrink-0', className)} fill=\"none\">\n          <rect width=\"24\" height=\"24\" rx=\"4\" fill=\"#004879\" />\n          <path d=\"M3.5 15.5C8 9.5 16 8 20.5 12c-5-2-12-1-15 3.5z\" fill=\"#D03027\" />\n          <rect x=\"7\" y=\"9\" width=\"10\" height=\"2\" rx=\"1\" fill=\"#FFFFFF\" />\n        </svg>\n      )\n    case 'svb':\n      return (\n        <svg viewBox=\"0 0 24 24\" className={cn('size-5 shrink-0', className)} fill=\"none\">\n          <rect width=\"24\" height=\"24\" rx=\"4\" fill=\"#005587\" />\n          <path d=\"M6 15l6-9 6 9-3 1.5-3-4.5-3 4.5L6 15z\" fill=\"#FFFFFF\" />\n          <path d=\"M9 16.5l3-4.5 3 4.5-3 1.5-3-1.5z\" fill=\"#00A3E0\" />\n        </svg>\n      )\n    default:\n      return <Building2 className={cn('text-muted-foreground size-5', className)} />\n  }\n}\n\nexport function BankAccountConnector({\n  initialBankId,\n  className,\n  onConnect,\n  onCancel,\n  onSelectBank,\n}: BankAccountConnectorProps) {\n  const [searchQuery, setSearchQuery] = React.useState('')\n  const [selectedBankId, setSelectedBankId] = React.useState<string | null>(initialBankId ?? null)\n  const [selectedAccountId, setSelectedAccountId] = React.useState<string>('acc-chk-4892')\n  const [isConnecting, setIsConnecting] = React.useState(false)\n  const [isConnected, setIsConnected] = React.useState(false)\n\n  React.useEffect(() => {\n    if (initialBankId) {\n      setSelectedBankId(initialBankId)\n    }\n  }, [initialBankId])\n\n  const filteredInstitutions = React.useMemo(() => {\n    const query = searchQuery.trim().toLowerCase()\n    if (!query) return institutions\n    return institutions.filter((b) => b.name.toLowerCase().includes(query) || b.fullName.toLowerCase().includes(query))\n  }, [searchQuery])\n\n  const activeBank = React.useMemo(() => {\n    if (!selectedBankId) return institutions[0]\n    return (\n      institutions.find((b) => b.id === selectedBankId) ?? {\n        id: selectedBankId,\n        name: searchQuery ? searchQuery : 'Custom Institution',\n        fullName: 'Financial Institution',\n        tagline: 'Direct Open Banking',\n      }\n    )\n  }, [selectedBankId, searchQuery])\n\n  const selectedAccount = React.useMemo(() => {\n    return accounts.find((a) => a.id === selectedAccountId) ?? accounts[0]\n  }, [selectedAccountId])\n\n  function handleSelectBank(bankId: string) {\n    setSelectedBankId(bankId)\n    onSelectBank?.(bankId)\n  }\n\n  function handleConnect() {\n    if (!selectedBankId) {\n      setSelectedBankId(institutions[0].id)\n      return\n    }\n\n    setIsConnecting(true)\n    setTimeout(() => {\n      setIsConnecting(false)\n      setIsConnected(true)\n      onConnect?.({\n        bankId: activeBank.id,\n        bankName: activeBank.name,\n        accountId: selectedAccount.id,\n        accountName: selectedAccount.name,\n        last4: selectedAccount.last4,\n        balance: selectedAccount.balance,\n      })\n      setTimeout(() => {\n        setIsConnected(false)\n      }, 2500)\n    }, 900)\n  }\n\n  function handleCancel() {\n    if (selectedBankId && !initialBankId) {\n      setSelectedBankId(null)\n    }\n    onCancel?.()\n  }\n\n  return (\n    <Card className={cn('mx-auto w-full max-w-lg overflow-hidden border shadow-sm', className)}>\n      <CardContent className=\"space-y-5 p-6\">\n        {/* Step 1: Institution Selection */}\n        {!selectedBankId ? (\n          <div className=\"space-y-5\">\n            {/* Header */}\n            <div className=\"flex items-start gap-3.5\">\n              <div className=\"bg-success/10 text-success ring-success/5 dark:bg-success/10 text-success flex size-10 shrink-0 items-center justify-center rounded-full ring-4\">\n                <Shield className=\"size-5\" />\n              </div>\n              <div className=\"min-w-0 space-y-1\">\n                <h3 className=\"text-foreground text-base leading-tight font-semibold tracking-tight\">\n                  Connect your bank account\n                </h3>\n                <p className=\"text-muted-foreground text-xs\">\n                  Secure 256-bit encrypted connection powered by open banking.\n                </p>\n              </div>\n            </div>\n\n            {/* Search input */}\n            <div className=\"relative\">\n              <Input\n                value={searchQuery}\n                onChange={(e) => setSearchQuery(e.target.value)}\n                prefixIcon={<Search className=\"size-4\" />}\n                placeholder=\"Search institutions (e.g. Chase, Wells Fargo)...\"\n                className=\"text-sm\"\n              />\n            </div>\n\n            {/* Popular Institutions Grid */}\n            <div className=\"space-y-2.5\">\n              <div className=\"flex items-center justify-between\">\n                <p className=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                  Popular institutions\n                </p>\n                <span className=\"text-muted-foreground text-xs\">10,000+ supported</span>\n              </div>\n\n              {filteredInstitutions.length > 0 ? (\n                <div className=\"grid grid-cols-2 gap-2.5 sm:grid-cols-3\">\n                  {filteredInstitutions.map((bank) => (\n                    <button\n                      key={bank.id}\n                      type=\"button\"\n                      className=\"group border-border bg-card hover:border-primary/40 hover:bg-muted/40 focus-visible:ring-ring flex flex-col items-center justify-center rounded-xl border p-3.5 text-center transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none\"\n                      onClick={() => handleSelectBank(bank.id)}\n                    >\n                      <div className=\"border-border/60 bg-muted/60 dark:bg-muted/30 mb-2 flex size-10 items-center justify-center rounded-lg border shadow-xs transition-transform group-hover:scale-105\">\n                        <BankIcon id={bank.id} />\n                      </div>\n                      <span className=\"text-foreground w-full truncate text-xs font-semibold\">{bank.name}</span>\n                      <span className=\"text-muted-foreground mt-0.5 w-full truncate text-xs\">{bank.tagline}</span>\n                    </button>\n                  ))}\n                </div>\n              ) : (\n                /* Direct search fallback */\n                <div className=\"border-border rounded-xl border border-dashed p-4 text-center\">\n                  <p className=\"text-muted-foreground mb-2 text-xs\">\n                    No popular bank matched &ldquo;{searchQuery}&rdquo;. Connect via direct institution link:\n                  </p>\n                  <Button\n                    size=\"sm\"\n                    variant=\"outline\"\n                    className=\"gap-1.5 text-xs\"\n                    onClick={() => handleSelectBank(searchQuery.toLowerCase().replace(/\\s+/g, '-'))}\n                  >\n                    <Building2 className=\"text-muted-foreground size-3.5\" />\n                    Connect with {searchQuery}\n                  </Button>\n                </div>\n              )}\n            </div>\n          </div>\n        ) : (\n          /* Step 2: Account Selection Step */\n          <div className=\"space-y-5\">\n            {/* Back and Banner Header */}\n            <div className=\"space-y-3\">\n              <div className=\"flex items-center justify-between\">\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  className=\"text-muted-foreground hover:text-foreground -ml-2 h-7 gap-1 px-2 text-xs\"\n                  onClick={() => setSelectedBankId(null)}\n                >\n                  <ArrowLeft className=\"size-3.5\" />\n                  Back to banks\n                </Button>\n                <span className=\"text-muted-foreground font-mono text-xs\">Step 2 of 2</span>\n              </div>\n\n              {/* Bank Banner */}\n              <div className=\"border-border bg-muted/40 flex items-center justify-between rounded-xl border p-3.5\">\n                <div className=\"flex min-w-0 items-center gap-3\">\n                  <div className=\"border-border/80 bg-background flex size-10 shrink-0 items-center justify-center rounded-lg border shadow-xs\">\n                    <BankIcon id={activeBank.id} />\n                  </div>\n\n                  <div className=\"min-w-0\">\n                    <p className=\"text-foreground truncate text-sm font-semibold\">\n                      {activeBank.name} Checking &amp; Savings\n                    </p>\n                    <div className=\"mt-0.5 flex items-center gap-1.5\">\n                      <span className=\"bg-success size-1.5 animate-pulse rounded-full\" />\n                      <span className=\"text-success text-xs font-medium\">Connected</span>\n                    </div>\n                  </div>\n                </div>\n\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  className=\"text-muted-foreground hover:text-foreground h-7 shrink-0 text-xs\"\n                  onClick={() => setSelectedBankId(null)}\n                >\n                  Change\n                </Button>\n              </div>\n            </div>\n\n            {/* Account Radio Cards */}\n            <div className=\"space-y-2.5\">\n              <div className=\"space-y-1\">\n                <h4 className=\"text-foreground text-sm font-semibold\">Select an account</h4>\n                <p className=\"text-muted-foreground text-xs\">\n                  Choose the primary account to link for transactions and balance tracking.\n                </p>\n              </div>\n\n              <div role=\"radiogroup\" aria-label=\"Bank accounts\" className=\"space-y-2\">\n                {accounts.map((account) => {\n                  const isSelected = selectedAccountId === account.id\n                  return (\n                    <div\n                      key={account.id}\n                      role=\"radio\"\n                      aria-checked={isSelected}\n                      tabIndex={0}\n                      className={cn(\n                        'group focus-visible:ring-ring relative flex cursor-pointer items-center justify-between rounded-xl border p-3.5 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',\n                        isSelected\n                          ? 'border-primary bg-primary/5 ring-primary/20 shadow-xs ring-1'\n                          : 'border-border bg-card hover:border-border/80 hover:bg-muted/30',\n                      )}\n                      onClick={() => setSelectedAccountId(account.id)}\n                      onKeyDown={(e) => {\n                        if (e.key === 'Enter' || e.key === ' ') {\n                          e.preventDefault()\n                          setSelectedAccountId(account.id)\n                        }\n                      }}\n                    >\n                      <div className=\"flex min-w-0 items-center gap-3\">\n                        {/* Custom Radio Indicator */}\n                        <div\n                          className={cn(\n                            'flex size-4 shrink-0 items-center justify-center rounded-full border transition-colors',\n                            isSelected\n                              ? 'border-primary bg-primary text-primary-foreground'\n                              : 'border-muted-foreground/30 bg-background group-hover:border-muted-foreground/60',\n                          )}\n                        >\n                          {isSelected && <div className=\"bg-primary-foreground size-1.5 rounded-full\" />}\n                        </div>\n\n                        {/* Account Info */}\n                        <div className=\"min-w-0 space-y-0.5\">\n                          <div className=\"flex flex-wrap items-center gap-2\">\n                            <span className=\"text-foreground truncate text-sm font-medium\">{account.name}</span>\n                            <span className=\"text-muted-foreground font-mono text-xs\">•••• {account.last4}</span>\n                            <Badge\n                              variant={account.type === 'Credit' ? 'outline' : 'secondary'}\n                              className=\"h-5 px-2 py-0 text-xs font-normal\"\n                            >\n                              {account.type}\n                            </Badge>\n                          </div>\n                          <p className=\"text-muted-foreground text-xs\">{account.subtitle}</p>\n                        </div>\n                      </div>\n\n                      {/* Balance */}\n                      <div className=\"shrink-0 pl-3 text-right\">\n                        <div className=\"text-foreground font-mono text-sm font-semibold tabular-nums\">\n                          {account.balance}\n                        </div>\n                        <div className=\"text-muted-foreground text-xs\">{account.balanceLabel}</div>\n                      </div>\n                    </div>\n                  )\n                })}\n              </div>\n            </div>\n          </div>\n        )}\n\n        {/* Security Footer */}\n        <div className=\"space-y-4 pt-2\">\n          <Separator />\n\n          <div className=\"flex flex-wrap items-center justify-between gap-2\">\n            <Badge\n              variant=\"outline\"\n              className=\"text-muted-foreground border-border/80 gap-1.5 py-1 text-xs font-normal\"\n            >\n              <Lock className=\"text-success size-3 shrink-0\" />\n              <span>Encrypted with AES-256</span>\n            </Badge>\n\n            <a\n              href=\"#\"\n              className=\"text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-1 text-xs underline underline-offset-4 transition-colors\"\n              onClick={(e) => e.preventDefault()}\n            >\n              Privacy policy\n              <ExternalLink className=\"size-3\" />\n            </a>\n          </div>\n\n          <div className=\"flex flex-col-reverse gap-2 pt-1 sm:flex-row sm:items-center sm:justify-end\">\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              className=\"text-muted-foreground hover:text-foreground h-9 text-xs\"\n              onClick={handleCancel}\n            >\n              Cancel\n            </Button>\n            <Button\n              size=\"sm\"\n              className=\"h-9 text-xs font-medium\"\n              disabled={isConnecting || isConnected}\n              onClick={handleConnect}\n            >\n              {isConnecting ? (\n                <>\n                  <Loader2 className=\"mr-1.5 size-3.5 animate-spin\" />\n                  Connecting…\n                </>\n              ) : isConnected ? (\n                <>\n                  <CheckCircle2 className=\"text-success mr-1.5 size-3.5\" />\n                  Account Connected!\n                </>\n              ) : !selectedBankId ? (\n                'Continue'\n              ) : (\n                'Connect Selected Account'\n              )}\n            </Button>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/BankAccountConnector.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/input.json",
    "https://uipkge.dev/r/react/separator.json"
  ],
  "description": "Plaid/Tink style bank linking modal and account selector with institution search, popular bank grid (Chase, Bank of America, Wells Fargo, Citibank, Capital One, SVB), account radio selection with balances and badges, and an AES-256 encrypted security footer.",
  "categories": [
    "finance",
    "dashboard"
  ]
}