소스 검색

visited series modal

Viktoriia 1 일 전
부모
커밋
a90f476f9d

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

@@ -10,3 +10,4 @@ export * from './use-post-save-notification-token';
 export * from './use-post-get-update';
 export * from './use-post-authenticate';
 export * from './use-post-update-email';
+export * from './use-post-get-profile-series-data';

+ 22 - 0
src/modules/api/user/queries/use-post-get-profile-series-data.tsx

@@ -0,0 +1,22 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { userQueryKeys } from '../user-query-keys';
+import { type PostGetProfileSeriesDataReturn, userApi } from '../user-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostGetProfileSeriesData = (
+  token: string,
+  user_id: number,
+  series_id: number,
+  enabled: boolean
+) => {
+  return useQuery<PostGetProfileSeriesDataReturn, BaseAxiosError>({
+    queryKey: userQueryKeys.getProfileSeriesData(user_id, series_id),
+    queryFn: async () => {
+      const response = await userApi.getProfileSeriesData(token, user_id, series_id);
+      return response.data;
+    },
+    enabled
+  });
+};

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

@@ -362,6 +362,19 @@ export interface PostGetUpdateReturn<T extends string> extends ResponseType {
   data: PostGetUpdateReturnData<T>;
 }
 
+export interface PostGetProfileSeriesDataReturn extends ResponseType {
+  series_name: string;
+  series_icon: string;
+  items_grouped: {
+    name: string;
+    icon: string | null;
+    items: {
+      item_id: number;
+      name: string;
+    }[];
+  }[];
+}
+
 export const userApi = {
   getProfileData: (token: string) =>
     request.postForm<PostGetProfileData>(API.GET_USER_SETTINGS_DATA, { token }),
@@ -414,5 +427,11 @@ export const userApi = {
   autenticate: (token: string, profile_id: number) =>
     request.postForm<ResponseType>(API.AUTHENTICATE, { token, profile_id }),
   updateEmail: (token: string, email: string) =>
-    request.postForm<ResponseType>(API.UPDATE_EMAIL, { token, email })
+    request.postForm<ResponseType>(API.UPDATE_EMAIL, { token, email }),
+  getProfileSeriesData: (token: string, user_id: number, series_id: number) =>
+    request.postForm<PostGetProfileSeriesDataReturn>(API.GET_PROFILE_SERIES_DATA, {
+      token,
+      user_id,
+      series_id
+    })
 };

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

@@ -11,4 +11,6 @@ export const userQueryKeys = {
   getUpdate: (userId: number, type: string) => ['getUpdate', userId, type] as const,
   autenticate: () => ['autenticate'] as const,
   updateEmail: () => ['updateEmail'] as const,
+  getProfileSeriesData: (userId: number, seriesId: number) =>
+    ['getProfileSeriesData', userId, seriesId] as const
 };

+ 36 - 18
src/screens/InAppScreens/ProfileScreen/Components/PersonalInfo.tsx

@@ -33,6 +33,9 @@ import { usePostFriendRequestMutation, usePostUpdateFriendStatusMutation } from
 import FriendStatus from './FriendStatus';
 import UpdatesRenderer from '../UpdatesRenderer';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
+import { SheetManager } from 'react-native-actions-sheet';
+import SeriesModal from './SeriesModal';
+import { useSubscription } from 'src/screens/OfflineMapsScreen/useSubscription';
 
 type PersonalInfoProps = {
   data: {
@@ -102,6 +105,8 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
     []
   );
 
+  const { isPremium } = useSubscription();
+
   const [showMoreSeries, setShowMoreSeries] = useState(false);
   const [type, setType] = useState<string>('nm');
   const [isModalVisible, setIsModalVisible] = useState(false);
@@ -509,24 +514,35 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
         {data.series?.length > 0 && (
           <InfoItem showMore={showMoreSeries} inline={true} title={'SERIES'}>
             {data.series?.slice(0, showMoreSeries ? data.series.length : 8).map((data, index) => (
-              <Tooltip
-                isVisible={toolTipVisible === index}
-                content={<Text style={{}}>{data.name}</Text>}
-                contentStyle={{ backgroundColor: Colors.FILL_LIGHT }}
-                placement="top"
-                onClose={() => setToolTipVisible(null)}
+              <TouchableOpacity
                 key={index}
-                backgroundColor="transparent"
-                allowChildInteraction={false}
+                style={styles.series}
+                onPress={() => {
+                  if (isPremium) {
+                    SheetManager.show('visited-series-modal', {
+                      payload: {
+                        userId,
+                        seriesId: data.id
+                      } as any
+                    });
+                  } else {
+                    setModalInfo({
+                      isVisible: true,
+                      type: 'success',
+                      action: () => {},
+                      message:
+                        'This feature is available to Premium users. Premium account settings can be managed on our website.',
+                      title: 'Premium Feature'
+                    });
+                  }
+                }}
               >
-                <TouchableOpacity style={styles.series} onPress={() => setToolTipVisible(index)}>
-                  <Image
-                    source={{ uri: API_HOST + data.app_icon }}
-                    style={{ width: 28, height: 28 }}
-                  />
-                  <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
-                </TouchableOpacity>
-              </Tooltip>
+                <Image
+                  source={{ uri: API_HOST + data.app_icon }}
+                  style={{ width: 28, height: 28 }}
+                />
+                <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
+              </TouchableOpacity>
             ))}
           </InfoItem>
         )}
@@ -593,9 +609,11 @@ export const PersonalInfo: FC<PersonalInfoProps> = ({
         isVisible={modalInfo.isVisible}
         message={modalInfo.message}
         action={modalInfo.action}
-        onClose={() => setModalInfo({ ...modalInfo, isVisible: false })}
-        title=""
+        onClose={() => setModalInfo({ ...modalInfo, isVisible: false, title: '' })}
+        title={modalInfo.title}
       />
+
+      <SeriesModal />
     </View>
   );
 };

+ 202 - 0
src/screens/InAppScreens/ProfileScreen/Components/SeriesModal.tsx

@@ -0,0 +1,202 @@
+import React, { useState } from 'react';
+import { View, Text, Image, StyleSheet, TouchableOpacity, Platform } from 'react-native';
+import { FlashList } from '@shopify/flash-list';
+import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
+import { Colors } from 'src/theme';
+import { API_HOST } from 'src/constants';
+import { getFontSize } from 'src/utils';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { Loading } from 'src/components';
+import { usePostGetProfileSeriesData } from '@api/user';
+import { storage, StoreType } from 'src/storage';
+
+import CheckSvg from 'assets/icons/travels-screens/circle-check.svg';
+import CloseSVG from 'assets/icons/close.svg';
+
+const SeriesModal = () => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const insets = useSafeAreaInsets();
+
+  const [userId, setUserId] = useState<number | null>(null);
+  const [seriesId, setSeriesId] = useState<number | null>(null);
+
+  const { data: seriesData } = usePostGetProfileSeriesData(
+    token,
+    userId ?? 0,
+    seriesId ?? 0,
+    seriesId && userId ? true : false
+  );
+
+  const handleSheetOpen = (payload: any) => {
+    setSeriesId(payload?.seriesId);
+    setUserId(payload?.userId);
+  };
+
+  const renderCountryItem = ({ item: country }: { item: any }) => {
+    const visibleItems = country.items;
+
+    return (
+      <View style={styles.countryContainer}>
+        {country.icon || country.name ? (
+          <>
+            <View style={styles.countryHeader}>
+              {country.icon ? (
+                <Image source={{ uri: `${API_HOST}${country.icon}` }} style={styles.flagIcon} />
+              ) : null}
+              {country.name ? <Text style={styles.countryName}>{country.name}</Text> : null}
+            </View>
+
+            <View style={styles.divider}></View>
+          </>
+        ) : null}
+
+        <View style={styles.sitesContainer}>
+          {visibleItems.map((site: { item_id: number; name: string }, index: number) => (
+            <View key={index} style={styles.siteItem}>
+              <View style={styles.checkIcon}>
+                <CheckSvg width={16} height={16} fill={Colors.DARK_BLUE} />
+              </View>
+              <Text style={styles.siteName}>{site.name}</Text>
+            </View>
+          ))}
+        </View>
+      </View>
+    );
+  };
+
+  const renderHeader = () => (
+    <View style={styles.headerContainer}>
+      <Image source={{ uri: `${API_HOST}${seriesData?.series_icon}` }} style={styles.seriesIcon} />
+      <Text style={styles.seriesTitle}>{seriesData?.series_name}</Text>
+    </View>
+  );
+
+  return (
+    <ActionSheet
+      id="visited-series-modal"
+      gestureEnabled={Platform.OS === 'ios'}
+      onBeforeShow={(sheetRef) => {
+        const payload = sheetRef || null;
+        handleSheetOpen(payload);
+      }}
+      containerStyle={styles.sheetContainer}
+      defaultOverlayOpacity={0.5}
+    >
+      {Platform.OS === 'android' && (
+        <TouchableOpacity
+          onPress={() => SheetManager.hide('visited-series-modal')}
+          style={{ marginLeft: 14, padding: 4 }}
+          hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
+        >
+          <CloseSVG />
+        </TouchableOpacity>
+      )}
+
+      {seriesData ? (
+        <View style={[styles.container, { paddingBottom: insets.bottom }]}>
+          {renderHeader()}
+          <View style={styles.listContainer}>
+            <FlashList
+              keyExtractor={(item, index) => item.name + index.toString()}
+              data={seriesData.items_grouped}
+              renderItem={renderCountryItem}
+              estimatedItemSize={200}
+              showsVerticalScrollIndicator={false}
+              ItemSeparatorComponent={() => <View style={styles.separator} />}
+            />
+          </View>
+        </View>
+      ) : (
+        <View style={[styles.container, { paddingBottom: insets.bottom, paddingTop: 0 }]}>
+          <Loading />
+        </View>
+      )}
+    </ActionSheet>
+  );
+};
+
+const styles = StyleSheet.create({
+  sheetContainer: {
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    height: '92%'
+  },
+  container: {
+    backgroundColor: 'white',
+    paddingHorizontal: 16,
+    paddingTop: 8,
+    height: '100%'
+  },
+  listContainer: {
+    flex: 1,
+    minHeight: 300
+  },
+  headerContainer: {
+    alignItems: 'center',
+    paddingBottom: 12,
+    gap: 8
+  },
+  seriesIcon: {
+    width: 32,
+    height: 32
+  },
+  seriesTitle: {
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700',
+    color: Colors.DARK_BLUE,
+    textAlign: 'center'
+  },
+  countryContainer: {
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8,
+    padding: 12,
+    gap: 8
+  },
+  countryHeader: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 8
+  },
+  flagIcon: {
+    width: 24,
+    height: 24,
+    borderRadius: 12,
+    borderWidth: 0.5,
+    borderColor: Colors.BORDER_LIGHT
+  },
+  countryName: {
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700',
+    color: Colors.DARK_BLUE
+  },
+  divider: {
+    height: 1,
+    backgroundColor: '#D3D3D3'
+  },
+  sitesContainer: {
+    gap: 8
+  },
+  siteItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 8
+  },
+  checkIcon: {
+    width: 24,
+    height: 24,
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  siteName: {
+    fontSize: getFontSize(12),
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    flex: 1
+  },
+  separator: {
+    height: 8
+  }
+});
+
+export default SeriesModal;

