IMP-198: Move layout loader to index.html (#294)

This commit is contained in:
Ildar Galeev 2024-04-11 16:24:40 +07:00 committed by GitHub
parent 1efe8e7b37
commit 39d97e6d63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 128 additions and 150 deletions

View File

@ -15,8 +15,37 @@
/>
<title>Checkout</title>
</head>
<body>
<body style="background: #163735">
<div id="spinner" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%)">
<svg
height="64px"
viewBox="0 0 57 57"
width="64px"
style="position: relative; animation: spin 1s linear infinite"
>
<defs>
<linearGradient id="loaderGradient" x1="100%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stop-color="#ff7808" />
<stop offset="100%" stop-color="#FFDB57" />
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
<g stroke="url(#loaderGradient)" stroke-width="4" transform="translate(-655.000000, -383.000000)">
<circle cx="683.5" cy="411.5" r="26.5" />
</g>
</g>
</svg>
</div>
<div id="app"></div>
<script defer type="module" src="/src/main.tsx"></script>
<style>
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
</style>
</body>
</html>

View File

@ -1,8 +1,9 @@
import { ChakraBaseProvider, extendBaseTheme, theme as chakraTheme } from '@chakra-ui/react';
import { useEffect } from 'react';
import { CompletePaymentContext } from './common/contexts';
import { isNil } from './common/utils';
import { CompletePaymentContext } from 'checkout/contexts';
import { isNil } from 'checkout/utils';
import { CommunicatorEvents } from './communicator';
import { InitializationFailed, AppLayout } from './components';
import { useInitialize } from './useInitialize';
@ -37,28 +38,26 @@ export function App() {
}, []);
return (
<>
<ChakraBaseProvider theme={theme}>
{state.status === 'SUCCESS' && (
<ChakraBaseProvider theme={theme}>
<CompletePaymentContext.Provider
value={{
onComplete: () =>
setTimeout(() => {
const [transport, params] = state.data;
transport.emit(CommunicatorEvents.finished);
transport.destroy();
const redirectUrl = params.initConfig?.redirectUrl;
if (!isNil(redirectUrl)) {
window.open(redirectUrl, '_self');
}
}, ON_COMPLETE_TIMEOUT_MS),
}}
>
<AppLayout initParams={state.data[1]} />
</CompletePaymentContext.Provider>
</ChakraBaseProvider>
<CompletePaymentContext.Provider
value={{
onComplete: () =>
setTimeout(() => {
const [transport, params] = state.data;
transport.emit(CommunicatorEvents.finished);
transport.destroy();
const redirectUrl = params.initConfig?.redirectUrl;
if (!isNil(redirectUrl)) {
window.open(redirectUrl, '_self');
}
}, ON_COMPLETE_TIMEOUT_MS),
}}
>
<AppLayout initParams={state.data[1]} />
</CompletePaymentContext.Provider>
)}
{state.status === 'FAILURE' && <InitializationFailed error={state.error} />}
</>
</ChakraBaseProvider>
);
}

View File

