Browse Source

authentication

Viktoriia 6 months ago
parent
commit
4fb5516346

+ 10 - 0
assets/icons/authenticate-user.svg

@@ -0,0 +1,10 @@
+<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4450_40479)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.65 0.400391C7.18604 0.400391 8.45 1.66435 8.45 3.20039C8.45 4.73643 7.18604 6.00039 5.65 6.00039C4.11396 6.00039 2.85 4.73643 2.85 3.20039C2.85 1.66435 4.11396 0.400391 5.65 0.400391ZM4.65031 7.05039H6.64969C7.10031 7.05039 7.53344 7.12695 7.93594 7.26695C7.81562 7.64101 7.75 8.03695 7.75 8.45039C7.75 9.75195 8.39531 10.9026 9.38625 11.6004H1.39969C1.04094 11.6004 0.75 11.3095 0.75 10.9507C0.75 8.79601 2.49563 7.05039 4.65031 7.05039ZM8.45 8.45039C8.45 6.72235 9.87196 5.30039 11.6 5.30039C13.328 5.30039 14.75 6.72235 14.75 8.45039C14.75 10.1784 13.328 11.6004 11.6 11.6004C9.87196 11.6004 8.45 10.1784 8.45 8.45039ZM13.552 7.84178L13.5502 7.83997C13.7217 7.67033 13.7198 7.39308 13.5502 7.22163C13.3806 7.05019 13.1033 7.05019 12.9319 7.22163L10.9073 9.24806L10.05 8.3908C9.88041 8.21935 9.60316 8.22116 9.43171 8.3908C9.26027 8.56042 9.26027 8.83766 9.43171 9.00912L10.599 10.1765C10.7687 10.3479 11.0459 10.3479 11.2174 10.1765L13.552 7.84178Z" fill="#ED9334"/>
+</g>
+<defs>
+<clipPath id="clip0_4450_40479">
+<rect width="14" height="11.2" fill="white" transform="translate(0.75 0.400391)"/>
+</clipPath>
+</defs>
+</svg>

+ 29 - 0
src/components/WarningModal/index.tsx

@@ -11,6 +11,7 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { ButtonVariants } from 'src/types/components';
 import { Button } from '../Button';
 import AddFriendSvg from '../../../assets/icons/user-plus.svg';
+import AuthIcon from 'assets/icons/authenticate-user.svg';
 
 export const WarningModal = ({
   isVisible,
@@ -159,6 +160,30 @@ export const WarningModal = ({
           borderColor: Colors.DARK_BLUE
         }
       ]
+    },
+    authenticate: {
+      message,
+      buttons: [
+        {
+          text: 'No',
+          textColor: Colors.DARK_BLUE,
+          color: Colors.WHITE,
+          action: () => {
+            onClose();
+          },
+          borderColor: Colors.DARK_BLUE
+        },
+        {
+          text: 'Yes',
+          textColor: Colors.WHITE,
+          color: Colors.DARK_BLUE,
+          action: () => {
+            onClose();
+            action && action();
+          },
+          borderColor: Colors.DARK_BLUE
+        }
+      ]
     }
   };
 
@@ -178,6 +203,10 @@ export const WarningModal = ({
               <View style={{ marginBottom: 16 }}>
                 <AddFriendSvg fill={Colors.ORANGE} width={40} height={40} />
               </View>
+            ) : type === 'authenticate' ? (
+              <View style={{ marginBottom: 16 }}>
+                <AuthIcon fill={Colors.ORANGE} width={40} height={40} />
+              </View>
             ) : (
               <Text style={styles.modalTitle}>{title ?? 'Oops!'}</Text>
             )}

+ 1 - 0
src/modules/api/user/queries/index.ts

@@ -8,3 +8,4 @@ export * from './use-post-get-profile-updates';
 export * from './use-post-get-map-years';
 export * from './use-post-save-notification-token';
 export * from './use-post-get-update';
+export * from './use-post-authenticate';

+ 25 - 0
src/modules/api/user/queries/use-post-authenticate.tsx

@@ -0,0 +1,25 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { userQueryKeys } from '../user-query-keys';
+import { userApi } from '../user-api';
+import { ResponseType } from '@api/response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostAuthenticateMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    {
+      token: string;
+      profile_id: number;
+    },
+    ResponseType
+  >({
+    mutationKey: userQueryKeys.autenticate(),
+    mutationFn: async (variables) => {
+      const response = await userApi.autenticate(variables.token, variables.profile_id);
+      return response.data;
+    }
+  });
+};

