aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-05-11 18:56:28 -0400
committerGitHub <noreply@github.com>2021-05-11 18:56:28 -0400
commit68d10064cf1dcd2a774a4b2299f3a64f8fb75c60 (patch)
treee8367c2a8cdb19e52c2a70b73ab2a89865efd54a /src
parent610b6c9ddd2414b3b0d5c4cc24c35ef6e9e68513 (diff)
parent51f397132d227edf5e07d48d673ee167d2aa5937 (diff)
Merge pull request #409 from IvanIFChen/tma799-comment-likes
[TMA-799] Comments likes
Diffstat (limited to 'src')
-rw-r--r--src/assets/images/heart-filled.pngbin0 -> 1208 bytes
-rw-r--r--src/assets/images/heart-outlined.pngbin0 -> 1055 bytes
-rw-r--r--src/components/comments/CommentTile.tsx133
-rw-r--r--src/components/comments/CommentsContainer.tsx1
-rw-r--r--src/components/common/LikeButton.tsx38
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/profile/Friends.tsx97
-rw-r--r--src/constants/api.ts2
-rw-r--r--src/routes/main/MainStackNavigator.tsx11
-rw-r--r--src/routes/main/MainStackScreen.tsx8
-rw-r--r--src/screens/profile/CommentReactionScreen.tsx69
-rw-r--r--src/screens/profile/FriendsListScreen.tsx4
-rw-r--r--src/screens/profile/MomentCommentsScreen.tsx2
-rw-r--r--src/screens/profile/index.ts1
-rw-r--r--src/services/CommentService.ts95
-rw-r--r--src/types/types.ts11
16 files changed, 334 insertions, 139 deletions
diff --git a/src/assets/images/heart-filled.png b/src/assets/images/heart-filled.png
new file mode 100644
index 00000000..59bf0ab1
--- /dev/null
+++ b/src/assets/images/heart-filled.png
Binary files differ
diff --git a/src/assets/images/heart-outlined.png b/src/assets/images/heart-outlined.png
new file mode 100644
index 00000000..aeb87a99
--- /dev/null
+++ b/src/assets/images/heart-outlined.png
Binary files differ
diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx
index ecdb4c30..ee32f889 100644
--- a/src/components/comments/CommentTile.tsx
+++ b/src/components/comments/CommentTile.tsx
@@ -11,7 +11,11 @@ import Trash from '../../assets/ionicons/trash-outline.svg';
import {TAGG_LIGHT_BLUE} from '../../constants';
import {ERROR_FAILED_TO_DELETE_COMMENT} from '../../constants/strings';
import {CommentContext} from '../../screens/profile/MomentCommentsScreen';
-import {deleteComment, getCommentsCount} from '../../services';
+import {
+ deleteComment,
+ getCommentsCount,
+ handleLikeUnlikeComment,
+} from '../../services';
import {RootState} from '../../store/rootReducer';
import {
CommentThreadType,
@@ -19,13 +23,9 @@ import {
ScreenType,
UserType,
} from '../../types';
-import {
- getTimePosted,
- navigateToProfile,
- normalize,
- SCREEN_WIDTH,
-} from '../../utils';
+import {getTimePosted, navigateToProfile, normalize} from '../../utils';
import {mentionPartTypes, renderTextWithMentions} from '../../utils/comments';
+import {LikeButton} from '../common';
import {ProfilePreview} from '../profile';
import CommentsContainer from './CommentsContainer';
@@ -55,6 +55,7 @@ const CommentTile: React.FC<CommentTileProps> = ({
const [showReplies, setShowReplies] = useState<boolean>(false);
const [showKeyboard, setShowKeyboard] = useState<boolean>(false);
const [shouldUpdateChild, setShouldUpdateChild] = useState(true);
+ const [liked, setLiked] = useState(commentObject.user_reaction !== null);
const swipeRef = useRef<Swipeable>(null);
const {replyPosted} = useSelector((state: RootState) => state.user);
const state: RootState = useStore().getState();
@@ -100,7 +101,7 @@ const CommentTile: React.FC<CommentTileProps> = ({
showReplies
? 'Hide'
: comment.replies_count > 0
- ? `Replies (${comment.replies_count})`
+ ? `Replies (${comment.replies_count}) `
: 'Replies';
const renderRightAction = (text: string, color: string) => {
@@ -143,11 +144,19 @@ const CommentTile: React.FC<CommentTileProps> = ({
containerStyle={styles.swipableContainer}>
<View
style={[styles.container, isThread ? styles.moreMarginWithThread : {}]}>
- <ProfilePreview
- profilePreview={commentObject.commenter}
- previewType={'Comment'}
- screenType={screenType}
- />
+ <View style={styles.commentHeaderContainer}>
+ <ProfilePreview
+ profilePreview={commentObject.commenter}
+ previewType={'Comment'}
+ screenType={screenType}
+ />
+ <LikeButton
+ liked={liked}
+ setLiked={setLiked}
+ onPress={() => handleLikeUnlikeComment(commentObject, liked)}
+ style={styles.likeButton}
+ />
+ </View>
<TouchableOpacity style={styles.body} onPress={toggleAddComment}>
{renderTextWithMentions({
value: commentObject.comment,
@@ -156,33 +165,53 @@ const CommentTile: React.FC<CommentTileProps> = ({
onPress: (user: UserType) =>
navigateToProfile(state, dispatch, navigation, screenType, user),
})}
- <View style={styles.clockIconAndTime}>
- <ClockIcon style={styles.clockIcon} />
- <Text style={styles.date_time}>{' ' + timePosted}</Text>
- <View style={styles.flexer} />
+ <View style={styles.commentInfoContainer}>
+ <View style={styles.row}>
+ <ClockIcon style={styles.clockIcon} />
+ <Text style={styles.date_time}>{' ' + timePosted}</Text>
+ </View>
+ <View style={styles.row}>
+ <TouchableOpacity
+ style={styles.row}
+ disabled={commentObject.reaction_count === 0}
+ onPress={() => {
+ navigation.navigate('CommentReactionScreen', {
+ comment: commentObject,
+ screenType: screenType,
+ });
+ }}>
+ <Text style={[styles.date_time, styles.likeCount]}>
+ {commentObject.user_reaction !== null
+ ? commentObject.reaction_count + (liked ? 0 : -1)
+ : commentObject.reaction_count + (liked ? 1 : 0)}
+ </Text>
+ <Text style={styles.date_time}>Likes</Text>
+ </TouchableOpacity>
+ {/* Show replies text only if there are some replies present */}
+ {!isThread && (commentObject as CommentType).replies_count > 0 && (
+ <TouchableOpacity
+ style={styles.repliesTextAndIconContainer}
+ onPress={toggleReplies}>
+ <Text style={styles.repliesText}>
+ {getRepliesText(commentObject as CommentType)}
+ </Text>
+ <Arrow
+ width={12}
+ height={11}
+ color={TAGG_LIGHT_BLUE}
+ style={
+ !showReplies
+ ? styles.repliesDownArrow
+ : styles.repliesUpArrow
+ }
+ />
+ </TouchableOpacity>
+ )}
+ </View>
</View>
</TouchableOpacity>
- {/*** Show replies text only if there are some replies present */}
- {!isThread && (commentObject as CommentType).replies_count > 0 && (
- <TouchableOpacity
- style={styles.repliesTextAndIconContainer}
- onPress={toggleReplies}>
- <Text style={styles.repliesText}>
- {getRepliesText(commentObject as CommentType)}
- </Text>
- <Arrow
- width={12}
- height={11}
- color={TAGG_LIGHT_BLUE}
- style={
- !showReplies ? styles.repliesDownArrow : styles.repliesUpArrow
- }
- />
- </TouchableOpacity>
- )}
</View>
-
- {/*** Show replies if toggle state is true */}
+ {/* Show replies if toggle state is true */}
{showReplies && (
<View>
<CommentsContainer
@@ -206,8 +235,8 @@ const styles = StyleSheet.create({
flexDirection: 'column',
flex: 1,
paddingTop: '3%',
- paddingBottom: '5%',
- marginLeft: '7%',
+ marginLeft: '5%',
+ paddingBottom: '2%',
},
swipeActions: {
flexDirection: 'row',
@@ -215,6 +244,14 @@ const styles = StyleSheet.create({
moreMarginWithThread: {
marginLeft: '14%',
},
+ commentHeaderContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ likeButton: {
+ marginRight: 10,
+ },
body: {
marginLeft: 56,
},
@@ -231,18 +268,23 @@ const styles = StyleSheet.create({
height: 12,
alignSelf: 'center',
},
- clockIconAndTime: {
+ commentInfoContainer: {
flexDirection: 'row',
marginTop: '3%',
+ justifyContent: 'space-between',
+ alignItems: 'center',
},
- flexer: {
- flex: 1,
+ likeCount: {
+ color: 'black',
+ marginRight: 5,
+ },
+ row: {
+ flexDirection: 'row',
},
repliesTextAndIconContainer: {
flexDirection: 'row',
alignItems: 'center',
- marginTop: '5%',
- marginLeft: 56,
+ paddingLeft: 10,
},
repliesText: {
color: TAGG_LIGHT_BLUE,
@@ -250,9 +292,6 @@ const styles = StyleSheet.create({
fontSize: normalize(12),
marginRight: '1%',
},
- repliesBody: {
- width: SCREEN_WIDTH,
- },
repliesDownArrow: {
transform: [{rotate: '270deg'}],
marginTop: '1%',
diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx
index 0bfd5ad6..595ec743 100644
--- a/src/components/comments/CommentsContainer.tsx
+++ b/src/components/comments/CommentsContainer.tsx
@@ -136,7 +136,6 @@ const CommentsContainer: React.FC<CommentsContainerProps> = ({
};
const styles = StyleSheet.create({
- scrollView: {},
scrollViewContent: {
justifyContent: 'center',
},
diff --git a/src/components/common/LikeButton.tsx b/src/components/common/LikeButton.tsx
new file mode 100644
index 00000000..81383eca
--- /dev/null
+++ b/src/components/common/LikeButton.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import {Image, ImageStyle, StyleSheet, TouchableOpacity} from 'react-native';
+import {normalize} from '../../utils';
+
+interface LikeButtonProps {
+ onPress: () => void;
+ style: ImageStyle;
+ liked: boolean;
+ setLiked: (liked: boolean) => void;
+}
+const LikeButton: React.FC<LikeButtonProps> = ({
+ onPress,
+ style,
+ liked,
+ setLiked,
+}) => {
+ const uri = liked
+ ? require('../../assets/images/heart-filled.png')
+ : require('../../assets/images/heart-outlined.png');
+ return (
+ <TouchableOpacity
+ onPress={() => {
+ setLiked(!liked);
+ onPress();
+ }}>
+ <Image style={[styles.image, style]} source={uri} />
+ </TouchableOpacity>
+ );
+};
+
+const styles = StyleSheet.create({
+ image: {
+ width: normalize(18),
+ height: normalize(15),
+ },
+});
+
+export default LikeButton;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index b38056c6..48abb8b8 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -26,3 +26,4 @@ export {default as BasicButton} from './BasicButton';
export {default as Avatar} from './Avatar';
export {default as TaggTypeahead} from './TaggTypeahead';
export {default as TaggUserRowCell} from './TaggUserRowCell';
+export {default as LikeButton} from './LikeButton';
diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx
index a7a06567..f800597b 100644
--- a/src/components/profile/Friends.tsx
+++ b/src/components/profile/Friends.tsx
@@ -1,98 +1,39 @@
-import React, {useEffect, useState} from 'react';
+import React from 'react';
import {ScrollView, StyleSheet, Text, View} from 'react-native';
-import {checkPermission} from 'react-native-contacts';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {useDispatch, useStore} from 'react-redux';
import {TAGG_LIGHT_BLUE} from '../../constants';
-import {usersFromContactsService} from '../../services';
import {NO_USER} from '../../store/initialStates';
import {RootState} from '../../store/rootReducer';
import {ProfilePreviewType, ScreenType} from '../../types';
-import {
- extractContacts,
- normalize,
- SCREEN_HEIGHT,
- SCREEN_WIDTH,
-} from '../../utils';
-import {handleAddFriend, handleUnfriend} from '../../utils/friends';
+import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {handleUnfriend} from '../../utils/friends';
import {ProfilePreview} from '../profile';
interface FriendsProps {
result: Array<ProfilePreviewType>;
screenType: ScreenType;
userId: string | undefined;
+ hideFriendsFeature?: boolean;
}
-const Friends: React.FC<FriendsProps> = ({result, screenType, userId}) => {
+const Friends: React.FC<FriendsProps> = ({
+ result,
+ screenType,
+ userId,
+ hideFriendsFeature,
+}) => {
const state: RootState = useStore().getState();
const dispatch = useDispatch();
const {user: loggedInUser = NO_USER} = state.user;
- const [usersFromContacts, setUsersFromContacts] = useState<
- ProfilePreviewType[]
- >([]);
-
- useEffect(() => {
- const handleFindFriends = () => {
- extractContacts().then(async (contacts) => {
- const permission = await checkPermission();
- if (permission === 'authorized') {
- let response = await usersFromContactsService(contacts);
- setUsersFromContacts(response.existing_tagg_users);
- } else {
- console.log('Authorize access to contacts');
- }
- });
- };
- handleFindFriends();
- }, []);
-
- const UsersFromContacts = () => (
- <>
- {usersFromContacts?.splice(0, 2).map((profilePreview) => (
- <View key={profilePreview.id} style={styles.container}>
- <View style={styles.friend}>
- <ProfilePreview
- {...{profilePreview}}
- previewType={'Friend'}
- screenType={screenType}
- />
- </View>
- <TouchableOpacity
- style={styles.addFriendButton}
- onPress={() => {
- handleAddFriend(screenType, profilePreview, dispatch, state).then(
- (success) => {
- if (success) {
- let users = usersFromContacts;
- setUsersFromContacts(
- users.filter(
- (user) => user.username !== profilePreview.username,
- ),
- );
- }
- },
- );
- }}>
- <Text style={styles.addFriendButtonTitle}>Add Friend</Text>
- </TouchableOpacity>
- </View>
- ))}
- </>
- );
return (
<>
- {loggedInUser.userId === userId && usersFromContacts.length !== 0 && (
- <View style={styles.subheader}>
- <View style={styles.addFriendHeaderContainer}>
- <Text style={[styles.subheaderText]}>Contacts on Tagg</Text>
- </View>
- <UsersFromContacts />
- </View>
+ {!hideFriendsFeature && (
+ <Text style={[styles.subheaderText, styles.friendsSubheaderText]}>
+ Friends
+ </Text>
)}
- <Text style={[styles.subheaderText, styles.friendsSubheaderText]}>
- Friends
- </Text>
<ScrollView
keyboardShouldPersistTaps={'always'}
style={styles.scrollView}
@@ -129,7 +70,6 @@ const styles = StyleSheet.create({
alignSelf: 'center',
width: SCREEN_WIDTH * 0.85,
},
- firstScrollView: {},
scrollViewContent: {
alignSelf: 'center',
paddingBottom: SCREEN_HEIGHT / 7,
@@ -142,7 +82,6 @@ const styles = StyleSheet.create({
marginBottom: '3%',
marginTop: '2%',
},
- header: {flexDirection: 'row'},
subheader: {
alignSelf: 'center',
width: SCREEN_WIDTH * 0.85,
@@ -154,20 +93,12 @@ const styles = StyleSheet.create({
fontWeight: '600',
lineHeight: normalize(14.32),
},
- findFriendsButton: {flexDirection: 'row'},
friendsSubheaderText: {
alignSelf: 'center',
width: SCREEN_WIDTH * 0.85,
marginVertical: '1%',
marginBottom: '2%',
},
- findFriendsSubheaderText: {
- marginLeft: '5%',
- color: '#08E2E2',
- fontSize: normalize(12),
- fontWeight: '600',
- lineHeight: normalize(14.32),
- },
container: {
alignSelf: 'center',
flexDirection: 'row',
diff --git a/src/constants/api.ts b/src/constants/api.ts
index e5ce9e77..9d3f70c9 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -33,6 +33,8 @@ export const MOMENTS_ENDPOINT: string = API_URL + 'moments/';
export const MOMENT_THUMBNAIL_ENDPOINT: string = API_URL + 'moment-thumbnail/';
export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/';
export const COMMENTS_ENDPOINT: string = API_URL + 'comments/';
+export const COMMENT_REACTIONS_ENDPOINT: string = API_URL + 'reaction-comment/';
+export const COMMENT_REACTIONS_REPLY_ENDPOINT: string = API_URL + 'reaction-reply/';
export const FRIENDS_ENDPOINT: string = API_URL + 'friends/';
export const ALL_USERS_ENDPOINT: string = API_URL + 'users/';
export const REPORT_ISSUE_ENDPOINT: string = API_URL + 'report/';
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index 1f173569..3b183cc0 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -3,7 +3,12 @@
*/
import {createStackNavigator} from '@react-navigation/stack';
import {Image} from 'react-native-image-crop-picker';
-import {MomentType, ScreenType, SearchCategoryType} from '../../types';
+import {
+ CommentBaseType,
+ MomentType,
+ ScreenType,
+ SearchCategoryType,
+} from '../../types';
export type MainStackParams = {
SuggestedPeople: {
@@ -46,6 +51,10 @@ export type MainStackParams = {
screenType: ScreenType;
comment_id?: string;
};
+ CommentReactionScreen: {
+ comment: CommentBaseType;
+ screenType: ScreenType;
+ };
FriendsListScreen: {
userXId: string | undefined;
screenType: ScreenType;
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index f5100e58..d76f9137 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -12,6 +12,7 @@ import {
CategorySelection,
ChatListScreen,
ChatScreen,
+ CommentReactionScreen,
CreateCustomCategory,
DiscoverUsers,
EditProfile,
@@ -217,6 +218,13 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
}}
/>
<MainStack.Screen
+ name="CommentReactionScreen"
+ component={CommentReactionScreen}
+ options={{
+ ...headerBarOptions('black', 'Likes'),
+ }}
+ />
+ <MainStack.Screen
name="MomentUploadPrompt"
component={MomentUploadPromptScreen}
initialParams={{screenType}}
diff --git a/src/screens/profile/CommentReactionScreen.tsx b/src/screens/profile/CommentReactionScreen.tsx
new file mode 100644
index 00000000..0596a184
--- /dev/null
+++ b/src/screens/profile/CommentReactionScreen.tsx
@@ -0,0 +1,69 @@
+import {RouteProp, useNavigation} from '@react-navigation/native';
+import React, {useEffect, useState} from 'react';
+import {Alert, ScrollView, StyleSheet, View} from 'react-native';
+import {SafeAreaView} from 'react-native-safe-area-context';
+import {Friends} from '../../components';
+import {ERROR_SOMETHING_WENT_WRONG} from '../../constants/strings';
+import {MainStackParams} from '../../routes/main';
+import {getUsersReactedToAComment} from '../../services';
+import {ProfilePreviewType} from '../../types';
+import {HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+
+type CommentReactionScreenRouteProps = RouteProp<
+ MainStackParams,
+ 'CommentReactionScreen'
+>;
+
+interface CommentReactionScreenProps {
+ route: CommentReactionScreenRouteProps;
+}
+
+const CommentReactionScreen: React.FC<CommentReactionScreenProps> = ({
+ route,
+}) => {
+ const navigation = useNavigation();
+ const {comment, screenType} = route.params;
+ const [users, setUsers] = useState<ProfilePreviewType[]>([]);
+
+ useEffect(() => {
+ const loadUsers = async () => {
+ const response = await getUsersReactedToAComment(comment);
+ if (response.length !== 0) {
+ setUsers(response);
+ } else {
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG);
+ navigation.goBack();
+ }
+ };
+ loadUsers();
+ }, []);
+
+ return (
+ <View style={styles.background}>
+ <SafeAreaView>
+ <ScrollView style={styles.container}>
+ <Friends
+ result={users}
+ screenType={screenType}
+ userId={undefined}
+ hideFriendsFeature
+ />
+ </ScrollView>
+ </SafeAreaView>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ background: {
+ backgroundColor: 'white',
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ },
+ container: {
+ marginTop: HeaderHeight,
+ height: SCREEN_HEIGHT - HeaderHeight,
+ },
+});
+
+export default CommentReactionScreen;
diff --git a/src/screens/profile/FriendsListScreen.tsx b/src/screens/profile/FriendsListScreen.tsx
index 1d10bc86..73364f3b 100644
--- a/src/screens/profile/FriendsListScreen.tsx
+++ b/src/screens/profile/FriendsListScreen.tsx
@@ -36,10 +36,6 @@ const FriendsListScreen: React.FC<FriendsListScreenProps> = ({route}) => {
};
const styles = StyleSheet.create({
- background: {
- backgroundColor: 'white',
- height: '100%',
- },
backButton: {
marginLeft: 10,
},
diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx
index bf07ae30..4b332b56 100644
--- a/src/screens/profile/MomentCommentsScreen.tsx
+++ b/src/screens/profile/MomentCommentsScreen.tsx
@@ -102,7 +102,7 @@ const styles = StyleSheet.create({
},
body: {
marginTop: HeaderHeight,
- width: SCREEN_WIDTH * 0.9,
+ width: SCREEN_WIDTH * 0.95,
height: SCREEN_HEIGHT * 0.8,
paddingTop: '3%',
},
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index d5377494..ea0505a2 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -12,3 +12,4 @@ export {default as PrivacyScreen} from './PrivacyScreen';
export {default as AccountType} from './AccountType';
export {default as CategorySelection} from './CategorySelection';
export {default as CreateCustomCategory} from './CreateCustomCategory';
+export {default as CommentReactionScreen} from './CommentReactionScreen';
diff --git a/src/services/CommentService.ts b/src/services/CommentService.ts
index 2faaa8db..6d71ce9c 100644
--- a/src/services/CommentService.ts
+++ b/src/services/CommentService.ts
@@ -1,8 +1,18 @@
import AsyncStorage from '@react-native-community/async-storage';
import {Alert} from 'react-native';
-import {COMMENTS_ENDPOINT, COMMENT_THREAD_ENDPOINT} from '../constants';
+import {
+ COMMENTS_ENDPOINT,
+ COMMENT_REACTIONS_ENDPOINT,
+ COMMENT_REACTIONS_REPLY_ENDPOINT,
+ COMMENT_THREAD_ENDPOINT,
+} from '../constants';
import {ERROR_FAILED_TO_COMMENT} from '../constants/strings';
-import {CommentType} from '../types';
+import {
+ CommentThreadType,
+ CommentType,
+ ProfilePreviewType,
+ ReactionOptionsType,
+} from '../types';
export const getComments = async (
objectId: string,
@@ -116,3 +126,84 @@ export const deleteComment = async (id: string, isThread: boolean) => {
return false;
}
};
+
+/**
+ * If `user_reaction` is undefined, we like the comment, if `user_reaction`
+ * is defined, we unlike the comment.
+ *
+ * @param comment the comment object that contains `user_reaction` (or not)
+ * @returns
+ */
+export const handleLikeUnlikeComment = async (
+ comment: CommentType | CommentThreadType,
+ liked: boolean,
+) => {
+ try {
+ const isReply = 'parent_comment' in comment;
+ const token = await AsyncStorage.getItem('token');
+ let url = isReply
+ ? COMMENT_REACTIONS_REPLY_ENDPOINT
+ : COMMENT_REACTIONS_ENDPOINT;
+ if (liked) {
+ // unlike a comment
+ url += `${comment.comment_id}/?reaction_type=LIKE`;
+ const response = await fetch(url, {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ return response.status === 200;
+ } else {
+ // like a comment
+ const form = new FormData();
+ form.append('comment_id', comment.comment_id);
+ form.append('reaction_type', ReactionOptionsType.Like);
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
+ },
+ body: form,
+ });
+ return response.status === 200;
+ }
+ } catch (error) {
+ console.log('Unable to like/unlike a comment');
+ console.error(error);
+ }
+};
+
+export const getUsersReactedToAComment = async (
+ comment: CommentType | CommentThreadType,
+) => {
+ try {
+ const isReply = 'parent_comment' in comment;
+ const token = await AsyncStorage.getItem('token');
+ let url = isReply
+ ? COMMENT_REACTIONS_REPLY_ENDPOINT
+ : COMMENT_REACTIONS_ENDPOINT;
+ url += `?comment_id=${comment.comment_id}`;
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ const typedResponse: {
+ reaction: ReactionOptionsType;
+ user_list: ProfilePreviewType[];
+ }[] = await response.json();
+ for (const obj of typedResponse) {
+ if (obj.reaction === ReactionOptionsType.Like) {
+ return obj.user_list;
+ }
+ }
+ return [];
+ } catch (error) {
+ console.log('Unable to fetch list of users whom reacted to a comment');
+ console.error(error);
+ }
+ return [];
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index 00501d49..e9975529 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -122,6 +122,8 @@ export interface CommentBaseType {
comment: string;
date_created: string;
commenter: ProfilePreviewType;
+ user_reaction: ReactionType | null;
+ reaction_count: number;
}
export interface CommentType extends CommentBaseType {
@@ -316,3 +318,12 @@ export type ChatContextType = {
>;
chatClient: StreamChat;
};
+
+export enum ReactionOptionsType {
+ Like = 'LIKE',
+}
+
+export type ReactionType = {
+ id: string;
+ type: ReactionOptionsType;
+};