123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148 |
- import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
- import {
- View,
- Text,
- Image,
- TouchableOpacity,
- Linking,
- Dimensions,
- FlatList,
- Platform,
- ActivityIndicator,
- Animated
- } 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 ImageView from 'better-react-native-image-viewing';
- 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,
- useGetEventForEditingMutation,
- useGetEventQuery,
- useGetPhotosForRegionQuery,
- usePostDeleteFileMutation,
- usePostEventAddFileMutation,
- usePostGetPhotosForRegionMutation,
- 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';
- import { PhotosData } from '../../MapScreen/RegionViewScreen/types';
- import ImageCarousel from '../../MapScreen/RegionViewScreen/ImageCarousel';
- import EditSvg from 'assets/icons/events/edit.svg';
- import ChatIcon from 'assets/icons/bottom-navigation/messages.svg';
- import InfoIcon from 'assets/icons/info-solid.svg';
- 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.STRING) as string) ?? 0;
- const navigation = useNavigation();
- const { width: windowWidth } = useWindowDimensions();
- const contentWidth = windowWidth * 0.9;
- const scrollViewRef = useRef<ScrollView>(null);
- const keyboardAwareScrollViewRef = useRef<KeyboardAwareScrollView>(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 { mutateAsync: getForEditing } = useGetEventForEditingMutation();
- const [isExpanded, setIsExpanded] = useState(false);
- const [tooltipUser, setTooltipUser] = useState<number | null>(null);
- const [tooltipInterested, setTooltipInterested] = 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 [filteredInterested, setFilteredInterested] = 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 [photosForRegion, setPhotosForRegion] = useState<{ uriSmall: string; uri: string }[]>([]);
- const [activeIndex, setActiveIndex] = useState(0);
- const [isImageModalVisible, setIsImageModalVisible] = useState(false);
- const [currentImageIndex, setCurrentImageIndex] = useState(0);
- const [nmId, setNmId] = useState<number | null>(null);
- const [tooltipVisible, setTooltipVisible] = useState(false);
- const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);
- const { data: photosData } = useGetPhotosForRegionQuery(nmId ?? 0, nmId !== null);
- const { mutateAsync: getPhotosForRegion } = usePostGetPhotosForRegionMutation();
- const [regions, setRegions] = useState<any[]>([]);
- const [modalInfo, setModalInfo] = useState({
- visible: false,
- type: 'success',
- title: '',
- message: '',
- buttonTitle: 'OK',
- action: () => {}
- });
- const [showScrollToTop, setShowScrollToTop] = useState(false);
- const scrollToTopOpacity = useRef(new Animated.Value(0)).current;
- const [toolTipVisible, setToolTipVisible] = useState<boolean>(false);
- useEffect(() => {
- if (regions && regions.length > 0 && event && event.type === 4) {
- handleGetPhotosForAllRegions(regions);
- } else {
- setPhotosForRegion([]);
- }
- }, [regions, event]);
- const handleGetPhotosForAllRegions = useCallback(
- async (regionsArray: any[]) => {
- if (!regionsArray || regionsArray.length === 0) {
- setPhotosForRegion([]);
- return;
- }
- const allPhotos: any[] = [];
- try {
- for (const region of regionsArray) {
- await getPhotosForRegion(
- { region_id: region },
- {
- onSuccess: (res) => {
- if (res.photos && res.photos.length > 0) {
- allPhotos.push(...res.photos);
- }
- },
- onError: (error) => {
- console.log(`Error loading photos for region ${region}:`, error);
- }
- }
- );
- }
- setPhotosForRegion(
- allPhotos.map((item) => ({
- uriSmall: `${API_HOST}/ajax/pic/${item}/small`,
- uri: `${API_HOST}/ajax/pic/${item}/full`
- }))
- );
- } catch (error) {
- setPhotosForRegion([]);
- }
- },
- [getPhotosForRegion, token]
- );
- useEffect(() => {
- photosData &&
- setPhotosForRegion(
- photosData?.photos?.map((item) => ({
- uriSmall: `${API_HOST}/ajax/pic/${item}/small`,
- uri: `${API_HOST}/ajax/pic/${item}/full`
- })) ?? []
- );
- }, [photosData]);
- useEffect(() => {
- if (data && data.data) {
- setEvent(data.data);
- setJoined(data.data.joined);
- setNmId(data.data.settings.nm_region ?? null);
- if (data.data.settings.nm_regions && data.data.type === 4) {
- const ids = JSON.parse(data.data.settings.nm_regions);
- setRegions(ids);
- }
- setMyFiles(data.data.files ?? []);
- setPhotos(data.data.photos);
- const partisipantsWidth = contentWidth / 2;
- setMaxVisibleParticipants(Math.floor(partisipantsWidth / 24));
- setMaxVisibleParticipantsWithGap(Math.floor(partisipantsWidth / 32));
- setFilteredParticipants(
- data.data.type === 4 ? data.data.participants_approved_data : data.data.participants_data
- );
- setFilteredInterested(data.data.participants_data);
- setRegistrationInfo(() => {
- if (data.data.full) {
- return {
- color: Colors.LIGHT_GRAY,
- name: 'FULL'
- };
- } else if (data.data.closed) {
- return {
- color: Colors.LIGHT_GRAY,
- name: 'CLOSED'
- };
- } 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 handleScroll = (event: any) => {
- const currentScrollY = event.nativeEvent.contentOffset.y;
- const shouldShow = currentScrollY > 350;
- if (shouldShow !== showScrollToTop) {
- setShowScrollToTop(shouldShow);
- Animated.timing(scrollToTopOpacity, {
- toValue: shouldShow ? 0.8 : 0,
- duration: 300,
- useNativeDriver: true
- }).start();
- }
- };
- const scrollToTop = () => {
- keyboardAwareScrollViewRef.current?.scrollToPosition(0, 0, true);
- };
- const openModal = (index: number) => {
- setCurrentImageIndex(index);
- setIsImageModalVisible(true);
- };
- 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 (!token) {
- setIsWarningModalVisible(true);
- return;
- }
- if (event.settings.type !== 1 && event.settings.type !== 4) {
- setModalInfo({
- visible: true,
- type: 'success',
- title: 'Success',
- buttonTitle: 'OK',
- message: `Thank you for joining, 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.date_from && event.date_to) {
- if (event.date_tentative) {
- const dateFrom = moment(event.date_from, 'YYYY-MM').format('MMM YYYY');
- const dateTo = moment(event.date_to, 'YYYY-MM').format('MMM YYYY');
- if (dateFrom === dateTo) {
- return (
- <View>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE
- }}
- >
- {dateFrom}
- </Text>
- </View>
- );
- }
- return (
- <View>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE,
- flex: 1
- }}
- >
- {dateFrom}{' '}
- </Text>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE,
- flex: 1
- }}
- >
- {dateTo}
- </Text>
- </View>
- );
- }
- return (
- <View>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE,
- flex: 1
- }}
- >
- {moment(event.settings.date_from, 'YYYY-MM-DD').format('DD MMM YYYY')}{' '}
- </Text>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE,
- flex: 1
- }}
- >
- {moment(event.settings.date_to, 'YYYY-MM-DD').format('DD MMM YYYY')}
- </Text>
- </View>
- );
- } else {
- let date = moment(event.date, 'YYYY-MM-DD').format('DD MMM YYYY');
- if (event.date_tentative) {
- return (
- <View>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE
- }}
- >
- {moment(event.date, 'YYYY-MM').format('MMM YYYY')}
- </Text>
- </View>
- );
- }
- return (
- <View>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE
- }}
- >
- {date}
- </Text>
- {event.time_from && event.time_to ? (
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE
- }}
- >
- {event.time_from} - {event.time_to}
- </Text>
- </View>
- ) : event.time ? (
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE
- }}
- >
- {event.time}
- </Text>
- </View>
- ) : null}
- </View>
- );
- }
- };
- 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 openMap = () => {
- const rawCoords = event.settings.location;
- const coords =
- typeof rawCoords === 'string' ? JSON.parse(rawCoords.replace(/'/g, '"')) : rawCoords;
- const lat = coords.lat;
- const lon = coords.lon;
- const url =
- Platform.OS === 'ios'
- ? `http://maps.apple.com/?ll=${lat},${lon}`
- : `geo:${lat},${lon}?q=${lat},${lon}`;
- const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${lat},${lon}`;
- Linking.canOpenURL(googleMapsUrl)
- .then((supported) => {
- if (supported) {
- Linking.openURL(googleMapsUrl);
- } else {
- return Linking.canOpenURL(url).then((supportedFallback) => {
- if (supportedFallback) {
- Linking.openURL(url);
- } else {
- navigation.dispatch(
- CommonActions.reset({
- index: 1,
- routes: [
- {
- name: NAVIGATION_PAGES.IN_APP_MAP_TAB,
- state: {
- routes: [
- {
- name: NAVIGATION_PAGES.MAP_TAB,
- params: { lat, lon }
- }
- ]
- }
- }
- ]
- })
- );
- }
- });
- }
- })
- .catch((err) => console.error('Error opening event location', err));
- };
- const photoUrl = API_HOST + '/webapi/events/get-main-photo/' + event.id;
- return (
- <View style={styles.container}>
- <TouchableOpacity
- onPress={() => {
- navigation.goBack();
- }}
- style={styles.backButton}
- >
- <View style={styles.chevronWrapper}>
- <ChevronLeft fill={Colors.WHITE} />
- </View>
- </TouchableOpacity>
- <KeyboardAwareScrollView
- ref={keyboardAwareScrollViewRef}
- showsVerticalScrollIndicator={false}
- onScroll={handleScroll}
- scrollEventThrottle={16}
- >
- <ScrollView
- ref={scrollViewRef}
- contentContainerStyle={{ minHeight: '100%' }}
- nestedScrollEnabled={true}
- showsVerticalScrollIndicator={false}
- removeClippedSubviews={false}
- >
- {(event.settings.type === 1 || event.settings.type === 4) &&
- photosForRegion.length > 0 ? (
- <ImageCarousel
- photos={photosForRegion as PhotosData[]}
- activeIndex={activeIndex}
- setActiveIndex={setActiveIndex}
- openModal={openModal}
- containerStyles={{ marginBottom: 0 }}
- />
- ) : (
- <Image source={{ uri: photoUrl }} style={{ width: '100%', height: 220 }} />
- )}
- {registrationInfo && (
- <View
- style={{
- position: 'absolute',
- width: 'auto',
- 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 }}>
- {event.settings.type === 1 && event?.flag ? (
- <Tooltip
- isVisible={tooltipVisible}
- content={<Text>{event.country}</Text>}
- contentStyle={{ backgroundColor: Colors.FILL_LIGHT }}
- placement="top"
- onClose={() => setTooltipVisible(false)}
- backgroundColor="transparent"
- allowChildInteraction={false}
- >
- <TouchableOpacity onPress={() => setTooltipVisible(true)}>
- <Image
- source={{ uri: API_HOST + event.flag }}
- style={{
- width: 20,
- height: 20,
- borderRadius: 10,
- borderWidth: 0.5,
- borderColor: Colors.DARK_LIGHT
- }}
- />
- </TouchableOpacity>
- </Tooltip>
- ) : (
- <EarthIcon fill={Colors.DARK_BLUE} height={20} width={20} />
- )}
- <Text
- style={{
- fontSize: getFontSize(12),
- fontWeight: '600',
- color: Colors.DARK_BLUE,
- flex: 1
- }}
- >
- {event.address1}
- </Text>
- </View>
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flex: 1 }}>
- <CalendarIcon fill={Colors.DARK_BLUE} height={20} width={20} />
- {formatEventDate(event)}
- </View>
- </View>
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
- {event.settings.address2 && (
- <TouchableOpacity
- onPress={openMap}
- disabled={!event.settings.location}
- 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>
- </TouchableOpacity>
- )}
- {event.settings.registrations_info !== 1 && event.type !== 4 && (
- <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 }]}
- />
- ) : (
- <AvatarWithInitials
- text={
- event.settings.host_data.first_name[0] +
- event.settings.host_data.last_name[0]
- }
- flag={API_HOST + event.settings.host_data?.flag}
- size={28}
- fontSize={12}
- borderColor={Colors.DARK_LIGHT}
- borderWidth={1}
- />
- )}
- <View style={{ justifyContent: 'space-between' }}>
- <Text
- style={{
- fontFamily: 'montserrat-700',
- fontSize: 12,
- color: Colors.DARK_BLUE
- }}
- >
- {event.settings.host_data.first_name}
- </Text>
- <Text
- style={{
- fontFamily: 'montserrat-700',
- fontSize: 12,
- color: Colors.DARK_BLUE
- }}
- >
- {event.settings.host_data.last_name}
- </Text>
- </View>
- </View>
- </TouchableOpacity>
- </View>
- ) : (
- <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
- )}
- {(filteredParticipants.length > 0 && event.type !== 4) ||
- (filteredInterested.length === 0 &&
- event.type === 4 &&
- filteredParticipants.length > 0) ? (
- <View style={{ gap: 8, flex: 1 }}>
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>
- Going{` (${filteredParticipants.length})`}
- </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
- style={index !== 0 ? { marginLeft: -10 } : {}}
- onPress={() => setTooltipUser(index)}
- >
- {user.avatar ? (
- <Image
- key={index}
- source={{ uri: API_HOST + user.avatar }}
- style={[styles.userImage]}
- />
- ) : (
- <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>
- ))}
- <TouchableOpacity
- style={styles.userCountContainer}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.PARTICIPANTS_LIST,
- {
- participants:
- event.type === 4
- ? event.participants_approved_full_data
- : event.participants_full_data,
- eventId: event.id,
- isHost: event.settings.host_profile === +currentUserId,
- isTrip: event.type === 4
- }
- ] as never)
- )
- }
- >
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- </TouchableOpacity>
- </View>
- </View>
- </View>
- ) : null}
- {filteredParticipants.length === 0 &&
- event.type === 4 &&
- filteredInterested.length > 0 ? (
- <View style={{ gap: 8, flex: 1 }}>
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>
- Interested{` (${filteredInterested.length})`}
- </Text>
- <View style={[styles.statItem, { justifyContent: 'flex-start' }]}>
- <View style={styles.userImageContainer}>
- {(filteredInterested.length > maxVisibleParticipants
- ? filteredInterested.slice(0, maxVisibleParticipants - 1)
- : filteredInterested
- ).map((user, index) => (
- <Tooltip
- isVisible={tooltipInterested === index}
- content={
- <TouchableOpacity
- onPress={() => {
- setTooltipInterested(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={() => setTooltipInterested(null)}
- key={index}
- backgroundColor="transparent"
- >
- <TouchableOpacity
- style={index !== 0 ? { marginLeft: -10 } : {}}
- onPress={() => setTooltipInterested(index)}
- >
- {user.avatar ? (
- <Image
- key={index}
- source={{ uri: API_HOST + user.avatar }}
- style={[styles.userImage]}
- />
- ) : (
- <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>
- ))}
- <TouchableOpacity
- style={styles.userCountContainer}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.PARTICIPANTS_LIST,
- {
- participants: event.participants_full_data,
- eventId: event.id,
- isHost: event.settings.host_profile === +currentUserId,
- interested: true,
- isTrip: event.type === 4
- }
- ] as never)
- )
- }
- >
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- </TouchableOpacity>
- </View>
- </View>
- </View>
- ) : null}
- </View>
- {filteredInterested.length > 0 &&
- filteredParticipants.length > 0 &&
- event.type === 4 && (
- <View style={styles.stats}>
- {filteredInterested.length > 0 ? (
- <View style={{ gap: 8, flex: 1 }}>
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>
- Interested{` (${filteredInterested.length})`}
- </Text>
- <View style={[styles.statItem, { justifyContent: 'flex-start' }]}>
- <View style={styles.userImageContainer}>
- {(filteredInterested.length > maxVisibleParticipants
- ? filteredInterested.slice(0, maxVisibleParticipants - 1)
- : filteredInterested
- ).map((user, index) => (
- <Tooltip
- isVisible={tooltipInterested === index}
- content={
- <TouchableOpacity
- onPress={() => {
- setTooltipInterested(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={() => setTooltipInterested(null)}
- key={index}
- backgroundColor="transparent"
- >
- <TouchableOpacity
- style={index !== 0 ? { marginLeft: -10 } : {}}
- onPress={() => setTooltipInterested(index)}
- >
- {user.avatar ? (
- <Image
- key={index}
- source={{ uri: API_HOST + user.avatar }}
- style={[styles.userImage]}
- />
- ) : (
- <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>
- ))}
- <TouchableOpacity
- style={styles.userCountContainer}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.PARTICIPANTS_LIST,
- {
- participants: event.participants_full_data,
- eventId: event.id,
- isHost: event.settings.host_profile === +currentUserId,
- interested: true,
- isTrip: event.type === 4
- }
- ] as never)
- )
- }
- >
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- </TouchableOpacity>
- </View>
- </View>
- </View>
- ) : (
- <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
- )}
- {filteredParticipants.length > 0 ? (
- <View style={{ gap: 8, flex: 1 }}>
- <Text style={[styles.travelSeriesTitle, { fontSize: 12 }]}>
- Going{` (${filteredParticipants.length})`}
- </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
- style={index !== 0 ? { marginLeft: -10 } : {}}
- onPress={() => setTooltipUser(index)}
- >
- {user.avatar ? (
- <Image
- key={index}
- source={{ uri: API_HOST + user.avatar }}
- style={[styles.userImage]}
- />
- ) : (
- <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>
- ))}
- <TouchableOpacity
- style={styles.userCountContainer}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.PARTICIPANTS_LIST,
- {
- participants:
- event.type === 4
- ? event.participants_approved_full_data
- : event.participants_full_data,
- eventId: event.id,
- isHost: event.settings.host_profile === +currentUserId,
- isTrip: event.type === 4
- }
- ] as never)
- )
- }
- >
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- <View style={styles.dots}></View>
- </TouchableOpacity>
- </View>
- </View>
- </View>
- ) : (
- <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
- )}
- </View>
- )}
- {/* TO DO */}
- {event.settings.host_profile === +currentUserId &&
- (event.settings.type === 1 || event.settings.type === 4) &&
- !event.archived ? (
- <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={() =>
- getForEditing(
- { token, event_id: event.id },
- {
- onSuccess: (res) => {
- event.settings.type === 4
- ? navigation.navigate(
- ...([
- NAVIGATION_PAGES.CREATE_SHARED_TRIP,
- {
- eventId: event.id,
- event: res,
- full: event.full
- }
- ] as never)
- )
- : navigation.navigate(
- ...([
- NAVIGATION_PAGES.CREATE_EVENT,
- {
- eventId: event.id,
- event: res
- }
- ] as never)
- );
- }
- }
- )
- }
- >
- <EditSvg fill={Colors.WHITE} width={16} height={16} />
- <Text
- style={{
- color: Colors.WHITE,
- fontSize: getFontSize(14),
- fontFamily: 'montserrat-700',
- textTransform: 'uppercase'
- }}
- >
- Edit
- </Text>
- </TouchableOpacity>
- ) : null}
- {event.settings.host_profile === +currentUserId ||
- event.archived === 1 ? null : joined ? (
- <View style={{ flexDirection: 'row', gap: 8 }}>
- <TouchableOpacity
- style={{
- flex: 1,
- 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.settings?.chat_token && (
- <TouchableOpacity
- style={{
- flex: 1,
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: 8,
- paddingHorizontal: 12,
- borderRadius: 20,
- backgroundColor: Colors.ORANGE,
- gap: 6,
- borderWidth: 1,
- borderColor: Colors.ORANGE
- }}
- onPress={() =>
- navigation.dispatch(
- CommonActions.reset({
- index: 1,
- routes: [
- {
- name: 'DrawerApp',
- state: {
- routes: [
- {
- name: NAVIGATION_PAGES.IN_APP_MESSAGES_TAB,
- state: {
- routes: [
- { name: NAVIGATION_PAGES.CHATS_LIST },
- {
- name: NAVIGATION_PAGES.GROUP_CHAT,
- params: { group_token: event.settings?.chat_token }
- }
- ]
- }
- }
- ]
- }
- }
- ]
- })
- )
- }
- >
- <ChatIcon fill={Colors.WHITE} width={16} height={16} />
- <Text
- style={{
- color: Colors.WHITE,
- fontSize: getFontSize(14),
- fontFamily: 'montserrat-700'
- }}
- >
- Chat
- </Text>
- </TouchableOpacity>
- )}
- </View>
- ) : !event.full && !event.closed ? (
- <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.type === 1 ? (
- <>
- <GigtIcon fill={Colors.WHITE} width={16} height={16} />
- <Text
- style={{
- color: Colors.WHITE,
- fontSize: getFontSize(14),
- fontFamily: 'montserrat-700',
- textTransform: 'uppercase'
- }}
- >
- Going
- </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}
- {event.type === 4 ? (
- <TouchableOpacity
- onPress={() => setToolTipVisible(true)}
- style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}
- >
- <Tooltip
- isVisible={toolTipVisible}
- content={
- <Text style={{ fontSize: 12, color: Colors.DARK_BLUE }}>
- Disclaimer: This trip has been created and published by a community member.
- NomadMania is not the organizer and is not involved in planning, managing, or
- overseeing this trip. We take no responsibility for its content, safety, or
- execution. Please exercise your own judgment when joining.
- </Text>
- }
- contentStyle={{ backgroundColor: Colors.WHITE }}
- placement="bottom"
- onClose={() => setToolTipVisible(false)}
- backgroundColor="transparent"
- allowChildInteraction={false}
- >
- <TouchableOpacity
- onPress={() => setToolTipVisible(true)}
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
- >
- <InfoIcon fill={Colors.DARK_BLUE} width={16} height={16} />
- </TouchableOpacity>
- </Tooltip>
- <Text style={{ fontSize: 12, color: Colors.DARK_BLUE, fontWeight: '600' }}>
- Disclaimer [read more]
- </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}
- />
- <ImageView
- images={photosForRegion}
- imageIndex={currentImageIndex}
- visible={isImageModalVisible}
- onRequestClose={() => setIsImageModalVisible(false)}
- backgroundColor={Colors.DARK_BLUE}
- onImageIndexChange={setActiveIndex}
- />
- <WarningModal
- type={'unauthorized'}
- isVisible={isWarningModalVisible}
- onClose={() => setIsWarningModalVisible(false)}
- />
- {showScrollToTop && (
- <Animated.View
- style={{
- position: 'absolute',
- bottom: 20,
- right: 20,
- opacity: scrollToTopOpacity,
- zIndex: 1000
- }}
- >
- <TouchableOpacity
- onPress={scrollToTop}
- style={{
- backgroundColor: Colors.DARK_BLUE,
- width: 40,
- height: 40,
- borderRadius: 20,
- justifyContent: 'center',
- alignItems: 'center',
- shadowColor: Colors.DARK_BLUE,
- shadowOffset: {
- width: 0,
- height: 2
- },
- shadowOpacity: 0.25,
- shadowRadius: 3,
- elevation: 5
- }}
- >
- <MaterialCommunityIcons name="chevron-up" size={24} color={Colors.WHITE} />
- </TouchableOpacity>
- </Animated.View>
- )}
- </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}\/)*[^":]+)"/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;
|