Skip to content

Commit

Permalink
feat: Add the option to change conversation priority (#709)
Browse files Browse the repository at this point in the history
Co-authored-by: iamsivin <iamsivin@gmail.com>
  • Loading branch information
muhsin-k and iamsivin committed Jun 22, 2023
1 parent 4525745 commit 5c27a98
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 5 deletions.
41 changes: 41 additions & 0 deletions src/constants/PrioritySVG.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/constants/fluent-icons.json
Expand Up @@ -105,7 +105,7 @@
"paper-plane": "M21 4a1.31 1.31 0 0 0-.06-.27v-.09a1 1 0 0 0-.2-.3a1 1 0 0 0-.29-.19h-.09a.86.86 0 0 0-.31-.15H20a1 1 0 0 0-.3 0l-18 6a1 1 0 0 0 0 1.9l8.53 2.84l2.84 8.53a1 1 0 0 0 1.9 0l6-18A1 1 0 0 0 21 4Zm-4.7 2.29l-5.57 5.57L5.16 10ZM14 18.84l-1.86-5.57l5.57-5.57Z",
"double-tick": "M16.62 6.21a1 1 0 0 0-1.41.17l-7 9l-3.43-4.18a1 1 0 1 0-1.56 1.25l4.17 5.18a1 1 0 0 0 .78.37a1 1 0 0 0 .83-.38l7.83-10a1 1 0 0 0-.21-1.41Zm5 0a1 1 0 0 0-1.41.17l-7 9l-.61-.75l-1.26 1.62l1.1 1.37a1 1 0 0 0 .78.37a1 1 0 0 0 .78-.38l7.83-10a1 1 0 0 0-.21-1.4Z",
"camera-outline": "M13.925 2.504a2.25 2.25 0 0 1 1.94 1.11l.814 1.387h2.071A3.25 3.25 0 0 1 22 8.25v9.5A3.25 3.25 0 0 1 18.75 21H5.25A3.25 3.25 0 0 1 2 17.75v-9.5A3.25 3.25 0 0 1 5.25 5h2.08l.875-1.424a2.25 2.25 0 0 1 1.917-1.073h3.803Zm0 1.5h-3.803a.75.75 0 0 0-.574.268l-.065.09l-1.093 1.78a.75.75 0 0 1-.639.358h-2.5A1.75 1.75 0 0 0 3.5 8.25v9.5c0 .966.784 1.75 1.75 1.75h13.5a1.75 1.75 0 0 0 1.75-1.75v-9.5a1.75 1.75 0 0 0-1.75-1.75h-2.5a.75.75 0 0 1-.647-.37l-1.032-1.757a.75.75 0 0 0-.646-.37ZM12 8a4.5 4.5 0 1 1 0 9a4.5 4.5 0 0 1 0-9Zm0 1.5a3 3 0 1 0 0 6a3 3 0 0 0 0-6Z",

"priority-outline": "M13.25 14.5a1 1 0 1 1-2 0a1 1 0 0 1 2 0ZM11.5 6.75v5a.75.75 0 0 0 1.5 0v-5a.75.75 0 0 0-1.5 0ZM4 4.5A2.5 2.5 0 0 1 6.5 2H18a2.5 2.5 0 0 1 2.5 2.5v14.25a.75.75 0 0 1-.75.75H5.5a1 1 0 0 0 1 1h13.25a.75.75 0 0 1 0 1.5H6.5A2.5 2.5 0 0 1 4 19.5v-15ZM19 18V4.5a1 1 0 0 0-1-1H6.5a1 1 0 0 0-1 1V18H19Z",
"priority-urgent": [
"M2.33325 2.91667C2.33325 2.27233 2.85559 1.75 3.49992 1.75C4.14425 1.75 4.66659 2.27233 4.66659 2.91667V8.16667C4.66659 8.811 4.14425 9.33333 3.49992 9.33333C2.85559 9.33333 2.33325 8.811 2.33325 8.16667V2.91667Z",
"M2.33325 11.0833C2.33325 10.439 2.85559 9.91667 3.49992 9.91667C4.14425 9.91667 4.66659 10.439 4.66659 11.0833C4.66659 11.7277 4.14425 12.25 3.49992 12.25C2.85559 12.25 2.33325 11.7277 2.33325 11.0833Z",
Expand Down
21 changes: 20 additions & 1 deletion src/i18n/en.json
Expand Up @@ -81,13 +81,32 @@
"SNOOZE_UNTIL_TOMORROW": "Until tomorrow",
"SNOOZE_UNTIL_NEXT_WEEK": "Until next week",
"SNOOZE": "Snooze conversation",
"CHANGE_PRIORITY": "Change priority",
"MESSAGES": "Messages",
"UNDEFINED_VARIABLES_TITLE": "Undefined variables",
"UNDEFINED_VARIABLES_CLOSE": "Close",
"DELETE_MESSAGE_TITLE": "Are you sure you want to delete this message?",
"DELETE_MESSAGE_SUB_TITLE": "This action cannot be undone.",
"COPY_MESSAGE": "Message copied to clipboard"
"COPY_MESSAGE": "Message copied to clipboard",
"PRIORITY": {
"TITLE": "Priority",
"OPTIONS": {
"NONE": "None",
"URGENT": "Urgent",
"HIGH": "High",
"MEDIUM": "Medium",
"LOW": "Low"
},
"CHANGE_PRIORITY": {
"SELECT_PLACEHOLDER": "None",
"INPUT_PLACEHOLDER": "Select priority",
"NO_RESULTS": "No results found",
"SUCCESSFUL": "Changed priority of conversation id %{conversationId} to %{priority}",
"FAILED": "Couldn't change priority. Please try again."
}
}
},

"FILTER": {
"ALL_INBOXES": "All inboxes",
"FILTER_BY_ASSIGNEE_TYPE": "Filter by assignee type",
Expand Down
20 changes: 20 additions & 0 deletions src/reducer/conversationSlice.action.js
Expand Up @@ -300,6 +300,26 @@ const actions = {
}
},
),
togglePriority: createAsyncThunk(
'conversations/togglePriority',
async ({ conversationId, priority }, { rejectWithValue }) => {
try {
const apiUrl = `conversations/${conversationId}/toggle_priority`;
await axios.post(apiUrl, {
priority,
});
return {
id: conversationId,
priority,
};
} catch (error) {
if (!error.response) {
throw error;
}
return rejectWithValue(error.response.data);
}
},
),
};

