The codebase that powers boop.cat boop.cat

fix: custom domains not working, add loading for checking for custom domains

+36 -7
+25 -3
client/src/pages/DashboardSite.jsx
··· 24 24 Eye, 25 25 EyeOff, 26 26 Trash2, 27 - Plus 27 + Plus, 28 + Loader2 28 29 } from 'lucide-react'; 29 30 30 31 function Toast({ message, onClose }) { ··· 357 358 const [visibleEnvKeys, setVisibleEnvKeys] = useState(new Set()); 358 359 const [envModalOpen, setEnvModalOpen] = useState(false); 359 360 const [editingEnv, setEditingEnv] = useState(null); // { key, value } for edit mode 361 + const [pollingDomains, setPollingDomains] = useState(new Set()); 360 362 361 363 const repoInfo = useMemo(() => { 362 364 const gitUrl = site?.git?.url || ''; ··· 707 709 } 708 710 709 711 async function pollCustomDomain(customDomainId) { 712 + setPollingDomains((prev) => new Set(prev).add(customDomainId)); 710 713 try { 711 714 const data = await api( 712 715 `/api/sites/${encodeURIComponent(site.id)}/custom-domains/${encodeURIComponent(customDomainId)}/poll`, 713 716 { method: 'POST' } 714 717 ); 715 718 setCustomDomains((prev) => prev.map((d) => (d.id === customDomainId ? { ...d, ...data } : d))); 719 + setToast('Domain status updated.'); 716 720 } catch (e) { 717 721 console.error('Poll failed:', e); 722 + setError(e.message || 'Failed to check domain status.'); 723 + } finally { 724 + setPollingDomains((prev) => { 725 + const next = new Set(prev); 726 + next.delete(customDomainId); 727 + return next; 728 + }); 718 729 } 719 730 } 720 731 ··· 1097 1108 </div> 1098 1109 <div className="actions"> 1099 1110 {isPending && ( 1100 - <button className="btn ghost" onClick={() => pollCustomDomain(d.id)}> 1101 - Check 1111 + <button 1112 + className="btn ghost" 1113 + disabled={pollingDomains.has(d.id)} 1114 + onClick={() => pollCustomDomain(d.id)} 1115 + > 1116 + {pollingDomains.has(d.id) ? ( 1117 + <> 1118 + <Loader2 size={14} className="animate-spin" style={{ marginRight: 6 }} /> 1119 + Checking... 1120 + </> 1121 + ) : ( 1122 + 'Check' 1123 + )} 1102 1124 </button> 1103 1125 )} 1104 1126 <button className="btn danger" onClick={() => removeCustomDomain(d.id)}>
+5
client/src/styles.css
··· 1719 1719 } 1720 1720 1721 1721 @keyframes spin { 1722 + from { 1723 + transform: rotate(0deg); 1724 + } 1725 + 1722 1726 to { 1723 1727 transform: rotate(360deg); 1724 1728 } ··· 1726 1730 1727 1731 .animate-spin { 1728 1732 animation: spin 1s linear infinite; 1733 + display: inline-block; 1729 1734 } 1730 1735 1731 1736 .sidebarUser {
+4
edge/worker.js
··· 82 82 const hostname = (request.headers.get('x-forwarded-host') || url.hostname).toLowerCase(); 83 83 const { ROOT_DOMAIN, B2_DOWNLOAD_BASE, B2_BUCKET_NAME, B2_KEY_ID, B2_APP_KEY, ROUTING } = env; 84 84 85 + if (hostname === ROOT_DOMAIN) { 86 + return fetch(request); 87 + } 88 + 85 89 if (!B2_DOWNLOAD_BASE || !B2_BUCKET_NAME) { 86 90 return new Response('Service misconfigured', { status: 500 }); 87 91 }
+2 -4
edge/wrangler.toml
··· 5 5 # Enable workers.dev subdomain (needed for custom domain fallback origin) 6 6 workers_dev = true 7 7 8 - # Handle *.boop.cat subdomains 9 - # Custom domains use Cloudflare for SaaS and route through sites.boop.cat fallback 8 + # Handle all traffic on the zone, including CF for SaaS custom hostnames 10 9 routes = [ 11 - { pattern = "*.boop.cat/*", zone_name = "boop.cat" }, 12 - { pattern = "sites.boop.cat", custom_domain = true } 10 + { pattern = "*/*", zone_name = "boop.cat" } 13 11 ] 14 12 15 13 # Custom domain support - the fallback origin (sites.boop.cat) should CNAME to this worker