|
@@ -1,17 +1,27 @@
|
|
|
-import React, { FC, useCallback, useEffect, useState } from 'react';
|
|
|
-import { View, Text, Image, TouchableOpacity, Linking, Dimensions, FlatList } from 'react-native';
|
|
|
-import ImageView from 'better-react-native-image-viewing';
|
|
|
+import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
|
|
+import {
|
|
|
+ View,
|
|
|
+ Text,
|
|
|
+ Image,
|
|
|
+ TouchableOpacity,
|
|
|
+ Linking,
|
|
|
+ Dimensions,
|
|
|
+ FlatList,
|
|
|
+ Platform,
|
|
|
+ ActivityIndicator
|
|
|
+} from 'react-native';
|
|
|
import { styles } from './styles';
|
|
|
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
|
|
|
import { Colors } from 'src/theme';
|
|
|
-import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
|
|
|
+import FileViewer from 'react-native-file-viewer';
|
|
|
+import * as FileSystem from 'expo-file-system';
|
|
|
+import * as DocumentPicker from 'react-native-document-picker';
|
|
|
+import * as ImagePicker from 'expo-image-picker';
|
|
|
|
|
|
import { ScrollView } from 'react-native-gesture-handler';
|
|
|
import { NAVIGATION_PAGES } from 'src/types';
|
|
|
-import { API_HOST } from 'src/constants';
|
|
|
+import { API_HOST, APP_VERSION } from 'src/constants';
|
|
|
import { StoreType, storage } from 'src/storage';
|
|
|
-import { ButtonVariants } from 'src/types/components';
|
|
|
-import formatNumber from '../../TravelsScreen/utils/formatNumber';
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
|
|
|
import ChevronLeft from 'assets/icons/chevron-left.svg';
|
|
@@ -19,6 +29,7 @@ import MapSvg from 'assets/icons/travels-screens/map-location.svg';
|
|
|
import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
|
|
|
import ShareIcon from 'assets/icons/share.svg';
|
|
|
import GigtIcon from 'assets/icons/events/gift.svg';
|
|
|
+import CalendarCrossedIcon from 'assets/icons/events/calendar-crossed.svg';
|
|
|
import CalendarCheckIcon from 'assets/icons/events/calendar-check.svg';
|
|
|
import CalendarIcon from 'assets/icons/events/calendar-solid.svg';
|
|
|
import EarthIcon from 'assets/icons/travels-section/earth.svg';
|
|
@@ -27,65 +38,360 @@ import LocationIcon from 'assets/icons/bottom-navigation/map.svg';
|
|
|
import FileIcon from 'assets/icons/events/file-solid.svg';
|
|
|
import ImageIcon from 'assets/icons/events/image.svg';
|
|
|
import { getFontSize } from 'src/utils';
|
|
|
+import {
|
|
|
+ EventAttachments,
|
|
|
+ EventData,
|
|
|
+ EventPhotos,
|
|
|
+ useGetEventQuery,
|
|
|
+ usePostDeleteFileMutation,
|
|
|
+ usePostEventAddFileMutation,
|
|
|
+ usePostJoinEventMutation,
|
|
|
+ usePostUnjoinEventMutation,
|
|
|
+ usePostUploadPhotoMutation,
|
|
|
+ usePostUploadTempFileMutation
|
|
|
+} from '@api/events';
|
|
|
+import { Input, Loading, WarningModal } from 'src/components';
|
|
|
+import moment from 'moment';
|
|
|
+import { renderSpotsText } from '../EventsScreen/utils';
|
|
|
|
|
|
-const testData = [
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- title: 'Attachment 1',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- title: 'Attachment 2 dgfgs sgfbfsg tsrgrsz sgb',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- title: 'Attachment 3',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 4,
|
|
|
- title: 'Attachment 4',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 5,
|
|
|
- title: 'Attachment 5',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 6,
|
|
|
- title: 'Attachment 6',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 7,
|
|
|
- title: 'Attachment 7',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 8,
|
|
|
- title: 'Attachment 8',
|
|
|
- url: ''
|
|
|
- },
|
|
|
- {
|
|
|
- id: 9,
|
|
|
- title: 'Attachment 9',
|
|
|
- url: ''
|
|
|
- }
|
|
|
-];
|
|
|
+import { useWindowDimensions } from 'react-native';
|
|
|
+import RenderHtml from 'react-native-render-html';
|
|
|
+import { PhotoItem } from './PhotoItem';
|
|
|
+import Share from 'react-native-share';
|
|
|
+import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
|
|
|
+import { Dropdown } from 'react-native-searchable-dropdown-kj';
|
|
|
+import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
|
|
+import Tooltip from 'react-native-walkthrough-tooltip';
|
|
|
+
|
|
|
+type TempFile = {
|
|
|
+ filetype: string;
|
|
|
+ name: string;
|
|
|
+ temp_name: string;
|
|
|
+ isSending: boolean;
|
|
|
+ type: 1 | 2 | 3;
|
|
|
+ description: string;
|
|
|
+};
|
|
|
|
|
|
const fileWidth = Dimensions.get('window').width / 5;
|
|
|
|
|
|
-const EventScreen = () => {
|
|
|
+const EventScreen = ({ route }: { route: any }) => {
|
|
|
+ const eventUrl = route.params?.url;
|
|
|
const token = (storage.get('token', StoreType.STRING) as string) ?? null;
|
|
|
+ const currentUserId = (storage.get('uid', StoreType.NUMBER) as number) ?? 0;
|
|
|
const navigation = useNavigation();
|
|
|
- const [isLoading, setIsLoading] = useState(true);
|
|
|
+ const { width: windowWidth } = useWindowDimensions();
|
|
|
+ const contentWidth = windowWidth * 0.9;
|
|
|
+ const scrollViewRef = useRef<ScrollView>(null);
|
|
|
+
|
|
|
+ const { data, refetch } = useGetEventQuery(token, eventUrl, true);
|
|
|
+ const { mutateAsync: joinEvent } = usePostJoinEventMutation();
|
|
|
+ const { mutateAsync: unjoinEvent } = usePostUnjoinEventMutation();
|
|
|
+ const { mutateAsync: uploadTempFile } = usePostUploadTempFileMutation();
|
|
|
+ const { mutateAsync: saveFile } = usePostEventAddFileMutation();
|
|
|
+ const { mutateAsync: deleteFile } = usePostDeleteFileMutation();
|
|
|
+ const { mutateAsync: uploadPhoto } = usePostUploadPhotoMutation();
|
|
|
+
|
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
|
+ const [tooltipUser, setTooltipUser] = useState<number | null>(null);
|
|
|
+
|
|
|
+ const [event, setEvent] = useState<EventData | null>(null);
|
|
|
+ const [registrationInfo, setRegistrationInfo] = useState<{ color: string; name: string } | null>(
|
|
|
+ null
|
|
|
+ );
|
|
|
+ const [filteredParticipants, setFilteredParticipants] = useState<EventData['participants_data']>(
|
|
|
+ []
|
|
|
+ );
|
|
|
+ const [maxVisibleParticipants, setMaxVisibleParticipants] = useState(0);
|
|
|
+ const [maxVisibleParticipantsWithGap, setMaxVisibleParticipantsWithGap] = useState(0);
|
|
|
+ const [joined, setJoined] = useState<0 | 1>(0);
|
|
|
+
|
|
|
+ const [uploadProgress, setUploadProgress] = useState<{ [key: string]: number }>({});
|
|
|
+ const [myTempFiles, setMyTempFiles] = useState<TempFile[]>([]);
|
|
|
+ const [myFiles, setMyFiles] = useState<EventAttachments[]>([]);
|
|
|
+ const [photos, setPhotos] = useState<(EventPhotos & { isSending?: boolean })[]>([]);
|
|
|
+
|
|
|
+ const [modalInfo, setModalInfo] = useState({
|
|
|
+ visible: false,
|
|
|
+ title: '',
|
|
|
+ message: '',
|
|
|
+ buttonTitle: 'OK',
|
|
|
+ action: () => {}
|
|
|
+ });
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (data && data.data) {
|
|
|
+ setEvent(data.data);
|
|
|
+ setJoined(data.data.joined);
|
|
|
+
|
|
|
+ setMyFiles(data.data.files ?? []);
|
|
|
+ setPhotos(data.data.photos);
|
|
|
+
|
|
|
+ const partisipantsWidth = contentWidth / 2;
|
|
|
+ setMaxVisibleParticipants(Math.floor(partisipantsWidth / 20));
|
|
|
+ setMaxVisibleParticipantsWithGap(Math.floor(partisipantsWidth / 32));
|
|
|
+ setFilteredParticipants(data.data.participants_data.filter((p) => p.avatar));
|
|
|
+
|
|
|
+ setRegistrationInfo(() => {
|
|
|
+ if (data.data.full) {
|
|
|
+ return {
|
|
|
+ color: Colors.LIGHT_GRAY,
|
|
|
+ name: 'FULL'
|
|
|
+ };
|
|
|
+ } else if (data.data.settings.type === 2) {
|
|
|
+ return {
|
|
|
+ color: Colors.ORANGE,
|
|
|
+ name: 'TOUR'
|
|
|
+ };
|
|
|
+ } else if (data.data.settings.type === 3) {
|
|
|
+ return {
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ name: 'CONF'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, [data]);
|
|
|
+
|
|
|
+ useFocusEffect(() => {
|
|
|
+ refetch();
|
|
|
+ });
|
|
|
+
|
|
|
+ const handlePreviewDocument = useCallback(async (url: string, fileName: string) => {
|
|
|
+ try {
|
|
|
+ const dirExist = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR);
|
|
|
+ if (!dirExist.exists) {
|
|
|
+ await FileSystem.makeDirectoryAsync(CACHED_ATTACHMENTS_DIR, { intermediates: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ const fileUri = `${CACHED_ATTACHMENTS_DIR}${fileName}`;
|
|
|
+
|
|
|
+ const fileExists = await FileSystem.getInfoAsync(fileUri);
|
|
|
+ if (fileExists.exists && fileExists.size > 1024) {
|
|
|
+ await FileViewer.open(fileUri, {
|
|
|
+ showOpenWithDialog: true,
|
|
|
+ showAppsSuggestions: true
|
|
|
+ });
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const downloadResumable = FileSystem.createDownloadResumable(
|
|
|
+ url,
|
|
|
+ fileUri,
|
|
|
+ {},
|
|
|
+ (downloadProgress) => {
|
|
|
+ const progress =
|
|
|
+ downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite;
|
|
|
+ setUploadProgress((prev) => ({ ...prev, [fileName]: progress * 100 }));
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const { uri: localUri } = await FileSystem.downloadAsync(url, fileUri, {
|
|
|
+ headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
|
|
|
+ });
|
|
|
+
|
|
|
+ await FileViewer.open(localUri, {
|
|
|
+ showOpenWithDialog: true,
|
|
|
+ showAppsSuggestions: true
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error previewing document:', error);
|
|
|
+ } finally {
|
|
|
+ setUploadProgress((prev) => {
|
|
|
+ const newProgress = { ...prev };
|
|
|
+ delete newProgress[fileName];
|
|
|
+ return newProgress;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const handleUploadFile = useCallback(async () => {
|
|
|
+ try {
|
|
|
+ const response = await DocumentPicker.pick({
|
|
|
+ type: [DocumentPicker.types.allFiles],
|
|
|
+ allowMultiSelection: true
|
|
|
+ });
|
|
|
+
|
|
|
+ for (const res of response) {
|
|
|
+ let file: any = {
|
|
|
+ uri: res.uri,
|
|
|
+ name: res.name,
|
|
|
+ type: res.type
|
|
|
+ };
|
|
|
+
|
|
|
+ if ((file.name && !file.name.includes('.')) || !file.type) {
|
|
|
+ file = {
|
|
|
+ ...file,
|
|
|
+ type: file.type || 'application/octet-stream'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ await uploadTempFile(
|
|
|
+ {
|
|
|
+ token,
|
|
|
+ file,
|
|
|
+ onUploadProgress: (progressEvent) => {
|
|
|
+ // if (progressEvent.lengthComputable) {
|
|
|
+ // const progress = Math.round(
|
|
|
+ // (progressEvent.loaded / (progressEvent.total ?? 100)) * 100
|
|
|
+ // );
|
|
|
+ // setUploadProgress((prev) => ({ ...prev, [file!.uri]: progress }));
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ onSuccess: (result) => {
|
|
|
+ setMyTempFiles((prev) => [
|
|
|
+ { ...result, type: 1, description: '', isSending: false },
|
|
|
+ ...prev
|
|
|
+ ]);
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ console.error('Upload error:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } catch {}
|
|
|
+ }, [token]);
|
|
|
+
|
|
|
+ const handleUploadPhoto = useCallback(async () => {
|
|
|
+ if (!event) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
|
+ if (!perm.granted) {
|
|
|
+ console.warn('Permission for gallery not granted');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await ImagePicker.launchImageLibraryAsync({
|
|
|
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
|
+ allowsMultipleSelection: true,
|
|
|
+ quality: 1,
|
|
|
+ selectionLimit: 4
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!result.canceled && result.assets) {
|
|
|
+ const files = result.assets.map((asset) => ({
|
|
|
+ uri: asset.uri,
|
|
|
+ type: asset.mimeType ?? 'image',
|
|
|
+ name: asset.uri ? (asset.uri.split('/').pop() as string) : 'image'
|
|
|
+ }));
|
|
|
+
|
|
|
+ for (const file of files) {
|
|
|
+ const staticPhoto: any = {
|
|
|
+ id: new Date().getTime(),
|
|
|
+ filetype: file.type,
|
|
|
+ uid: +currentUserId,
|
|
|
+ name: '',
|
|
|
+ avatar: null,
|
|
|
+ isSending: true,
|
|
|
+ preview: 1,
|
|
|
+ data: 1
|
|
|
+ };
|
|
|
+ setPhotos((prev) => [staticPhoto, ...prev]);
|
|
|
+
|
|
|
+ await uploadPhoto(
|
|
|
+ {
|
|
|
+ token,
|
|
|
+ event_id: event.id,
|
|
|
+ file,
|
|
|
+ onUploadProgress: (progressEvent) => {
|
|
|
+ // if (progressEvent.lengthComputable) {
|
|
|
+ // const progress = Math.round(
|
|
|
+ // (progressEvent.loaded / (progressEvent.total ?? 100)) * 100
|
|
|
+ // );
|
|
|
+ // setUploadProgress((prev) => ({ ...prev, [file!.uri]: progress }));
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ onSuccess: (result) => {
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: () => {
|
|
|
+ refetch();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch {}
|
|
|
+ }, [token, event]);
|
|
|
+
|
|
|
+ if (!event) return <Loading />;
|
|
|
+
|
|
|
+ const handleShare = async () => {
|
|
|
+ if (!event) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // TO DO
|
|
|
+ const uri = `${API_HOST}/event/${eventUrl}`;
|
|
|
+ if (uri) {
|
|
|
+ await Share.open({ url: uri });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error sharing the event url:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleJoinEvent = async () => {
|
|
|
+ if (!event.settings.free) {
|
|
|
+ // TO DO
|
|
|
+ Linking.openURL(API_HOST + event.settings.shop_url);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await joinEvent(
|
|
|
+ { token, id: event.id },
|
|
|
+ {
|
|
|
+ onSuccess: () => {
|
|
|
+ setJoined(1);
|
|
|
+ refetch();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUnjoinEvent = async () => {
|
|
|
+ await unjoinEvent(
|
|
|
+ { token, id: event.id },
|
|
|
+ {
|
|
|
+ onSuccess: () => {
|
|
|
+ setJoined(0);
|
|
|
+ refetch();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDeleteFile = async (file: EventAttachments) => {
|
|
|
+ setModalInfo({
|
|
|
+ visible: true,
|
|
|
+ title: 'Delete file',
|
|
|
+ buttonTitle: 'Delete',
|
|
|
+ message: `Are you sure you want to delete this file?`,
|
|
|
+ action: async () => {
|
|
|
+ await deleteFile(
|
|
|
+ {
|
|
|
+ token,
|
|
|
+ id: file.id,
|
|
|
+ event_id: event.id
|
|
|
+ },
|
|
|
+ {
|
|
|
+ onSuccess: () => {
|
|
|
+ setMyFiles(myFiles.filter((f) => f.id !== file.id));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderItem = ({ item, index }: { item: EventAttachments; index: number }) => {
|
|
|
+ const totalItems = event.attachments.length;
|
|
|
|
|
|
- const renderItem = ({ item, index }) => {
|
|
|
- const totalItems = testData.length;
|
|
|
if (!isExpanded && index === 7 && totalItems > 8) {
|
|
|
return (
|
|
|
<TouchableOpacity
|
|
@@ -94,7 +400,9 @@ const EventScreen = () => {
|
|
|
alignItems: 'center',
|
|
|
gap: 4
|
|
|
}}
|
|
|
- onPress={() => setIsExpanded(true)}
|
|
|
+ onPress={() => {
|
|
|
+ setIsExpanded(true);
|
|
|
+ }}
|
|
|
>
|
|
|
<View
|
|
|
style={{
|
|
@@ -119,9 +427,12 @@ const EventScreen = () => {
|
|
|
alignItems: 'center',
|
|
|
gap: 4
|
|
|
}}
|
|
|
- onPress={() => {
|
|
|
- // Linking.openURL(item.url);
|
|
|
- }}
|
|
|
+ onPress={() =>
|
|
|
+ handlePreviewDocument(
|
|
|
+ API_HOST + '/webapi/events/get-attachment/' + event.id + '/' + item.id,
|
|
|
+ event.id + '-' + item.filename
|
|
|
+ )
|
|
|
+ }
|
|
|
>
|
|
|
<View
|
|
|
style={{
|
|
@@ -133,395 +444,785 @@ const EventScreen = () => {
|
|
|
width: fileWidth
|
|
|
}}
|
|
|
>
|
|
|
- <MaterialCommunityIcons name="file" size={36} color={Colors.DARK_BLUE} />
|
|
|
+ <MaterialCommunityIcons
|
|
|
+ name={item.filetype.startsWith('image') ? 'image' : 'file'}
|
|
|
+ size={36}
|
|
|
+ color={Colors.DARK_BLUE}
|
|
|
+ />
|
|
|
</View>
|
|
|
<Text
|
|
|
style={{ fontSize: 12, fontWeight: '600', color: Colors.DARK_BLUE }}
|
|
|
numberOfLines={2}
|
|
|
>
|
|
|
- {item.title}
|
|
|
+ {item.filename}
|
|
|
</Text>
|
|
|
</TouchableOpacity>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- return (
|
|
|
- <View style={styles.container}>
|
|
|
+ const renderItemFile = ({ item, index }: { item: EventAttachments; index: number }) => {
|
|
|
+ return (
|
|
|
<TouchableOpacity
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ backgroundColor: Colors.FILL_LIGHT,
|
|
|
+ flex: 1,
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ paddingVertical: 12,
|
|
|
+ borderRadius: 8
|
|
|
+ }}
|
|
|
onPress={() => {
|
|
|
- navigation.goBack();
|
|
|
+ handlePreviewDocument(
|
|
|
+ `${API_HOST}/webapi/events/get-file/${event.id}/${item.id}/?token=${token}`,
|
|
|
+ item.filename
|
|
|
+ );
|
|
|
}}
|
|
|
- style={styles.backButton}
|
|
|
- >
|
|
|
- <View style={styles.chevronWrapper}>
|
|
|
- <ChevronLeft fill={Colors.WHITE} />
|
|
|
- </View>
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <ScrollView
|
|
|
- contentContainerStyle={{}}
|
|
|
- nestedScrollEnabled={true}
|
|
|
- showsVerticalScrollIndicator={false}
|
|
|
>
|
|
|
- <View style={styles.emptyImage}>
|
|
|
- <Image
|
|
|
- source={require('../../../../../assets/images/logo-opacity.png')}
|
|
|
- style={{ width: 100, height: 100 }}
|
|
|
- />
|
|
|
- <Text style={styles.emptyImageText}>No image available at this location</Text>
|
|
|
- </View>
|
|
|
-
|
|
|
- <TouchableOpacity
|
|
|
- onPress={() => {
|
|
|
- // route.params?.isTravelsScreen || route.params?.isProfileScreen
|
|
|
- // ? navigation.dispatch(
|
|
|
- // CommonActions.reset({
|
|
|
- // index: 1,
|
|
|
- // routes: [
|
|
|
- // {
|
|
|
- // name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
|
|
|
- // state: {
|
|
|
- // routes: [
|
|
|
- // {
|
|
|
- // name: NAVIGATION_PAGES.MAP_TAB,
|
|
|
- // params: { id: regionId, type: type === 'nm' ? 'regions' : 'places' }
|
|
|
- // }
|
|
|
- // ]
|
|
|
- // }
|
|
|
- // }
|
|
|
- // ]
|
|
|
- // })
|
|
|
- // )
|
|
|
- // : navigation.goBack();
|
|
|
- }}
|
|
|
- style={styles.goToMapBtn}
|
|
|
- >
|
|
|
- <View style={styles.chevronWrapper}>
|
|
|
- <MapSvg fill={Colors.WHITE} />
|
|
|
+ <View style={{ gap: 8, flex: 3.5 }}>
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1 }}>
|
|
|
+ <FileIcon fill={Colors.DARK_BLUE} height={18} />
|
|
|
+ <Text style={{ color: Colors.DARK_BLUE, fontSize: 13, fontWeight: '600' }}>
|
|
|
+ {item.filename}
|
|
|
+ </Text>
|
|
|
</View>
|
|
|
- </TouchableOpacity>
|
|
|
|
|
|
- <View
|
|
|
- style={{
|
|
|
- position: 'absolute',
|
|
|
- width: 71,
|
|
|
- height: 31,
|
|
|
- top: 170,
|
|
|
- left: 0,
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center',
|
|
|
- zIndex: 2,
|
|
|
- backgroundColor: Colors.ORANGE,
|
|
|
- borderTopRightRadius: 4,
|
|
|
- borderBottomRightRadius: 4,
|
|
|
- paddingRight: 10,
|
|
|
- paddingLeft: 16
|
|
|
- }}
|
|
|
- >
|
|
|
- <Text
|
|
|
- style={{
|
|
|
- textTransform: 'uppercase',
|
|
|
- fontSize: getFontSize(16),
|
|
|
- fontWeight: '700',
|
|
|
- color: Colors.WHITE
|
|
|
- }}
|
|
|
- >
|
|
|
- Tour
|
|
|
+ <Text style={{ color: Colors.TEXT_GRAY, fontSize: 12, fontWeight: '500' }}>
|
|
|
+ {item.type === 1 ? 'passport' : item.type === 2 ? 'disclaimer' : 'other'}
|
|
|
</Text>
|
|
|
- </View>
|
|
|
|
|
|
- <View style={styles.wrapper}>
|
|
|
- <View style={styles.nameContainer}>
|
|
|
- <Text style={styles.title}>Meeting in San Clemente</Text>
|
|
|
- <TouchableOpacity
|
|
|
- onPress={() => {}}
|
|
|
- style={{
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
- paddingLeft: 8
|
|
|
- }}
|
|
|
- >
|
|
|
- <ShareIcon
|
|
|
- width={20}
|
|
|
- height={20}
|
|
|
- fill={Colors.DARK_BLUE}
|
|
|
- style={{ alignSelf: 'center' }}
|
|
|
- />
|
|
|
- </TouchableOpacity>
|
|
|
- </View>
|
|
|
+ {item.description ? (
|
|
|
+ <Text style={{ color: Colors.DARK_BLUE, fontSize: 13, fontWeight: '500' }}>
|
|
|
+ {item.description}
|
|
|
+ </Text>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
|
|
|
+ <View style={{ flex: 1 }}>
|
|
|
<TouchableOpacity
|
|
|
style={{
|
|
|
flexDirection: 'row',
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
+ gap: 8,
|
|
|
+ backgroundColor: Colors.RED,
|
|
|
paddingVertical: 8,
|
|
|
- paddingHorizontal: 12,
|
|
|
- borderRadius: 20,
|
|
|
- backgroundColor: Colors.ORANGE,
|
|
|
- gap: 6
|
|
|
+ paddingHorizontal: 4,
|
|
|
+ borderRadius: 20
|
|
|
}}
|
|
|
+ onPress={() => handleDeleteFile(item)}
|
|
|
>
|
|
|
- {true ? (
|
|
|
- <CalendarCheckIcon fill={Colors.WHITE} width={16} height={16} />
|
|
|
- ) : (
|
|
|
- <GigtIcon fill={Colors.WHITE} width={16} height={16} />
|
|
|
- )}
|
|
|
<Text
|
|
|
style={{
|
|
|
color: Colors.WHITE,
|
|
|
- fontSize: getFontSize(14),
|
|
|
- fontFamily: 'montserrat-700',
|
|
|
- textTransform: 'uppercase'
|
|
|
+ fontSize: getFontSize(13),
|
|
|
+ fontWeight: '700'
|
|
|
}}
|
|
|
>
|
|
|
- Join - 2000$
|
|
|
+ Delete
|
|
|
</Text>
|
|
|
</TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ );
|
|
|
+ };
|
|
|
|
|
|
- <View style={styles.divider} />
|
|
|
+ const formatEventDate = (event: EventData) => {
|
|
|
+ if (event.settings.date_from && event.settings.date_to) {
|
|
|
+ return `${moment(event.settings.date_from, 'YYYY-MM-DD').format('DD MMMM YYYY')} - ${moment(event.settings.date_to, 'YYYY-MM-DD').format('DD MMMM YYYY')}`;
|
|
|
+ } else {
|
|
|
+ return moment(event.settings.date, 'YYYY-MM-DD').format('DD MMMM YYYY');
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
- <CalendarIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
- <Text
|
|
|
- style={{
|
|
|
- fontSize: getFontSize(12),
|
|
|
- fontWeight: '600',
|
|
|
- color: Colors.DARK_BLUE,
|
|
|
- flex: 1
|
|
|
- }}
|
|
|
- >
|
|
|
- 13 January, 2025
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
+ const handleSaveFile = async (file: TempFile) => {
|
|
|
+ setMyTempFiles(() =>
|
|
|
+ myTempFiles.map((f) => (f.temp_name === file.temp_name ? { ...f, isSending: true } : f))
|
|
|
+ );
|
|
|
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
- <EarthIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
- <Text
|
|
|
- style={{
|
|
|
- fontSize: getFontSize(12),
|
|
|
- fontWeight: '600',
|
|
|
- color: Colors.DARK_BLUE,
|
|
|
- flex: 1
|
|
|
- }}
|
|
|
- >
|
|
|
- San Clemente, USA
|
|
|
- </Text>
|
|
|
+ await saveFile(
|
|
|
+ {
|
|
|
+ token,
|
|
|
+ event_id: event.id,
|
|
|
+ type: file.type,
|
|
|
+ description: file.description,
|
|
|
+ filetype: file.filetype,
|
|
|
+ filename: file.name,
|
|
|
+ temp_filename: file.temp_name
|
|
|
+ },
|
|
|
+ {
|
|
|
+ onSuccess: () => {
|
|
|
+ setMyTempFiles(myTempFiles.filter((f) => f.temp_name !== file.temp_name));
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: () => {
|
|
|
+ setMyTempFiles(() =>
|
|
|
+ myTempFiles.map((f) =>
|
|
|
+ f.temp_name === file.temp_name ? { ...f, isSending: false } : f
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const staticImgUrl =
|
|
|
+ event.type === 2
|
|
|
+ ? '/static/img/events/trip.webp'
|
|
|
+ : event.type === 3
|
|
|
+ ? '/static/img/events/conference.webp'
|
|
|
+ : '/static/img/events/meeting.webp';
|
|
|
+ const photoUrl = event.photo_available
|
|
|
+ ? API_HOST + '/webapi/events/get-main-photo/' + event.id
|
|
|
+ : API_HOST + staticImgUrl;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View style={styles.container}>
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => {
|
|
|
+ navigation.goBack();
|
|
|
+ }}
|
|
|
+ style={styles.backButton}
|
|
|
+ >
|
|
|
+ <View style={styles.chevronWrapper}>
|
|
|
+ <ChevronLeft fill={Colors.WHITE} />
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ <KeyboardAwareScrollView>
|
|
|
+ <ScrollView
|
|
|
+ ref={scrollViewRef}
|
|
|
+ contentContainerStyle={{ minHeight: '100%' }}
|
|
|
+ nestedScrollEnabled={true}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ removeClippedSubviews={false}
|
|
|
+ >
|
|
|
+ <Image source={{ uri: photoUrl }} style={{ width: '100%', height: 220 }} />
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => {
|
|
|
+ // navigation.dispatch(
|
|
|
+ // CommonActions.reset({
|
|
|
+ // index: 1,
|
|
|
+ // routes: [
|
|
|
+ // {
|
|
|
+ // name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
|
|
|
+ // state: {
|
|
|
+ // routes: [
|
|
|
+ // {
|
|
|
+ // name: NAVIGATION_PAGES.MAP_TAB,
|
|
|
+ // params: { id: regionId, type: type === 'nm' ? 'regions' : 'places' }
|
|
|
+ // }
|
|
|
+ // ]
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // ]
|
|
|
+ // })
|
|
|
+ // )
|
|
|
+ }}
|
|
|
+ style={styles.goToMapBtn}
|
|
|
+ >
|
|
|
+ <View style={styles.chevronWrapper}>
|
|
|
+ <MapSvg fill={Colors.WHITE} />
|
|
|
</View>
|
|
|
- </View>
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
- <LocationIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
+ {registrationInfo && (
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ position: 'absolute',
|
|
|
+ width: 71,
|
|
|
+ height: 31,
|
|
|
+ top: 170,
|
|
|
+ left: 0,
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ zIndex: 2,
|
|
|
+ backgroundColor: registrationInfo.color,
|
|
|
+ borderTopRightRadius: 4,
|
|
|
+ borderBottomRightRadius: 4,
|
|
|
+ paddingRight: 10,
|
|
|
+ paddingLeft: 16
|
|
|
+ }}
|
|
|
+ >
|
|
|
<Text
|
|
|
style={{
|
|
|
- fontSize: getFontSize(12),
|
|
|
- fontWeight: '600',
|
|
|
- color: Colors.DARK_BLUE,
|
|
|
- flex: 1
|
|
|
+ textTransform: 'uppercase',
|
|
|
+ fontSize: getFontSize(16),
|
|
|
+ fontWeight: '700',
|
|
|
+ color: Colors.WHITE
|
|
|
}}
|
|
|
>
|
|
|
- 301 N El Camino Real
|
|
|
+ {registrationInfo.name}
|
|
|
</Text>
|
|
|
</View>
|
|
|
+ )}
|
|
|
|
|
|
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
- <NomadsIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
- <Text
|
|
|
+ <View style={styles.wrapper}>
|
|
|
+ <View style={styles.nameContainer}>
|
|
|
+ <Text style={styles.title}>{event.settings.name}</Text>
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={handleShare}
|
|
|
style={{
|
|
|
- fontSize: getFontSize(12),
|
|
|
- fontWeight: '600',
|
|
|
- color: Colors.DARK_BLUE,
|
|
|
- flex: 1
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ paddingLeft: 8,
|
|
|
+ marginLeft: 4
|
|
|
}}
|
|
|
>
|
|
|
- <Text style={{ fontWeight: '700' }}>164/300</Text> Spots Available
|
|
|
- </Text>
|
|
|
+ <ShareIcon
|
|
|
+ width={20}
|
|
|
+ height={20}
|
|
|
+ fill={Colors.DARK_BLUE}
|
|
|
+ style={{ alignSelf: 'center' }}
|
|
|
+ />
|
|
|
+ </TouchableOpacity>
|
|
|
</View>
|
|
|
- </View>
|
|
|
-
|
|
|
- <View style={styles.stats}>
|
|
|
- {true ? (
|
|
|
- <View style={{ gap: 8, flex: 1 }}>
|
|
|
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Host</Text>
|
|
|
- <TouchableOpacity
|
|
|
- style={[styles.statItem, { justifyContent: 'flex-start' }]}
|
|
|
- onPress={() =>
|
|
|
- navigation.navigate(
|
|
|
- ...([
|
|
|
- NAVIGATION_PAGES.USERS_LIST,
|
|
|
- {
|
|
|
- id: 720,
|
|
|
- isFromHere: true,
|
|
|
- type: 'nm'
|
|
|
- }
|
|
|
- ] as never)
|
|
|
- )
|
|
|
- }
|
|
|
- >
|
|
|
- <View style={styles.userImageContainer}>
|
|
|
- <Image
|
|
|
- source={{ uri: API_HOST }}
|
|
|
- style={[styles.userImage, { marginLeft: 0 }]}
|
|
|
- />
|
|
|
- <View style={{ justifyContent: 'space-between' }}>
|
|
|
- <Text
|
|
|
- style={{
|
|
|
- fontFamily: 'montserrat-700',
|
|
|
- fontSize: 12,
|
|
|
- color: Colors.DARK_BLUE
|
|
|
- }}
|
|
|
- >
|
|
|
- Harry Mitsidis
|
|
|
- </Text>
|
|
|
- <Text style={{ fontWeight: '600', fontSize: 12, color: Colors.DARK_BLUE }}>
|
|
|
- NM: <Text style={{ fontFamily: 'montserrat-700' }}>1284</Text> / UN:{' '}
|
|
|
- <Text style={{ fontFamily: 'montserrat-700' }}>193</Text>
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
- </View>
|
|
|
- </TouchableOpacity>
|
|
|
- </View>
|
|
|
- ) : (
|
|
|
- <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
|
|
|
- )}
|
|
|
-
|
|
|
- {true ? (
|
|
|
- <View style={{ gap: 8, flex: 1 }}>
|
|
|
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Participants</Text>
|
|
|
-
|
|
|
- <TouchableOpacity
|
|
|
- style={[styles.statItem, { justifyContent: 'flex-end' }]}
|
|
|
- onPress={() =>
|
|
|
- navigation.navigate(
|
|
|
- ...([
|
|
|
- NAVIGATION_PAGES.USERS_LIST,
|
|
|
- {
|
|
|
- id: 720,
|
|
|
- isFromHere: false,
|
|
|
- type: 'nm'
|
|
|
- }
|
|
|
- ] as never)
|
|
|
- )
|
|
|
- }
|
|
|
- >
|
|
|
- <View style={styles.userImageContainer}>
|
|
|
- {/* {data?.data.users_who_visited_region &&
|
|
|
- data?.data.users_who_visited_region.length > 0 &&
|
|
|
- data?.data.users_who_visited_region?.map((user, index) => (
|
|
|
- <Image
|
|
|
- key={index}
|
|
|
- source={{ uri: API_HOST + user }}
|
|
|
- style={[styles.userImage]}
|
|
|
- />
|
|
|
- ))} */}
|
|
|
- <View style={styles.userCountContainer}>
|
|
|
- <Text style={styles.userCount}>
|
|
|
- {/* {formatNumber(data?.data.users_who_visited_region_count ?? 0)} */}
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
- </View>
|
|
|
- </TouchableOpacity>
|
|
|
- </View>
|
|
|
- ) : (
|
|
|
- <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
|
|
|
- )}
|
|
|
- </View>
|
|
|
-
|
|
|
- <View style={[styles.divider]} />
|
|
|
-
|
|
|
- <View style={{ gap: 16 }}>
|
|
|
- <Text style={styles.travelSeriesTitle}>Details</Text>
|
|
|
- <Text style={{ fontWeight: '600', fontSize: 13, color: Colors.DARK_BLUE }}>
|
|
|
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
|
|
|
- has been the industry's standard dummy text ever since the 1500s, when an unknown
|
|
|
- printer took a galley of type and scrambled it to make a type specimen book. It has
|
|
|
- survived not only five centuries, but also the leap into electronic typesetting,
|
|
|
- remaining essentially unchanged. It was popularised in the 1960s with the release of
|
|
|
- Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
|
|
|
- publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
|
|
|
- <View style={{ gap: 16 }}>
|
|
|
- <View
|
|
|
- style={{
|
|
|
- flexDirection: 'row',
|
|
|
- justifyContent: 'space-between',
|
|
|
- alignItems: 'center'
|
|
|
- }}
|
|
|
- >
|
|
|
- <Text style={styles.travelSeriesTitle}>Attachments</Text>
|
|
|
+ {joined ? (
|
|
|
<TouchableOpacity
|
|
|
style={{
|
|
|
flexDirection: 'row',
|
|
|
- backgroundColor: Colors.ORANGE,
|
|
|
- gap: 6,
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
- paddingVertical: 7,
|
|
|
+ paddingVertical: 8,
|
|
|
paddingHorizontal: 12,
|
|
|
- borderRadius: 20
|
|
|
+ borderRadius: 20,
|
|
|
+ backgroundColor: Colors.WHITE,
|
|
|
+ gap: 6,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: Colors.DARK_BLUE
|
|
|
}}
|
|
|
+ onPress={handleUnjoinEvent}
|
|
|
>
|
|
|
- <Text style={{ fontSize: getFontSize(13), fontWeight: '700', color: Colors.WHITE }}>
|
|
|
- Add
|
|
|
+ <CalendarCrossedIcon fill={Colors.DARK_BLUE} width={16} height={16} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ fontSize: getFontSize(14),
|
|
|
+ fontFamily: 'montserrat-700'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Cancel
|
|
|
</Text>
|
|
|
- <FileIcon fill={Colors.WHITE} height={18} />
|
|
|
</TouchableOpacity>
|
|
|
- </View>
|
|
|
- <FlatList
|
|
|
- data={isExpanded ? testData : testData.slice(0, 8)}
|
|
|
- renderItem={renderItem}
|
|
|
- style={{ flex: 1 }}
|
|
|
- keyExtractor={(item) => item.id.toString()}
|
|
|
- numColumns={4}
|
|
|
- columnWrapperStyle={{
|
|
|
- justifyContent: 'flex-start',
|
|
|
- gap: 12
|
|
|
- }}
|
|
|
- contentContainerStyle={{
|
|
|
- gap: 8,
|
|
|
- alignSelf: 'center'
|
|
|
- }}
|
|
|
- showsVerticalScrollIndicator={false}
|
|
|
- scrollEnabled={false}
|
|
|
- />
|
|
|
- </View>
|
|
|
-
|
|
|
- <View style={{ gap: 16 }}>
|
|
|
- <View
|
|
|
- style={{
|
|
|
- flexDirection: 'row',
|
|
|
- justifyContent: 'space-between',
|
|
|
- alignItems: 'center'
|
|
|
- }}
|
|
|
- >
|
|
|
- <Text style={styles.travelSeriesTitle}>Photos</Text>
|
|
|
+ ) : !event.full ? (
|
|
|
<TouchableOpacity
|
|
|
style={{
|
|
|
flexDirection: 'row',
|
|
|
- backgroundColor: Colors.ORANGE,
|
|
|
- gap: 6,
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
- paddingVertical: 7,
|
|
|
+ paddingVertical: 8,
|
|
|
paddingHorizontal: 12,
|
|
|
- borderRadius: 20
|
|
|
+ borderRadius: 20,
|
|
|
+ backgroundColor: Colors.ORANGE,
|
|
|
+ gap: 6,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: Colors.ORANGE
|
|
|
}}
|
|
|
+ onPress={handleJoinEvent}
|
|
|
>
|
|
|
- <Text style={{ fontSize: getFontSize(13), fontWeight: '700', color: Colors.WHITE }}>
|
|
|
- Add
|
|
|
- </Text>
|
|
|
- <ImageIcon fill={Colors.WHITE} width={18} />
|
|
|
+ {event.settings.free ? (
|
|
|
+ <>
|
|
|
+ <GigtIcon fill={Colors.WHITE} width={16} height={16} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ color: Colors.WHITE,
|
|
|
+ fontSize: getFontSize(14),
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
+ textTransform: 'uppercase'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Join - free
|
|
|
+ </Text>
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <CalendarCheckIcon fill={Colors.WHITE} width={16} height={16} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ color: Colors.WHITE,
|
|
|
+ fontSize: getFontSize(14),
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
+ textTransform: 'uppercase'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Join - {event.settings.price}
|
|
|
+ </Text>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</TouchableOpacity>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ <View style={styles.divider} />
|
|
|
+
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
+ <CalendarIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(12),
|
|
|
+ fontWeight: '600',
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ flex: 1
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {formatEventDate(event)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
+ <EarthIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(12),
|
|
|
+ fontWeight: '600',
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ flex: 1
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {event.settings.address1}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
|
+ {event.settings.address2 && (
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
+ <LocationIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(12),
|
|
|
+ fontWeight: '600',
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ flex: 1
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {event.settings.address2}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {event.settings.registrations_info !== 1 && (
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
|
|
|
+ <NomadsIcon fill={Colors.DARK_BLUE} height={20} width={20} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(12),
|
|
|
+ fontWeight: '600',
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ flex: 1
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {renderSpotsText(event)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.stats}>
|
|
|
+ {event.settings.host_data ? (
|
|
|
+ <View style={{ gap: 8, flex: 1 }}>
|
|
|
+ <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Host</Text>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.statItem, { justifyContent: 'flex-start' }]}
|
|
|
+ onPress={() =>
|
|
|
+ navigation.navigate(
|
|
|
+ ...([
|
|
|
+ NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
|
|
|
+ {
|
|
|
+ userId: event.settings.host_profile
|
|
|
+ }
|
|
|
+ ] as never)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ disabled={!event.settings.host_profile}
|
|
|
+ >
|
|
|
+ <View style={styles.userImageContainer}>
|
|
|
+ {event.settings.host_data.avatar ? (
|
|
|
+ <Image
|
|
|
+ source={{
|
|
|
+ uri: API_HOST + '/img/avatars/' + event.settings.host_data.avatar
|
|
|
+ }}
|
|
|
+ style={[styles.userImage, { marginLeft: 0 }]}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ <View style={{ justifyContent: 'space-between' }}>
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
+ fontSize: 12,
|
|
|
+ color: Colors.DARK_BLUE
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {event.settings.host_data.first_name} {event.settings.host_data.last_name}
|
|
|
+ </Text>
|
|
|
+ <Text style={{ fontWeight: '600', fontSize: 12, color: Colors.DARK_BLUE }}>
|
|
|
+ NM:{' '}
|
|
|
+ <Text style={{ fontFamily: 'montserrat-700' }}>
|
|
|
+ {event.settings.host_data.nm}
|
|
|
+ </Text>{' '}
|
|
|
+ / UN:{' '}
|
|
|
+ <Text style={{ fontFamily: 'montserrat-700' }}>
|
|
|
+ {event.settings.host_data.un}
|
|
|
+ </Text>
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {filteredParticipants.length > 0 ? (
|
|
|
+ <View style={{ gap: 8, flex: 1 }}>
|
|
|
+ <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>Participants</Text>
|
|
|
+
|
|
|
+ <View style={[styles.statItem, { justifyContent: 'flex-end' }]}>
|
|
|
+ <View style={styles.userImageContainer}>
|
|
|
+ {(filteredParticipants.length > maxVisibleParticipants
|
|
|
+ ? filteredParticipants.slice(0, maxVisibleParticipants - 1)
|
|
|
+ : filteredParticipants
|
|
|
+ ).map((user, index) => (
|
|
|
+ <Tooltip
|
|
|
+ isVisible={tooltipUser === index}
|
|
|
+ content={
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => {
|
|
|
+ setTooltipUser(null);
|
|
|
+ navigation.navigate(
|
|
|
+ ...([
|
|
|
+ NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
|
|
|
+ { userId: user.uid }
|
|
|
+ ] as never)
|
|
|
+ );
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Text>{user.name}</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ }
|
|
|
+ contentStyle={{ backgroundColor: Colors.FILL_LIGHT }}
|
|
|
+ placement="top"
|
|
|
+ onClose={() => setTooltipUser(null)}
|
|
|
+ key={index}
|
|
|
+ backgroundColor="transparent"
|
|
|
+ >
|
|
|
+ <TouchableOpacity onPress={() => setTooltipUser(index)}>
|
|
|
+ <Image
|
|
|
+ key={index}
|
|
|
+ source={{ uri: API_HOST + user.avatar }}
|
|
|
+ style={[
|
|
|
+ styles.userImage,
|
|
|
+ filteredParticipants.length > maxVisibleParticipantsWithGap
|
|
|
+ ? { marginLeft: -10 }
|
|
|
+ : {}
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </TouchableOpacity>
|
|
|
+ </Tooltip>
|
|
|
+ ))}
|
|
|
+ {maxVisibleParticipants < filteredParticipants.length ? (
|
|
|
+ <View style={styles.userCountContainer}>
|
|
|
+ <Text style={styles.userCount}>{event.participants}</Text>
|
|
|
+ </View>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
|
|
|
+ )}
|
|
|
</View>
|
|
|
+
|
|
|
+ <View style={[styles.divider]} />
|
|
|
+
|
|
|
+ {event.settings.details && event.settings.details.length ? (
|
|
|
+ <View style={{ gap: 8 }}>
|
|
|
+ <Text style={styles.travelSeriesTitle}>Details</Text>
|
|
|
+ <WebDisplay html={event.settings.details} />
|
|
|
+ </View>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ {event.attachments.length > 0 ? (
|
|
|
+ <View style={{ gap: 16 }}>
|
|
|
+ <Text style={styles.travelSeriesTitle}>Attachments</Text>
|
|
|
+ <FlatList
|
|
|
+ data={isExpanded ? event.attachments : event.attachments.slice(0, 8)}
|
|
|
+ renderItem={renderItem}
|
|
|
+ keyExtractor={(item) => item.id.toString()}
|
|
|
+ numColumns={4}
|
|
|
+ columnWrapperStyle={{
|
|
|
+ justifyContent: 'flex-start',
|
|
|
+ gap: 12
|
|
|
+ }}
|
|
|
+ contentContainerStyle={{
|
|
|
+ gap: 8,
|
|
|
+ alignSelf: 'center'
|
|
|
+ }}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ scrollEnabled={false}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ {(photos && photos.length) ||
|
|
|
+ (event.joined && event.settings.participants_can_add_photos) ? (
|
|
|
+ <View style={{ gap: 16 }}>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ alignItems: 'center'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Text style={styles.travelSeriesTitle}>Photos</Text>
|
|
|
+ {event.settings.participants_can_add_photos && event.joined ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ backgroundColor: Colors.ORANGE,
|
|
|
+ gap: 6,
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ paddingVertical: 7,
|
|
|
+ paddingHorizontal: 12,
|
|
|
+ borderRadius: 20
|
|
|
+ }}
|
|
|
+ onPress={handleUploadPhoto}
|
|
|
+ >
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(13),
|
|
|
+ fontWeight: '700',
|
|
|
+ color: Colors.WHITE
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Add
|
|
|
+ </Text>
|
|
|
+ <ImageIcon fill={Colors.WHITE} width={18} height={18} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+ {photos && photos.length > 0 ? (
|
|
|
+ <PhotoItem photos={photos} eventId={event.id} photosLeft={event.photos_left} />
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ {(event.files && event.files.length) ||
|
|
|
+ (event.joined && event.settings.participants_can_add_files) ? (
|
|
|
+ <View style={{ gap: 16, paddingBottom: 16 }}>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ alignItems: 'center'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Text style={styles.travelSeriesTitle}>My files</Text>
|
|
|
+ {event.settings.participants_can_add_files && event.joined ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ backgroundColor: Colors.ORANGE,
|
|
|
+ gap: 6,
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ paddingVertical: 7,
|
|
|
+ paddingHorizontal: 12,
|
|
|
+ borderRadius: 20
|
|
|
+ }}
|
|
|
+ onPress={handleUploadFile}
|
|
|
+ >
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: getFontSize(13),
|
|
|
+ fontWeight: '700',
|
|
|
+ color: Colors.WHITE
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Add
|
|
|
+ </Text>
|
|
|
+ <FileIcon fill={Colors.WHITE} height={18} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {myTempFiles && myTempFiles.length
|
|
|
+ ? myTempFiles.map((file) => {
|
|
|
+ return (
|
|
|
+ <View
|
|
|
+ key={file.temp_name}
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ backgroundColor: Colors.FILL_LIGHT,
|
|
|
+ flex: 1,
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ paddingVertical: 12,
|
|
|
+ borderRadius: 8
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <View style={{ gap: 8, flex: 3 }}>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ flex: 1
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <FileIcon fill={Colors.DARK_BLUE} height={18} />
|
|
|
+ <Text
|
|
|
+ style={{ color: Colors.DARK_BLUE, fontSize: 13, fontWeight: '500' }}
|
|
|
+ >
|
|
|
+ {file.name}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <Input
|
|
|
+ height={36}
|
|
|
+ backgroundColor={Colors.WHITE}
|
|
|
+ placeholder="Add comment here"
|
|
|
+ multiline={true}
|
|
|
+ onChange={(text) => {
|
|
|
+ setMyTempFiles(() =>
|
|
|
+ myTempFiles.map((f) =>
|
|
|
+ f.temp_name === file.temp_name ? { ...f, description: text } : f
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ <Dropdown
|
|
|
+ style={{
|
|
|
+ height: 36,
|
|
|
+ backgroundColor: Colors.WHITE,
|
|
|
+ borderRadius: 4,
|
|
|
+ paddingHorizontal: 8
|
|
|
+ }}
|
|
|
+ placeholderStyle={{
|
|
|
+ fontSize: 14,
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ fontWeight: '500'
|
|
|
+ }}
|
|
|
+ selectedTextStyle={{
|
|
|
+ fontSize: 14,
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ fontWeight: '500'
|
|
|
+ }}
|
|
|
+ data={[
|
|
|
+ { label: 'passport', value: 1 },
|
|
|
+ { label: 'disclaimer', value: 2 },
|
|
|
+ { label: 'other', value: 3 }
|
|
|
+ ]}
|
|
|
+ labelField="label"
|
|
|
+ valueField="value"
|
|
|
+ value={file.type}
|
|
|
+ placeholder="First visit"
|
|
|
+ onChange={(item: { value: 1 | 2 | 3 }) => {
|
|
|
+ setMyTempFiles(() =>
|
|
|
+ myTempFiles.map((f) =>
|
|
|
+ f.temp_name === file.temp_name ? { ...f, type: item.value } : f
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }}
|
|
|
+ containerStyle={{ borderRadius: 4 }}
|
|
|
+ renderItem={(item) => (
|
|
|
+ <View style={{ paddingVertical: 12, paddingHorizontal: 16 }}>
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ fontSize: 14,
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ fontWeight: '500'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {item.label}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={{ flex: 1 }}>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ gap: 8,
|
|
|
+ backgroundColor: Colors.DARK_BLUE,
|
|
|
+ paddingVertical: 8,
|
|
|
+ paddingHorizontal: 4,
|
|
|
+ borderRadius: 20
|
|
|
+ }}
|
|
|
+ onPress={() => handleSaveFile(file)}
|
|
|
+ disabled={file.isSending}
|
|
|
+ >
|
|
|
+ {file.isSending ? (
|
|
|
+ <View>
|
|
|
+ <ActivityIndicator
|
|
|
+ size={16}
|
|
|
+ color={Colors.WHITE}
|
|
|
+ style={{ transform: 'scale(0.9)' }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ color: Colors.WHITE,
|
|
|
+ fontSize: getFontSize(13),
|
|
|
+ fontWeight: '700'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Save
|
|
|
+ </Text>
|
|
|
+ )}
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+ })
|
|
|
+ : null}
|
|
|
+ <FlatList
|
|
|
+ data={myFiles}
|
|
|
+ renderItem={renderItemFile}
|
|
|
+ keyExtractor={(item) => item.id.toString()}
|
|
|
+ contentContainerStyle={{ gap: 8 }}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ scrollEnabled={false}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ ) : null}
|
|
|
</View>
|
|
|
- </View>
|
|
|
- </ScrollView>
|
|
|
+ </ScrollView>
|
|
|
+ </KeyboardAwareScrollView>
|
|
|
+ <WarningModal
|
|
|
+ type={'delete'}
|
|
|
+ isVisible={modalInfo.visible}
|
|
|
+ buttonTitle={modalInfo.buttonTitle}
|
|
|
+ message={modalInfo.message}
|
|
|
+ action={modalInfo.action}
|
|
|
+ onClose={() => setModalInfo({ ...modalInfo, visible: false })}
|
|
|
+ title={modalInfo.title}
|
|
|
+ />
|
|
|
</View>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+const WebDisplay = React.memo(function WebDisplay({ html }: { html: string }) {
|
|
|
+ const { width: windowWidth } = useWindowDimensions();
|
|
|
+ const contentWidth = windowWidth * 0.9;
|
|
|
+
|
|
|
+ const processedHtml = React.useMemo(() => {
|
|
|
+ return html.replace(/src="\/img\//g, `src="${API_HOST}/img/`);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ return <RenderHtml contentWidth={contentWidth} source={{ html: processedHtml }} />;
|
|
|
+});
|
|
|
+
|
|
|
export default EventScreen;
|