export default actions;
8 changes: 8 additions & 0 deletions src/reducer/conversationSlice.js
Expand Up @@ -215,6 +215,14 @@ const conversationSlice = createSlice({
state.isAllMessagesFetched = false;
state.isConversationFetching = false;
}
})
.addCase(actions.togglePriority.fulfilled, (state, { payload }) => {
const { id, priority } = payload;
const conversation = state.entities[id];
if (!conversation) {
return;
}
conversation.priority = priority;
});
},
});
Expand Down
27 changes: 27 additions & 0 deletions src/reducer/specs/conversationSlice.reducer.spec.js
Expand Up @@ -128,4 +128,31 @@ describe('conversations reducer', () => {
loading: false,
});
});
it('sets priority when togglePriority is called', () => {
expect(
conversationSlice(
{
ids: [conversations[0].id],
entities: {
1: conversations[0],
},
},
{
type: 'conversations/togglePriority/fulfilled',
payload: {
priority: 'urgent',
id: 1,
},
},
),
).toEqual({
entities: {
1: {
...conversations[0],
priority: 'urgent',
},
},
ids: [conversations[0].id],
});
});
});
31 changes: 30 additions & 1 deletion src/screens/ChatScreen/components/ChatHeader.js
Expand Up @@ -26,6 +26,7 @@ const deviceHeight = Dimensions.get('window').height;

