mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Fix/cleanup (#276)
* chore: update changelog * 4.0.0-alpha.10 * fix: add dnd provider as top level wrapper * fix: add forgotten password link to password auth * fix: remove unecceesary dnd component * fix: check for usedTokenError
This commit is contained in:
parent
e81918ee48
commit
1f46d3b21b
@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
The latest version of this document is always available in
|
||||
[releases][releases-url].
|
||||
|
||||
# 4.0.0-alpha.10
|
||||
- fix: password
|
||||
# 4.0.0-alpha.9
|
||||
- fix: optimizations
|
||||
- feat: user profile
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "unleash-frontend",
|
||||
"description": "unleash your features",
|
||||
"version": "4.0.0-alpha.9",
|
||||
"version": "4.0.0-alpha.10",
|
||||
"keywords": [
|
||||
"unleash",
|
||||
"feature toggle",
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import React from 'react';
|
||||
|
||||
function useDNDProviderElement(props) {
|
||||
|
||||
if (!props.children) return null;
|
||||
|
||||
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
|
||||
}
|
||||
|
||||
export default function DragAndDrop(props) {
|
||||
const DNDElement = useDNDProviderElement(props);
|
||||
return <React.Fragment>{DNDElement}</React.Fragment>;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
@ -6,7 +6,6 @@ import arrayMove from 'array-move';
|
||||
import { Button } from '@material-ui/core';
|
||||
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import DragAndDrop from '../../common/drag-and-drop';
|
||||
import HeaderTitle from '../../common/HeaderTitle';
|
||||
import { updateIndexInArray } from '../../common/util';
|
||||
import styles from './strategy.module.scss';
|
||||
@ -27,7 +26,9 @@ const StrategiesList = props => {
|
||||
const [delStrategy, setDelStrategy] = useState(null);
|
||||
const [showCreateStrategy, setShowCreateStrategy] = useState(false);
|
||||
const [showAlert, setShowAlert] = useState(true);
|
||||
const [editableStrategies, updateEditableStrategies] = useState(cloneDeep(props.configuredStrategies));
|
||||
const [editableStrategies, updateEditableStrategies] = useState(
|
||||
cloneDeep(props.configuredStrategies)
|
||||
);
|
||||
const [editStrategyIndex, setEditStrategyIndex] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
@ -39,7 +40,11 @@ const StrategiesList = props => {
|
||||
|
||||
const updateStrategy = index => strategy => {
|
||||
const newStrategy = { ...strategy };
|
||||
const newStrategies = updateIndexInArray(editableStrategies, index, newStrategy);
|
||||
const newStrategies = updateIndexInArray(
|
||||
editableStrategies,
|
||||
index,
|
||||
newStrategy
|
||||
);
|
||||
updateEditableStrategies(newStrategies);
|
||||
};
|
||||
|
||||
@ -87,9 +92,13 @@ const StrategiesList = props => {
|
||||
const strategy = editableStrategies[delStrategy];
|
||||
|
||||
if (!strategy.new) {
|
||||
await props.saveStrategies(props.configuredStrategies.filter((_, i) => i !== delStrategy));
|
||||
await props.saveStrategies(
|
||||
props.configuredStrategies.filter((_, i) => i !== delStrategy)
|
||||
);
|
||||
}
|
||||
updateEditableStrategies(editableStrategies.filter((_, i) => i !== delStrategy));
|
||||
updateEditableStrategies(
|
||||
editableStrategies.filter((_, i) => i !== delStrategy)
|
||||
);
|
||||
|
||||
setDelStrategy(undefined);
|
||||
setShowDelDialog(null);
|
||||
@ -100,7 +109,12 @@ const StrategiesList = props => {
|
||||
setEditStrategyIndex(undefined);
|
||||
};
|
||||
|
||||
const { strategies, configuredStrategies, featureToggleName, editable } = props;
|
||||
const {
|
||||
strategies,
|
||||
configuredStrategies,
|
||||
featureToggleName,
|
||||
editable,
|
||||
} = props;
|
||||
|
||||
const resolveStrategyDefinition = strategyName => {
|
||||
if (!strategies || strategies.length === 0) {
|
||||
@ -139,67 +153,77 @@ const StrategiesList = props => {
|
||||
strategy={editingStrategy}
|
||||
updateStrategy={updateStrategy(editStrategyIndex)}
|
||||
saveStrategy={saveStrategy(editStrategyIndex)}
|
||||
strategyDefinition={resolveStrategyDefinition(editingStrategy.name)}
|
||||
strategyDefinition={resolveStrategyDefinition(
|
||||
editingStrategy.name
|
||||
)}
|
||||
onCancel={clearAll}
|
||||
/>
|
||||
) : null}
|
||||
<DragAndDrop>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<HeaderTitle
|
||||
title="Activation strategies"
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!featureToggleName}
|
||||
color="primary"
|
||||
onClick={() => setShowCreateStrategy(true)}
|
||||
>
|
||||
Add strategy
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={showAlert}
|
||||
show={
|
||||
<Alert severity="info" className={styles.infoCard} onClose={() => setShowAlert(false)}>
|
||||
Strategies allow you fine grained control over how to activate your features, and are
|
||||
composable blocks that are executed in an OR fashion. As an example, you can have a gradual
|
||||
rollout that targets 80% of users in a region of the world (using the enterprise feature of
|
||||
constraints), and another gradual rollout that targets 20% of the users in another region.
|
||||
If you don't add a strategy, the default strategy is activated which means that the feature
|
||||
will be strictly on/off for your entire userbase.
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={!configuredStrategies || configuredStrategies.length === 0}
|
||||
show={
|
||||
<p style={{ padding: '0 16px' }}>
|
||||
<i>No activation strategies selected.</i>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<HeaderTitle
|
||||
title="Activation strategies"
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!featureToggleName}
|
||||
color="primary"
|
||||
onClick={() => setShowCreateStrategy(true)}
|
||||
>
|
||||
Add strategy
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={showAlert}
|
||||
show={
|
||||
<Alert
|
||||
severity="info"
|
||||
className={styles.infoCard}
|
||||
onClose={() => setShowAlert(false)}
|
||||
>
|
||||
Strategies allow you fine grained control over how to
|
||||
activate your features, and are composable blocks that
|
||||
are executed in an OR fashion. As an example, you can
|
||||
have a gradual rollout that targets 80% of users in a
|
||||
region of the world (using the enterprise feature of
|
||||
constraints), and another gradual rollout that targets
|
||||
20% of the users in another region. If you don't add a
|
||||
strategy, the default strategy is activated which means
|
||||
that the feature will be strictly on/off for your entire
|
||||
userbase.
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
!configuredStrategies || configuredStrategies.length === 0
|
||||
}
|
||||
show={
|
||||
<p style={{ padding: '0 16px' }}>
|
||||
<i>No activation strategies selected.</i>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
|
||||
<Dialogue
|
||||
title="Really delete strategy?"
|
||||
open={showDelDialog}
|
||||
onClick={() => removeStrategy()}
|
||||
onClose={() => {
|
||||
setDelStrategy(null);
|
||||
setShowDelDialog(false);
|
||||
}}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={cards.length > 0}
|
||||
show={<div className={styles.strategyListCards}>{cards}</div>}
|
||||
/>
|
||||
</DragAndDrop>
|
||||
<Dialogue
|
||||
title="Really delete strategy?"
|
||||
open={showDelDialog}
|
||||
onClick={() => removeStrategy()}
|
||||
onClose={() => {
|
||||
setDelStrategy(null);
|
||||
setShowDelDialog(false);
|
||||
}}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={cards.length > 0}
|
||||
show={<div className={styles.strategyListCards}>{cards}</div>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,16 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Button,
|
||||
TextField,
|
||||
Typography,
|
||||
IconButton,
|
||||
} from '@material-ui/core';
|
||||
import { Button, TextField, Typography, IconButton } from '@material-ui/core';
|
||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useCommonStyles } from '../../../common.styles';
|
||||
import { useStyles } from './PasswordAuth.styles';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
const commonStyles = useCommonStyles();
|
||||
@ -76,11 +72,7 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
const { usernameError, passwordError, apiError } = errors;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
action={authDetails.path}
|
||||
>
|
||||
|
||||
<form onSubmit={handleSubmit} action={authDetails.path}>
|
||||
<Typography variant="subtitle2" className={styles.apiError}>
|
||||
{apiError}
|
||||
</Typography>
|
||||
@ -113,6 +105,11 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Link to="/forgotten-password">
|
||||
<Typography variant="body2">
|
||||
Forgot your password?
|
||||
</Typography>
|
||||
</Link>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@ -129,11 +126,14 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
const renderWithOptions = options => (
|
||||
<div>
|
||||
{options.map(o => (
|
||||
<div key={o.type} className={classnames(
|
||||
styles.contentContainer,
|
||||
commonStyles.contentSpacingY
|
||||
)}>
|
||||
<Button color="primary" variant="contained" href={o.path}>
|
||||
<div
|
||||
key={o.type}
|
||||
className={classnames(
|
||||
styles.contentContainer,
|
||||
commonStyles.contentSpacingY
|
||||
)}
|
||||
>
|
||||
<Button color="primary" variant="contained" href={o.path}>
|
||||
{o.value}
|
||||
</Button>
|
||||
</div>
|
||||
@ -142,7 +142,7 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
condition={showFields}
|
||||
show={renderLoginForm()}
|
||||
elseShow={
|
||||
<IconButton onClick={onShowOptions} >
|
||||
<IconButton onClick={onShowOptions}>
|
||||
{' '}
|
||||
Show more options
|
||||
</IconButton>
|
||||
@ -155,15 +155,12 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="subtitle1">
|
||||
{authDetails.message}
|
||||
</Typography>
|
||||
<ConditionallyRender
|
||||
condition={options.length > 0}
|
||||
show={renderWithOptions(options)}
|
||||
elseShow={renderLoginForm()}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle1">{authDetails.message}</Typography>
|
||||
<ConditionallyRender
|
||||
condition={options.length > 0}
|
||||
show={renderWithOptions(options)}
|
||||
elseShow={renderLoginForm()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ const getFetcher = (token: string) => () =>
|
||||
}).then(res => res.json());
|
||||
|
||||
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
|
||||
const USED_TOKEN_ERROR = 'UsedTokenError';
|
||||
|
||||
const useResetPassword = () => {
|
||||
const query = useQueryParams();
|
||||
@ -30,7 +31,9 @@ const useResetPassword = () => {
|
||||
setLoading(!error && !data);
|
||||
}, [data, error]);
|
||||
|
||||
const invalidToken = !loading && data?.name === INVALID_TOKEN_ERROR;
|
||||
const invalidToken =
|
||||
(!loading && data?.name === INVALID_TOKEN_ERROR) ||
|
||||
data?.name === USED_TOKEN_ERROR;
|
||||
|
||||
return { token, data, error, loading, setLoading, invalidToken, retry };
|
||||
};
|
||||
|
@ -8,6 +8,8 @@ import { Provider } from 'react-redux';
|
||||
import { ThemeProvider, CssBaseline } from '@material-ui/core';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { StylesProvider } from '@material-ui/core/styles';
|
||||
|
||||
import mainTheme from './themes/main-theme';
|
||||
@ -39,18 +41,20 @@ metricsPoller.start();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={unleashStore}>
|
||||
<AccessProvider store={unleashStore}>
|
||||
<HashRouter>
|
||||
<ThemeProvider theme={mainTheme}>
|
||||
<StylesProvider injectFirst>
|
||||
<CssBaseline />
|
||||
<ScrollToTop>
|
||||
<Route path="/" component={App} />
|
||||
</ScrollToTop>
|
||||
</StylesProvider>
|
||||
</ThemeProvider>
|
||||
</HashRouter>
|
||||
</AccessProvider>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<AccessProvider store={unleashStore}>
|
||||
<HashRouter>
|
||||
<ThemeProvider theme={mainTheme}>
|
||||
<StylesProvider injectFirst>
|
||||
<CssBaseline />
|
||||
<ScrollToTop>
|
||||
<Route path="/" component={App} />
|
||||
</ScrollToTop>
|
||||
</StylesProvider>
|
||||
</ThemeProvider>
|
||||
</HashRouter>
|
||||
</AccessProvider>
|
||||
</DndProvider>
|
||||
</Provider>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user