PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / AutoLaunch / LaunchPage.jsx

LaunchPage.jsx in Extendify 3.2.1, at src/AutoLaunch/LaunchPage.jsx

321 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { CreatingSite } from '@auto-launch/components/CreatingSite';
2 import { DescriptionGathering } from '@auto-launch/components/DescriptionGathering';
3 import { LaunchUpdate } from '@auto-launch/components/LaunchUpdate';
4 import { Logo } from '@auto-launch/components/Logo';
5 import {
6 ModalHeading,
7 ModalLink,
8 ModalText,
9 } from '@auto-launch/components/Modal';
10 import { ShaderBackground } from '@auto-launch/components/ShaderBackground';
11 import { activeLogo } from '@auto-launch/functions/design';
12 import { getAbTest } from '@auto-launch/functions/getAbTest';
13 import { preLaunchFunctions } from '@auto-launch/functions/setup';
14 import { updateOption } from '@auto-launch/functions/wp';
15 import { useExtendifyCodeConnector } from '@auto-launch/hooks/useExtendifyCodeConnector';
16 import { useInstallRequiredPlugins } from '@auto-launch/hooks/useInstallRequiredPlugins';
17 import { useMigrateChoice } from '@auto-launch/hooks/useMigrateChoice';
18 import { useNeedsTheme } from '@auto-launch/hooks/useNeedsTheme';
19 import { useRestartLaunch } from '@auto-launch/hooks/useRestartLaunch';
20 import { useLaunchDataStore } from '@auto-launch/state/launch-data';
21 import { launchStrings } from '@auto-launch/strings';
22 import {
23 activeChoiceTemplate,
24 activeDescriptionTemplate,
25 activeModalTemplate,
26 } from '@auto-launch/templates';
27 import { ActionButton, SecondaryButton } from '@auto-launch/templates/button';
28 import { Heading, PageLink } from '@auto-launch/templates/heading';
29 import { digest } from '@shared/api/digest';
30 import { registerCoreBlocks } from '@wordpress/block-library';
31 import { getBlockTypes } from '@wordpress/blocks';
32 import { useSelect } from '@wordpress/data';
33 import { useEffect, useState } from '@wordpress/element';
34 import classNames from 'classnames';
35 import { AnimatePresence, motion } from 'framer-motion';
36 import { checkIn, reportRestApiStatus } from './functions/insights';
37
38 export const LaunchPage = () => {
39 const theme = useSelect((select) => select('core').getCurrentTheme());
40 // Checking `theme` here makes sure the data is populated
41 const needsTheme = theme && theme?.textdomain !== 'extendable';
42
43 const oldPages = window.extLaunchData.resetSiteInformation.pagesIds ?? [];
44 const needsToReset = oldPages.length > 0;
45
46 const pluginUpdateNeeded = Boolean(window.extLaunchData?.pluginUpdateNeeded);
47 const themeUpdateNeeded = Boolean(window.extLaunchData?.themeUpdateNeeded);
48 const pluginUpdateViaRest = Boolean(
49 window.extLaunchData?.pluginUpdateViaRest,
50 );
51 const launchUpdateNeeded = pluginUpdateNeeded || themeUpdateNeeded;
52 const launchUpdateAttempt = window.extLaunchData?.launchUpdateAttempt ?? 0;
53 const launchUpdateStale = window.extLaunchData?.launchUpdateStale ?? {};
54 const launchUpdateGaveUp = Object.keys(launchUpdateStale).length > 0;
55
56 const {
57 title,
58 descriptionRaw,
59 go,
60 urlParams,
61 designBuild,
62 setData,
63 showExtendifyCodeScreen,
64 } = useLaunchDataStore();
65 const skipDescription =
66 Boolean(urlParams?.['build-id']) ||
67 designBuild ||
68 ((title || descriptionRaw) && go);
69 const showConnector = !skipDescription && showExtendifyCodeScreen;
70 const showExitLink =
71 !skipDescription && !window.extLaunchData?.hideAutoLaunchExitLink;
72 const inMigrateVariant =
73 getAbTest('AutoLaunch.MigrateScreen').variant === 'B';
74 const [choosingMigration, setChoosingMigration] = useState(inMigrateVariant);
75 const showMigrateChoice = !skipDescription && choosingMigration;
76
77 // Deleting the old pages first leaves the user on a theme Launch cannot build on.
78 const blocker = launchUpdateNeeded
79 ? 'update'
80 : needsTheme
81 ? 'theme'
82 : needsToReset
83 ? 'reset'
84 : null;
85
86 // Only the describe screen commits to a build; every other screen can still leave.
87 useInstallRequiredPlugins({
88 enabled:
89 !blocker && !skipDescription && !showConnector && !showMigrateChoice,
90 });
91
92 useEffect(() => {
93 if (launchUpdateNeeded) return;
94 if (launchUpdateGaveUp) {
95 digest({
96 error: new Error('Launch went ahead without the pending update'),
97 details: {
98 source: 'auto-launch',
99 caller: 'launch-update',
100 stale: launchUpdateStale,
101 },
102 });
103 }
104 // translators: Launch is a noun.
105 document.title = launchStrings().documentTitle;
106 updateOption('extendify_launch_loaded', new Date().toISOString());
107 // We load core blocks so we can parse them
108 if (getBlockTypes().length === 0) registerCoreBlocks();
109
110 preLaunchFunctions();
111 checkIn({ stage: 'launch_page' });
112 reportRestApiStatus();
113 }, [launchUpdateNeeded, launchUpdateGaveUp]);
114
115 const modal =
116 blocker === 'update' ? (
117 <UpdateScreen
118 pluginUpdateNeeded={pluginUpdateNeeded}
119 themeUpdateNeeded={themeUpdateNeeded}
120 pluginUpdateViaRest={pluginUpdateViaRest}
121 pluginSlug={window.extLaunchData?.pluginSlug}
122 attempt={launchUpdateAttempt}
123 />
124 ) : blocker === 'theme' ? (
125 <NeedsThemeScreen />
126 ) : blocker === 'reset' ? (
127 <RestartScreen pages={oldPages} />
128 ) : null;
129 // Site creation must not start behind a modal the user has not cleared.
130 const screen = modal
131 ? 'description'
132 : showConnector
133 ? 'connector'
134 : showMigrateChoice
135 ? 'migrate'
136 : skipDescription
137 ? 'creating'
138 : 'description';
139
140 return (
141 <Viewport modal={modal} screen={screen}>
142 {screen === 'creating' ? (
143 <CreatingSite />
144 ) : screen === 'connector' ? (
145 <ConnectorScreen
146 onProceed={() => setData('go', true)}
147 footer={
148 <BackLink
149 onClick={() => setData('showExtendifyCodeScreen', false)}
150 />
151 }
152 />
153 ) : screen === 'migrate' ? (
154 <MigrateScreen
155 onBuildNew={() => setChoosingMigration(false)}
156 footer={showExitLink ? <ExitLink /> : null}
157 />
158 ) : (
159 <DescriptionPage
160 heading={<TheTitle />}
161 footer={showExitLink ? <ExitLink /> : null}
162 >
163 {/* Matches the migrate screen's height, so choosing Build does not jump. */}
164 <div className={classNames({ 'md:h-72.75': inMigrateVariant })}>
165 <div className="mx-auto w-full">
166 <DescriptionGathering autoFocus={!modal} />
167 </div>
168 </div>
169 </DescriptionPage>
170 )}
171 </Viewport>
172 );
173 };
174
175 const NeedsThemeScreen = () => {
176 const { title, body, action } = useNeedsTheme();
177 return (
178 <ModalPage
179 heading={<ModalHeading title={title} />}
180 body={<ModalText>{body}</ModalText>}
181 actions={<ModalLink href={action.href} label={action.label} />}
182 />
183 );
184 };
185
186 const UpdateScreen = (props) => <LaunchUpdate {...props} />;
187
188 const RestartScreen = ({ pages }) => {
189 const { title, body, note, exit, confirm } = useRestartLaunch({ pages });
190 return (
191 <ModalPage
192 left
193 heading={<ModalHeading title={title} />}
194 body={
195 <>
196 <ModalText>{body}</ModalText>
197 <ModalText strong>{note}</ModalText>
198 </>
199 }
200 actions={
201 <>
202 <SecondaryButton
203 disabled={confirm.processing}
204 label={exit.label}
205 onClick={exit.onClick}
206 />
207 <ActionButton {...confirm} />
208 </>
209 }
210 />
211 );
212 };
213
214 const ConnectorScreen = ({ onProceed, footer }) => {
215 const { title, subtitle, options } = useExtendifyCodeConnector({ onProceed });
216 return (
217 <ChoicePage
218 heading={title && <Heading subtitle={subtitle} title={title} />}
219 options={options}
220 footer={footer}
221 />
222 );
223 };
224
225 const MigrateScreen = ({ onBuildNew, footer }) => {
226 const { title, options } = useMigrateChoice({ onBuildNew });
227 return (
228 <ChoicePage
229 heading={<Heading title={title} />}
230 options={options}
231 footer={footer}
232 />
233 );
234 };
235
236 const Viewport = ({ children, screen, modal }) => (
237 // Inline inset: utilities are !important here and would win over it.
238 <div
239 style={{ zIndex: 99999 + 1, top: 0, right: 0, bottom: 0, left: 0 }}
240 className="group fixed bg-ui-page"
241 >
242 <AnimatePresence mode="wait" initial={false}>
243 <motion.div
244 key={screen}
245 className="absolute inset-0"
246 // React 18 drops a boolean inert; only a string reaches the DOM.
247 inert={modal ? '' : undefined}
248 initial={{ opacity: 0 }}
249 animate={{ opacity: 1 }}
250 exit={{ opacity: 0 }}
251 transition={{ duration: 0.25 }}
252 >
253 {children}
254 </motion.div>
255 </AnimatePresence>
256 {modal}
257 <ShaderBackground />
258 </div>
259 );
260
261 const DescriptionPage = ({ children, footer, heading }) => {
262 const Page = activeDescriptionTemplate();
263 const { partnerLogo, partnerName } = window.extSharedData ?? {};
264 // A template cannot tell a heading that renders null from one that draws.
265 const has = {
266 heading: Boolean(heading),
267 footer: Boolean(footer),
268 };
269 const parts = {
270 logo: <Logo name={partnerName} src={activeLogo() ?? partnerLogo} />,
271 heading,
272 content: children,
273 footer,
274 };
275
276 return <Page parts={parts} has={has} />;
277 };
278
279 const ModalPage = ({ heading, body, actions, left }) => {
280 const Page = activeModalTemplate();
281 const has = {
282 heading: Boolean(heading),
283 actions: Boolean(actions),
284 card: true,
285 left: Boolean(left),
286 };
287
288 return <Page parts={{ heading, body, actions }} has={has} />;
289 };
290
291 const ChoicePage = ({ heading, options, footer }) => {
292 const Page = activeChoiceTemplate();
293 const { partnerLogo, partnerName } = window.extSharedData ?? {};
294 const has = {
295 heading: Boolean(heading),
296 footer: Boolean(footer),
297 };
298 const parts = {
299 logo: <Logo name={partnerName} src={activeLogo() ?? partnerLogo} />,
300 heading,
301 options,
302 footer,
303 };
304
305 return <Page parts={parts} has={has} />;
306 };
307
308 const ExitLink = () => (
309 <PageLink
310 href={window.extSharedData.adminUrl}
311 label={launchStrings().exitLink}
312 onClick={() => checkIn({ stage: 'exit_to_wp_admin' })}
313 />
314 );
315
316 const BackLink = ({ onClick }) => (
317 <PageLink label={launchStrings().back} onClick={onClick} />
318 );
319
320 const TheTitle = () => <Heading title={launchStrings().heading} />;
321