Add badges to safety number change dialog

This commit is contained in:
Evan Hahn 2021-11-17 15:58:34 -06:00 committed by GitHub
parent 42b45a14b7
commit c0444f66a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 82 additions and 4 deletions

View file

@ -26,6 +26,7 @@ export const App = ({
cancelMessagesPendingConversationVerification, cancelMessagesPendingConversationVerification,
conversationsStoppingMessageSendBecauseOfVerification, conversationsStoppingMessageSendBecauseOfVerification,
hasInitialLoadCompleted, hasInitialLoadCompleted,
getPreferredBadge,
i18n, i18n,
isCustomizingPreferredReactions, isCustomizingPreferredReactions,
numberOfMessagesPendingBecauseOfVerification, numberOfMessagesPendingBecauseOfVerification,
@ -52,6 +53,7 @@ export const App = ({
conversationsStoppingMessageSendBecauseOfVerification conversationsStoppingMessageSendBecauseOfVerification
} }
hasInitialLoadCompleted={hasInitialLoadCompleted} hasInitialLoadCompleted={hasInitialLoadCompleted}
getPreferredBadge={getPreferredBadge}
i18n={i18n} i18n={i18n}
isCustomizingPreferredReactions={isCustomizingPreferredReactions} isCustomizingPreferredReactions={isCustomizingPreferredReactions}
numberOfMessagesPendingBecauseOfVerification={ numberOfMessagesPendingBecauseOfVerification={
@ -61,6 +63,7 @@ export const App = ({
renderCustomizingPreferredReactionsModal renderCustomizingPreferredReactionsModal
} }
renderSafetyNumber={renderSafetyNumber} renderSafetyNumber={renderSafetyNumber}
theme={theme}
verifyConversationsStoppingMessageSend={ verifyConversationsStoppingMessageSend={
verifyConversationsStoppingMessageSend verifyConversationsStoppingMessageSend
} }

View file

@ -23,6 +23,7 @@ import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGr
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import type { Props as SafetyNumberViewerProps } from '../state/smart/SafetyNumberViewer'; import type { Props as SafetyNumberViewerProps } from '../state/smart/SafetyNumberViewer';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { ThemeType } from '../types/Util';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -67,6 +68,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
declineCall: action('decline-call'), declineCall: action('decline-call'),
getGroupCallVideoFrameSource: (_: string, demuxId: number) => getGroupCallVideoFrameSource: (_: string, demuxId: number) =>
fakeGetGroupCallVideoFrameSource(demuxId), fakeGetGroupCallVideoFrameSource(demuxId),
getPreferredBadge: () => undefined,
getPresentingSources: action('get-presenting-sources'), getPresentingSources: action('get-presenting-sources'),
hangUp: action('hang-up'), hangUp: action('hang-up'),
i18n, i18n,
@ -98,6 +100,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
setOutgoingRing: action('set-outgoing-ring'), setOutgoingRing: action('set-outgoing-ring'),
startCall: action('start-call'), startCall: action('start-call'),
stopRingtone: action('stop-ringtone'), stopRingtone: action('stop-ringtone'),
theme: ThemeType.light,
toggleParticipants: action('toggle-participants'), toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'), togglePip: action('toggle-pip'),
toggleScreenRecordingPermissionsDialog: action( toggleScreenRecordingPermissionsDialog: action(

View file

@ -26,6 +26,7 @@ import {
GroupCallJoinState, GroupCallJoinState,
} from '../types/Calling'; } from '../types/Calling';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { import type {
AcceptCallType, AcceptCallType,
CancelCallType, CancelCallType,
@ -39,7 +40,7 @@ import type {
SetRendererCanvasType, SetRendererCanvasType,
StartCallType, StartCallType,
} from '../state/ducks/calling'; } from '../state/ducks/calling';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType, ThemeType } from '../types/Util';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
@ -58,6 +59,7 @@ export type PropsType = {
conversationId: string, conversationId: string,
demuxId: number demuxId: number
) => VideoFrameSource; ) => VideoFrameSource;
getPreferredBadge: PreferredBadgeSelectorType;
getPresentingSources: () => void; getPresentingSources: () => void;
incomingCall?: incomingCall?:
| { | {
@ -96,6 +98,7 @@ export type PropsType = {
setRendererCanvas: (_: SetRendererCanvasType) => void; setRendererCanvas: (_: SetRendererCanvasType) => void;
stopRingtone: () => unknown; stopRingtone: () => unknown;
hangUp: (_: HangUpType) => void; hangUp: (_: HangUpType) => void;
theme: ThemeType;
togglePip: () => void; togglePip: () => void;
toggleScreenRecordingPermissionsDialog: () => unknown; toggleScreenRecordingPermissionsDialog: () => unknown;
toggleSettings: () => void; toggleSettings: () => void;
@ -116,6 +119,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
isGroupCallOutboundRingEnabled, isGroupCallOutboundRingEnabled,
keyChangeOk, keyChangeOk,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
getPreferredBadge,
getPresentingSources, getPresentingSources,
me, me,
openSystemPreferencesAction, openSystemPreferencesAction,
@ -129,6 +133,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
setRendererCanvas, setRendererCanvas,
setOutgoingRing, setOutgoingRing,
startCall, startCall,
theme,
toggleParticipants, toggleParticipants,
togglePip, togglePip,
toggleScreenRecordingPermissionsDialog, toggleScreenRecordingPermissionsDialog,
@ -343,6 +348,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
confirmText={i18n('continueCall')} confirmText={i18n('continueCall')}
contacts={activeCall.conversationsWithSafetyNumberChanges} contacts={activeCall.conversationsWithSafetyNumberChanges}
getPreferredBadge={getPreferredBadge}
i18n={i18n} i18n={i18n}
onCancel={() => { onCancel={() => {
hangUp({ conversationId: activeCall.conversation.id }); hangUp({ conversationId: activeCall.conversation.id });
@ -351,6 +357,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
keyChangeOk({ conversationId: activeCall.conversation.id }); keyChangeOk({ conversationId: activeCall.conversation.id });
}} }}
renderSafetyNumber={renderSafetyNumberViewer} renderSafetyNumber={renderSafetyNumberViewer}
theme={theme}
/> />
) : null} ) : null}
</> </>

View file

@ -7,7 +7,8 @@ import type * as Backbone from 'backbone';
import type { SafetyNumberProps } from './SafetyNumberChangeDialog'; import type { SafetyNumberProps } from './SafetyNumberChangeDialog';
import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog'; import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { LocalizerType, ThemeType } from '../types/Util';
type InboxViewType = Backbone.View & { type InboxViewType = Backbone.View & {
onEmpty?: () => void; onEmpty?: () => void;
@ -22,11 +23,13 @@ export type PropsType = {
cancelMessagesPendingConversationVerification: () => void; cancelMessagesPendingConversationVerification: () => void;
conversationsStoppingMessageSendBecauseOfVerification: Array<ConversationType>; conversationsStoppingMessageSendBecauseOfVerification: Array<ConversationType>;
hasInitialLoadCompleted: boolean; hasInitialLoadCompleted: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType; i18n: LocalizerType;
isCustomizingPreferredReactions: boolean; isCustomizingPreferredReactions: boolean;
numberOfMessagesPendingBecauseOfVerification: number; numberOfMessagesPendingBecauseOfVerification: number;
renderCustomizingPreferredReactionsModal: () => JSX.Element; renderCustomizingPreferredReactionsModal: () => JSX.Element;
renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element; renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element;
theme: ThemeType;
verifyConversationsStoppingMessageSend: () => void; verifyConversationsStoppingMessageSend: () => void;
}; };
@ -34,11 +37,13 @@ export const Inbox = ({
cancelMessagesPendingConversationVerification, cancelMessagesPendingConversationVerification,
conversationsStoppingMessageSendBecauseOfVerification, conversationsStoppingMessageSendBecauseOfVerification,
hasInitialLoadCompleted, hasInitialLoadCompleted,
getPreferredBadge,
i18n, i18n,
isCustomizingPreferredReactions, isCustomizingPreferredReactions,
numberOfMessagesPendingBecauseOfVerification, numberOfMessagesPendingBecauseOfVerification,
renderCustomizingPreferredReactionsModal, renderCustomizingPreferredReactionsModal,
renderSafetyNumber, renderSafetyNumber,
theme,
verifyConversationsStoppingMessageSend, verifyConversationsStoppingMessageSend,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const hostRef = useRef<HTMLDivElement | null>(null); const hostRef = useRef<HTMLDivElement | null>(null);
@ -82,10 +87,12 @@ export const Inbox = ({
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
confirmText={confirmText} confirmText={confirmText}
contacts={conversationsStoppingMessageSendBecauseOfVerification} contacts={conversationsStoppingMessageSendBecauseOfVerification}
getPreferredBadge={getPreferredBadge}
i18n={i18n} i18n={i18n}
onCancel={cancelMessagesPendingConversationVerification} onCancel={cancelMessagesPendingConversationVerification}
onConfirm={verifyConversationsStoppingMessageSend} onConfirm={verifyConversationsStoppingMessageSend}
renderSafetyNumber={renderSafetyNumber} renderSafetyNumber={renderSafetyNumber}
theme={theme}
/> />
); );
} }

View file

@ -9,6 +9,8 @@ import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -48,11 +50,15 @@ const contactWithNothing = getDefaultConversation({
title: 'Unknown contact', title: 'Unknown contact',
}); });
const useTheme = () => React.useContext(StorybookThemeContext);
storiesOf('Components/SafetyNumberChangeDialog', module) storiesOf('Components/SafetyNumberChangeDialog', module)
.add('Single Contact Dialog', () => { .add('Single Contact Dialog', () => {
const theme = useTheme();
return ( return (
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
contacts={[contactWithAllData]} contacts={[contactWithAllData]}
getPreferredBadge={() => undefined}
i18n={i18n} i18n={i18n}
onCancel={action('cancel')} onCancel={action('cancel')}
onConfirm={action('confirm')} onConfirm={action('confirm')}
@ -60,14 +66,17 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
action('renderSafetyNumber'); action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>; return <div>This is a mock Safety Number View</div>;
}} }}
theme={theme}
/> />
); );
}) })
.add('Different Confirmation Text', () => { .add('Different Confirmation Text', () => {
const theme = useTheme();
return ( return (
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
confirmText="You are awesome" confirmText="You are awesome"
contacts={[contactWithAllData]} contacts={[contactWithAllData]}
getPreferredBadge={() => undefined}
i18n={i18n} i18n={i18n}
onCancel={action('cancel')} onCancel={action('cancel')}
onConfirm={action('confirm')} onConfirm={action('confirm')}
@ -75,10 +84,12 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
action('renderSafetyNumber'); action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>; return <div>This is a mock Safety Number View</div>;
}} }}
theme={theme}
/> />
); );
}) })
.add('Multi Contact Dialog', () => { .add('Multi Contact Dialog', () => {
const theme = useTheme();
return ( return (
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
contacts={[ contacts={[
@ -87,6 +98,7 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
contactWithJustNumber, contactWithJustNumber,
contactWithNothing, contactWithNothing,
]} ]}
getPreferredBadge={() => undefined}
i18n={i18n} i18n={i18n}
onCancel={action('cancel')} onCancel={action('cancel')}
onConfirm={action('confirm')} onConfirm={action('confirm')}
@ -94,10 +106,34 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
action('renderSafetyNumber'); action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>; return <div>This is a mock Safety Number View</div>;
}} }}
theme={theme}
/>
);
})
.add('Multiple contacts, all with badges', () => {
const theme = useTheme();
return (
<SafetyNumberChangeDialog
contacts={[
contactWithAllData,
contactWithJustProfile,
contactWithJustNumber,
contactWithNothing,
]}
getPreferredBadge={() => getFakeBadge()}
i18n={i18n}
onCancel={action('cancel')}
onConfirm={action('confirm')}
renderSafetyNumber={() => {
action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>;
}}
theme={theme}
/> />
); );
}) })
.add('Scroll Dialog', () => { .add('Scroll Dialog', () => {
const theme = useTheme();
return ( return (
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
contacts={[ contacts={[
@ -112,6 +148,7 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
contactWithAllData, contactWithAllData,
contactWithAllData, contactWithAllData,
]} ]}
getPreferredBadge={() => undefined}
i18n={i18n} i18n={i18n}
onCancel={action('cancel')} onCancel={action('cancel')}
onConfirm={action('confirm')} onConfirm={action('confirm')}
@ -119,6 +156,7 @@ storiesOf('Components/SafetyNumberChangeDialog', module)
action('renderSafetyNumber'); action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>; return <div>This is a mock Safety Number View</div>;
}} }}
theme={theme}
/> />
); );
}); });

