diff --git a/apps/super-admin/src/components/Toast.module.css b/apps/super-admin/src/components/Toast.module.css index f3b1817..130eaae 100644 --- a/apps/super-admin/src/components/Toast.module.css +++ b/apps/super-admin/src/components/Toast.module.css @@ -2,7 +2,8 @@ position: fixed; bottom: 24px; right: 24px; - z-index: 300; + /* Above Modal overlay (z-index 300) so success/error feedback is visible while dialogs are open */ + z-index: 400; display: flex; flex-direction: column; align-items: flex-end; diff --git a/apps/super-admin/src/pages/BusinessesPage.tsx b/apps/super-admin/src/pages/BusinessesPage.tsx index a92293e..2a24042 100644 --- a/apps/super-admin/src/pages/BusinessesPage.tsx +++ b/apps/super-admin/src/pages/BusinessesPage.tsx @@ -692,23 +692,39 @@ export function BusinessesPage() { } } + const domainSubmitGenRef = useRef(0) + + function closeDomainModal() { + domainSubmitGenRef.current += 1 + setDomainSubmitting(false) + setDomainOpen(false) + setDomainBusiness(null) + setDomainId(null) + setDomainHost('') + setDomainGitRepoUrl('') + setDomainError('') + } + async function submitDomain() { if (!domainBusiness) return const host = domainHost.trim() if (!host) return const gitRepoUrl = domainGitRepoUrl.trim() + const businessId = domainBusiness.id + const submitId = ++domainSubmitGenRef.current setDomainSubmitting(true) setDomainError('') try { if (domainId) { - const updated = (await updateBusinessDomain(domainBusiness.id, domainId, { + const updated = (await updateBusinessDomain(businessId, domainId, { host, ...(gitRepoUrl ? { gitRepoUrl } : {}), })) as { id: number host: string deploySlug?: string | null + gitRepoUrl?: string | null provisionError?: string | null } setData((prev) => { @@ -716,7 +732,18 @@ export function BusinessesPage() { return { ...prev, items: prev.items.map((item) => - item.id === domainBusiness.id ? { ...item, domain: host } : item, + item.id === businessId + ? { + ...item, + domain: host, + ...(gitRepoUrl && !updated.provisionError + ? { + gitRepoUrl: updated.gitRepoUrl ?? gitRepoUrl, + deploySlug: updated.deploySlug ?? item.deploySlug, + } + : {}), + } + : item, ), } }) @@ -732,7 +759,7 @@ export function BusinessesPage() { showToast(`Domain updated to "${host}".`, 'success') } } else { - const created = (await addBusinessDomain(domainBusiness.id, { + const created = (await addBusinessDomain(businessId, { host, isPrimary: true, ...(gitRepoUrl ? { gitRepoUrl } : {}), @@ -741,6 +768,7 @@ export function BusinessesPage() { host: string sslEnabled: boolean deploySlug?: string | null + gitRepoUrl?: string | null provisionError?: string | null } setData((prev) => { @@ -748,12 +776,18 @@ export function BusinessesPage() { return { ...prev, items: prev.items.map((item) => - item.id === domainBusiness.id + item.id === businessId ? { ...item, domainId: created.id, domain: created.host, sslEnabled: created.sslEnabled ?? false, + ...(gitRepoUrl && !created.provisionError + ? { + gitRepoUrl: created.gitRepoUrl ?? gitRepoUrl, + deploySlug: created.deploySlug ?? null, + } + : {}), } : item, ), @@ -771,14 +805,20 @@ export function BusinessesPage() { showToast(`Domain "${host}" added.`, 'success') } } + if (submitId !== domainSubmitGenRef.current) return setDomainOpen(false) setDomainBusiness(null) setDomainId(null) + setDomainHost('') setDomainGitRepoUrl('') + setDomainError('') } catch (err) { + if (submitId !== domainSubmitGenRef.current) return setDomainError(err instanceof ApiError ? err.message : 'Unable to save domain.') } finally { - setDomainSubmitting(false) + if (submitId === domainSubmitGenRef.current) { + setDomainSubmitting(false) + } } } @@ -1270,19 +1310,18 @@ export function BusinessesPage() { { - setDomainOpen(false) - setDomainBusiness(null) - setDomainId(null) - setDomainGitRepoUrl('') - setDomainError('') - }} + onClose={closeDomainModal} > {domainError ? (

{domainError}

) : null} + {domainSubmitting && domainGitRepoUrl.trim() ? ( +

+ Saving domain and setting up storefront on the websites server… +

+ ) : null}
setDomainGitRepoUrl(e.target.value)} - placeholder="https://git.meshkee.com/Meshkee-Websites/oaktasty.git" + placeholder="https://git.meshkee.com/Meshkee-Websites/oaktasty.git" disabled={domainSubmitting} autoComplete="off" spellCheck={false} @@ -1315,8 +1354,7 @@ export function BusinessesPage() { @@ -1326,7 +1364,7 @@ export function BusinessesPage() { onClick={() => void submitDomain()} disabled={domainSubmitting || !domainHost.trim()} > - Save + {domainSubmitting ? 'Saving…' : 'Save'}
diff --git a/apps/super-admin/src/pages/WebsitesPage.tsx b/apps/super-admin/src/pages/WebsitesPage.tsx index 91da1d2..213b9b9 100644 --- a/apps/super-admin/src/pages/WebsitesPage.tsx +++ b/apps/super-admin/src/pages/WebsitesPage.tsx @@ -10,7 +10,6 @@ import { RotateCcw, Search, ShieldPlus, - Unlock, } from 'lucide-react' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { DomainLinksOverflowMenu } from '../components/DomainLinksOverflowMenu' @@ -26,7 +25,6 @@ import { listDomains, removeDomain, setDomainActive, - setDomainSsl, syncSsl, updateDomain, } from '../services/domainService' @@ -105,7 +103,6 @@ export function WebsitesPage() { const [removeTarget, setRemoveTarget] = useState(null) const [togglingActiveId, setTogglingActiveId] = useState(null) - const [togglingSslId, setTogglingSslId] = useState(null) const [deployingId, setDeployingId] = useState(null) const [syncingSsl, setSyncingSsl] = useState(false) const [issuingWebsiteSsl, setIssuingWebsiteSsl] = useState(false) @@ -236,38 +233,6 @@ export function WebsitesPage() { } } - async function handleToggleSsl(domain: DomainListItem, sslEnabled: boolean) { - setTogglingSslId(domain.id) - setError('') - setData((prev) => { - if (!prev) return prev - return { - ...prev, - items: prev.items.map((item) => (item.id === domain.id ? { ...item, sslEnabled } : item)), - } - }) - try { - await setDomainSsl(domain.id, sslEnabled) - showToast( - `SSL ${sslEnabled ? 'enabled' : 'disabled'} for "${domain.host}".`, - 'success', - ) - } catch (err) { - setData((prev) => { - if (!prev) return prev - return { - ...prev, - items: prev.items.map((item) => - item.id === domain.id ? { ...item, sslEnabled: !sslEnabled } : item, - ), - } - }) - setError(err instanceof ApiError ? err.message : 'Unable to update SSL status.') - } finally { - setTogglingSslId(null) - } - } - async function handleSyncSsl() { if (syncingSsl || issuingWebsiteSsl) return @@ -309,18 +274,9 @@ export function WebsitesPage() { } else { showToast(result.message || 'Website SSL updated.', 'success') } - if (result.issued.length > 0) { - setData((prev) => { - if (!prev) return prev - const issued = new Set(result.issued) - return { - ...prev, - items: prev.items.map((item) => - issued.has(item.host) ? { ...item, sslEnabled: true } : item, - ), - } - }) - } + // Re-load so ssl_enabled matches live probes (including corrected false flags). + const refreshed = await listDomains({ page, pageSize: PAGE_SIZE, ...appliedFilters }) + setData(refreshed) } catch (err) { const message = err instanceof ApiError ? err.message : 'Unable to issue website SSL.' setError(message) @@ -331,13 +287,16 @@ export function WebsitesPage() { } async function handleIssueDomainSsl(domain: DomainListItem) { - if (!domain.deploySlug || issuingSslId != null) return + if (issuingSslId != null) return flushSync(() => { setIssuingSslId(domain.id) }) setError('') - showToast(`Issuing SSL for "${domain.host}"…`, 'info') + showToast( + `Checking SSL for ${domain.host}, business.${domain.host}, customer.${domain.host}…`, + 'info', + ) try { const result = await issueDomainSsl(domain.id) @@ -350,9 +309,15 @@ export function WebsitesPage() { ), } }) - showToast(result.message || `SSL issued for "${domain.host}".`, 'success') + if (result.failed.length > 0) { + const detail = result.failed.map((f) => `${f.host}: ${f.error}`).join(' · ') + setError(detail) + showToast(result.message, result.issued.length > 0 ? 'info' : 'error') + } else { + showToast(result.message || `SSL ok for "${domain.host}".`, 'success') + } } catch (err) { - const message = err instanceof ApiError ? err.message : 'Unable to issue SSL.' + const message = err instanceof ApiError ? err.message : 'Unable to ensure SSL.' setError(message) showToast(message, 'error') } finally { @@ -468,7 +433,7 @@ export function WebsitesPage() { onClick={() => void handleIssueWebsiteSsl()} disabled={issuingWebsiteSsl || syncingSsl} aria-busy={issuingWebsiteSsl} - title="Issue Let's Encrypt certs for storefront domains missing SSL" + title="Probe storefront HTTPS and issue Let's Encrypt where the cert is missing or wrong" > {issuingWebsiteSsl ? ( @@ -665,46 +630,31 @@ export function WebsitesPage() { )} ) : null} - {domain.deploySlug && !domain.sslEnabled ? ( - - ) : ( - - )} +