Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor & added animations to cf #2841

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
3 changes: 2 additions & 1 deletion apps/kyb-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
},
"dependencies": {
"@ballerine/blocks": "0.2.26",
"@ballerine/ui": "0.5.44",
"@ballerine/common": "^0.9.51",
"@ballerine/ui": "0.5.44",
"@ballerine/workflow-browser-sdk": "0.6.64",
"@lukemorales/query-key-factory": "^1.0.3",
"@radix-ui/react-icons": "^1.3.0",
Expand All @@ -38,6 +38,7 @@
"dompurify": "^3.0.6",
"emblor": "^1.4.6",
"form-data-encoder": "^3.0.0",
"framer-motion": "^8.3.4",
"i18n-iso-countries": "^7.6.0",
"i18n-nationality": "^1.3.0",
"i18next": "^22.4.9",
Expand Down
29 changes: 18 additions & 11 deletions apps/kyb-app/src/common/components/layouts/Signup/Background.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FunctionComponent } from 'react';
import { motion } from 'framer-motion';
import { FunctionComponent, useState } from 'react';
import { useSignupLayout } from './hooks/useSignupLayout';

interface IBackgroundProps {
Expand All @@ -9,19 +10,25 @@ interface IBackgroundProps {
export const Background: FunctionComponent<IBackgroundProps> = props => {
const { themeParams } = useSignupLayout();
const { imageSrc, styles } = { ...props, ...themeParams?.background };
const [isLoaded, setIsLoaded] = useState(false);

if (!imageSrc) return null;

return (
<div
className="h-full min-w-[62%] flex-1"
style={{
...styles,
backgroundImage: `url(${imageSrc})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
></div>
<motion.div className="h-screen min-w-[62%] flex-1 overflow-hidden">
<motion.img
src={imageSrc}
className="h-full w-full object-cover"
style={styles}
initial={{ opacity: 0, scale: 1.2 }}
animate={isLoaded ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 1.2 }}
exit={{ opacity: 0 }}
transition={{
duration: 0.6,
ease: 'easeOut',
}}
onLoad={() => setIsLoaded(true)}
/>
</motion.div>
);
chesterkmr marked this conversation as resolved.
Show resolved Hide resolved
};
6 changes: 5 additions & 1 deletion apps/kyb-app/src/common/components/layouts/Signup/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DOMPurify from 'dompurify';
import { motion } from 'framer-motion';
import { CSSProperties, FunctionComponent } from 'react';
import { useSignupLayout } from './hooks/useSignupLayout';

Expand All @@ -14,9 +15,12 @@ export const Footer: FunctionComponent<IFooterProps> = props => {
if (!rawHtml) return null;

return (
<div
<motion.div
className="font-inter text-base text-[#94A3B8]"
style={styles}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.6 }}
dangerouslySetInnerHTML={{ __html: DOMPurify(window).sanitize(rawHtml) }}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { motion } from 'framer-motion';
import { CSSProperties, FunctionComponent } from 'react';
import { useSignupLayout } from './hooks/useSignupLayout';

Expand All @@ -17,8 +18,14 @@ export const FormContainer: FunctionComponent<IFormContainerProps> = ({
};

return (
<div className="my-6 flex flex-col gap-4 pr-10" style={containerStyles}>
<motion.div
className="my-6 flex flex-col gap-4 pr-10"
style={containerStyles}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.35 }}
>
{children}
</div>
</motion.div>
);
};
23 changes: 19 additions & 4 deletions apps/kyb-app/src/common/components/layouts/Signup/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { motion } from 'framer-motion';
import { CSSProperties, FunctionComponent } from 'react';
import { useSignupLayout } from './hooks/useSignupLayout';

Expand All @@ -15,9 +16,23 @@ export const Header: FunctionComponent<IHeaderProps> = props => {
};

return (
<div className="flex flex-col gap-6 pb-6" style={containerStyles}>
<h1 className="text-2xl font-bold">{headingText}</h1>
<p className="text-base">{subheadingText}</p>
</div>
<motion.div className="flex flex-col gap-6 pb-6" style={containerStyles}>
<motion.h1
className="text-2xl font-bold"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
{headingText}
</motion.h1>
<motion.p
className="text-base"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.3 }}
>
{subheadingText}
</motion.p>
</motion.div>
);
};
15 changes: 13 additions & 2 deletions apps/kyb-app/src/common/components/layouts/Signup/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CSSProperties, FunctionComponent } from 'react';
import { motion } from 'framer-motion';
import { CSSProperties, FunctionComponent, useState } from 'react';
import { useSignupLayout } from './hooks/useSignupLayout';

interface ILogoProps {
Expand All @@ -9,8 +10,18 @@ interface ILogoProps {
export const Logo: FunctionComponent<ILogoProps> = props => {
const { themeParams } = useSignupLayout();
const { imageSrc, styles } = { ...props, ...themeParams?.companyLogo };
const [isLoaded, setIsLoaded] = useState(false);

if (!imageSrc) return null;

return <img src={imageSrc} style={styles} />;
return (
<motion.img
src={imageSrc}
style={styles}
initial={{ opacity: 0, scale: 0.8 }}
animate={isLoaded ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.8 }}
transition={{ duration: 0.25 }}
onLoad={() => setIsLoaded(true)}
/>
);
};

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AccessTokenIsMissingError } from '@/common/errors/access-token-is-missing';
import { InvalidAccessTokenError } from '@/common/errors/invalid-access-token';
import { ServerNotAvailableError } from '@/common/errors/server-not-available';
import { useRouteError } from 'react-router-dom';
import { AppErrorScreen } from '../../molecules/AppErrorScreen';
import { InvalidAccessTokenErrorScreen } from './InvalidAccessToken';
import { MissingTokenErrorScreen } from './MissingTokenErrorScreen';
import { ServerNotAvailableErrorScreen } from './ServerNotAvailable';

export const ErrorScreen = () => {
const error = useRouteError();
Expand All @@ -16,6 +18,10 @@ export const ErrorScreen = () => {
return <InvalidAccessTokenErrorScreen />;
}

if (error instanceof ServerNotAvailableError) {
return <ServerNotAvailableErrorScreen />;
}

return (
<AppErrorScreen
title="Something went wrong"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { AppErrorScreen } from '../../molecules/AppErrorScreen';

export const ServerNotAvailableErrorScreen = () => {
return (
<AppErrorScreen
title="Server Not Available"
description={
<div className="!text-muted-foreground flex flex-col gap-1">
<p>We are unable to connect to our servers at this time.</p>
<p>This could be due to maintenance or temporary technical difficulties.</p>
<ul>
<li>
<b>1.</b> Please wait a few minutes and try refreshing the page
</li>
<li>
<b>2.</b> Check your internet connection
</li>
<li>
<b>3.</b> If the problem persists, our servers may be undergoing maintenance. Please
try again later or contact support
</li>
</ul>
</div>
}
/>
);
};
5 changes: 5 additions & 0 deletions apps/kyb-app/src/common/errors/server-not-available.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ServerNotAvailableError extends Error {
constructor() {
super('Server not available');
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { InvalidAccessTokenError } from '@/common/errors/invalid-access-token';
import { ServerNotAvailableError } from '@/common/errors/server-not-available';
import { queryClient } from '@/common/utils/query-client';
import {
collectionFlowQuerykeys,
fetchCustomer,
fetchFlowContext,
fetchUISchema,
} from '@/domains/collection-flow';
import { useCustomerQuery } from '@/hooks/useCustomerQuery';
import { useEndUserQuery } from '@/hooks/useEndUserQuery';
import { useFlowContextQuery } from '@/hooks/useFlowContextQuery';
import { useLanguage } from '@/hooks/useLanguage';
import { useRefValue } from '@/hooks/useRefValue';
import { useUISchemasQuery } from '@/hooks/useUISchemasQuery';
import { LoadingScreen } from '@/pages/CollectionFlow/components/atoms/LoadingScreen';
import { HTTPError } from 'ky';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react';
import { DependenciesContext } from './dependencies-context';
import { getJsonErrors, isShouldIgnoreErrors } from './helpers';

interface IDependenciesProviderProps {
Expand All @@ -14,29 +27,76 @@ export const DependenciesProvider: FunctionComponent<IDependenciesProviderProps>
children,
}: IDependenciesProviderProps) => {
const [error, setError] = useState<Error | null>(null);
const { refetch: refetchEndUser, data: endUser } = useEndUserQuery();
const [isInitialLoading, setIsInitialLoading] = useState(true);

const language = useLanguage();

const dependancyQueries = [
useCustomerQuery(),
useFlowContextQuery(),
useUISchemasQuery({ language }),
] as const satisfies readonly [
ReturnType<typeof useCustomerQuery>,
ReturnType<typeof useFlowContextQuery>,
ReturnType<typeof useUISchemasQuery>,
];

const isLoading = useMemo(() => {
const queriesRef = useRefValue(dependancyQueries);

const isFetching = useMemo(() => {
return dependancyQueries.length
? dependancyQueries.some(dependency => dependency.isLoading && !dependency.isLoaded)
? dependancyQueries.some(dependency => dependency.isLoading)
: false;
}, [dependancyQueries]);

const isLoading = useMemo(() => {
return isInitialLoading && dependancyQueries.some(dependency => dependency.isLoading);
}, [isInitialLoading, dependancyQueries]);

useEffect(() => {
if (isLoading) return;

setIsInitialLoading(false);
}, [isLoading]);
chesterkmr marked this conversation as resolved.
Show resolved Hide resolved

const errors = useMemo(() => {
return dependancyQueries.filter(dependency => dependency.error);
}, [dependancyQueries]);

const refetchAll = useCallback(async () => {
const { data: endUser } = await refetchEndUser();

const [uiSchema, customer, flowContext] = await Promise.all([
fetchUISchema(language, endUser?.id),
fetchCustomer(),
fetchFlowContext(),
]);

queryClient.setQueryData(
collectionFlowQuerykeys.getUISchema({ language, endUserId: endUser?.id }).queryKey,
uiSchema,
);
queryClient.setQueryData(collectionFlowQuerykeys.getCustomer().queryKey, customer);
queryClient.setQueryData(collectionFlowQuerykeys.getContext().queryKey, flowContext);
}, [refetchEndUser, language]);
chesterkmr marked this conversation as resolved.
Show resolved Hide resolved

const context = useMemo(
() => ({ refetchAll, isLoading, isFetching }),
[refetchAll, isLoading, isFetching],
);

useEffect(() => {
if (!Array.isArray(errors) || !errors?.length) return;

const handleErrors = async (errors: HTTPError[]) => {
// If there is no response, it means that the server is not available
if (errors.every(error => !error.response)) {
setError(new ServerNotAvailableError());

return;
}

const isShouldIgnore = await isShouldIgnoreErrors(errors);

if (isShouldIgnore) return;
Expand All @@ -53,15 +113,17 @@ export const DependenciesProvider: FunctionComponent<IDependenciesProviderProps>
};

void handleErrors(errors.map(error => error.error) as HTTPError[]);
}, [errors]);
}, []);

if (isLoading) {
return <LoadingScreen />;
}
console.log({ error });

if (error) {
throw error;
}

return <>{children}</>;
if (isInitialLoading) {
return <LoadingScreen />;
}

return <DependenciesContext.Provider value={context}>{children}</DependenciesContext.Provider>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from 'react';
import { IDependenciesContext } from './types';

export const DependenciesContext = createContext<IDependenciesContext>({
refetchAll: async () => {},
isLoading: false,
isFetching: false,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useDependencies';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { DependenciesContext } from '../../dependencies-context';

export const useDependencies = () => useContext(DependenciesContext);
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './DependenciesProvider';
export * from './hooks/useDependencies';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IDependenciesContext {
refetchAll: () => Promise<void>;
isLoading: boolean;
isFetching: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface Props {

export const ThemeProvider = ({ children }: Props) => {
const language = new URLSearchParams(window.location.search).get(APP_LANGUAGE_QUERY_KEY) || 'en';
const { data: uiSchema, isLoading, error } = useUISchemasQuery(language);
const { data: uiSchema, isLoading, error } = useUISchemasQuery({ language });

const theme = useMemo(() => {
if (isLoading) return null;
Expand Down
Loading
Loading