+ 4 - 1
src/modules/api/user/user-api.tsx

@@ -195,6 +195,7 @@ export type WHS = {
 export interface PostGetProfileDataReturn extends ResponseType {
   data: {
     can_authenticate: 0 | 1;
+    can_be_authenticated: 0 | 1;
     can_see_updates: 0 | 1;
     can_send_friend_request: 0 | 1;
     friend_request_received: 0 | 1;
@@ -406,5 +407,7 @@ export const userApi = {
   getMapYears: (token: string, profile_id: number) =>
     request.postForm<PostGetMapYearsReturn>(API.GET_MAP_YEARS, { token, profile_id }),
   getUpdate: <T extends string>(token: string, profile_id: number, type: T) =>
-    request.postForm<PostGetUpdateReturn<T>>(API.GET_UPDATE, { token, profile_id, type })
+    request.postForm<PostGetUpdateReturn<T>>(API.GET_UPDATE, { token, profile_id, type }),
+  autenticate: (token: string, profile_id: number) =>
+    request.postForm<ResponseType>(API.AUTHENTICATE, { token, profile_id })
 };

+ 2 - 1
src/modules/api/user/user-query-keys.tsx

@@ -8,5 +8,6 @@ export const userQueryKeys = {
   getProfileUpdates: (userId: number) => ['getProfileUpdates', userId] as const,
   getMapYears: (userId: number) => ['getMapYears', userId] as const,
   setNotificationToken: () => ['setNotificationToken'] as const,
-  getUpdate: (userId: number, type: string) => ['getUpdate', userId, type] as const
+  getUpdate: (userId: number, type: string) => ['getUpdate', userId, type] as const,
+  autenticate: () => ['autenticate'] as const
 };

+ 123 - 102
src/screens/InAppScreens/ProfileScreen/Components/FriendStatus.tsx

@@ -13,6 +13,7 @@ type FriendStatusProps = {
   setModalInfo: (info: any) => void;
   handleSendFriendRequest: () => Promise<void>;
   handleUpdateFriendStatus: (status: number) => Promise<void>;
+  authButton?: 0 | 1;
 };
 
 const FriendStatus: FC<FriendStatusProps> = ({
@@ -20,130 +21,150 @@ const FriendStatus: FC<FriendStatusProps> = ({
   data,
   setModalInfo,
   handleSendFriendRequest,
-  handleUpdateFriendStatus
+  handleUpdateFriendStatus,
+  authButton = 0
 }) => {
   const isSmallScreen = Dimensions.get('window').width < 385;
 
   switch (status) {
     case 4:
       return (
-        <View>
-          <TouchableOpacity
-            style={[
-              styles.statusBtn,
-              {
-                paddingVertical: 11,
-                paddingHorizontal: 12,
-                backgroundColor: Colors.ORANGE
-              }
-            ]}
-            onPress={() =>
-              setModalInfo({
-                isVisible: true,
-                type: 'friend',
-                message: `Are you sure you want to send a friend request to ${data.firstName} ${data.lastName}?`,
-                action: handleSendFriendRequest,
-                title: ''
-              })
+        <TouchableOpacity
+          style={[
+            styles.statusBtn,
+            {
+              flex: 1,
+              paddingVertical: 10,
+              borderWidth: 1,
+              borderColor: Colors.ORANGE,
+              backgroundColor: Colors.ORANGE
             }
-          >
-            <Text style={[styles.statusText, { fontSize: getFontSize(15) }]}>Add friend</Text>
-          </TouchableOpacity>
-        </View>
+          ]}
+          onPress={() =>
+            setModalInfo({
+              isVisible: true,
+              type: 'friend',
+              message: `Are you sure you want to send a friend request to ${data.firstName} ${data.lastName}?`,
+              action: handleSendFriendRequest,
+              title: ''
+            })
+          }
+        >
+          <Text style={[styles.statusText, { fontSize: getFontSize(14) }]}>Add friend</Text>
+        </TouchableOpacity>
       );
     case 2:
       return (
-        <View style={styles.statusContainer}>
-          <Text style={[styles.statusTitle, isSmallScreen ? { flex: 1.5 } : {}]}>
-            Friendship pending
-          </Text>
-          <TouchableOpacity
-            style={[
-              styles.statusBtn,
-              {
-                paddingVertical: 11,
-                paddingHorizontal: 12,
-                backgroundColor: Colors.ORANGE,
-                flex: 1
-              }
-            ]}
-            onPress={() =>
-              setModalInfo({
-                isVisible: true,
-                type: 'confirm',
-                message: `Are you sure you want to accept the friend request from ${data.firstName} ${data.lastName}?`,
-                action: () => handleUpdateFriendStatus(1),
-                title: ''
-              })
-            }
-          >
-            <Text
+        <View style={{ gap: 8, flex: 1, flexDirection: authButton === 0 ? 'row' : 'column' }}>
+          {authButton === 1 ? (
+            <Text style={[styles.statusTitle, { textAlign: 'center' }]}>Friendship pending</Text>
+          ) : null}
+
+          <View style={[styles.statusContainer]}>
+            {authButton === 0 ? <Text style={[styles.statusTitle]}>Friendship pending</Text> : null}
+            <TouchableOpacity
               style={[
-                styles.statusText,
-                { fontSize: isSmallScreen ? getFontSize(14) : getFontSize(15) }
+                styles.statusBtn,
+                {
+                  paddingVertical: 10,
+                  backgroundColor: Colors.ORANGE,
+                  borderWidth: 1,
+                  flex: 1.5,
+                  borderColor: Colors.ORANGE
+                }
               ]}
+              onPress={() =>
+                setModalInfo({
+                  isVisible: true,
+                  type: 'confirm',
+                  message: `Are you sure you want to accept the friend request from ${data.firstName} ${data.lastName}?`,
+                  action: () => handleUpdateFriendStatus(1),
+                  title: ''
+                })
+              }
             >
-              Approve
-            </Text>
-          </TouchableOpacity>
+              <Text
+                style={[
+                  styles.statusText,
+                  { fontSize: isSmallScreen ? getFontSize(13) : getFontSize(14) }
+                ]}
+              >
+                Approve
+              </Text>
+            </TouchableOpacity>
 
-          <TouchableOpacity
-            style={[
-              styles.statusBtn,
-              {
-                paddingVertical: 11,
-                paddingHorizontal: 12,
-                backgroundColor: Colors.RED,
-                flex: 1
-              }
-            ]}
-            onPress={() =>
-              setModalInfo({
-                isVisible: true,
-                type: 'confirm',
-                message: `Are you sure you want to reject the friend request from ${data.firstName} ${data.lastName}?`,
-                action: () => handleUpdateFriendStatus(2),
-                title: ''
-              })
-            }
-          >
-            <Text
+            <TouchableOpacity
               style={[
-                styles.statusText,
-                { fontSize: isSmallScreen ? getFontSize(14) : getFontSize(15) }
+                styles.statusBtn,
+                {
+                  paddingVertical: 10,
+                  backgroundColor: Colors.RED,
+                  borderWidth: 1,
+                  flex: 1,
+                  borderColor: Colors.RED
+                }
               ]}
+              onPress={() =>
+                setModalInfo({
+                  isVisible: true,
+                  type: 'confirm',
+                  message: `Are you sure you want to reject the friend request from ${data.firstName} ${data.lastName}?`,
+                  action: () => handleUpdateFriendStatus(2),
+                  title: ''
+                })
+              }
             >
-              Deny
-            </Text>
-          </TouchableOpacity>
+              <Text
+                style={[
+                  styles.statusText,
+                  { fontSize: isSmallScreen ? getFontSize(13) : getFontSize(14) }
+                ]}
+              >
+                Deny
+              </Text>
+            </TouchableOpacity>
+          </View>
         </View>
       );
     case 3:
       return (
-        <View style={styles.statusContainer}>
-          <Text style={styles.statusTitle}>Friendship pending</Text>
-          <TouchableOpacity
-            style={[
-              styles.statusBtn,
-              {
-                paddingVertical: 8,
-                paddingHorizontal: 12,
-                backgroundColor: Colors.RED,
-                flex: 1
+        <View
+          style={{
+            gap: 8,
+            flex: 1,
+            flexDirection: authButton === 0 ? 'row' : 'column'
+          }}
+        >
+          {authButton === 1 ? (
+            <Text style={[styles.statusTitle, { textAlign: 'center' }]}>Friendship pending</Text>
+          ) : null}
+
+          <View style={styles.statusContainer}>
+            {authButton === 0 ? <Text style={[styles.statusTitle]}>Friendship pending</Text> : null}
+            <TouchableOpacity
+              style={[
+                styles.statusBtn,
+                {
+                  padding: 10,
+                  backgroundColor: Colors.RED,
+                  flex: 1,
+                  borderWidth: 1,
+                  borderColor: Colors.RED
+                }
+              ]}
+              onPress={() =>
+                setModalInfo({
+                  isVisible: true,
+                  type: 'confirm',
+                  message: `Are you sure you want to cancel the friend request sent to ${data.firstName} ${data.lastName}?`,
+                  action: () => handleUpdateFriendStatus(4),
+                  title: ''
+                })
               }
-            ]}
-            onPress={() =>
-              setModalInfo({
-                isVisible: true,
-                type: 'confirm',
-                message: `Are you sure you want to cancel the friend request sent to ${data.firstName} ${data.lastName}?`,
-                action: () => handleUpdateFriendStatus(4),
-                title: ''
-              })
-            }
-          >
-            <Text style={styles.statusText}>Cancel</Text>
-          </TouchableOpacity>
+            >
+              <Text style={styles.statusText}>Cancel</Text>
+            </TouchableOpacity>
+          </View>
         </View>
       );
     default:

+ 49 - 9
src/screens/InAppScreens/ProfileScreen/Components/PersonalInfo.tsx

@@ -1,10 +1,16 @@
 import { FC, useCallback, useEffect, useState } from 'react';
 import { TouchableOpacity, View, Text, Image, Dimensions } from 'react-native';
-import { Series, usePostGetProfileRegions, usePostGetUpdateQuery } from '@api/user';
+import {
+  Series,
+  usePostAuthenticateMutation,
+  usePostGetProfileRegions,
+  usePostGetUpdateQuery
+} from '@api/user';
 import { NavigationProp } from '@react-navigation/native';
 import Modal from 'react-native-modal';
 import Tooltip from 'react-native-walkthrough-tooltip';
 import RegionsRenderer from '../RegionsRenderer';
+import moment from 'moment';
 
 import CompassIcon from 'assets/icons/travels-section/compass.svg';
 import FlagsIcon from 'assets/icons/travels-section/flags.svg';
@@ -13,6 +19,7 @@ import SeriesIcon from 'assets/icons/travels-section/series.svg';
 import WHSIcon from 'assets/icons/travels-section/whs.svg';
 import ArrowIcon from 'assets/icons/next.svg';
 import UNPIcon from 'assets/icons/travels-section/unp.svg';
+import AuthIcon from 'assets/icons/authenticate-user.svg';
 
 import { styles } from './styles';
 import { InfoItem } from './InfoItem';
@@ -24,7 +31,6 @@ import { usePostFriendRequestMutation, usePostUpdateFriendStatusMutation } from
 import FriendStatus from './FriendStatus';
 import UpdatesRenderer from '../UpdatesRenderer';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
-import moment from 'moment';
 
 type PersonalInfoProps = {
   data: {
@@ -50,6 +56,8 @@ type PersonalInfoProps = {
     lastSeenRegion: string | null;
     lastSeenDate: string;
     lastSeenFlag: string | null;
+    canBeAuthenticated: 0 | 1;
+    setCanBeAuthenticated: (value: 0 | 1) => void;
   };
   updates: {
     un_visited: number;
@@ -87,6 +95,7 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
   const [tooltipUser, setTooltipUser] = useState<number | null>(null);
   const { mutateAsync: sendFriendRequest } = usePostFriendRequestMutation();
   const { mutateAsync: updateFriendStatus } = usePostUpdateFriendStatusMutation();
+  const { mutateAsync: authenticateUser } = usePostAuthenticateMutation();
   const [friendStatus, setFriendStatus] = useState<number | null>(null);
   const [modalInfo, setModalInfo] = useState({
     type: 'friend',
@@ -223,6 +232,17 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
     return date.fromNow();
   };
 
+  const handleAuthenticate = () => {
+    authenticateUser(
+      { token: token as string, profile_id: userId },
+      {
+        onSuccess: () => {
+          data.setCanBeAuthenticated(0);
+        }
+      }
+    );
+  };
+
   const screenWidth = Dimensions.get('window').width;
   const availableWidth = screenWidth * (1 - 2 * SCREEN_PADDING_PERCENT);
   const maxAvatars = Math.floor(availableWidth / (AVATAR_SIZE - AVATAR_MARGIN)) - 2;
@@ -253,13 +273,33 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
         </View>
 
         {isPublicView && token ? (
-          <FriendStatus
-            status={friendStatus}
-            data={data}
-            setModalInfo={setModalInfo}
-            handleSendFriendRequest={handleSendFriendRequest}
-            handleUpdateFriendStatus={handleUpdateFriendStatus}
-          />
+          <View style={{ gap: 8, flexDirection: 'row', alignItems: 'flex-end' }}>
+            <FriendStatus
+              status={friendStatus}
+              data={data}
+              setModalInfo={setModalInfo}
+              handleSendFriendRequest={handleSendFriendRequest}
+              handleUpdateFriendStatus={handleUpdateFriendStatus}
+              authButton={data.canBeAuthenticated}
+            />
+            {data.canBeAuthenticated ? (
+              <TouchableOpacity
+                onPress={() =>
+                  setModalInfo({
+                    isVisible: true,
+                    type: 'authenticate',
+                    message: `Please confirm that you have personally met ${data.firstName} ${data.lastName}.`,
+                    action: handleAuthenticate,
+                    title: ''
+                  })
+                }
+                style={styles.authBtn}
+              >
+                <AuthIcon />
+                <Text style={styles.authText}>Authenticate</Text>
+              </TouchableOpacity>
+            ) : null}
+          </View>
         ) : null}
 
         {data.friends.length > 0 ? (

+ 17 - 1
src/screens/InAppScreens/ProfileScreen/Components/styles.tsx

@@ -106,7 +106,7 @@ export const styles = StyleSheet.create({
     fontSize: getFontSize(10),
     fontFamily: 'redhat-600'
   },
-  statusContainer: { flexDirection: 'row', gap: 8, alignItems: 'center' },
+  statusContainer: { flexDirection: 'row', gap: 8, alignItems: 'center', flex: 1 },
   statusTitle: { flex: 2, color: Colors.DARK_BLUE, fontWeight: '600' },
   statusTitleSmall: { fontSize: 11, color: Colors.TEXT_GRAY, fontWeight: '500' },
   lastSeenFlag: {
@@ -115,5 +115,21 @@ export const styles = StyleSheet.create({
     borderRadius: 20 / 2,
     borderWidth: 0.5,
     borderColor: Colors.BORDER_LIGHT
+  },
+  authBtn: {
+    flex: 1,
+    flexDirection: 'row',
+    gap: 4,
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingVertical: 10,
+    borderRadius: 20,
+    borderColor: Colors.ORANGE,
+    borderWidth: 1
+  },
+  authText: {
+    fontSize: getFontSize(14),
+    fontFamily: 'redhat-700',
+    color: Colors.ORANGE
   }
 });

+ 5 - 1
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -71,7 +71,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
     true
   );
   const { mutateAsync: updateFriendStatus } = usePostUpdateFriendStatusMutation();
-  const [isFriend, setIsFriend] = React.useState<0 | 1>(0);
+  const [isFriend, setIsFriend] = useState<0 | 1>(0);
+  const [canBeAuthenticated, setCanBeAuthenticated] = useState<0 | 1>(0);
   const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState(false);
   const [modalState, setModalState] = useState({
     isModalVisible: false,
@@ -100,6 +101,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
 
   useEffect(() => {
     setIsFriend(userData?.data?.is_friend ?? 0);
+    setCanBeAuthenticated(userData?.data?.can_be_authenticated ?? 0);
 
     if (userData && userData?.data?.own_profile === 1) {
       const userInfo = {
@@ -441,6 +443,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
             friendRequestSent: data.friend_request_sent,
             friendRequestReceived: data.friend_request_received,
             isFriend,
+            canBeAuthenticated,
+            setCanBeAuthenticated,
             friendDbId: data.friend_db_id,
             ownProfile: data.own_profile,
             locationSharing: data.location_sharing,

+ 2 - 0
src/types/api.ts

@@ -158,6 +158,7 @@ export enum API_ENDPOINT {
   UPDATE_LOCATION = 'update-location',
   GET_USERS_LOCATION = 'get-users-location',
   IS_FEATURE_ACTIVE = 'is-feature-active',
+  AUTHENTICATE = 'authenticate',
 }
 
 export enum API {
@@ -289,6 +290,7 @@ export enum API {
   UPDATE_LOCATION = `${API_ROUTE.LOCATION}/${API_ENDPOINT.UPDATE_LOCATION}`,
   GET_USERS_LOCATION = `${API_ROUTE.LOCATION}/${API_ENDPOINT.GET_USERS_LOCATION}`,
   IS_FEATURE_ACTIVE = `${API_ROUTE.LOCATION}/${API_ENDPOINT.IS_FEATURE_ACTIVE}`,
+  AUTHENTICATE = `${API_ROUTE.USER}/${API_ENDPOINT.AUTHENTICATE}`,
 }
 
 export type BaseAxiosError = AxiosError;