1import { bindLocaleRerender, initOnce, tText } from '../../client-utils.js'; 2import { initLoadingDots } from '../../ui/loading.js'; 3import { createModal, createModalShell } from '../../ui/modal.js'; 4 5const PUSH_CONFIRMATION_QUERY_PARAM = 'confirmPush'; 6const PUSH_CONFIRMATION_ENDPOINT = '/api/domain_name_owner_change_history/confirm_domain_owner_change'; 7const COPY_KEY_BASE = 'backend.dashboard.dashboard.confirmPushModal'; 8 9const RESULT_BUTTON_BASE_CLASSNAME = 10 'shadow-button inline-flex min-w-25 items-center justify-center rounded-md border px-4 py-2.5 text-xl font-extrabold outline-hidden transition-colors duration-300 focus:outline-hidden focus-visible:ring-4'; 11const RESULT_BUTTON_CLASSNAMES = Object.freeze({ 12 error: 13 'border-error bg-error text-white shadow-error/20 hover:bg-error/80 focus-visible:bg-error/80 focus-visible:ring-error/20', 14 success: 15 'border-yellow bg-yellow text-foreground shadow-yellow/20 hover:bg-yellow-light focus-visible:bg-yellow-light focus-visible:ring-yellow/20', 16}); 17 18const PUSH_CONFIRMATION_COPY = Object.freeze({ 19 error: Object.freeze({ 20 titleKey: `${COPY_KEY_BASE}.fail.title`, 21 descriptionKey: `${COPY_KEY_BASE}.fail.text`, 22 buttonKey: `${COPY_KEY_BASE}.fail.button`, 23 }), 24 success: Object.freeze({ 25 titleKey: `${COPY_KEY_BASE}.success.title`, 26 descriptionKey: `${COPY_KEY_BASE}.success.text`, 27 buttonKey: `${COPY_KEY_BASE}.success.button`, 28 }), 29}); 30 31const createElement = (documentRef, tagName, { className = '', attributes = {} } = {}) => { 32 const element = documentRef.createElement(tagName); 33 if (className) { 34 element.className = className; 35 } 36 Object.entries(attributes).forEach(([name, value]) => element.setAttribute(name, String(value ?? ''))); 37 return element; 38}; 39 40const getPushConfirmationToken = (locationLike = globalThis.location) => { 41 try { 42 const href = String(locationLike?.href || ''); 43 const origin = String(locationLike?.origin || 'https://site.invalid'); 44 const url = new URL(href || String(locationLike?.search || ''), origin); 45 return String(url.searchParams.get(PUSH_CONFIRMATION_QUERY_PARAM) || '').trim(); 46 } catch { 47 return ''; 48 } 49}; 50 51const requestPushConfirmation = (apiFetcher, token) => { 52 const normalizedToken = String(token || '').trim(); 53 if (!normalizedToken) { 54 throw new Error('Missing push confirmation token'); 55 } 56 if (!apiFetcher || typeof apiFetcher.getJson !== 'function') { 57 throw new Error('Missing fetcher'); 58 } 59 60 return apiFetcher.getJson(PUSH_CONFIRMATION_ENDPOINT, { 61 cacheMode: 'no-store', 62 credentials: 'include', 63 searchParams: { 64 [PUSH_CONFIRMATION_QUERY_PARAM]: normalizedToken, 65 }, 66 }); 67}; 68 69const processPushConfirmation = async ({ apiFetcher, token, onPhaseChange } = {}) => { 70 onPhaseChange?.('loading'); 71 72 try { 73 await requestPushConfirmation(apiFetcher, token); 74 onPhaseChange?.('success'); 75 return { status: 'success', error: null }; 76 } catch (error) { 77 onPhaseChange?.('error'); 78 return { status: 'error', error }; 79 } 80}; 81 82const getPushConfirmationCopy = (phase, { locale } = {}) => { 83 const copyDefinition = PUSH_CONFIRMATION_COPY[phase] || PUSH_CONFIRMATION_COPY.error; 84 return { 85 loading: tText('common.loading', { locale }), 86 close: tText('backend.dashboard.dashboard.settings.apiKeys.createModal.close', { locale }), 87 title: tText(copyDefinition.titleKey, { locale }), 88 description: tText(copyDefinition.descriptionKey, { locale }), 89 button: tText(copyDefinition.buttonKey, { locale }), 90 }; 91}; 92 93const createPushConfirmationModal = ({ 94 documentRef = globalThis.document, 95 onClose, 96 createModalImpl = createModal, 97 createModalShellImpl = createModalShell, 98 createLoadingDotsImpl = initLoadingDots, 99} = {}) => { 100 const shell = createModalShellImpl({ 101 describedBy: 'dashboard-push-confirmation-description', 102 documentRef, 103 labelledBy: 'dashboard-push-confirmation-title', 104 panelClassName: 'text-center', 105 panelId: 'dashboard-push-confirmation-panel', 106 rootAttributes: { 107 'data-dashboard-push-confirmation-modal': '', 108 }, 109 size: 'lg', 110 }); 111 if (!shell) { 112 return null; 113 } 114 115 const { content, overlay, panel, root } = shell; 116 content.className = 'min-h-48'; 117 118 const closeButton = createElement(documentRef, 'button', { 119 className: 120 'absolute top-2.5 right-2.5 inline-flex rounded-full bg-transparent p-2 text-orange transition-colors hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-orange/30', 121 attributes: { 122 type: 'button', 123 'data-modal-close': '', 124 'data-dashboard-push-confirmation-close': '', 125 }, 126 }); 127 const closeIcon = createElement(documentRef, 'span', { 128 className: 'icon-cross text-2xl', 129 attributes: { 'aria-hidden': 'true' }, 130 }); 131 closeButton.appendChild(closeIcon); 132 panel.appendChild(closeButton); 133 134 const loading = createElement(documentRef, 'div', { 135 className: 'flex min-h-48 items-center justify-center text-foreground', 136 attributes: { 137 role: 'status', 138 'aria-live': 'polite', 139 'data-dashboard-push-confirmation-loading': '', 140 }, 141 }); 142 const loadingDots = createLoadingDotsImpl({ size: 18 }); 143 loadingDots.setAttribute('aria-hidden', 'true'); 144 const loadingLabel = createElement(documentRef, 'span', { 145 className: 'sr-only', 146 attributes: { 'data-dashboard-push-confirmation-loading-label': '' }, 147 }); 148 loading.append(loadingDots, loadingLabel); 149 150 const result = createElement(documentRef, 'div', { 151 className: 'hidden min-h-48 text-center', 152 attributes: { 153 'data-dashboard-push-confirmation-result': '', 154 }, 155 }); 156 result.hidden = true; 157 const title = createElement(documentRef, 'h2', { 158 className: 'm-0 text-2xl font-extrabold', 159 attributes: { 160 id: 'dashboard-push-confirmation-title', 161 'data-dashboard-push-confirmation-title': '', 162 }, 163 }); 164 const description = createElement(documentRef, 'p', { 165 className: 'my-5 leading-7', 166 attributes: { 167 id: 'dashboard-push-confirmation-description', 168 'data-dashboard-push-confirmation-description': '', 169 }, 170 }); 171 const okayButton = createElement(documentRef, 'button', { 172 attributes: { 173 type: 'button', 174 'data-modal-close': '', 175 'data-dashboard-push-confirmation-okay': '', 176 }, 177 }); 178 result.append(title, description, okayButton); 179 content.append(loading, result); 180 181 const modal = createModalImpl({ 182 root, 183 panel, 184 overlay, 185 closeOnOverlay: true, 186 closeOnEsc: true, 187 trapFocus: true, 188 restoreFocus: true, 189 initialFocus: () => (result.hidden ? closeButton : okayButton), 190 onClose, 191 }); 192 if (!modal) { 193 root.remove(); 194 return null; 195 } 196
197 const setView = ({ copy, phase } = {}) => { 198 const isLoading = phase === 'loading'; 199 const resultPhase = phase === 'success' ? 'success' : 'error'; 200 201 panel.setAttribute('aria-busy', isLoading ? 'true' : 'false'); 202 closeButton.setAttribute('aria-label', copy?.close || ''); 203 loadingLabel.textContent = copy?.loading || ''; 204 loading.hidden = !isLoading; 205 loading.classList.toggle('hidden', !isLoading); 206 result.hidden = isLoading; 207 result.classList.toggle('hidden', isLoading); 208 209 if (isLoading) { 210 result.removeAttribute('role'); 211 result.removeAttribute('aria-live'); 212 return; 213 } 214 215 result.setAttribute('role', resultPhase === 'error' ? 'alert' : 'status'); 216 result.setAttribute('aria-live', resultPhase === 'error' ? 'assertive' : 'polite'); 217 title.textContent = copy?.title || ''; 218 description.textContent = copy?.description || ''; 219 okayButton.textContent = copy?.button || ''; 220 okayButton.className = `${RESULT_BUTTON_BASE_CLASSNAME} ${RESULT_BUTTON_CLASSNAMES[resultPhase]}`; 221 }; 222 223 return { 224 close: () => modal.close(), 225 destroy() { 226 modal.destroy(); 227 root.remove(); 228 }, 229 isOpen: () => modal.isOpen(), 230 open: () => modal.open(),
231 setView, 232 }; 233}; 234 235const initDashboardPushConfirmation = async ({ 236 documentRef = globalThis.document, 237 windowRef = globalThis.window, 238 createModalImpl = createPushConfirmationModal, 239 bindLocaleRerenderImpl = bindLocaleRerender, 240} = {}) => { 241 const root = documentRef?.querySelector?.('[data-dashboard-page]'); 242 const token = getPushConfirmationToken(windowRef?.location); 243 if (!(root instanceof HTMLElement) || !token || !initOnce(root, 'dashboardPushConfirmationInitialized')) { 244 return false; 245 } 246 247 let phase = 'loading'; 248 let currentLocale; 249 let unsubscribeLocale = () => {}; 250 const modal = createModalImpl({ 251 documentRef, 252 onClose: () => unsubscribeLocale(), 253 }); 254 if (!modal) { 255 return false; 256 } 257 258 const render = (locale) => { 259 currentLocale = locale || currentLocale; 260 modal.setView({ 261 phase, 262 copy: getPushConfirmationCopy(phase, { locale: currentLocale }), 263 }); 264 }; 265 266 render(); 267 modal.open(); 268 unsubscribeLocale = bindLocaleRerenderImpl({ 269 hasServerMarkup: () => true, 270 render, 271 }); 272 273 const result = await processPushConfirmation({ 274 apiFetcher: windowRef?.fetcher, 275 token, 276 onPhaseChange: (nextPhase) => { 277 phase = nextPhase; 278 render(currentLocale); 279 }, 280 }); 281 282 if (result.error) { 283 console.error('Failed to confirm internal domain transfer:', result.error); 284 } 285 286 return result.status; 287}; 288 289export { 290 PUSH_CONFIRMATION_ENDPOINT, 291 PUSH_CONFIRMATION_QUERY_PARAM, 292 createPushConfirmationModal, 293 getPushConfirmationCopy, 294 getPushConfirmationToken, 295 initDashboardPushConfirmation, 296 processPushConfirmation, 297 requestPushConfirmation, 298};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.