123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350 |
- 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 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, APP_VERSION } from 'src/constants';
- import { StoreType, storage } from 'src/storage';
- import { MaterialCommunityIcons } from '@expo/vector-icons';
- import * as Progress from 'react-native-progress';
- import ChevronLeft from 'assets/icons/chevron-left.svg';
- 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';
- import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
- 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 { AvatarWithInitials, Input, Loading, WarningModal } from 'src/components';
- import moment from 'moment';
- import { renderSpotsText } from '../EventsScreen/utils';
- import { useWindowDimensions } from 'react-native';
- import RenderHtml, { HTMLElementModel, TNode } 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';
- import WebView from 'react-native-webview';
- 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 = ({ 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 { 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 [isUploading, setIsUploading] = useState(false);
- const [modalInfo, setModalInfo] = useState({
- visible: false,
- type: 'success',
- 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 / 22));
- setMaxVisibleParticipantsWithGap(Math.floor(partisipantsWidth / 32));
- setFilteredParticipants(data.data.participants_data);
- 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(
- useCallback(() => {
- refetch();
- }, [navigation])
- );
- 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
- });
- setIsUploading(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
- ]);
- setIsUploading(false);
- },
- onError: (error) => {
- console.error('Upload error:', error);
- }
- }
- );
- }
- } catch {
- setIsUploading(false);
- } finally {
- setIsUploading(false);
- }
- }, [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.type !== 1) {
- setModalInfo({
- visible: true,
- type: 'success',
- title: 'Success',
- buttonTitle: 'OK',
- message: `Thank you for joing, we’ll get back to you soon.`,
- action: () => {}
- });
- }
- 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,
- type: 'delete',
- 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;
- if (!isExpanded && index === 7 && totalItems > 8) {
- return (
- <TouchableOpacity
- style={{
- width: fileWidth,
- alignItems: 'center',
- gap: 4
- }}
- onPress={() => {
- setIsExpanded(true);
- }}
- >
- <View
- style={{
- backgroundColor: Colors.FILL_LIGHT,
- borderRadius: 8,
- alignItems: 'center',
- justifyContent: 'center',
- height: fileWidth,
- width: fileWidth
- }}
- >
- <MaterialCommunityIcons name="dots-horizontal" size={36} color={Colors.DARK_BLUE} />
- </View>
- </TouchableOpacity>
- );
- }
- return (
- <TouchableOpacity
- style={{
- width: fileWidth,
- alignItems: 'center',
- gap: 4
- }}
- onPress={() =>
- handlePreviewDocument(
- API_HOST + '/webapi/events/get-attachment/' + event.id + '/' + item.id,
- event.id + '-' + item.filename
- )
- }
- >
- <View
- style={{
- backgroundColor: Colors.FILL_LIGHT,
- borderRadius: 8,
- alignItems: 'center',
- justifyContent: 'center',
- height: fileWidth,
- width: fileWidth
- }}
- >
- <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.filename}
- </Text>
- </TouchableOpacity>
- );
- };
- 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={() => {
- handlePreviewDocument(
- `${API_HOST}/webapi/events/get-file/${event.id}/${item.id}/?token=${token}`,
- item.filename
- );
- }}
- >
- <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>
- <Text style={{ color: Colors.TEXT_GRAY, fontSize: 12, fontWeight: '500' }}>
- {item.type === 1 ? 'passport' : item.type === 2 ? 'disclaimer' : 'other'}
- </Text>
- {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: 4,
- borderRadius: 20
- }}
- onPress={() => handleDeleteFile(item)}
- >
- <Text
- style={{
- color: Colors.WHITE,
- fontSize: getFontSize(13),
- fontWeight: '700'
- }}
- >
- Delete
- </Text>
- </TouchableOpacity>
- </View>
- </TouchableOpacity>
- );
- };
- 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 {
- let date = moment(event.settings.date, 'YYYY-MM-DD').format('DD MMMM YYYY');
- if (event.time) {
- date += `, ${event.time}`;
- }
- return date;
- }
- };
- const handleSaveFile = async (file: TempFile) => {
- setMyTempFiles(() =>
- myTempFiles.map((f) => (f.temp_name === file.temp_name ? { ...f, isSending: true } : f))
- );
- 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 showsVerticalScrollIndicator={false}>
- <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>
- </TouchableOpacity> */}
- {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={{
- textTransform: 'uppercase',
- fontSize: getFontSize(16),
- fontWeight: '700',
- color: Colors.WHITE
- }}
- >
- {registrationInfo.name}
- </Text>
- </View>
- )}
- <View style={styles.wrapper}>
- <View style={styles.nameContainer}>
- <Text style={styles.title}>{event.settings.name}</Text>
- <TouchableOpacity
- onPress={handleShare}
- style={{
- alignItems: 'center',
- justifyContent: 'center',
- paddingLeft: 8,
- marginLeft: 4
- }}
- >
- <ShareIcon
- width={20}
- height={20}
- fill={Colors.DARK_BLUE}
- style={{ alignSelf: 'center' }}
- />
- </TouchableOpacity>
- </View>
- <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-start' }]}>
- <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)}>
- {user.avatar ? (
- <Image
- key={index}
- source={{ uri: API_HOST + user.avatar }}
- style={[
- styles.userImage,
- (filteredParticipants.length > maxVisibleParticipantsWithGap ||
- filteredParticipants.length < (event.participants ?? 0)) &&
- index !== 0
- ? { marginLeft: -10 }
- : {}
- ]}
- />
- ) : (
- <AvatarWithInitials
- text={
- user.name?.split(' ')[0][0] + (user.name?.split(' ')[1][0] ?? '')
- }
- flag={API_HOST + user?.flag}
- size={28}
- fontSize={12}
- borderColor={Colors.DARK_LIGHT}
- borderWidth={1}
- />
- )}
- </TouchableOpacity>
- </Tooltip>
- ))}
- {maxVisibleParticipants < filteredParticipants.length ||
- filteredParticipants.length < (event.participants ?? 0) ? (
- <View style={styles.userCountContainer}>
- <Text style={styles.userCount}>{event.participants}</Text>
- </View>
- ) : null}
- </View>
- </View>
- </View>
- ) : (
- <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
- )}
- </View>
- {joined ? (
- <TouchableOpacity
- style={{
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: 8,
- paddingHorizontal: 12,
- borderRadius: 20,
- backgroundColor: Colors.WHITE,
- gap: 6,
- borderWidth: 1,
- borderColor: Colors.DARK_BLUE
- }}
- onPress={handleUnjoinEvent}
- >
- <CalendarCrossedIcon fill={Colors.DARK_BLUE} width={16} height={16} />
- <Text
- style={{
- color: Colors.DARK_BLUE,
- fontSize: getFontSize(14),
- fontFamily: 'montserrat-700'
- }}
- >
- Cancel
- </Text>
- </TouchableOpacity>
- ) : !event.full ? (
- <TouchableOpacity
- style={{
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: 8,
- paddingHorizontal: 12,
- borderRadius: 20,
- backgroundColor: Colors.ORANGE,
- gap: 6,
- borderWidth: 1,
- borderColor: Colors.ORANGE
- }}
- onPress={handleJoinEvent}
- >
- {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'
- }}
- >
- Interested
- </Text>
- </>
- )}
- </TouchableOpacity>
- ) : null}
- <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>
- {isUploading && (
- <View
- style={{
- alignItems: 'center',
- justifyContent: 'center'
- }}
- >
- <Progress.CircleSnail
- borderWidth={0}
- color={Colors.DARK_BLUE}
- unfilledColor="rgba(0, 0, 0, 0.1)"
- />
- </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>
- </ScrollView>
- </KeyboardAwareScrollView>
- <WarningModal
- type={modalInfo.type}
- isVisible={modalInfo.visible}
- buttonTitle={modalInfo.buttonTitle}
- message={modalInfo.message}
- action={modalInfo.action}
- onClose={() => setModalInfo({ ...modalInfo, visible: false })}
- title={modalInfo.title}
- />
- </View>
- );
- };
- const iframeModel = HTMLElementModel.fromCustomModel({
- tagName: 'iframe',
- contentModel: 'block' as any
- });
- const WebDisplay = React.memo(function WebDisplay({ html }: { html: string }) {
- const { width: windowWidth } = useWindowDimensions();
- const contentWidth = windowWidth * 0.9;
- const token = storage.get('token', StoreType.STRING) as string;
- const processedHtml = React.useMemo(() => {
- let updatedHtml = html;
- const hrefRegex = /href="((?!http)[^"]+)"/g;
- const imgSrcRegex = /src="((?:\.{0,2}\/)*img\/[^"]*)"/g;
- const normalizePath = (path: string): string => {
- const segments = path.split('/').filter(Boolean);
- const resolved: string[] = [];
- for (const segment of segments) {
- if (segment === '..') resolved.pop();
- else if (segment !== '.') resolved.push(segment);
- }
- return '/' + resolved.join('/');
- };
- updatedHtml = updatedHtml
- .replace(hrefRegex, (match, rawPath) => {
- const normalizedPath = normalizePath(rawPath);
- const fullUrl = `${API_HOST}${normalizedPath}`;
- if (normalizedPath.includes('shop')) {
- const separator = fullUrl.includes('?') ? '&' : '?';
- return `href="${fullUrl}${separator}token=${encodeURIComponent(token)}"`;
- }
- return `href="${fullUrl}"`;
- })
- .replace(imgSrcRegex, (_match, rawSrc) => {
- const normalizedImg = normalizePath(rawSrc);
- return `src="${API_HOST}${normalizedImg}"`;
- });
- return updatedHtml;
- }, [html, token]);
- const renderers = {
- iframe: ({ tnode }: { tnode: TNode }) => {
- const src = tnode.attributes.src || '';
- const width = contentWidth;
- const height = Number(tnode.attributes.height) || 250;
- return (
- <WebView
- source={{ uri: src }}
- style={{ width, height }}
- javaScriptEnabled
- domStorageEnabled
- startInLoadingState
- scalesPageToFit
- />
- );
- }
- };
- const customHTMLElementModels = {
- iframe: iframeModel
- };
- return (
- <RenderHtml
- contentWidth={contentWidth}
- source={{ html: processedHtml }}
- customHTMLElementModels={customHTMLElementModels}
- renderers={renderers}
- />
- );
- });
- export default EventScreen;
|