// Bottom sheet items
import ConversationAction from '../../ConversationAction/ConversationAction';
import ConversationPriority from './ConversationPriority';
import SnoozeConversationItems from './SnoozeConversation';
import AssignTeamConversationItems from './ConversationTeams';
import LabelConversationItems from './ConversationLabels';
Expand Down Expand Up @@ -242,6 +243,10 @@ const ChatHeader = ({
closeActionModal();
toggleSnoozeActionModal();
}
if (itemType === 'priority') {
closeActionModal();
togglePriorityActionModal();
}
if (itemType === 'share') {
onClickShareConversationURL();
}
Expand Down Expand Up @@ -299,7 +304,6 @@ const ChatHeader = ({
actionModal.current?.dismiss();
}, []);

// Conversation action modal
const snoozeActionModal = useRef(null);
const snoozeActionModalSnapPoints = useMemo(() => [deviceHeight - 400, deviceHeight - 400], []);
const toggleSnoozeActionModal = useCallback(() => {
Expand All @@ -309,6 +313,16 @@ const ChatHeader = ({
snoozeActionModal.current?.dismiss();
}, []);

// Conversation action modal
const priorityActionModal = useRef(null);
const priorityActionModalSnapPoints = useMemo(() => [deviceHeight - 400, deviceHeight - 400], []);
const togglePriorityActionModal = useCallback(() => {
priorityActionModal.current.present() || priorityActionModal.current?.dismiss();
}, []);
const closePriorityActionModal = useCallback(() => {
priorityActionModal.current?.dismiss();
}, []);

const conversationActionModalSnapPoints = useMemo(
() => [deviceHeight - 120, deviceHeight - 120],
[],
Expand Down Expand Up @@ -436,6 +450,21 @@ const ChatHeader = ({
/>
}
/>
<BottomSheetModal
bottomSheetModalRef={priorityActionModal}
initialSnapPoints={priorityActionModalSnapPoints}
showHeader
headerTitle={i18n.t('CONVERSATION.CHANGE_PRIORITY')}
closeFilter={closePriorityActionModal}
children={
<ConversationPriority
colors={colors}
conversationId={conversationId}
activePriority={conversationDetails.priority}
closeModal={closePriorityActionModal}
/>
}
/>
<BottomSheetModal
bottomSheetModalRef={assignTeamModal}
initialSnapPoints={conversationActionModalSnapPoints}
Expand Down
137 changes: 137 additions & 0 deletions src/screens/ChatScreen/components/ConversationPriority.js
@@ -0,0 +1,137 @@
import React, { useMemo } from 'react';
import { View, StyleSheet } from 'react-native';
import i18n from 'i18n';
import { useDispatch } from 'react-redux';
import { SvgXml } from 'react-native-svg';
import { useTheme } from '@react-navigation/native';
import PropTypes from 'prop-types';
import { Text, Icon, Pressable } from 'components';
import AnalyticsHelper from 'helpers/AnalyticsHelper';
import conversationActions from 'reducer/conversationSlice.action';
import { CONVERSATION_EVENTS } from 'constants/analyticsEvents';
import { CONVERSATION_PRIORITY } from 'src/constants/index';
import { HIGH, MEDIUM, LOW, NONE, URGENT } from 'src/constants/PrioritySVG';

const propTypes = {
colors: PropTypes.object,
conversationId: PropTypes.number,
activePriority: PropTypes.string,
closeModal: PropTypes.func,
};

const createStyles = theme => {
const { spacing, colors, borderRadius } = theme;
return StyleSheet.create({
bottomSheet: {
flex: 1,
paddingHorizontal: spacing.small,
},
bottomSheetContent: {
marginTop: spacing.small,
marginBottom: spacing.large,
},
bottomSheetItem: {
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
paddingVertical: spacing.smaller,
paddingHorizontal: spacing.half,
backgroundColor: colors.background,
marginBottom: spacing.tiny,
borderWidth: 0.6,
height: 42,
borderColor: 'transparent',
borderRadius: borderRadius.small,
},
bottomSheetItemActive: {
backgroundColor: colors.backgroundLight,
borderColor: colors.borderLight,
},
itemView: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
itemText: {
marginLeft: spacing.smaller,
},
});
};

const priorityItems = [
{
id: null,
name: i18n.t('CONVERSATION.PRIORITY.OPTIONS.NONE'),
icon: NONE,
},
{
id: CONVERSATION_PRIORITY.URGENT,
name: i18n.t('CONVERSATION.PRIORITY.OPTIONS.URGENT'),
icon: URGENT,
},
{
id: CONVERSATION_PRIORITY.HIGH,
name: i18n.t('CONVERSATION.PRIORITY.OPTIONS.HIGH'),
icon: HIGH,
},
{
id: CONVERSATION_PRIORITY.MEDIUM,
name: i18n.t('CONVERSATION.PRIORITY.OPTIONS.MEDIUM'),
icon: MEDIUM,
},
{
id: CONVERSATION_PRIORITY.LOW,
name: i18n.t('CONVERSATION.PRIORITY.OPTIONS.LOW'),
icon: LOW,
},
];

const SnoozeConversation = ({ colors, conversationId, activePriority, closeModal }) => {
const theme = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const dispatch = useDispatch();

const changePriority = item => {
try {
AnalyticsHelper.track(CONVERSATION_EVENTS.TOGGLE_STATUS);
dispatch(
conversationActions.togglePriority({
conversationId: conversationId,
priority: item,
}),
);
} catch (error) {}
closeModal();
};

return (
<View style={styles.bottomSheet}>
<View style={styles.bottomSheetContent}>
{priorityItems.map((item, index) => (
<Pressable
style={[
styles.bottomSheetItem,
activePriority === item.id && styles.bottomSheetItemActive,
]}
key={item.id}
onPress={() => changePriority(item.id)}>
<View style={styles.itemView}>
<SvgXml xml={item.icon} width={22} height={22} />
<Text sm medium color={colors.textDark} style={styles.itemText}>
{item.name}
</Text>
</View>
<View>
{activePriority === item.id && (
<Icon icon="checkmark-outline" color={colors.textDark} size={18} />
)}
</View>
</Pressable>
))}
</View>
</View>
);
};

SnoozeConversation.propTypes = propTypes;
export default SnoozeConversation;
1 change: 1 addition & 0 deletions src/screens/ChatScreen/components/ReplyBox.js
Expand Up @@ -295,6 +295,7 @@ const ReplyBox = ({ conversationId, inboxId, conversationDetails, enableReplyBut
textStyle: { fontWeight: 'bold', color: 'white', backgroundColor: '#8c9eb6' },
},
{
// eslint-disable-next-line no-useless-escape
pattern: /\[([^\]]+)\]\(([^\)]+)\)/g,
textStyle: { color: colors.primaryColor },
},
Expand Down
13 changes: 13 additions & 0 deletions src/screens/ConversationAction/ConversationAction.js
Expand Up @@ -57,6 +57,10 @@ const ConversationActionComponent = ({ onPressAction, conversationDetails }) =>
return i18n.t('CONVERSATION.SNOOZE_UNTIL_NEXT_REPLY');
};

const priority = conversationDetails.priority
? conversationDetails.priority.toUpperCase()
: 'NONE';

return (
<React.Fragment>
<View style={styles.actionSheetView}>
Expand Down Expand Up @@ -97,6 +101,15 @@ const ConversationActionComponent = ({ onPressAction, conversationDetails }) =>
itemType="snooze"
/>

<ConversationActionItem
onPressItem={onPressAction}
iconName="priority-outline"
text={i18n.t('CONVERSATION.CHANGE_PRIORITY')}
colors={colors}
name={i18n.t(`CONVERSATION.PRIORITY.OPTIONS.${priority}`)}
itemType="priority"
/>

{shouldShowSelfAssign && (
<ConversationActionItem
onPressItem={onPressAction}
Expand Down

0 comments on commit 5c27a98

Please sign in to comment.