PageSourceSearch

https://site.fr/_static/ad2521ddc1b5acb4/scripts/features/dashboard/push-confirmation.js

js site.fr collected 2026-10-02 04:05:30 UTC 9,559 bytes, 298 lines download raw bytes

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.