View file

@ -10,7 +10,8 @@ import { InContactsIcon } from './InContactsIcon';
import { Modal } from './Modal'; import { Modal } from './Modal';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { LocalizerType, ThemeType } from '../types/Util';
import { isInSystemContacts } from '../util/isInSystemContacts'; import { isInSystemContacts } from '../util/isInSystemContacts';
export type SafetyNumberProps = { export type SafetyNumberProps = {
@ -21,19 +22,23 @@ export type SafetyNumberProps = {
export type Props = { export type Props = {
readonly confirmText?: string; readonly confirmText?: string;
readonly contacts: Array<ConversationType>; readonly contacts: Array<ConversationType>;
readonly getPreferredBadge: PreferredBadgeSelectorType;
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly onCancel: () => void; readonly onCancel: () => void;
readonly onConfirm: () => void; readonly onConfirm: () => void;
readonly renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element; readonly renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element;
readonly theme: ThemeType;
}; };
export const SafetyNumberChangeDialog = ({ export const SafetyNumberChangeDialog = ({
confirmText, confirmText,
contacts, contacts,
getPreferredBadge,
i18n, i18n,
onCancel, onCancel,
onConfirm, onConfirm,
renderSafetyNumber, renderSafetyNumber,
theme,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const [selectedContact, setSelectedContact] = React.useState< const [selectedContact, setSelectedContact] = React.useState<
ConversationType | undefined ConversationType | undefined
@ -89,6 +94,7 @@ export const SafetyNumberChangeDialog = ({
<Avatar <Avatar
acceptedMessageRequest={contact.acceptedMessageRequest} acceptedMessageRequest={contact.acceptedMessageRequest}
avatarPath={contact.avatarPath} avatarPath={contact.avatarPath}
badge={getPreferredBadge(contact.badges)}
color={contact.color} color={contact.color}
conversationType="direct" conversationType="direct"
i18n={i18n} i18n={i18n}
@ -96,6 +102,7 @@ export const SafetyNumberChangeDialog = ({
name={contact.name} name={contact.name}
phoneNumber={contact.phoneNumber} phoneNumber={contact.phoneNumber}
profileName={contact.profileName} profileName={contact.profileName}
theme={theme}
title={contact.title} title={contact.title}
sharedGroupNames={contact.sharedGroupNames} sharedGroupNames={contact.sharedGroupNames}
size={52} size={52}

View file

@ -11,6 +11,8 @@ import React from 'react';
import { unmountComponentAtNode, render } from 'react-dom'; import { unmountComponentAtNode, render } from 'react-dom';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
import { SafetyNumberChangeDialog } from '../components/SafetyNumberChangeDialog'; import { SafetyNumberChangeDialog } from '../components/SafetyNumberChangeDialog';
import { getPreferredBadgeSelector } from '../state/selectors/badges';
import { getTheme } from '../state/selectors/user';
export type SafetyNumberChangeViewProps = { export type SafetyNumberChangeViewProps = {
confirmText?: string; confirmText?: string;
@ -42,10 +44,15 @@ export function showSafetyNumberChangeDialog(
dialogContainerNode = document.createElement('div'); dialogContainerNode = document.createElement('div');
document.body.appendChild(dialogContainerNode); document.body.appendChild(dialogContainerNode);
const reduxState = window.reduxStore.getState();
const getPreferredBadge = getPreferredBadgeSelector(reduxState);
const theme = getTheme(reduxState);
render( render(
<SafetyNumberChangeDialog <SafetyNumberChangeDialog
confirmText={options.confirmText} confirmText={options.confirmText}
contacts={options.contacts.map(contact => contact.format())} contacts={options.contacts.map(contact => contact.format())}
getPreferredBadge={getPreferredBadge}
i18n={window.i18n} i18n={window.i18n}
onCancel={() => { onCancel={() => {
options.reject(); options.reject();
@ -61,6 +68,7 @@ export function showSafetyNumberChangeDialog(
props props
); );
}} }}
theme={theme}
/>, />,
dialogContainerNode dialogContainerNode
); );

View file

@ -10,6 +10,7 @@ import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredR
import { SmartGlobalModalContainer } from './GlobalModalContainer'; import { SmartGlobalModalContainer } from './GlobalModalContainer';
import { SmartSafetyNumberViewer } from './SafetyNumberViewer'; import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
import type { StateType } from '../reducer'; import type { StateType } from '../reducer';
import { getPreferredBadgeSelector } from '../selectors/badges';
import { getIntl, getTheme } from '../selectors/user'; import { getIntl, getTheme } from '../selectors/user';
import { import {
getConversationsStoppingMessageSendBecauseOfVerification, getConversationsStoppingMessageSendBecauseOfVerification,
@ -24,6 +25,7 @@ const mapStateToProps = (state: StateType) => {
...state.app, ...state.app,
conversationsStoppingMessageSendBecauseOfVerification: conversationsStoppingMessageSendBecauseOfVerification:
getConversationsStoppingMessageSendBecauseOfVerification(state), getConversationsStoppingMessageSendBecauseOfVerification(state),
getPreferredBadge: getPreferredBadgeSelector(state),
i18n: getIntl(state), i18n: getIntl(state),
isCustomizingPreferredReactions: getIsCustomizingPreferredReactions(state), isCustomizingPreferredReactions: getIsCustomizingPreferredReactions(state),
numberOfMessagesPendingBecauseOfVerification: numberOfMessagesPendingBecauseOfVerification:

View file

@ -7,7 +7,7 @@ import { memoize } from 'lodash';
import { mapDispatchToProps } from '../actions'; import { mapDispatchToProps } from '../actions';
import { CallManager } from '../../components/CallManager'; import { CallManager } from '../../components/CallManager';
import { calling as callingService } from '../../services/calling'; import { calling as callingService } from '../../services/calling';
import { getUserUuid, getIntl } from '../selectors/user'; import { getUserUuid, getIntl, getTheme } from '../selectors/user';
import { getMe, getConversationSelector } from '../selectors/conversations'; import { getMe, getConversationSelector } from '../selectors/conversations';
import { getActiveCall } from '../ducks/calling'; import { getActiveCall } from '../ducks/calling';
import type { ConversationType } from '../ducks/conversations'; import type { ConversationType } from '../ducks/conversations';
@ -35,6 +35,7 @@ import {
notificationService, notificationService,
} from '../../services/notifications'; } from '../../services/notifications';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import { getPreferredBadgeSelector } from '../selectors/badges';
function renderDeviceSelection(): JSX.Element { function renderDeviceSelection(): JSX.Element {
return <SmartCallingDeviceSelection />; return <SmartCallingDeviceSelection />;
@ -306,6 +307,7 @@ const mapStateToProps = (state: StateType) => ({
bounceAppIconStop, bounceAppIconStop,
availableCameras: state.calling.availableCameras, availableCameras: state.calling.availableCameras,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
getPreferredBadge: getPreferredBadgeSelector(state),
i18n: getIntl(state), i18n: getIntl(state),
isGroupCallOutboundRingEnabled: isGroupCallOutboundRingEnabled(), isGroupCallOutboundRingEnabled: isGroupCallOutboundRingEnabled(),
incomingCall: mapStateToIncomingCallProp(state), incomingCall: mapStateToIncomingCallProp(state),
@ -320,6 +322,7 @@ const mapStateToProps = (state: StateType) => ({
stopRingtone, stopRingtone,
renderDeviceSelection, renderDeviceSelection,
renderSafetyNumberViewer, renderSafetyNumberViewer,
theme: getTheme(state),
}); });
const smart = connect(mapStateToProps, mapDispatchToProps); const smart = connect(mapStateToProps, mapDispatchToProps);