mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
Feat/splash (#491)
* splash screen * add styles for controllers * feat: animated circles * fix: remove unused code * fix: folder structure * create splash screens for envs * add styles and ui changes * fix: revert App.tsx * add splash state to store * add splash to app.tsx + add a loader * fix: mobile view + desktop view * fix: render splash condition + styling fix * fix: change splash display to full screen * Update src/hooks/api/actions/useSplashApi/useSplashApi.ts Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com> * fix: change function type Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com> * fix: disable incrementing counter when matching the components length. * fix: add SWR configuration * fix: spelling mistakes in splash screen * fix: add keys and adjust styling * fix: tests * fix: tests * fix: default command timeout Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
00b9a6c38d
commit
c34d8439bd
@ -1,3 +1,4 @@
|
||||
{
|
||||
"projectId": "tc2qff"
|
||||
"projectId": "tc2qff",
|
||||
"defaultCommandTimeout": 12000
|
||||
}
|
||||
|
@ -67,6 +67,16 @@ describe('feature toggle', () => {
|
||||
cy.get('[data-test="LOGIN_PASSWORD_ID"]').type('qY70$NDcJNXA');
|
||||
|
||||
cy.get("[data-test='LOGIN_BUTTON']").click();
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
Cypress.config().baseUrl
|
||||
}/api/admin/features/${featureToggleName}`,
|
||||
headers: {
|
||||
Authorization: authToken,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
cy.get('[data-test=LOGIN_EMAIL_ID]').type('test@unleash-e2e.com');
|
||||
cy.get('[data-test=LOGIN_BUTTON]').click();
|
||||
@ -74,6 +84,12 @@ describe('feature toggle', () => {
|
||||
});
|
||||
|
||||
it('Creates a feature toggle', () => {
|
||||
if (
|
||||
document.querySelectorAll("[data-test='CLOSE_SPLASH']").length > 0
|
||||
) {
|
||||
cy.get("[data-test='CLOSE_SPLASH']").click();
|
||||
}
|
||||
|
||||
cy.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click();
|
||||
|
||||
cy.intercept('POST', '/api/admin/features').as('createFeature');
|
||||
|
BIN
frontend/src/assets/img/env-splash-2.png
Normal file
BIN
frontend/src/assets/img/env-splash-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 454 KiB |
19
frontend/src/assets/img/splash_env1.svg
Normal file
19
frontend/src/assets/img/splash_env1.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 81 KiB |
2
frontend/src/assets/img/splash_env2.svg
Normal file
2
frontend/src/assets/img/splash_env2.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 67 KiB |
BIN
frontend/src/assets/img/unleash_logo_icon_dark _ alpha.gif
Normal file
BIN
frontend/src/assets/img/unleash_logo_icon_dark _ alpha.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
@ -10,25 +10,36 @@ import { routes } from './menu/routes';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
import IAuthStatus from '../interfaces/user';
|
||||
import { useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import NotFound from './common/NotFound/NotFound';
|
||||
import Feedback from './common/Feedback';
|
||||
import useToast from '../hooks/useToast';
|
||||
import SWRProvider from './providers/SWRProvider/SWRProvider';
|
||||
import ConditionallyRender from './common/ConditionallyRender';
|
||||
import EnvironmentSplash from './common/EnvironmentSplash/EnvironmentSplash';
|
||||
import Loader from './common/Loader/Loader';
|
||||
import useUser from '../hooks/api/getters/useUser/useUser';
|
||||
|
||||
interface IAppProps extends RouteComponentProps {
|
||||
user: IAuthStatus;
|
||||
fetchUiBootstrap: any;
|
||||
feedback: any;
|
||||
}
|
||||
|
||||
const App = ({ location, user, fetchUiBootstrap, feedback }: IAppProps) => {
|
||||
const App = ({ location, user, fetchUiBootstrap }: IAppProps) => {
|
||||
const { toast, setToastData } = useToast();
|
||||
// because we need the userId when the component load.
|
||||
const { splash, user: userFromUseUser } = useUser();
|
||||
const [showSplash, setShowSplash] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchUiBootstrap();
|
||||
/* eslint-disable-next-line */
|
||||
}, [user.authDetails?.type]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowSplash(!splash?.environments && !isUnauthorized());
|
||||
/* eslint-disable-next-line */
|
||||
}, [splash]);
|
||||
|
||||
const renderMainLayoutRoutes = () => {
|
||||
return routes.filter(route => route.layout === 'main').map(renderRoute);
|
||||
};
|
||||
@ -79,29 +90,46 @@ const App = ({ location, user, fetchUiBootstrap, feedback }: IAppProps) => {
|
||||
setToastData={setToastData}
|
||||
isUnauthorized={isUnauthorized}
|
||||
>
|
||||
{' '}
|
||||
<div className={styles.container}>
|
||||
<LayoutPicker location={location}>
|
||||
<Switch>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/"
|
||||
unauthorized={isUnauthorized()}
|
||||
component={Redirect}
|
||||
renderProps={{ to: '/features' }}
|
||||
<ConditionallyRender
|
||||
condition={!isUnauthorized() && !userFromUseUser?.id}
|
||||
show={<Loader />}
|
||||
elseShow={
|
||||
<div className={styles.container}>
|
||||
<ConditionallyRender
|
||||
condition={showSplash}
|
||||
show={
|
||||
<EnvironmentSplash onFinish={setShowSplash} />
|
||||
}
|
||||
elseShow={
|
||||
<LayoutPicker location={location}>
|
||||
<Switch>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/"
|
||||
unauthorized={isUnauthorized()}
|
||||
component={Redirect}
|
||||
renderProps={{ to: '/features' }}
|
||||
/>
|
||||
{renderMainLayoutRoutes()}
|
||||
{renderStandaloneRoutes()}
|
||||
<Route
|
||||
path="/404"
|
||||
component={NotFound}
|
||||
/>
|
||||
<Redirect to="/404" />
|
||||
</Switch>
|
||||
<Feedback
|
||||
feedbackId="pnps"
|
||||
openUrl="http://feedback.unleash.run"
|
||||
/>
|
||||
</LayoutPicker>
|
||||
}
|
||||
/>
|
||||
{renderMainLayoutRoutes()}
|
||||
{renderStandaloneRoutes()}
|
||||
<Route path="/404" component={NotFound} />
|
||||
<Redirect to="/404" />
|
||||
</Switch>
|
||||
<Feedback
|
||||
feedbackId="pnps"
|
||||
openUrl="http://feedback.unleash.run"
|
||||
/>
|
||||
</LayoutPicker>
|
||||
{toast}
|
||||
</div>
|
||||
|
||||
{toast}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</SWRProvider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
splashContainer: {
|
||||
position: 'fixed',
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginBottom: '20px',
|
||||
lineHeight: '1.3',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
marginTop: '1rem',
|
||||
},
|
||||
},
|
||||
topDescription: {
|
||||
padding: '0px 40px',
|
||||
marginBottom: '15px',
|
||||
fontSize: '17px',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
padding: '0 20px',
|
||||
},
|
||||
},
|
||||
bottomDescription: {
|
||||
padding: '0px 20px',
|
||||
fontSize: '17px',
|
||||
marginTop: '15px',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
padding: '0 20px',
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
fontSize: '150px',
|
||||
display: 'block',
|
||||
margin: 'auto',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
fontSize: '90px',
|
||||
},
|
||||
},
|
||||
logo: {
|
||||
width: '70%',
|
||||
height: '60%',
|
||||
display: 'block',
|
||||
margin: 'auto',
|
||||
marginTop: '2rem',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
marginTop: '0rem',
|
||||
},
|
||||
},
|
||||
linkList: {
|
||||
padding: '30px 25px',
|
||||
},
|
||||
link: {
|
||||
color: '#fff',
|
||||
},
|
||||
}));
|
@ -0,0 +1,204 @@
|
||||
import Splash from '../Splash/Splash';
|
||||
import EnvironmentSplashPage from './EnvironmentSplashPage/EnvironmentSplashPage';
|
||||
import { VpnKey, CloudCircle } from '@material-ui/icons';
|
||||
import { useStyles } from './EnvironmentSplash.styles';
|
||||
import { ReactComponent as Logo1 } from '../../../assets/img/splash_env1.svg';
|
||||
import { ReactComponent as Logo2 } from '../../../assets/img/splash_env2.svg';
|
||||
import { useEffect } from 'react';
|
||||
import useSplashApi from '../../../hooks/api/actions/useSplashApi/useSplashApi';
|
||||
|
||||
interface IEnvironmentSplashProps {
|
||||
onFinish: (status: boolean) => void;
|
||||
}
|
||||
|
||||
const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
|
||||
const styles = useStyles();
|
||||
const { setSplashSeen } = useSplashApi();
|
||||
|
||||
useEffect(() => {
|
||||
setSplashSeen('environments');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Splash
|
||||
onFinish={onFinish}
|
||||
components={[
|
||||
<EnvironmentSplashPage
|
||||
key={1}
|
||||
title={
|
||||
<h2 className={styles.title}>
|
||||
Environments are coming to Unleash!
|
||||
</h2>
|
||||
}
|
||||
topDescription={
|
||||
<p className={styles.topDescription}>
|
||||
We are bringing native environment support to
|
||||
Unleash.{' '}
|
||||
<b>
|
||||
Your current configurations won’t be
|
||||
affected,
|
||||
</b>{' '}
|
||||
but you’ll have the option of adding strategies
|
||||
to specific environments going forward.
|
||||
</p>
|
||||
}
|
||||
bottomDescription={
|
||||
<p className={styles.bottomDescription}>
|
||||
By default you will get access to three
|
||||
environments: <b>default</b>, <b>development</b>{' '}
|
||||
and<b> production</b>. All of your current
|
||||
configurations will live in the default
|
||||
environment and{' '}
|
||||
<b>
|
||||
nothing will change until you make a
|
||||
conscious decision to change.
|
||||
</b>
|
||||
</p>
|
||||
}
|
||||
image={<CloudCircle className={styles.icon} />}
|
||||
/>,
|
||||
<EnvironmentSplashPage
|
||||
key={2}
|
||||
title={
|
||||
<h2 className={styles.title}>
|
||||
Strategies live in environments
|
||||
</h2>
|
||||
}
|
||||
topDescription={
|
||||
<p className={styles.topDescription}>
|
||||
A feature toggle lives as an entity across
|
||||
multiple environments, but your strategies will
|
||||
live in a specific environment. This allows you
|
||||
to have different configuration per environment
|
||||
for a feature toggle.
|
||||
</p>
|
||||
}
|
||||
image={<Logo1 className={styles.logo} />}
|
||||
/>,
|
||||
<EnvironmentSplashPage
|
||||
key={3}
|
||||
title={
|
||||
<h2 className={styles.title}>
|
||||
Environments are turned on per project
|
||||
</h2>
|
||||
}
|
||||
topDescription={
|
||||
<p className={styles.topDescription}>
|
||||
In order to enable an environment for a feature
|
||||
toggle you must first enable the environment in
|
||||
your project. Navigate to your project settings
|
||||
and enable the environments you want to be
|
||||
available. The toggles in that project will get
|
||||
access to all of the project’s enabled
|
||||
environments.
|
||||
</p>
|
||||
}
|
||||
image={<Logo2 className={styles.logo} />}
|
||||
/>,
|
||||
<EnvironmentSplashPage
|
||||
key={4}
|
||||
title={
|
||||
<h2 className={styles.title}>
|
||||
API Keys control which environment you get the
|
||||
configuration from
|
||||
</h2>
|
||||
}
|
||||
topDescription={
|
||||
<p className={styles.topDescription}>
|
||||
When you have set up environments for your
|
||||
feature toggles and added strategies to the
|
||||
specific environments, you must create
|
||||
environment-specific API keys — one for each
|
||||
environment.
|
||||
</p>
|
||||
}
|
||||
bottomDescription={
|
||||
<p className={styles.bottomDescription}>
|
||||
Environment-specific API keys lets the SDK
|
||||
receive configuration only for the specified
|
||||
environment.
|
||||
</p>
|
||||
}
|
||||
image={<VpnKey className={styles.icon} />}
|
||||
/>,
|
||||
<EnvironmentSplashPage
|
||||
key={5}
|
||||
title={
|
||||
<h2 className={styles.title}>Want to know more?</h2>
|
||||
}
|
||||
topDescription={
|
||||
<div className={styles.topDescription}>
|
||||
If you’d like some more info on environments,
|
||||
check out some of the resources below! The
|
||||
documentation or the video walkthrough is a
|
||||
great place to start. If you’d like to try it
|
||||
out in a risk-free setting first, how about
|
||||
heading to the demo instance?
|
||||
<ul className={styles.linkList}>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.loom.com/share/95239e875bbc4e09a5c5833e1942e4b0?t=0"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
Video walkthrough
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://app.unleash-hosted.com/demo/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
The Unleash demo instance
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://docs.getunleash.io/user_guide/environments"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
Environments reference documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.getunleash.io/blog/simplify-rollout-management-with-the-new-environments-feature"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
Blog post introducing environments
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
bottomDescription={
|
||||
<p className={styles.bottomDescription}>
|
||||
If you have any questions or need help, feel
|
||||
free to ping us on{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://slack.unleash.run/"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
slack!
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSplash;
|
@ -0,0 +1,24 @@
|
||||
interface EnvironmentSplashPageProps {
|
||||
title: React.ReactNode;
|
||||
topDescription: React.ReactNode;
|
||||
image?: React.ReactNode;
|
||||
bottomDescription?: React.ReactNode;
|
||||
}
|
||||
|
||||
const EnvironmentSplashPage = ({
|
||||
title,
|
||||
topDescription,
|
||||
image,
|
||||
bottomDescription,
|
||||
}: EnvironmentSplashPageProps) => {
|
||||
return (
|
||||
<div>
|
||||
{title}
|
||||
{topDescription}
|
||||
{image}
|
||||
{bottomDescription}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSplashPage;
|
14
frontend/src/component/common/Loader/Loader.styles.ts
Normal file
14
frontend/src/component/common/Loader/Loader.styles.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
loader: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
img: {
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
},
|
||||
}));
|
14
frontend/src/component/common/Loader/Loader.tsx
Normal file
14
frontend/src/component/common/Loader/Loader.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import logo from '../../../assets/img/unleash_logo_icon_dark _ alpha.gif';
|
||||
import { useStyles } from './Loader.styles';
|
||||
|
||||
const Loader = () => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.loader}>
|
||||
<img className={styles.img} src={logo} alt="loading..." />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
96
frontend/src/component/common/Splash/Splash.styles.ts
Normal file
96
frontend/src/component/common/Splash/Splash.styles.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
splashMainContainer: {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '3rem 0',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
padding: '0',
|
||||
},
|
||||
},
|
||||
splashContainer: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
position: 'relative',
|
||||
minHeight: '650px',
|
||||
width: '600px',
|
||||
padding: '2rem 1.5rem',
|
||||
borderRadius: '5px',
|
||||
color: '#fff',
|
||||
display: 'flex',
|
||||
overflowX: 'hidden',
|
||||
flexDirection: 'column',
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
padding: '2rem 0',
|
||||
zIndex: '500',
|
||||
position: 'fixed',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 0,
|
||||
},
|
||||
},
|
||||
closeButtonContainer: {
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'flex-end',
|
||||
color: '#fff',
|
||||
position: 'absolute',
|
||||
right: '-10px',
|
||||
top: '5px',
|
||||
},
|
||||
closeButton: {
|
||||
textDecoration: 'none',
|
||||
right: '10px',
|
||||
color: '#fff',
|
||||
'&:hover': {
|
||||
backgroundColor: 'inherit',
|
||||
},
|
||||
},
|
||||
controllers: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
height: 'inherit',
|
||||
marginBottom: 5,
|
||||
marginTop: 'auto',
|
||||
},
|
||||
circlesContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
circles: {
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: 20,
|
||||
marginBottom: 15,
|
||||
position: 'relative',
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
button: {
|
||||
textDecoration: 'none',
|
||||
width: '100px',
|
||||
color: '#fff',
|
||||
'&:hover': {
|
||||
backgroundColor: 'inherit',
|
||||
},
|
||||
},
|
||||
nextButton: {
|
||||
textDecoration: 'none',
|
||||
width: '100px',
|
||||
color: theme.palette.primary.light,
|
||||
backgroundColor: '#fff',
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
},
|
||||
}));
|
114
frontend/src/component/common/Splash/Splash.tsx
Normal file
114
frontend/src/component/common/Splash/Splash.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Button, IconButton } from '@material-ui/core';
|
||||
import { useStyles } from './Splash.styles';
|
||||
import {
|
||||
FiberManualRecord,
|
||||
FiberManualRecordOutlined,
|
||||
CloseOutlined,
|
||||
} from '@material-ui/icons';
|
||||
import { useState } from 'react';
|
||||
import ConditionallyRender from '../ConditionallyRender';
|
||||
import { CLOSE_SPLASH } from '../../../testIds';
|
||||
|
||||
interface ISplashProps {
|
||||
components: React.ReactNode[];
|
||||
onFinish: (status: boolean) => void;
|
||||
}
|
||||
|
||||
const Splash: React.FC<ISplashProps> = ({
|
||||
components,
|
||||
onFinish,
|
||||
}: ISplashProps) => {
|
||||
const styles = useStyles();
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
const onNext = () => {
|
||||
if (counter === components.length - 1) {
|
||||
onFinish(false);
|
||||
return;
|
||||
}
|
||||
setCounter(counter + 1);
|
||||
};
|
||||
|
||||
const onBack = () => {
|
||||
setCounter(counter - 1);
|
||||
};
|
||||
const onClose = () => {
|
||||
onFinish(false);
|
||||
};
|
||||
|
||||
const calculatePosition = () => {
|
||||
if (counter === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return counter * 24;
|
||||
};
|
||||
|
||||
const renderCircles = () => {
|
||||
return components.map((_, index) => {
|
||||
if (index === 0) {
|
||||
// Use index as key because the amount of pages will never dynamically change.
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<FiberManualRecordOutlined />
|
||||
<FiberManualRecord
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transition: 'transform 0.3s ease',
|
||||
left: '0',
|
||||
transform: `translateX(${calculatePosition()}px)`,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <FiberManualRecordOutlined />;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.splashMainContainer}>
|
||||
<div className={styles.splashContainer}>
|
||||
<div className={styles.closeButtonContainer}>
|
||||
<IconButton
|
||||
className={styles.closeButton}
|
||||
onClick={onClose}
|
||||
data-test={CLOSE_SPLASH}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
{components[counter]}
|
||||
<div className={styles.controllers}>
|
||||
<div className={styles.circlesContainer}>
|
||||
<div className={styles.circles}>{renderCircles()}</div>
|
||||
</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<ConditionallyRender
|
||||
condition={counter > 0}
|
||||
show={
|
||||
<Button
|
||||
className={styles.button}
|
||||
disabled={counter === 0}
|
||||
onClick={onBack}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Button className={styles.nextButton} onClick={onNext}>
|
||||
{counter === components.length - 1
|
||||
? 'Finish'
|
||||
: 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Splash;
|
27
frontend/src/hooks/api/actions/useSplashApi/useSplashApi.ts
Normal file
27
frontend/src/hooks/api/actions/useSplashApi/useSplashApi.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const useSplashApi = () => {
|
||||
const { makeRequest, createRequest } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const setSplashSeen = async (splashId: string) => {
|
||||
const path = `api/admin/splash/${splashId}`;
|
||||
const req = createRequest(path, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
return res;
|
||||
} catch (e) {
|
||||
console.log('An exception was caught and handled.');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
setSplashSeen,
|
||||
};
|
||||
};
|
||||
|
||||
export default useSplashApi;
|
@ -6,7 +6,13 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
|
||||
export const USER_CACHE_KEY = `api/admin/user`;
|
||||
|
||||
const useUser = (options: SWRConfiguration = {}) => {
|
||||
const useUser = (
|
||||
options: SWRConfiguration = {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
}
|
||||
) => {
|
||||
const fetcher = () => {
|
||||
const path = formatApiPath(`api/admin/user`);
|
||||
return fetch(path, {
|
||||
@ -31,6 +37,7 @@ const useUser = (options: SWRConfiguration = {}) => {
|
||||
user: data?.user || {},
|
||||
permissions: (data?.permissions || []) as IPermission[],
|
||||
feedback: data?.feedback || [],
|
||||
splash: data?.splash || {},
|
||||
authDetails: data || {},
|
||||
error,
|
||||
loading,
|
||||
|
@ -3,6 +3,11 @@ export interface IAuthStatus {
|
||||
showDialog: boolean;
|
||||
profile?: IUser;
|
||||
permissions: IPermission[];
|
||||
splash: ISplash;
|
||||
}
|
||||
|
||||
export interface ISplash {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
export interface IPermission {
|
||||
|
@ -9,6 +9,7 @@ const userStore = (state = new $Map({ permissions: [] }), action) => {
|
||||
.set('profile', action.value.user)
|
||||
.set('permissions', action.value.permissions || [])
|
||||
.set('feedback', action.value.feedback || [])
|
||||
.set('splash', action.value.splash || {})
|
||||
.set('showDialog', false)
|
||||
.set('authDetails', undefined);
|
||||
return state;
|
||||
|
@ -34,3 +34,6 @@ export const UPDATE_STRATEGY_BUTTON_ID = 'UPDATE_STRATEGY_BUTTON_ID';
|
||||
export const DELETE_STRATEGY_ID = 'DELETE_STRATEGY_ID';
|
||||
export const STRATEGY_INPUT_LIST = 'STRATEGY_INPUT_LIST';
|
||||
export const ADD_TO_STRATEGY_INPUT_LIST = 'ADD_TO_STRATEGY_INPUT_LIST';
|
||||
|
||||
/* SPLASH */
|
||||
export const CLOSE_SPLASH = 'CLOSE_SPLASH';
|
||||
|
Loading…
Reference in New Issue
Block a user