File "CreatingSite.jsx"

Full Path: /home/shadsolw/public_html/wp-content/plugins/extendify/src/AutoLaunch/components/CreatingSite.jsx
File size: 5.56 KB
MIME-type: text/x-java
Charset: utf-8

import { useCreateSite } from '@auto-launch/hooks/useCreateSite';
import { useRateLimitedCursor } from '@auto-launch/hooks/useRateLimitedCursor';
import { checkmark, pulser } from '@auto-launch/icons';
import { useLaunchDataStore } from '@auto-launch/state/launch-data';
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Icon, offline } from '@wordpress/icons';
import { AnimatePresence, motion } from 'framer-motion';

const itemVariants = {
	enter: { opacity: 0, y: 20 },
	center: { opacity: 1, y: 0 },
	exit: { opacity: 0, y: -20 },
};
const VISIBLE_MSG_COUNT = 6;

const { adminUrl, homeUrl } = window.extSharedData;

export const CreatingSite = ({ height }) => {
	const { done } = useCreateSite();
	const pos = useRef(0);
	const [messagesD, setMessagesD] = useState([]);
	const [loadAdmin, setLoadAdmin] = useState(false);
	const messages = messagesD.slice(-VISIBLE_MSG_COUNT);
	const {
		statusMessages,
		errorMessage,
		setErrorMessage,
		errorCount,
		needToStall,
		resetErrorCount,
		setPulse,
	} = useLaunchDataStore();

	useRateLimitedCursor(
		() => {
			if (errorMessage) return false;
			if (pos.current >= statusMessages.length) return false;

			const remaining = statusMessages.length - pos.current;
			const MAX_BACKLOG = 3;

			// Skip ahead silently if backed up
			if (remaining > MAX_BACKLOG && pos.current > 0) {
				pos.current = statusMessages.length - MAX_BACKLOG;
			}

			// The colors on the pulsing dot
			const hues = [50, 100, 150, 200, 250, 300, 350].filter(
				(h) => h !== messagesD[messagesD.length - 1]?.hue,
			);
			const hue = hues[Math.floor(Math.random() * hues.length)];
			const next = { text: statusMessages[pos.current], hue };
			pos.current += 1;

			setMessagesD((prev) => [...prev, next]);
			return pos.current < statusMessages.length;
		},
		// Variable timer to feel more natural, 2.5-3.25s
		Math.floor(2500 + Math.random() * 750),
		[statusMessages.length],
	);

	useEffect(() => {
		if (!errorMessage) return;
		const timeout = setTimeout(() => {
			// Clear after 5 seconds
			setErrorMessage(null);
		}, 5000);
		return () => clearTimeout(timeout);
	}, [errorMessage, setErrorMessage]);

	useEffect(() => {
		setPulse(!done);
	}, [done]);

	useEffect(() => {
		if (!done) return;
		setLoadAdmin(true);
		const timeout = setTimeout(() => {
			window.location.replace(
				`${homeUrl}?extendify-launch-success=1&extendify-agent-onboarding=1`,
			);
		}, 3000);
		return () => clearTimeout(timeout);
	}, [done]);

	useEffect(() => {
		if (!needToStall()) return;
		const timer = setTimeout(() => {
			resetErrorCount();
		}, 10000); // reset after 10 seconds
		return () => clearTimeout(timer);
	}, [needToStall, errorCount, resetErrorCount]);

	if (needToStall()) {
		return (
			<div className="bg-white w-full h-full p-6 lg:p-12 lg:py-8 overflow-hidden text-gray-900 flex flex-col rounded-lg">
				<h2 className="text-lg text-center text-gray-900 font-semibold px-4 py-0 m-0">
					{__('We are experiencing some delays', 'extendify-local')}
				</h2>
				<div className="grow flex items-center justify-center">
					<Icon icon={offline} size={90} fill="var(--ext-design-main)" />
				</div>
				<div className="m-0 p-0 leading-tight text-sm text-center">
					{__('Pausing for a few seconds', 'extendify-local')}
				</div>
			</div>
		);
	}

	return (
		<div
			className="w-full overflow-hidden items-center justify-end flex flex-col p-6"
			style={{ height: height || 'auto' }}
		>
			<AnimatePresence initial={false} mode="popLayout">
				{/* Spacer for the first 5 messages */}
				{Array.from({ length: VISIBLE_MSG_COUNT - messages.length }).map(
					(_, index) => (
						<motion.div
							key={`spacer-${index}`}
							layout
							variants={itemVariants}
							initial="enter"
							animate="center"
							exit="exit"
							transition={{ duration: 0.3 }}
							className="m-0 mb-2 p-0 leading-tight h-7 text-lg"
						>
							&nbsp;
						</motion.div>
					),
				)}
				{messages.map(({ text, hue }, index) => {
					const lastIndex = messages.length - 1;
					const isLast = index === lastIndex;

					return (
						<motion.div
							key={text}
							layout
							variants={itemVariants}
							initial="enter"
							animate="center"
							exit="exit"
							transition={{ duration: 0.3 }}
							className="m-0 mb-2 p-0 leading-tight h-7 text-lg"
						>
							<motion.span
								layout
								initial={false}
								animate={{
									fontSize: isLast ? '1.125rem' : '0.875rem',
									fontWeight: isLast ? 600 : 300,
									filter: isLast ? undefined : 'blur(0.85px)',
								}}
								transition={{ duration: 0.5 }}
								className="inline-flex text-banner-text items-center gap-1"
							>
								<span className="inline-flex w-5 h-5 items-center justify-center">
									{isLast ? (
										<Icon
											icon={pulser}
											style={{
												color: `hsl(${hue} 100% 35% / 1)`,
											}}
										/>
									) : (
										<Icon icon={checkmark} />
									)}
								</span>
								{text}
							</motion.span>
						</motion.div>
					);
				})}
			</AnimatePresence>
			{errorMessage ? (
				<div className="absolute -bottom-12 left-0 text-center w-full text-red-600 text-xs p-2 rounded-md bg-white">
					{errorMessage}
				</div>
			) : null}
			{loadAdmin ? <AdminLoader /> : null}
		</div>
	);
};

// iframe that loads the admin in the background to make sure
// all php functions that require admin context work properly.
const AdminLoader = () => (
	<iframe
		title="Admin Loader"
		src={adminUrl}
		style={{ display: 'none' }}
		sandbox="allow-same-origin allow-scripts allow-forms"
	/>
);