+ 4 - 2
src/types/api.ts

@@ -208,7 +208,8 @@ export enum API_ENDPOINT {
   PREMIUM_STATUS = 'premium-status',
   GET_MAP_DATA_FOR_REGION = 'get-map-data-for-region',
   GET_LAST_MAP_UPDATE_DATE = 'get-last-map-update-date',
-  GET_SIZE_FOR_BOUNDING_BOX = 'get-size-for-bounding-box'
+  GET_SIZE_FOR_BOUNDING_BOX = 'get-size-for-bounding-box',
+  GET_PROFILE_SERIES_DATA = 'get-profile-series-data'
 }
 
 export enum API {
@@ -389,7 +390,8 @@ export enum API {
   GET_PREMIUM_STATUS = `${API_ROUTE.APP}/${API_ENDPOINT.PREMIUM_STATUS}`,
   GET_MAP_DATA_FOR_REGION = `${API_ROUTE.MAPS}/${API_ENDPOINT.GET_MAP_DATA_FOR_REGION}`,
   GET_LAST_MAP_UPDATE_DATE = `${API_ROUTE.MAPS}/${API_ENDPOINT.GET_LAST_MAP_UPDATE_DATE}`,
-  GET_SIZE_FOR_BOUNDING_BOX = `${API_ROUTE.MAPS}/${API_ENDPOINT.GET_SIZE_FOR_BOUNDING_BOX}`
+  GET_SIZE_FOR_BOUNDING_BOX = `${API_ROUTE.MAPS}/${API_ENDPOINT.GET_SIZE_FOR_BOUNDING_BOX}`,
+  GET_PROFILE_SERIES_DATA = `${API_ROUTE.PROFILE}/${API_ENDPOINT.GET_PROFILE_SERIES_DATA}`
 }
 
 export type BaseAxiosError = AxiosError;