123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- import React, { useCallback, useEffect, useRef, useState } from 'react';
- import {
- View,
- Text,
- Image,
- TouchableOpacity,
- ScrollView,
- KeyboardAvoidingView,
- Platform,
- TouchableWithoutFeedback,
- Keyboard
- } from 'react-native';
- import { Formik } from 'formik';
- import * as yup from 'yup';
- import { useNavigation } from '@react-navigation/native';
- import { styles } from '../CreateEvent/styles';
- import { ButtonVariants } from 'src/types/components';
- import ImageView from 'better-react-native-image-viewing';
- import { StoreType, storage } from 'src/storage';
- import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
- import { Button, CheckBox, Header, Input, PageWrapper, WarningModal } from 'src/components';
- import { Colors } from 'src/theme';
- import { getFontSize } from 'src/utils';
- import { RichEditor, RichToolbar, actions } from 'react-native-pell-rich-editor';
- import CalendarSvg from '../../../../../assets/icons/calendar.svg';
- import RangeCalendar from 'src/components/Calendars/RangeCalendar';
- import {
- usePostAddSharedTripMutation,
- usePostCancelEventMutation,
- usePostGetPhotosForRegionMutation,
- usePostSetFullMutation,
- usePostUpdateEventMutation,
- usePostUpdateSharedTripMutation
- } from '@api/events';
- import { SheetManager } from 'react-native-actions-sheet';
- import PhotosForRegionModal from '../Components/PhotosForRegionModal/PhotosForRegionModal';
- import { API_HOST } from 'src/constants';
- import AddMapPinModal from '../Components/AddMapPinModal';
- import { NAVIGATION_PAGES } from 'src/types';
- import TrashSvg from 'assets/icons/travels-screens/trash-solid.svg';
- import { useGetRegionsWithFlagQuery } from '@api/regions';
- const EventSchema = yup.object({
- title: yup.string().required().min(3),
- start_date: yup.date().nullable().required(),
- end_date: yup.date().nullable().required(),
- tentative: yup.number().optional(),
- photo: yup.number().nullable().optional(),
- details: yup.string().required()
- });
- const CreateSharedTripScreen = ({ route }: { route: any }) => {
- const eventId = route.params?.eventId;
- const eventData = route.params?.event;
- const token = (storage.get('token', StoreType.STRING) as string) ?? null;
- const navigation = useNavigation();
- const scrollRef = useRef<ScrollView>(null);
- const richText = useRef<RichEditor | null>(null);
- const { mutateAsync: getPhotosForRegion } = usePostGetPhotosForRegionMutation();
- const { mutateAsync: addSharedTrip } = usePostAddSharedTripMutation();
- const { mutateAsync: updateEvent } = usePostUpdateSharedTripMutation();
- const { mutateAsync: cancelEvent } = usePostCancelEventMutation();
- const { mutateAsync: setFull } = usePostSetFullMutation();
- const { data: regionslist } = useGetRegionsWithFlagQuery(true);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [calendarVisible, setCalendarVisible] = useState<'start_date' | 'end_date' | null>(null);
- const [isViewerVisible, setIsViewerVisible] = useState(false);
- const [photos, setPhotos] = useState<any[]>([]);
- const [regions, setRegions] = useState<any[]>(route.params?.regionsToSave ?? []);
- const [regionsError, setRegionsError] = useState<string | null>(null);
- const [photosError, setPhotosError] = useState<string | null>(null);
- useEffect(() => {
- if (route.params?.regionsToSave) {
- setRegions(route.params.regionsToSave);
- }
- }, [route.params?.regionsToSave]);
- useEffect(() => {
- if (regions && regions.length > 0) {
- handleGetPhotosForAllRegions(regions);
- } else {
- setPhotos([]);
- }
- }, [regions]);
- const handleGetPhotosForAllRegions = useCallback(
- async (regionsArray: any[]) => {
- if (!regionsArray || regionsArray.length === 0) {
- setPhotos([]);
- return;
- }
- const allPhotos: any[] = [];
- try {
- for (const region of regionsArray) {
- await getPhotosForRegion(
- { region_id: region.id },
- {
- onSuccess: (res) => {
- if (res.photos && res.photos.length > 0) {
- allPhotos.push(...res.photos);
- }
- },
- onError: (error) => {
- console.log(`Error loading photos for region ${region.id}:`, error);
- }
- }
- );
- }
- setPhotos(allPhotos);
- } catch (error) {
- setPhotos([]);
- }
- },
- [getPhotosForRegion, token]
- );
- const initialData: any = {
- title: eventData?.title ?? '',
- start_date: eventData?.date_from ?? '',
- end_date: eventData?.date_to ?? '',
- tentative: eventData?.date_tentative ?? 0,
- photo: null,
- details: eventData?.details ?? ''
- };
- const [modalInfo, setModalInfo] = useState({
- visible: false,
- type: 'success',
- title: '',
- message: '',
- buttonTitle: 'OK',
- action: () => {}
- });
- useEffect(() => {
- if (eventData && eventData.regions && regionslist && regionslist.data) {
- const parsedRegions = JSON.parse(eventData.regions);
- let regionsData: any = [];
- parsedRegions.forEach((region: any) => {
- const regionWithFlag = regionslist.data?.find((r: any) => r.id === +region);
- regionsData.push({
- flag1: regionWithFlag?.flag ?? '',
- flag2: null,
- region_name: regionWithFlag?.name ?? '',
- id: regionWithFlag?.id ?? region.id
- });
- });
- setRegions(regionsData);
- }
- }, [eventData, regionslist]);
- const handleCancelTrip = () => {
- setModalInfo({
- visible: true,
- type: 'delete',
- title: 'Cancel trip',
- buttonTitle: 'Cancel',
- message: `Are you sure you want to cancel this trip?`,
- action: () => {
- cancelEvent(
- { token, event_id: eventId },
- {
- onSuccess: (res) => {
- navigation.navigate(NAVIGATION_PAGES.EVENTS as never);
- }
- }
- );
- }
- });
- };
- const handleDeleteRegion = (regionId: number) => {
- regions && setRegions(regions.filter((region) => region.id !== regionId));
- };
- return (
- <PageWrapper>
- <Header label="Add trip" />
- <KeyboardAvoidingView
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
- style={{ flex: 1 }}
- >
- <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
- <ScrollView ref={scrollRef} showsVerticalScrollIndicator={false}>
- <Formik
- validationSchema={EventSchema}
- initialValues={initialData}
- onSubmit={async (values) => {
- if (regions.length === 0) {
- return;
- }
- if (photos.length > 0 && !values.photo) {
- return;
- }
- setIsSubmitting(true);
- const regionsToSave = regions.map((region) => region.id);
- const newTrip: any = {
- title: values.title,
- start_date: values.start_date,
- end_date: values.end_date,
- tentative: values.tentative,
- regions: regionsToSave,
- details: values.details
- };
- if (values.photo) {
- newTrip.photo = values.photo;
- }
- if (eventId) {
- await updateEvent(
- { token, trip_id: eventId, trip: JSON.stringify(newTrip) },
- {
- onSuccess: (res) => {
- setIsSubmitting(false);
- navigation.goBack();
- },
- onError: (err) => {
- setIsSubmitting(false);
- }
- }
- );
- } else {
- await addSharedTrip(
- { token, trip: JSON.stringify(newTrip) },
- {
- onSuccess: (res) => {
- setIsSubmitting(false);
- setModalInfo({
- visible: true,
- type: 'success',
- title: 'Success',
- buttonTitle: 'OK',
- message: `Thank you for adding this new trip. It will undergo review soon. You'll be notified via email once it is approved.`,
- action: () => {}
- });
- },
- onError: (err) => {
- setIsSubmitting(false);
- }
- }
- );
- }
- }}
- >
- {(props) => (
- <View style={{ gap: 12 }}>
- <Input
- header={'Trip name'}
- placeholder={'Add trip name'}
- inputMode={'text'}
- onChange={props.handleChange('title')}
- value={props.values.title}
- onBlur={props.handleBlur('title')}
- formikError={props.touched.title && (props.errors.title as string)}
- />
- <View style={{ flexDirection: 'row', gap: 8 }}>
- <View style={{ flex: 1 }}>
- <Input
- header={'From'}
- placeholder={'Add start date'}
- inputMode={'none'}
- onChange={props.handleChange('start_date')}
- value={props.values.start_date}
- onBlur={props.handleBlur('start_date')}
- isFocused={() => setCalendarVisible('start_date')}
- formikError={
- props.touched.start_date && (props.errors.start_date as string)
- }
- icon={<CalendarSvg fill={Colors.LIGHT_GRAY} width={20} height={20} />}
- />
- </View>
- <View style={{ flex: 1 }}>
- <Input
- header={'To'}
- placeholder={'Add end date'}
- inputMode={'none'}
- onChange={props.handleChange('end_date')}
- value={props.values.end_date}
- onBlur={props.handleBlur('end_date')}
- isFocused={() => setCalendarVisible('end_date')}
- formikError={props.touched.end_date && (props.errors.end_date as string)}
- icon={<CalendarSvg fill={Colors.LIGHT_GRAY} width={20} height={20} />}
- />
- </View>
- </View>
- <TouchableOpacity
- onPress={() => {
- props.setFieldValue('tentative', props.values.tentative === 1 ? 0 : 1);
- }}
- style={styles.optionBtn}
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
- >
- <Text style={styles.optionText}>Tentative dates</Text>
- <CheckBox
- onChange={() => {
- props.setFieldValue('tentative', props.values.tentative === 1 ? 0 : 1);
- }}
- value={props.values.tentative === 1}
- color={Colors.DARK_BLUE}
- />
- </TouchableOpacity>
- <View>
- <Text
- style={{
- color: Colors.DARK_BLUE,
- fontSize: getFontSize(14),
- fontFamily: 'redhat-700',
- marginBottom: 8
- }}
- >
- NomadMania regions
- </Text>
- <TouchableOpacity
- style={styles.addRegionBtn}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.ADD_REGIONS,
- { regionsParams: regions, editId: eventId, isSharedTrip: true }
- ] as never)
- )
- }
- >
- <Text style={styles.addRegionBtntext}>Add Region</Text>
- </TouchableOpacity>
- {regions && regions.length ? (
- <View style={styles.regionsContainer}>
- {regions.map((region) => {
- const [name, ...rest] = region.region_name?.split(/ – | - /);
- const subname = rest?.join(' - ');
- return (
- <View key={region.id} style={styles.regionItem}>
- <View style={styles.regionHeader}>
- <Image
- source={{ uri: `${API_HOST}/img/flags_new/${region.flag1}` }}
- style={styles.flagStyle}
- />
- {region.flag2 && (
- <Image
- source={{
- uri: `${API_HOST}/img/flags_new/${region.flag2}`
- }}
- style={[styles.flagStyle, { marginLeft: -17 }]}
- />
- )}
- <View style={styles.nameContainer}>
- <Text style={styles.regionName}>{name}</Text>
- <Text style={styles.regionSubname}>{subname}</Text>
- </View>
- </View>
- <TouchableOpacity
- style={styles.trashBtn}
- onPress={() => handleDeleteRegion(region.id)}
- >
- <TrashSvg fill={Colors.WHITE} />
- </TouchableOpacity>
- </View>
- );
- })}
- </View>
- ) : regionsError ? (
- <Text
- style={{
- color: Colors.RED,
- fontSize: getFontSize(12),
- fontFamily: 'redhat-600',
- marginTop: 5
- }}
- >
- {regionsError}
- </Text>
- ) : (
- <Text style={styles.noRegiosText}>No regions at the moment</Text>
- )}
- </View>
- {regions && regions.length > 0 && photos.length > 0 ? (
- <Input
- header={'Photo'}
- placeholder={props.values.photo ? 'Photo selected' : 'Choose a photo'}
- inputMode={'none'}
- onBlur={props.handleBlur('photo')}
- isFocused={() =>
- SheetManager.show('photos-for-region-modal', {
- payload: {
- photos,
- selectPhoto: (photo: any) => {
- props.setFieldValue('photo', photo);
- setPhotosError(null);
- }
- } as any
- })
- }
- formikError={props.touched.photo && (props.errors.photo as string)}
- icon={<AddImgSvg fill={Colors.LIGHT_GRAY} width={20} height={20} />}
- />
- ) : null}
- {props.values.photo ? (
- <View
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- gap: 8
- }}
- >
- <TouchableOpacity
- style={{ width: '100%' }}
- onPress={async () => setIsViewerVisible(true)}
- >
- <Image
- source={{ uri: `${API_HOST}/webapi/photos/${props.values.photo}/small` }}
- style={{ width: '100%', height: 200, borderRadius: 4 }}
- />
- </TouchableOpacity>
- </View>
- ) : null}
- {photosError ? (
- <Text
- style={{
- color: Colors.RED,
- fontSize: getFontSize(12),
- fontFamily: 'redhat-600',
- marginTop: -5
- }}
- >
- {photosError}
- </Text>
- ) : null}
- <View>
- <Text
- style={{
- color: Colors.DARK_BLUE,
- fontSize: getFontSize(14),
- fontFamily: 'redhat-700',
- marginBottom: 8
- }}
- >
- Details
- </Text>
- <RichToolbar
- editor={richText}
- selectedButtonStyle={{ backgroundColor: Colors.LIGHT_GRAY }}
- style={[
- {
- borderTopLeftRadius: 4,
- borderTopRightRadius: 4,
- backgroundColor: Colors.FILL_LIGHT
- },
- props.touched.details && props.errors.details
- ? {
- borderTopColor: Colors.RED,
- borderLeftColor: Colors.RED,
- borderRightColor: Colors.RED,
- borderWidth: 1,
- borderBottomWidth: 0
- }
- : {}
- ]}
- actions={[
- actions.keyboard,
- actions.setBold,
- actions.setItalic,
- actions.setUnderline,
- actions.alignLeft,
- actions.alignCenter,
- actions.alignRight,
- actions.alignFull,
- actions.insertLink,
- actions.insertBulletsList,
- actions.insertOrderedList,
- actions.setStrikethrough,
- actions.indent,
- actions.outdent,
- actions.undo,
- actions.redo,
- actions.blockquote,
- actions.insertLine
- ]}
- />
- <RichEditor
- ref={richText}
- initialHeight={100}
- onFocus={() => {
- scrollRef.current?.scrollTo({
- y: 650,
- animated: true
- });
- }}
- placeholder="Add trip details"
- initialContentHTML={props.values.details}
- onChange={props.handleChange('details')}
- editorStyle={{
- contentCSSText: 'font-size: 14px; padding: 12px; color: #C8C8C8;',
- backgroundColor: Colors.FILL_LIGHT,
- color: Colors.DARK_BLUE
- }}
- style={[
- {
- minHeight: 100,
- borderBottomLeftRadius: 4,
- borderBottomRightRadius: 4
- },
- props.touched.details && props.errors.details
- ? {
- borderBottomColor: Colors.RED,
- borderLeftColor: Colors.RED,
- borderRightColor: Colors.RED,
- borderWidth: 1,
- borderTopWidth: 0
- }
- : {}
- ]}
- onBlur={() => props.handleBlur('details')}
- />
- {props.touched.details && props.errors.details ? (
- <Text
- style={{
- color: Colors.RED,
- fontSize: getFontSize(12),
- fontFamily: 'redhat-600',
- marginTop: 5
- }}
- >
- {props.errors.details as string}
- </Text>
- ) : null}
- </View>
- {eventId ? (
- <View style={{ marginTop: 15, marginBottom: 15, gap: 8 }}>
- <Button
- onPress={() => {
- if (regions.length === 0) {
- setRegionsError('please add at least one region');
- } else {
- setRegionsError(null);
- }
- if (photos.length > 0 && !props.values.photo) {
- setPhotosError('please select a photo');
- } else {
- setPhotosError(null);
- }
- props.handleSubmit();
- }}
- disabled={isSubmitting}
- >
- Update trip
- </Button>
- <Button
- variant={ButtonVariants.OPACITY}
- containerStyles={{
- backgroundColor: Colors.WHITE,
- borderColor: Colors.BORDER_LIGHT
- }}
- textStyles={{ color: Colors.DARK_BLUE }}
- onPress={async () => {
- await setFull({ token, id: eventId, full: 1 });
- navigation.goBack();
- }}
- >
- Set Full
- </Button>
- <Button
- variant={ButtonVariants.OPACITY}
- containerStyles={{
- backgroundColor: Colors.RED,
- borderColor: Colors.RED
- }}
- textStyles={{ color: Colors.WHITE }}
- onPress={handleCancelTrip}
- >
- Cancel trip
- </Button>
- </View>
- ) : (
- <View style={{ marginTop: 15, marginBottom: 15, gap: 8 }}>
- <Button
- onPress={() => {
- if (regions.length === 0) {
- setRegionsError('please add at least one region');
- } else {
- setRegionsError(null);
- }
- if (photos.length > 0 && !props.values.photo) {
- setPhotosError('please select a photo');
- } else {
- setPhotosError(null);
- }
- props.handleSubmit();
- }}
- disabled={isSubmitting}
- >
- Save
- </Button>
- <Button
- variant={ButtonVariants.OPACITY}
- containerStyles={{
- backgroundColor: Colors.WHITE,
- borderColor: Colors.BORDER_LIGHT
- }}
- textStyles={{ color: Colors.DARK_BLUE }}
- onPress={() => navigation.goBack()}
- >
- Cancel
- </Button>
- </View>
- )}
- <RangeCalendar
- isModalVisible={calendarVisible ? true : false}
- closeModal={(startDate?: string | null, endDate?: string | null) => {
- startDate && props.handleChange(calendarVisible)(startDate.toString());
- setCalendarVisible(null);
- }}
- allowRangeSelection={false}
- selectedDate={props.values[calendarVisible ?? 'start_date']}
- disablePastDates={true}
- />
- <ImageView
- images={[
- {
- uri: `${API_HOST}/webapi/photos/${props.values.photo}/full`
- }
- ]}
- keyExtractor={(imageSrc, index) => index.toString()}
- imageIndex={0}
- visible={isViewerVisible}
- onRequestClose={() => setIsViewerVisible(false)}
- swipeToCloseEnabled={false}
- backgroundColor={Colors.DARK_BLUE}
- />
- </View>
- )}
- </Formik>
- </ScrollView>
- </TouchableWithoutFeedback>
- </KeyboardAvoidingView>
- <PhotosForRegionModal />
- <AddMapPinModal />
- <WarningModal
- type={modalInfo.type}
- isVisible={modalInfo.visible}
- buttonTitle={modalInfo.buttonTitle}
- message={modalInfo.message}
- action={modalInfo.action}
- onClose={() => {
- if (modalInfo.type === 'success') {
- navigation.goBack();
- setModalInfo({ ...modalInfo, visible: false });
- } else {
- setModalInfo({ ...modalInfo, visible: false });
- }
- }}
- title={modalInfo.title}
- />
- </PageWrapper>
- );
- };
- export default CreateSharedTripScreen;
|