/**
* Hatch setup screen.
*
* Four steps, one question on each screen: connect Cloudflare, choose where the
* frontend lives, choose the WordPress account it reads with, then review and
* deploy. Back and Next move between them. Nothing changes until Deploy.
*
* Everything talks to the /hatch/v1/deploy/* REST routes. The token is typed
* here only long enough to send it to this site; the server stores it
* encrypted if the administrator asks it to.
*/
import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { HxIcon, HxBtn, HxBadge, HxCard, HxRow, HxToggle, HxInp, HxSelect, HxSeg, Chip, HxNotice, HxSpinner, HxField, hxFetch, hxErrorNode } from '../components.jsx';
import { IC } from '../icons.jsx';
const PROVIDER = 'cloudflare';
const ACCOUNT_ID_PATTERN = /^[a-f0-9]{32}$/i;
const STEP_COUNT = 4;
const IDLE_DEPLOY = { phase: 'idle', error: '', message: '', warnings: [] };
const IDLE_VERIFY = { phase: 'idle', error: '', code: '', accounts: [] };
/**
* The address visitors use, from the saved deploy state.
*
* @param {Object} state Deploy state returned by GET /deploy/status.
* @return {string} Full URL, or an empty string.
*/
function publicUrlOf(state) {
if (!state) return '';
if (state.domain) {
return 'https://' + state.domain + (state.mount_mode === 'subfolder' ? state.subpath || '' : '');
}
return state.origin || '';
}
/**
* Plain-language cause and fix for each token error the server returns.
* A code that is not listed falls back to the server's own message.
*
* @param {string} code Error code from the verify route.
* @return {{title: string, fix: string}|null} Copy to show, or null.
*/
function tokenErrorCopy(code) {
const again = {
title: __('Cloudflare did not answer', 'hatch-connect'),
fix: __('Nothing was changed. Wait a minute, then press Check token again.', 'hatch-connect'),
};
const rejected = {
title: __('Cloudflare does not recognise this token', 'hatch-connect'),
fix: __('Copy the whole token again, with nothing missing at the start or the end. If you cannot find it, create a new token with the button above.', 'hatch-connect'),
};
const map = {
hatch_cf_no_token: {
title: __('Paste your token first', 'hatch-connect'),
fix: __('Copy the token from Cloudflare, paste it in the box, then press Check token.', 'hatch-connect'),
},
hatch_cf_auth: rejected,
hatch_cf_rejected: rejected,
hatch_cf_token_inactive: {
title: __('This token is no longer active', 'hatch-connect'),
fix: __('It may have expired or been turned off in Cloudflare. Create a new token with the button above.', 'hatch-connect'),
},
hatch_cf_permission: {
title: __('The token is missing a permission', 'hatch-connect'),
fix: __('Create a new token with the button above. That page sets the permissions for you. Then paste the new token here.', 'hatch-connect'),
},
hatch_cf_rate_limited: again,
hatch_cf_unavailable: again,
hatch_cf_unreachable: again,
hatch_cf_bad_response: again,
};
return map[code] || null;
}
/**
* Progress indicator: "Step n of 4" and the four step names. The current step
* carries aria-current, so the position never rests on colour alone.
*
* @param {number} current 1 to STEP_COUNT. STEP_COUNT + 1 means every step is done.
*/
function Stepper({ current }) {
const steps = [
__('Connect Cloudflare', 'hatch-connect'),
__('Choose where it lives', 'hatch-connect'),
__('WordPress account', 'hatch-connect'),
__('Review and deploy', 'hatch-connect'),
];
return (
{/* translators: 1: current step number, 2: total number of steps. */}
{sprintf(__('Step %1$d of %2$d', 'hatch-connect'), Math.min(current, STEP_COUNT), STEP_COUNT)}
{steps.map((label, i) => {
const n = i + 1;
const done = current > n;
return (
{__('Open the Connection tab and choose Disconnect. Hatch forgets the stored account and visitors see your WordPress site again. The Worker stays in your Cloudflare account until you delete it there.', 'hatch-connect')}
{hasSavedToken && (
{__('Saved Cloudflare token', 'hatch-connect')}
{__('Your token is saved on this site, encrypted, so you can deploy again without pasting it. Forgetting it removes it from this site. It does not cancel the token in Cloudflare.', 'hatch-connect')}
{IC.check}{__('Cloudflare accepted your token.', 'hatch-connect')} { resetVerification(); setToken(''); }}>
{__('Use a different token', 'hatch-connect')}
{appPasswordsOff && (
{__('Open your profile', 'hatch-connect')} : null}
>
{__('WordPress turns them off when the site is not on https, or when a plugin or setting turns them off. Switch the site to https or turn them back on, then come back here.', 'hatch-connect')}
)}
{usingSavedWp ? (
{__('Cloudflare.', 'hatch-connect')}{' '}
{domain.trim()
? __('Adds one Worker, the small program that runs your frontend. Turns on its free workers.dev address and points your domain at it.', 'hatch-connect')
: __('Adds one Worker, the small program that runs your frontend. Turns on its free workers.dev address.', 'hatch-connect')}
{__('Your site.', 'hatch-connect')}{' '}{__('Visitors who open your WordPress address are sent to the frontend. Your theme stays as it is.', 'hatch-connect')}
{disc.permalinksPlain && (
{__('Post addresses.', 'hatch-connect')}{' '}
{__('Your post addresses are set to Plain, which the frontend cannot read. They change to Post name only if you turn on the switch below.', 'hatch-connect')}
)}
{needsPublicAddress && (
{ /* translators: %s: this site's address, for example http://localhost:8810. */ sprintf(__('%s is a local or private address, so the frontend cannot load your posts from it. Enter an address that works on the internet, or deploy later.', 'hatch-connect'), disc.wpUrl || '')}
setPublicAddress(e.target.value)} />