kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Onboarding: use a server setting instead of localStorage, fix steps when data is already present
rodzic
1deb4dfd2d
commit
41ab4f0a44
|
@ -1,101 +1,24 @@
|
||||||
import { mockStore, mockWindowProperty } from 'soapbox/jest/test-helpers';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import rootReducer from 'soapbox/reducers';
|
import { createTestStore, rootState } from 'soapbox/jest/test-helpers';
|
||||||
|
|
||||||
import { checkOnboardingStatus, startOnboarding, endOnboarding } from '../onboarding';
|
import { ONBOARDING_VERSION, endOnboarding } from '../onboarding';
|
||||||
|
|
||||||
describe('checkOnboarding()', () => {
|
|
||||||
let mockGetItem: any;
|
|
||||||
|
|
||||||
mockWindowProperty('localStorage', {
|
|
||||||
getItem: (key: string) => mockGetItem(key),
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockGetItem = jest.fn().mockReturnValue(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does nothing if localStorage item is not set', async() => {
|
|
||||||
mockGetItem = jest.fn().mockReturnValue(null);
|
|
||||||
|
|
||||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
|
||||||
const store = mockStore(state);
|
|
||||||
|
|
||||||
await store.dispatch(checkOnboardingStatus());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
expect(mockGetItem.mock.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does nothing if localStorage item is invalid', async() => {
|
|
||||||
mockGetItem = jest.fn().mockReturnValue('invalid');
|
|
||||||
|
|
||||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
|
||||||
const store = mockStore(state);
|
|
||||||
|
|
||||||
await store.dispatch(checkOnboardingStatus());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
expect(mockGetItem.mock.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dispatches the correct action', async() => {
|
|
||||||
mockGetItem = jest.fn().mockReturnValue('1');
|
|
||||||
|
|
||||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
|
||||||
const store = mockStore(state);
|
|
||||||
|
|
||||||
await store.dispatch(checkOnboardingStatus());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([{ type: 'ONBOARDING_START' }]);
|
|
||||||
expect(mockGetItem.mock.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('startOnboarding()', () => {
|
|
||||||
let mockSetItem: any;
|
|
||||||
|
|
||||||
mockWindowProperty('localStorage', {
|
|
||||||
setItem: (key: string, value: string) => mockSetItem(key, value),
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockSetItem = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dispatches the correct action', async() => {
|
|
||||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
|
||||||
const store = mockStore(state);
|
|
||||||
|
|
||||||
await store.dispatch(startOnboarding());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([{ type: 'ONBOARDING_START' }]);
|
|
||||||
expect(mockSetItem.mock.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('endOnboarding()', () => {
|
describe('endOnboarding()', () => {
|
||||||
let mockRemoveItem: any;
|
it('updates the onboardingVersion setting', async() => {
|
||||||
|
const store = createTestStore(rootState);
|
||||||
|
|
||||||
mockWindowProperty('localStorage', {
|
// Sanity check:
|
||||||
removeItem: (key: string) => mockRemoveItem(key),
|
// `onboardingVersion` should be `0` by default
|
||||||
});
|
const initialVersion = getSettings(store.getState()).get('onboardingVersion');
|
||||||
|
expect(initialVersion).toBe(0);
|
||||||
beforeEach(() => {
|
|
||||||
mockRemoveItem = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dispatches the correct action', async() => {
|
|
||||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
|
||||||
const store = mockStore(state);
|
|
||||||
|
|
||||||
await store.dispatch(endOnboarding());
|
await store.dispatch(endOnboarding());
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([{ type: 'ONBOARDING_END' }]);
|
// After dispatching, `onboardingVersion` is updated
|
||||||
expect(mockRemoveItem.mock.calls.length).toBe(1);
|
const updatedVersion = getSettings(store.getState()).get('onboardingVersion');
|
||||||
|
expect(updatedVersion).toBe(ONBOARDING_VERSION);
|
||||||
|
|
||||||
|
// Sanity check: `updatedVersion` is greater than `initialVersion`
|
||||||
|
expect(updatedVersion > initialVersion).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +1,13 @@
|
||||||
const ONBOARDING_START = 'ONBOARDING_START';
|
import { changeSettingImmediate } from 'soapbox/actions/settings';
|
||||||
const ONBOARDING_END = 'ONBOARDING_END';
|
|
||||||
|
|
||||||
const ONBOARDING_LOCAL_STORAGE_KEY = 'soapbox:onboarding';
|
/** Repeat the onboading process when we bump the version */
|
||||||
|
export const ONBOARDING_VERSION = 1;
|
||||||
|
|
||||||
type OnboardingStartAction = {
|
/** Finish onboarding and store the setting */
|
||||||
type: typeof ONBOARDING_START
|
const endOnboarding = () => (dispatch: React.Dispatch<any>) => {
|
||||||
}
|
dispatch(changeSettingImmediate(['onboardingVersion'], ONBOARDING_VERSION));
|
||||||
|
|
||||||
type OnboardingEndAction = {
|
|
||||||
type: typeof ONBOARDING_END
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OnboardingActions = OnboardingStartAction | OnboardingEndAction
|
|
||||||
|
|
||||||
const checkOnboardingStatus = () => (dispatch: React.Dispatch<OnboardingActions>) => {
|
|
||||||
const needsOnboarding = localStorage.getItem(ONBOARDING_LOCAL_STORAGE_KEY) === '1';
|
|
||||||
|
|
||||||
if (needsOnboarding) {
|
|
||||||
dispatch({ type: ONBOARDING_START });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const startOnboarding = () => (dispatch: React.Dispatch<OnboardingActions>) => {
|
|
||||||
localStorage.setItem(ONBOARDING_LOCAL_STORAGE_KEY, '1');
|
|
||||||
dispatch({ type: ONBOARDING_START });
|
|
||||||
};
|
|
||||||
|
|
||||||
const endOnboarding = () => (dispatch: React.Dispatch<OnboardingActions>) => {
|
|
||||||
localStorage.removeItem(ONBOARDING_LOCAL_STORAGE_KEY);
|
|
||||||
dispatch({ type: ONBOARDING_END });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ONBOARDING_END,
|
|
||||||
ONBOARDING_START,
|
|
||||||
checkOnboardingStatus,
|
|
||||||
endOnboarding,
|
endOnboarding,
|
||||||
startOnboarding,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const defaultSettings = ImmutableMap({
|
export const defaultSettings = ImmutableMap({
|
||||||
onboarded: false,
|
onboardingVersion: 0,
|
||||||
skinTone: 1,
|
skinTone: 1,
|
||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
underlineLinks: false,
|
underlineLinks: false,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { getFeatures } from 'soapbox/utils/features';
|
||||||
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
|
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
|
||||||
import { generateThemeCss } from 'soapbox/utils/theme';
|
import { generateThemeCss } from 'soapbox/utils/theme';
|
||||||
|
|
||||||
import { checkOnboardingStatus } from '../actions/onboarding';
|
import { ONBOARDING_VERSION } from '../actions/onboarding';
|
||||||
import { preload } from '../actions/preload';
|
import { preload } from '../actions/preload';
|
||||||
import ErrorBoundary from '../components/error_boundary';
|
import ErrorBoundary from '../components/error_boundary';
|
||||||
import UI from '../features/ui';
|
import UI from '../features/ui';
|
||||||
|
@ -44,9 +44,6 @@ createGlobals(store);
|
||||||
// Preload happens synchronously
|
// Preload happens synchronously
|
||||||
store.dispatch(preload());
|
store.dispatch(preload());
|
||||||
|
|
||||||
// This happens synchronously
|
|
||||||
store.dispatch(checkOnboardingStatus());
|
|
||||||
|
|
||||||
/** Load initial data from the backend */
|
/** Load initial data from the backend */
|
||||||
const loadInitial = () => {
|
const loadInitial = () => {
|
||||||
return async(dispatch, getState) => {
|
return async(dispatch, getState) => {
|
||||||
|
@ -76,6 +73,7 @@ const mapStateToProps = (state) => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const account = makeAccount(state, me);
|
const account = makeAccount(state, me);
|
||||||
const settings = getSettings(state);
|
const settings = getSettings(state);
|
||||||
|
const needsOnboarding = settings.get('onboardingVersion') < ONBOARDING_VERSION;
|
||||||
const soapboxConfig = getSoapboxConfig(state);
|
const soapboxConfig = getSoapboxConfig(state);
|
||||||
const locale = settings.get('locale');
|
const locale = settings.get('locale');
|
||||||
|
|
||||||
|
@ -95,7 +93,7 @@ const mapStateToProps = (state) => {
|
||||||
appleAppId: soapboxConfig.get('appleAppId'),
|
appleAppId: soapboxConfig.get('appleAppId'),
|
||||||
themeMode: settings.get('themeMode'),
|
themeMode: settings.get('themeMode'),
|
||||||
singleUserMode,
|
singleUserMode,
|
||||||
needsOnboarding: state.onboarding.needsOnboarding,
|
needsOnboarding,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,7 +155,7 @@ class SoapboxMount extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { me, account, themeCss, locale, singleUserMode } = this.props;
|
const { me, account, themeCss, locale, needsOnboarding, singleUserMode } = this.props;
|
||||||
if (me === null) return null;
|
if (me === null) return null;
|
||||||
if (me && !account) return null;
|
if (me && !account) return null;
|
||||||
if (!this.state.isLoaded) return null;
|
if (!this.state.isLoaded) return null;
|
||||||
|
@ -165,8 +163,7 @@ class SoapboxMount extends React.PureComponent {
|
||||||
|
|
||||||
const waitlisted = account && !account.getIn(['source', 'approved'], true);
|
const waitlisted = account && !account.getIn(['source', 'approved'], true);
|
||||||
|
|
||||||
const { needsOnboarding } = this.props;
|
if (account && !waitlisted && needsOnboarding) {
|
||||||
if (!waitlisted && needsOnboarding) {
|
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={locale} messages={this.state.messages}>
|
<IntlProvider locale={locale} messages={this.state.messages}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -176,8 +173,12 @@ class SoapboxMount extends React.PureComponent {
|
||||||
<meta name='theme-color' content={this.props.brandColor} />
|
<meta name='theme-color' content={this.props.brandColor} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<OnboardingWizard />
|
<ErrorBoundary>
|
||||||
<NotificationsContainer />
|
<BrowserRouter basename={FE_SUBDIRECTORY}>
|
||||||
|
<OnboardingWizard />
|
||||||
|
<NotificationsContainer />
|
||||||
|
</BrowserRouter>
|
||||||
|
</ErrorBoundary>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,17 @@ import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soap
|
||||||
import { useOwnAccount } from 'soapbox/hooks';
|
import { useOwnAccount } from 'soapbox/hooks';
|
||||||
import resizeImage from 'soapbox/utils/resize_image';
|
import resizeImage from 'soapbox/utils/resize_image';
|
||||||
|
|
||||||
|
/** Default avatar filenames from various backends */
|
||||||
|
const DEFAULT_AVATARS = [
|
||||||
|
'/avatars/original/missing.png', // Mastodon
|
||||||
|
'/images/avi.png', // Pleroma
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Check if the avatar is a default avatar */
|
||||||
|
const isDefaultAvatar = (url: string) => {
|
||||||
|
return DEFAULT_AVATARS.every(avatar => url.endsWith(avatar));
|
||||||
|
};
|
||||||
|
|
||||||
const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -18,6 +29,7 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const [selectedFile, setSelectedFile] = React.useState<string | null>();
|
const [selectedFile, setSelectedFile] = React.useState<string | null>();
|
||||||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
||||||
const [isDisabled, setDisabled] = React.useState<boolean>(true);
|
const [isDisabled, setDisabled] = React.useState<boolean>(true);
|
||||||
|
const isDefault = account ? isDefaultAvatar(account.avatar) : false;
|
||||||
|
|
||||||
const openFilePicker = () => {
|
const openFilePicker = () => {
|
||||||
fileInput.current?.click();
|
fileInput.current?.click();
|
||||||
|
@ -100,7 +112,7 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack justifyContent='center' space={2}>
|
<Stack justifyContent='center' space={2}>
|
||||||
<Button block theme='primary' type='submit' disabled={isDisabled || isSubmitting}>
|
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<FormattedMessage id='onboarding.saving' defaultMessage='Saving...' />
|
<FormattedMessage id='onboarding.saving' defaultMessage='Saving...' />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -6,11 +6,13 @@ import { useDispatch } from 'react-redux';
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||||
|
import { useOwnAccount } from 'soapbox/hooks';
|
||||||
|
|
||||||
const BioStep = ({ onNext }: { onNext: () => void }) => {
|
const BioStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [value, setValue] = React.useState<string>('');
|
const account = useOwnAccount();
|
||||||
|
const [value, setValue] = React.useState<string>(account?.source.get('note') || '');
|
||||||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
||||||
const [errors, setErrors] = React.useState<string[]>([]);
|
const [errors, setErrors] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,17 @@ import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soap
|
||||||
import { useOwnAccount } from 'soapbox/hooks';
|
import { useOwnAccount } from 'soapbox/hooks';
|
||||||
import resizeImage from 'soapbox/utils/resize_image';
|
import resizeImage from 'soapbox/utils/resize_image';
|
||||||
|
|
||||||
|
/** Default header filenames from various backends */
|
||||||
|
const DEFAULT_HEADERS = [
|
||||||
|
'/headers/original/missing.png', // Mastodon
|
||||||
|
'/images/banner.png', // Pleroma
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Check if the avatar is a default avatar */
|
||||||
|
const isDefaultHeader = (url: string) => {
|
||||||
|
return DEFAULT_HEADERS.every(header => url.endsWith(header));
|
||||||
|
};
|
||||||
|
|
||||||
const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -19,6 +30,7 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const [selectedFile, setSelectedFile] = React.useState<string | null>();
|
const [selectedFile, setSelectedFile] = React.useState<string | null>();
|
||||||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
||||||
const [isDisabled, setDisabled] = React.useState<boolean>(true);
|
const [isDisabled, setDisabled] = React.useState<boolean>(true);
|
||||||
|
const isDefault = account ? isDefaultHeader(account.header) : false;
|
||||||
|
|
||||||
const openFilePicker = () => {
|
const openFilePicker = () => {
|
||||||
fileInput.current?.click();
|
fileInput.current?.click();
|
||||||
|
@ -28,8 +40,6 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const maxPixels = 1920 * 1080;
|
const maxPixels = 1920 * 1080;
|
||||||
const [rawFile] = event.target.files || [] as any;
|
const [rawFile] = event.target.files || [] as any;
|
||||||
|
|
||||||
console.log('fike', rawFile);
|
|
||||||
|
|
||||||
resizeImage(rawFile, maxPixels).then((file) => {
|
resizeImage(rawFile, maxPixels).then((file) => {
|
||||||
const url = file ? URL.createObjectURL(file) : account?.header as string;
|
const url = file ? URL.createObjectURL(file) : account?.header as string;
|
||||||
|
|
||||||
|
@ -124,7 +134,7 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack justifyContent='center' space={2}>
|
<Stack justifyContent='center' space={2}>
|
||||||
<Button block theme='primary' type='submit' disabled={isDisabled || isSubmitting}>
|
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
||||||
{isSubmitting ? 'Saving...' : 'Next'}
|
{isSubmitting ? 'Saving...' : 'Next'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,13 @@ import { useDispatch } from 'react-redux';
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import { useOwnAccount } from 'soapbox/hooks';
|
||||||
|
|
||||||
const DisplayNameStep = ({ onNext }: { onNext: () => void }) => {
|
const DisplayNameStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [value, setValue] = React.useState<string>('');
|
const account = useOwnAccount();
|
||||||
|
const [value, setValue] = React.useState<string>(account?.display_name || '');
|
||||||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
||||||
const [errors, setErrors] = React.useState<string[]>([]);
|
const [errors, setErrors] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
theme='primary'
|
theme='primary'
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
>
|
>
|
||||||
Done
|
<FormattedMessage id='onboarding.done' defaultMessage='Done' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button block theme='link' type='button' onClick={onNext}>
|
<Button block theme='link' type='button' onClick={onNext}>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||||
import { fetchInstance } from 'soapbox/actions/instance';
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
import { startOnboarding } from 'soapbox/actions/onboarding';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { createAccount } from 'soapbox/actions/verification';
|
import { createAccount } from 'soapbox/actions/verification';
|
||||||
import { removeStoredVerification } from 'soapbox/actions/verification';
|
import { removeStoredVerification } from 'soapbox/actions/verification';
|
||||||
|
@ -41,7 +40,6 @@ const Registration = () => {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setShouldRedirect(true);
|
setShouldRedirect(true);
|
||||||
removeStoredVerification();
|
removeStoredVerification();
|
||||||
dispatch(startOnboarding());
|
|
||||||
dispatch(
|
dispatch(
|
||||||
snackbar.success(
|
snackbar.success(
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { ONBOARDING_START, ONBOARDING_END } from 'soapbox/actions/onboarding';
|
|
||||||
|
|
||||||
import reducer from '../onboarding';
|
|
||||||
|
|
||||||
describe('onboarding reducer', () => {
|
|
||||||
it('should return the initial state', () => {
|
|
||||||
expect(reducer(undefined, {})).toEqual({
|
|
||||||
needsOnboarding: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ONBOARDING_START', () => {
|
|
||||||
it('sets "needsOnboarding" to "true"', () => {
|
|
||||||
const initialState = { needsOnboarding: false };
|
|
||||||
const action = { type: ONBOARDING_START };
|
|
||||||
expect(reducer(initialState, action).needsOnboarding).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ONBOARDING_END', () => {
|
|
||||||
it('sets "needsOnboarding" to "false"', () => {
|
|
||||||
const initialState = { needsOnboarding: true };
|
|
||||||
const action = { type: ONBOARDING_END };
|
|
||||||
expect(reducer(initialState, action).needsOnboarding).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -39,7 +39,6 @@ import meta from './meta';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
import mutes from './mutes';
|
import mutes from './mutes';
|
||||||
import notifications from './notifications';
|
import notifications from './notifications';
|
||||||
import onboarding from './onboarding';
|
|
||||||
import patron from './patron';
|
import patron from './patron';
|
||||||
import pending_statuses from './pending_statuses';
|
import pending_statuses from './pending_statuses';
|
||||||
import polls from './polls';
|
import polls from './polls';
|
||||||
|
@ -119,7 +118,6 @@ const reducers = {
|
||||||
accounts_meta,
|
accounts_meta,
|
||||||
trending_statuses,
|
trending_statuses,
|
||||||
verification,
|
verification,
|
||||||
onboarding,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build a default state from all reducers: it has the key and `undefined`
|
// Build a default state from all reducers: it has the key and `undefined`
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { ONBOARDING_START, ONBOARDING_END } from 'soapbox/actions/onboarding';
|
|
||||||
|
|
||||||
import type { OnboardingActions } from 'soapbox/actions/onboarding';
|
|
||||||
|
|
||||||
type OnboardingState = {
|
|
||||||
needsOnboarding: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: OnboardingState = {
|
|
||||||
needsOnboarding: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function onboarding(state: OnboardingState = initialState, action: OnboardingActions): OnboardingState {
|
|
||||||
switch(action.type) {
|
|
||||||
case ONBOARDING_START:
|
|
||||||
return { ...state, needsOnboarding: true };
|
|
||||||
case ONBOARDING_END:
|
|
||||||
return { ...state, needsOnboarding: false };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
Ładowanie…
Reference in New Issue