mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-02 03:31:05 +05:30
Implemented visual indication for deleted accounts [deploy dev]
This commit is contained in:
parent
8075192472
commit
18a8037a0d
@ -11,6 +11,7 @@ import { setLogin } from 'app/components/auth/actions';
|
|||||||
import { Dispatch, State as RootState } from 'app/types';
|
import { Dispatch, State as RootState } from 'app/types';
|
||||||
|
|
||||||
import { Account } from './reducer';
|
import { Account } from './reducer';
|
||||||
|
import { User } from 'app/components/user';
|
||||||
|
|
||||||
jest.mock('app/i18n', () => ({
|
jest.mock('app/i18n', () => ({
|
||||||
en: {
|
en: {
|
||||||
@ -32,19 +33,21 @@ jest.mock('app/i18n', () => ({
|
|||||||
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbHl8MSJ9.pRJ7vakt2eIscjqwG__KhSxKb3qwGsdBBeDbBffJs_I';
|
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbHl8MSJ9.pRJ7vakt2eIscjqwG__KhSxKb3qwGsdBBeDbBffJs_I';
|
||||||
const legacyToken = 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOjF9.cRF-sQNrwWQ94xCb3vWioVdjxAZeefEE7GMGwh7708o';
|
const legacyToken = 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOjF9.cRF-sQNrwWQ94xCb3vWioVdjxAZeefEE7GMGwh7708o';
|
||||||
|
|
||||||
const account = {
|
const account: Account = {
|
||||||
id: 1,
|
id: 1,
|
||||||
username: 'username',
|
username: 'username',
|
||||||
email: 'email@test.com',
|
email: 'email@test.com',
|
||||||
token,
|
token,
|
||||||
refreshToken: 'bar',
|
refreshToken: 'bar',
|
||||||
|
isDeleted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = {
|
const user: Partial<User> = {
|
||||||
id: 1,
|
id: 1,
|
||||||
username: 'username',
|
username: 'username',
|
||||||
email: 'email@test.com',
|
email: 'email@test.com',
|
||||||
lang: 'be',
|
lang: 'be',
|
||||||
|
isDeleted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('components/accounts/actions', () => {
|
describe('components/accounts/actions', () => {
|
||||||
|
@ -12,13 +12,6 @@ import { add, remove, activate, reset, updateToken } from './actions/pure-action
|
|||||||
|
|
||||||
export { updateToken, activate, remove };
|
export { updateToken, activate, remove };
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Account|object} account
|
|
||||||
* @param {string} account.token
|
|
||||||
* @param {string} account.refreshToken
|
|
||||||
*
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
|
||||||
export function authenticate(
|
export function authenticate(
|
||||||
account:
|
account:
|
||||||
| Account
|
| Account
|
||||||
@ -59,6 +52,7 @@ export function authenticate(
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
token: newToken,
|
token: newToken,
|
||||||
refreshToken: newRefreshToken,
|
refreshToken: newRefreshToken,
|
||||||
|
isDeleted: user.isDeleted,
|
||||||
};
|
};
|
||||||
dispatch(add(newAccount));
|
dispatch(add(newAccount));
|
||||||
dispatch(activate(newAccount));
|
dispatch(activate(newAccount));
|
||||||
|
@ -59,4 +59,16 @@ export function updateToken(token: string): UpdateTokenAction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action = AddAction | RemoveAction | ActivateAction | ResetAction | UpdateTokenAction;
|
interface MarkAsDeletedAction extends ReduxAction {
|
||||||
|
type: 'accounts:markAsDeleted';
|
||||||
|
payload: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markAsDeleted(isDeleted: boolean): MarkAsDeletedAction {
|
||||||
|
return {
|
||||||
|
type: 'accounts:markAsDeleted',
|
||||||
|
payload: isDeleted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Action = AddAction | RemoveAction | ActivateAction | ResetAction | UpdateTokenAction | MarkAsDeletedAction;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import expect from 'app/test/unexpected';
|
import expect from 'app/test/unexpected';
|
||||||
|
|
||||||
import { updateToken } from './actions';
|
import { updateToken } from './actions';
|
||||||
import { add, remove, activate, reset } from './actions/pure-actions';
|
import { add, remove, activate, reset, markAsDeleted } from './actions/pure-actions';
|
||||||
import { AccountsState } from './index';
|
import { AccountsState } from './index';
|
||||||
import accounts, { Account } from './reducer';
|
import accounts, { Account } from './reducer';
|
||||||
|
|
||||||
@ -10,7 +10,9 @@ const account: Account = {
|
|||||||
username: 'username',
|
username: 'username',
|
||||||
email: 'email@test.com',
|
email: 'email@test.com',
|
||||||
token: 'foo',
|
token: 'foo',
|
||||||
} as Account;
|
refreshToken: '',
|
||||||
|
isDeleted: false,
|
||||||
|
};
|
||||||
|
|
||||||
describe('Accounts reducer', () => {
|
describe('Accounts reducer', () => {
|
||||||
let initial: AccountsState;
|
let initial: AccountsState;
|
||||||
@ -124,4 +126,20 @@ describe('Accounts reducer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('accounts:markAsDeleted', () => {
|
||||||
|
it('should mark account as deleted', () => {
|
||||||
|
const isDeleted = true;
|
||||||
|
|
||||||
|
expect(accounts({ active: account.id, available: [account] }, markAsDeleted(isDeleted)), 'to satisfy', {
|
||||||
|
active: account.id,
|
||||||
|
available: [
|
||||||
|
{
|
||||||
|
...account,
|
||||||
|
isDeleted,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ export type Account = {
|
|||||||
email: string;
|
email: string;
|
||||||
token: string;
|
token: string;
|
||||||
refreshToken: string | null;
|
refreshToken: string | null;
|
||||||
|
isDeleted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@ -23,6 +24,23 @@ export function getAvailableAccounts(state: { accounts: State }): Array<Account>
|
|||||||
return state.accounts.available;
|
return state.accounts.available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move deleted accounts to the end of the accounts list.
|
||||||
|
*/
|
||||||
|
export function getSortedAccounts(state: { accounts: State }): ReadonlyArray<Account> {
|
||||||
|
return state.accounts.available.sort((acc1, acc2) => {
|
||||||
|
if (acc1.isDeleted && !acc2.isDeleted) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acc1.isDeleted && acc2.isDeleted) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default function accounts(
|
export default function accounts(
|
||||||
state: State = {
|
state: State = {
|
||||||
active: null,
|
active: null,
|
||||||
@ -90,27 +108,33 @@ export default function accounts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'accounts:updateToken': {
|
case 'accounts:updateToken': {
|
||||||
if (typeof action.payload !== 'string') {
|
return partiallyUpdateActiveAccount(state, {
|
||||||
throw new Error('payload must be a jwt token');
|
token: action.payload,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { payload } = action;
|
case 'accounts:markAsDeleted': {
|
||||||
|
return partiallyUpdateActiveAccount(state, {
|
||||||
return {
|
isDeleted: action.payload,
|
||||||
...state,
|
});
|
||||||
available: state.available.map((account) => {
|
|
||||||
if (account.id === state.active) {
|
|
||||||
return {
|
|
||||||
...account,
|
|
||||||
token: payload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...account };
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function partiallyUpdateActiveAccount(state: State, payload: Partial<Account>): State {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
available: state.available.map((account) => {
|
||||||
|
if (account.id === state.active) {
|
||||||
|
return {
|
||||||
|
...account,
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...account };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -36,12 +36,13 @@ const AccountSwitcher: ComponentType<Props> = ({ accounts, onAccountClick }) =>
|
|||||||
<div
|
<div
|
||||||
className={clsx(styles.item, {
|
className={clsx(styles.item, {
|
||||||
[styles.inactiveItem]: selectedAccount && selectedAccount !== account.id,
|
[styles.inactiveItem]: selectedAccount && selectedAccount !== account.id,
|
||||||
|
[styles.deletedAccount]: account.isDeleted,
|
||||||
})}
|
})}
|
||||||
key={account.id}
|
key={account.id}
|
||||||
data-e2e-account-id={account.id}
|
data-e2e-account-id={account.id}
|
||||||
onClick={onAccountClickCallback(account)}
|
onClick={onAccountClickCallback(account)}
|
||||||
>
|
>
|
||||||
<PseudoAvatar index={index} className={styles.accountIcon} />
|
<PseudoAvatar index={index} deleted={account.isDeleted} className={styles.accountAvatar} />
|
||||||
<div className={styles.accountInfo}>
|
<div className={styles.accountInfo}>
|
||||||
<div className={styles.accountUsername}>{account.username}</div>
|
<div className={styles.accountUsername}>{account.username}</div>
|
||||||
<div className={styles.accountEmail}>{account.email}</div>
|
<div className={styles.accountEmail}>{account.email}</div>
|
||||||
|
@ -4,6 +4,7 @@ import { FormattedMessage as Message } from 'react-intl';
|
|||||||
|
|
||||||
import { connect } from 'app/functions';
|
import { connect } from 'app/functions';
|
||||||
import BaseAuthBody from 'app/components/auth/BaseAuthBody';
|
import BaseAuthBody from 'app/components/auth/BaseAuthBody';
|
||||||
|
import { getSortedAccounts } from 'app/components/accounts/reducer';
|
||||||
import type { Account } from 'app/components/accounts';
|
import type { Account } from 'app/components/accounts';
|
||||||
|
|
||||||
import AccountSwitcher from './AccountSwitcher';
|
import AccountSwitcher from './AccountSwitcher';
|
||||||
@ -15,7 +16,7 @@ import styles from './chooseAccount.scss';
|
|||||||
// So to provide accounts list to the component, I'll create connected version of
|
// So to provide accounts list to the component, I'll create connected version of
|
||||||
// the composes with already provided accounts list
|
// the composes with already provided accounts list
|
||||||
const ConnectedAccountSwitcher = connect((state) => ({
|
const ConnectedAccountSwitcher = connect((state) => ({
|
||||||
accounts: state.accounts.available,
|
accounts: getSortedAccounts(state),
|
||||||
}))(AccountSwitcher);
|
}))(AccountSwitcher);
|
||||||
|
|
||||||
export default class ChooseAccountBody extends BaseAuthBody {
|
export default class ChooseAccountBody extends BaseAuthBody {
|
||||||
|
@ -35,7 +35,7 @@ $border: 1px solid lighter($black);
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountIcon {
|
.accountAvatar {
|
||||||
font-size: 35px;
|
font-size: 35px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
@ -56,12 +56,20 @@ $border: 1px solid lighter($black);
|
|||||||
@extend %overflowText;
|
@extend %overflowText;
|
||||||
font-family: $font-family-title;
|
font-family: $font-family-title;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
|
.deletedAccount & {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountEmail {
|
.accountEmail {
|
||||||
@extend %overflowText;
|
@extend %overflowText;
|
||||||
color: #666;
|
color: #999;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
|
.deletedAccount & {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nextIcon {
|
.nextIcon {
|
||||||
|
@ -5,11 +5,23 @@ import styles from './pseudoAvatar.scss';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index?: number;
|
index?: number;
|
||||||
|
deleted?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PseudoAvatar: ComponentType<Props> = ({ index = 0, className }) => (
|
const PseudoAvatar: ComponentType<Props> = ({ index = 0, deleted, className }) => (
|
||||||
<div className={clsx(styles.pseudoAvatar, styles[`pseudoAvatar${index % 7}`], className)} />
|
<div
|
||||||
|
className={clsx(
|
||||||
|
styles.pseudoAvatarWrapper,
|
||||||
|
{
|
||||||
|
[styles.deletedPseudoAvatar]: deleted,
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={clsx(styles.pseudoAvatar, styles[`pseudoAvatar${index % 7}`])} />
|
||||||
|
{deleted ? <div className={styles.deletedIcon} /> : ''}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default PseudoAvatar;
|
export default PseudoAvatar;
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
@import '~app/components/ui/colors.scss';
|
@import '~app/components/ui/colors.scss';
|
||||||
|
|
||||||
|
.pseudoAvatarWrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex; // Needed to get right position of the cross icon
|
||||||
|
}
|
||||||
|
|
||||||
.pseudoAvatar {
|
.pseudoAvatar {
|
||||||
composes: minecraft-character from '~app/components/ui/icons.scss';
|
composes: minecraft-character from '~app/components/ui/icons.scss';
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
&0 {
|
&0 {
|
||||||
color: $green;
|
color: $green;
|
||||||
@ -30,4 +36,18 @@
|
|||||||
&6 {
|
&6 {
|
||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deletedPseudoAvatar & {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletedIcon {
|
||||||
|
composes: close from '~app/components/ui/icons.scss';
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0.16em;
|
||||||
|
left: -0.145em;
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: rgba($red, 0.75);
|
||||||
}
|
}
|
||||||
|
@ -72,12 +72,14 @@ const AccountSwitcher: ComponentType<Props> = ({
|
|||||||
|
|
||||||
{available.map((account, index) => (
|
{available.map((account, index) => (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.item, styles.accountSwitchItem)}
|
className={clsx(styles.item, styles.accountSwitchItem, {
|
||||||
|
[styles.deletedAccountItem]: account.isDeleted,
|
||||||
|
})}
|
||||||
key={account.id}
|
key={account.id}
|
||||||
data-e2e-account-id={account.id}
|
data-e2e-account-id={account.id}
|
||||||
onClick={onAccountClickCallback(account)}
|
onClick={onAccountClickCallback(account)}
|
||||||
>
|
>
|
||||||
<PseudoAvatar index={index + 1} className={styles.accountIcon} />
|
<PseudoAvatar index={index + 1} deleted={account.isDeleted} className={styles.accountIcon} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.logoutIcon}
|
className={styles.logoutIcon}
|
||||||
|
@ -10,6 +10,7 @@ const activeAccount = {
|
|||||||
email: 'mock@ely.by',
|
email: 'mock@ely.by',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
token: '',
|
token: '',
|
||||||
|
isDeleted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf('Components/Userbar', module)
|
storiesOf('Components/Userbar', module)
|
||||||
@ -27,6 +28,15 @@ storiesOf('Components/Userbar', module)
|
|||||||
email: 'mock-user2@ely.by',
|
email: 'mock-user2@ely.by',
|
||||||
token: '',
|
token: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
|
isDeleted: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
username: 'DeletedUser',
|
||||||
|
email: 'i-am-deleted@ely.by',
|
||||||
|
token: '',
|
||||||
|
refreshToken: '',
|
||||||
|
isDeleted: true,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onSwitchAccount={async (account) => action('onSwitchAccount')(account)}
|
onSwitchAccount={async (account) => action('onSwitchAccount')(account)}
|
||||||
|
@ -96,11 +96,19 @@ $lightBorderColor: #eee;
|
|||||||
font-family: $font-family-title;
|
font-family: $font-family-title;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
||||||
|
.deletedAccountItem & {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountEmail {
|
.accountEmail {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
||||||
|
.deletedAccountItem & {
|
||||||
|
color: #a9a9a9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.addIcon {
|
.addIcon {
|
||||||
|
@ -3,6 +3,7 @@ import React, { ComponentType, useCallback, useContext } from 'react';
|
|||||||
import { useReduxDispatch } from 'app/functions';
|
import { useReduxDispatch } from 'app/functions';
|
||||||
import { restoreAccount } from 'app/services/api/accounts';
|
import { restoreAccount } from 'app/services/api/accounts';
|
||||||
import { updateUser } from 'app/components/user/actions';
|
import { updateUser } from 'app/components/user/actions';
|
||||||
|
import { markAsDeleted } from 'app/components/accounts/actions/pure-actions';
|
||||||
import ProfileContext from 'app/components/profile/Context';
|
import ProfileContext from 'app/components/profile/Context';
|
||||||
|
|
||||||
import AccountDeleted from 'app/components/profile/AccountDeleted';
|
import AccountDeleted from 'app/components/profile/AccountDeleted';
|
||||||
@ -17,6 +18,7 @@ const AccountDeletedPage: ComponentType = () => {
|
|||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
dispatch(markAsDeleted(false));
|
||||||
context.goToProfile();
|
context.goToProfile();
|
||||||
}, [dispatch, context]);
|
}, [dispatch, context]);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { deleteAccount } from 'app/services/api/accounts';
|
|||||||
import { FormModel } from 'app/components/ui/form';
|
import { FormModel } from 'app/components/ui/form';
|
||||||
import DeleteAccount from 'app/components/profile/deleteAccount';
|
import DeleteAccount from 'app/components/profile/deleteAccount';
|
||||||
import { updateUser } from 'app/components/user/actions';
|
import { updateUser } from 'app/components/user/actions';
|
||||||
|
import { markAsDeleted } from 'app/components/accounts/actions/pure-actions';
|
||||||
import ProfileContext from 'app/components/profile/Context';
|
import ProfileContext from 'app/components/profile/Context';
|
||||||
|
|
||||||
const DeleteAccountPage: ComponentType = () => {
|
const DeleteAccountPage: ComponentType = () => {
|
||||||
@ -21,6 +22,7 @@ const DeleteAccountPage: ComponentType = () => {
|
|||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
dispatch(markAsDeleted(true));
|
||||||
context.goToProfile();
|
context.goToProfile();
|
||||||
}, [context]);
|
}, [context]);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { Link, useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useReduxDispatch, useReduxSelector } from 'app/functions';
|
import { useReduxDispatch, useReduxSelector } from 'app/functions';
|
||||||
import { authenticate, revoke } from 'app/components/accounts/actions';
|
import { authenticate, revoke } from 'app/components/accounts/actions';
|
||||||
import { Account } from 'app/components/accounts/reducer';
|
import { Account, getSortedAccounts } from 'app/components/accounts/reducer';
|
||||||
import buttons from 'app/components/ui/buttons.scss';
|
import buttons from 'app/components/ui/buttons.scss';
|
||||||
import LoggedInPanel from 'app/components/userbar/LoggedInPanel';
|
import LoggedInPanel from 'app/components/userbar/LoggedInPanel';
|
||||||
import * as loader from 'app/services/loader';
|
import * as loader from 'app/services/loader';
|
||||||
@ -20,7 +20,7 @@ interface Props {
|
|||||||
const Toolbar: ComponentType<Props> = ({ onLogoClick, account }) => {
|
const Toolbar: ComponentType<Props> = ({ onLogoClick, account }) => {
|
||||||
const dispatch = useReduxDispatch();
|
const dispatch = useReduxDispatch();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const availableAccounts = useReduxSelector((state) => state.accounts.available);
|
const availableAccounts = useReduxSelector(getSortedAccounts);
|
||||||
const switchAccount = useCallback((account: Account) => {
|
const switchAccount = useCallback((account: Account) => {
|
||||||
loader.show();
|
loader.show();
|
||||||
|
|
||||||
|
@ -420,6 +420,7 @@ describe('CompleteState', () => {
|
|||||||
username: 'thatUsername',
|
username: 'thatUsername',
|
||||||
token: '',
|
token: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
|
isDeleted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
|
Loading…
Reference in New Issue
Block a user