@ -9,7 +9,7 @@ import { toCustomizationContext } from './utils';
import { CustomizationContext } from '../../common/contexts';
import { InitParams } from '../../common/init';
import { getTheme } from '../../common/theme';
import { LayoutLoader, Overlay, ErrorBoundaryFallback } from '../legacy';
import { ErrorBoundaryFallback } from '../legacy';
type AppLayoutProps = {
initParams: InitParams;
@ -44,9 +44,7 @@ export function AppLayout({ initParams }: AppLayoutProps) {
return (
<ThemeProvider theme={theme}>
<Overlay />
<ModalContainer>
{modelsState.status === 'PROCESSING' && <LayoutLoader />}
{modelsState.status === 'INITIALIZED' && (
<CustomizationContext.Provider value={toCustomizationContext(initParams.initConfig)}>
<ErrorBoundary fallback={<ErrorBoundaryFallback />}>

View File

@ -1,18 +1,9 @@
/* eslint-disable react/jsx-max-depth */
import { Box } from '@chakra-ui/react';
import { useState } from 'react';
import {
Locale,
LocaleContext,
PaymentConditionsContext,
PaymentContext,
PaymentModelContext,
} from 'checkout/contexts';
import { PaymentConditionsContext, PaymentContext, PaymentModelContext } from 'checkout/contexts';
import { PaymentCondition } from 'checkout/paymentCondition';
import { PaymentModel } from 'checkout/paymentModel';
import { LocaleSelector } from './LocaleSelector';
import { usePaymentCondition } from './usePaymentCondition';
import { toContainer } from './utils';
import { RedirectContainer } from '../RedirectContainer';
@ -26,21 +17,15 @@ export type GlobalContainerProps = {
export function GlobalContainer({ paymentModel, initConditions }: GlobalContainerProps) {
const { conditions, startPayment, startWaitingPaymentResult } = usePaymentCondition(paymentModel, initConditions);
const containerName = toContainer(conditions);
const [locale, setLocale] = useState<{ l: Locale; localeCode: string }>({ l: {}, localeCode: 'en' });
return (
<PaymentModelContext.Provider value={{ paymentModel }}>
<PaymentConditionsContext.Provider value={{ conditions }}>
<PaymentContext.Provider value={{ startPayment, startWaitingPaymentResult }}>
<LocaleContext.Provider value={locale}>
<Box marginBottom={16} marginTop={16} width={['full', '75%', 'auto']}>
{containerName === 'ViewContainer' && <ViewContainer />}
{containerName === 'RedirectContainer' && <RedirectContainer />}
<Box paddingLeft="5" paddingRight="5" paddingTop="3">
<LocaleSelector onLocaleChange={setLocale} />
</Box>
</Box>
</LocaleContext.Provider>
<Box marginBottom={16} marginTop={16} width={['full', '75%', 'auto']}>
{containerName === 'ViewContainer' && <ViewContainer />}
{containerName === 'RedirectContainer' && <RedirectContainer />}
</Box>
</PaymentContext.Provider>
</PaymentConditionsContext.Provider>
</PaymentModelContext.Provider>

View File

@ -1,4 +1,6 @@
import { extractError } from '../common/utils';
import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@chakra-ui/react';
import { extractError } from 'checkout/utils';
export type InitializationFailedProps = {
error: unknown;
@ -6,5 +8,21 @@ export type InitializationFailedProps = {
export function InitializationFailed({ error }: InitializationFailedProps) {
console.error(`Application Initialization Failed: ${extractError(error)}`, error);
return <p>Application Initialization Failed: Please check the console for more details.</p>;
return (
<Alert
alignItems="center"
flexDirection="column"
height="200px"
justifyContent="center"
status="error"
textAlign="center"
variant="subtle"
>
<AlertIcon boxSize="40px" mr={0} />
<AlertTitle fontSize="lg" mb={1} mt={4}>
Application Initialization Failed!
</AlertTitle>
<AlertDescription maxWidth="sm">{extractError(error)}</AlertDescription>
</Alert>
);
}

View File

@ -3,7 +3,6 @@ import styled from 'styled-components';
import { prepareForm } from './utils';
import { BrowserRequest } from '../../common/backend/payments';
import { LayoutLoader } from '../legacy';
const RedirectContainer = styled.div`
visibility: hidden;
@ -29,10 +28,5 @@ export function SelfRedirectContainer({ origin, request }: SelfRedirectContainer
form && form.submit();
}, [form]);
return (
<>
<LayoutLoader />
<RedirectContainer ref={containerRef} />
</>
);
return <RedirectContainer ref={containerRef} />;
}

View File

@ -1,12 +1,21 @@
import { Box, Flex } from '@chakra-ui/react';
import { useContext, useMemo } from 'react';
/* eslint-disable react/jsx-max-depth */
import { LocaleContext, PaymentConditionsContext, PaymentModelContext, ViewModelContext } from 'checkout/contexts';
import { Box, Flex } from '@chakra-ui/react';
import { useContext, useMemo, useState } from 'react';
import {
Locale,
LocaleContext,
PaymentConditionsContext,
PaymentModelContext,
ViewModelContext,
} from 'checkout/contexts';
import { formatAmount } from 'checkout/utils';
import { ApiExtensionView } from './ApiExtensionView';
import { InfoContainer } from './InfoContainer';
import { Loader } from './Loader';
import { LocaleSelector } from './LocaleSelector';
import { NoAvailablePaymentMethodsView } from './NoAvailablePaymentMethodsView';
import { PaymentFormView } from './PaymentFormView';
import { PaymentMethodSelectorView } from './PaymentMethodSelectorView';
@ -17,48 +26,54 @@ import { TerminalSelectorView } from './TerminalSelectorView';
import { useViewModel } from './useViewModel';
export function ViewContainer() {
const { localeCode } = useContext(LocaleContext);
const { conditions } = useContext(PaymentConditionsContext);
const {
paymentModel: { paymentAmount, paymentMethods },
} = useContext(PaymentModelContext);
const { viewModel, goTo, forward, backward } = useViewModel(paymentMethods, conditions);
const viewAmount = useMemo(() => formatAmount(paymentAmount, localeCode), [localeCode]);
const [locale, setLocale] = useState<{ l: Locale; localeCode: string }>({ l: {}, localeCode: 'en' });
const viewAmount = useMemo(() => formatAmount(paymentAmount, locale.localeCode), [locale.localeCode]);
const { views, activeViewId, isLoading } = viewModel;
const activeView = views.get(activeViewId).name;
return (
<Flex
alignItems="stretch"
background="gray.50"
borderRadius="xl"
direction={['column', 'column', 'row']}
gap={4}
p={[4, 4, 8]}
>
<InfoContainer viewAmount={viewAmount}></InfoContainer>
<ViewModelContext.Provider value={{ viewModel, viewAmount, goTo, forward, backward }}>
<Box
background="white"
border="1px solid"
borderColor="gray.200"
<>
<LocaleContext.Provider value={locale}>
<Flex
alignItems="stretch"
background="gray.50"
borderRadius="xl"
padding={[4, 4, 6]}
position="relative"
width={['full', 'full', '420px']}
direction={['column', 'column', 'row']}
gap={4}
p={[4, 4, 8]}
>
{activeView === 'NoAvailablePaymentMethodsView' && <NoAvailablePaymentMethodsView />}
{activeView === 'PaymentMethodSelectorView' && <PaymentMethodSelectorView />}
{activeView === 'TerminalSelectorView' && <TerminalSelectorView />}
{activeView === 'PaymentFormView' && <PaymentFormView />}
{activeView === 'PaymentResultView' && <PaymentResultView />}
{activeView === 'QrCodeView' && <QrCodeView />}
{activeView === 'ApiExtensionView' && <ApiExtensionView />}
{activeView === 'PaymentProcessFailedView' && <PaymentProcessFailedView />}
{isLoading && <Loader />}
</Box>
</ViewModelContext.Provider>
</Flex>
<InfoContainer viewAmount={viewAmount}></InfoContainer>
<ViewModelContext.Provider value={{ viewModel, viewAmount, goTo, forward, backward }}>
<Box
background="white"
border="1px solid"
borderColor="gray.200"
borderRadius="xl"
padding={[4, 4, 6]}
position="relative"
width={['full', 'full', '420px']}
>
{activeView === 'NoAvailablePaymentMethodsView' && <NoAvailablePaymentMethodsView />}
{activeView === 'PaymentMethodSelectorView' && <PaymentMethodSelectorView />}
{activeView === 'TerminalSelectorView' && <TerminalSelectorView />}
{activeView === 'PaymentFormView' && <PaymentFormView />}
{activeView === 'PaymentResultView' && <PaymentResultView />}
{activeView === 'QrCodeView' && <QrCodeView />}
{activeView === 'ApiExtensionView' && <ApiExtensionView />}
{activeView === 'PaymentProcessFailedView' && <PaymentProcessFailedView />}
{isLoading && <Loader />}
</Box>
</ViewModelContext.Provider>
</Flex>
</LocaleContext.Provider>
<Box paddingLeft="5" paddingRight="5" paddingTop="3">
<LocaleSelector onLocaleChange={setLocale} />
</Box>
</>
);
}

View File

@ -1,41 +0,0 @@
import styled, { keyframes } from 'styled-components';
import { Loader } from './Loader';
import { device } from '../../common/utils';
const growth = keyframes`
from {
transform: scale(0);
}
to {
transform: scale(1);
}
`;
const LayoutLoaderWrapper = styled.div`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@media ${device.desktop} {
position: relative;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
justify-content: center;
transform: translate(0, 0);
animation: ${growth} 0.5s;
}
`;
export const LayoutLoader = () => (
<LayoutLoaderWrapper>
<Loader />
</LayoutLoaderWrapper>
);

View File

@ -1,17 +0,0 @@
import styled, { css } from 'styled-components';
const OverlayBg = styled.div`
position: fixed;
left: 0px;
top: 0px;
width: 100vw;
height: 100vh;
${({ theme }) => {
return css`
background: ${theme.background.gradient};
`;
}}
`;
export const Overlay = () => <OverlayBg key="overlay" />;

View File

@ -1,5 +1,3 @@
export { LayoutLoader } from './LayoutLoader';
export { Overlay } from './Overlay';
export { PayButton } from './PayButton';
export { HeaderWrapper } from './HeaderWrapper';
export { Title } from './Title';