Viktoriia 3 ay önce
ebeveyn
işleme
40e15aee9c
32 değiştirilmiş dosya ile 2167 ekleme ve 521 silme
  1. 29 1
      App.tsx
  2. 5 0
      Route.tsx
  3. 3 0
      assets/icons/events/calendar-crossed.svg
  4. 1 1
      assets/icons/events/file-solid.svg
  5. 10 0
      assets/icons/messages/poll.svg
  6. 0 1
      package.json
  7. 2 1
      src/components/Calendar/SpinnerDatePicker.tsx
  8. 194 0
      src/modules/api/events/events-api.ts
  9. 12 0
      src/modules/api/events/events-query-keys.tsx
  10. 3 0
      src/modules/api/events/index.ts
  11. 9 0
      src/modules/api/events/queries/index.ts
  12. 17 0
      src/modules/api/events/queries/use-post-delete-file.tsx
  13. 17 0
      src/modules/api/events/queries/use-post-event-add-file.tsx
  14. 17 0
      src/modules/api/events/queries/use-post-get-event-list.tsx
  15. 17 0
      src/modules/api/events/queries/use-post-get-event.tsx
  16. 22 0
      src/modules/api/events/queries/use-post-get-more-photos.tsx
  17. 17 0
      src/modules/api/events/queries/use-post-join-event.tsx
  18. 17 0
      src/modules/api/events/queries/use-post-unjoin-event.tsx
  19. 35 0
      src/modules/api/events/queries/use-post-upload-photo.tsx
  20. 35 0
      src/modules/api/events/queries/use-post-upload-temp-file.tsx
  21. 137 0
      src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen/index.tsx
  22. 24 0
      src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen/styles.tsx
  23. 13 0
      src/screens/InAppScreens/TravelsScreen/Components/styles.tsx
  24. 228 0
      src/screens/InAppScreens/TravelsScreen/EventScreen/PhotoItem/index.tsx
  25. 1078 377
      src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx
  26. 3 4
      src/screens/InAppScreens/TravelsScreen/EventScreen/styles.tsx
  27. 143 127
      src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx
  28. 15 3
      src/screens/InAppScreens/TravelsScreen/EventsScreen/styles.tsx
  29. 37 0
      src/screens/InAppScreens/TravelsScreen/EventsScreen/utils.tsx
  30. 2 2
      src/screens/InAppScreens/TravelsScreen/index.tsx
  31. 23 3
      src/types/api.ts
  32. 2 1
      src/types/navigation.ts

+ 29 - 1
App.tsx

@@ -18,11 +18,14 @@ import { Linking, Platform } from 'react-native';
 import { API_HOST, API_URL, APP_VERSION } from 'src/constants';
 import axios from 'axios';
 import { API } from 'src/types';
+import { storage, StoreType } from 'src/storage';
 
 const IOS_STORE_URL = 'https://apps.apple.com/app/id6502843543';
 const ANDROID_STORE_URL =
   'https://play.google.com/store/apps/details?id=com.nomadmania.presentation';
 
+const userId = (storage.get('uid', StoreType.STRING) as string) ?? 'not_logged_in';
+
 const routingInstrumentation = Sentry.reactNavigationIntegration({
   enableTimeToInitialDisplay: true
 });
@@ -31,7 +34,32 @@ Sentry.init({
   dsn: 'https://c9b37005f4be22a17a582603ebc17598@o4507781200543744.ingest.de.sentry.io/4507781253824592',
   integrations: [Sentry.reactNativeTracingIntegration({ routingInstrumentation })],
   debug: false,
-  ignoreErrors: ['Network Error', 'ECONNABORTED', 'timeout of 10000ms exceeded']
+  ignoreErrors: ['Network Error', 'ECONNABORTED', 'timeout of 10000ms exceeded'],
+  beforeSend(event, hint) {
+    if (userId) {
+      event.user = {
+        ...event.user,
+        userId: userId
+      };
+    }
+
+    const isNonError = hint?.originalException instanceof Error === false;
+    if (isNonError || event.message?.match(/Non-Error exception captured/)) {
+      return {
+        ...event,
+        message: `Processed Non-Error: ${event.message || 'No message'}`,
+        level: 'warning',
+        contexts: {
+          ...event.contexts,
+          non_error: {
+            type: typeof hint?.originalException,
+            value: JSON.stringify(hint?.originalException)
+          }
+        }
+      };
+    }
+    return event;
+  }
 });
 
 const linking = {

+ 5 - 0
Route.tsx

@@ -101,6 +101,7 @@ import EventScreen from 'src/screens/InAppScreens/TravelsScreen/EventScreen';
 import GroupChatScreen from 'src/screens/InAppScreens/MessagesScreen/GroupChatScreen';
 import GroupSettingScreen from 'src/screens/InAppScreens/MessagesScreen/GroupSettingsScreen';
 import MembersListScreen from 'src/screens/InAppScreens/MessagesScreen/MembersListScreen';
+import AllEventPhotosScreen from 'src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen';
 
 enableScreens();
 
@@ -322,6 +323,10 @@ const Route = () => {
             <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_FIXER} component={AddNewFixerScreen} />
             <ScreenStack.Screen name={NAVIGATION_PAGES.EVENTS} component={EventsScreen} />
             <ScreenStack.Screen name={NAVIGATION_PAGES.EVENT} component={EventScreen} />
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.ALL_EVENT_PHOTOS}
+              component={AllEventPhotosScreen}
+            />
             <ScreenStack.Screen
               name={NAVIGATION_PAGES.FIXERS_COMMENTS}
               component={FixersCommentsScreen}

+ 3 - 0
assets/icons/events/calendar-crossed.svg

@@ -0,0 +1,3 @@
+<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.5 0C4.91563 0 5.25 0.334375 5.25 0.75V2H9.75V0.75C9.75 0.334375 10.0844 0 10.5 0C10.9156 0 11.25 0.334375 11.25 0.75V2H12.5C13.6031 2 14.5 2.89687 14.5 4V4.5V6V14C14.5 15.1031 13.6031 16 12.5 16H2.5C1.39688 16 0.5 15.1031 0.5 14V6V4.5V4C0.5 2.89687 1.39688 2 2.5 2H3.75V0.75C3.75 0.334375 4.08437 0 4.5 0ZM13 6H2V14C2 14.275 2.225 14.5 2.5 14.5H12.5C12.775 14.5 13 14.275 13 14V6ZM10.0312 8.78125L8.5625 10.25L10.0312 11.7188C10.325 12.0125 10.325 12.4875 10.0312 12.7781C9.7375 13.0687 9.2625 13.0719 8.97188 12.7781L7.50313 11.3094L6.03438 12.7781C5.74063 13.0719 5.26562 13.0719 4.975 12.7781C4.68437 12.4844 4.68125 12.0094 4.975 11.7188L6.44375 10.25L4.975 8.78125C4.68125 8.4875 4.68125 8.0125 4.975 7.72188C5.26875 7.43125 5.74375 7.42813 6.03438 7.72188L7.50313 9.19063L8.97188 7.72188C9.26562 7.42813 9.74063 7.42813 10.0312 7.72188C10.3219 8.01562 10.325 8.49063 10.0312 8.78125Z" fill="#0F3F4F"/>
+</svg>

+ 1 - 1
assets/icons/events/file-solid.svg

@@ -1,3 +1,3 @@
 <svg width="14" height="19" viewBox="0 0 14 19" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.75 16.8125C12.0594 16.8125 12.3125 16.5594 12.3125 16.25V6.125H9.5C8.87773 6.125 8.375 5.62227 8.375 5V2.1875H2.75C2.44062 2.1875 2.1875 2.44062 2.1875 2.75V16.25C2.1875 16.5594 2.44062 16.8125 2.75 16.8125H11.75ZM0.5 2.75C0.5 1.50898 1.50898 0.5 2.75 0.5H8.56836C9.16602 0.5 9.73906 0.735547 10.1609 1.15742L13.3426 4.33906C13.7645 4.76094 14 5.33398 14 5.93164V16.25C14 17.491 12.991 18.5 11.75 18.5H2.75C1.50898 18.5 0.5 17.491 0.5 16.25V2.75Z" fill="white"/>
+<path d="M11.75 16.8125C12.0594 16.8125 12.3125 16.5594 12.3125 16.25V6.125H9.5C8.87773 6.125 8.375 5.62227 8.375 5V2.1875H2.75C2.44062 2.1875 2.1875 2.44062 2.1875 2.75V16.25C2.1875 16.5594 2.44062 16.8125 2.75 16.8125H11.75ZM0.5 2.75C0.5 1.50898 1.50898 0.5 2.75 0.5H8.56836C9.16602 0.5 9.73906 0.735547 10.1609 1.15742L13.3426 4.33906C13.7645 4.76094 14 5.33398 14 5.93164V16.25C14 17.491 12.991 18.5 11.75 18.5H2.75C1.50898 18.5 0.5 17.491 0.5 16.25V2.75Z"/>
 </svg>

+ 10 - 0
assets/icons/messages/poll.svg

@@ -0,0 +1,10 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4996_44978)">
+<path d="M5.30887 0C2.47227 0 0.166016 2.30625 0.166016 5.14286V30.8571C0.166016 33.6937 2.47227 36 5.30887 36H31.0232C33.8598 36 36.166 33.6937 36.166 30.8571V5.14286C36.166 2.30625 33.8598 0 31.0232 0H5.30887ZM10.4517 15.4286C11.8741 15.4286 13.0232 16.5777 13.0232 18V25.7143C13.0232 27.1366 11.8741 28.2857 10.4517 28.2857C9.02941 28.2857 7.8803 27.1366 7.8803 25.7143V18C7.8803 16.5777 9.02941 15.4286 10.4517 15.4286ZM15.5946 10.2857C15.5946 8.86339 16.7437 7.71429 18.166 7.71429C19.5883 7.71429 20.7374 8.86339 20.7374 10.2857V25.7143C20.7374 27.1366 19.5883 28.2857 18.166 28.2857C16.7437 28.2857 15.5946 27.1366 15.5946 25.7143V10.2857ZM25.8803 20.5714C27.3026 20.5714 28.4517 21.7205 28.4517 23.1429V25.7143C28.4517 27.1366 27.3026 28.2857 25.8803 28.2857C24.458 28.2857 23.3089 27.1366 23.3089 25.7143V23.1429C23.3089 21.7205 24.458 20.5714 25.8803 20.5714Z" fill="#ED9334"/>
+</g>
+<defs>
+<clipPath id="clip0_4996_44978">
+<rect width="36" height="36" fill="white" transform="translate(0.166016)"/>
+</clipPath>
+</defs>
+</svg>

+ 0 - 1
package.json

@@ -90,7 +90,6 @@
     "react-native-svg": "15.2.0",
     "react-native-tab-view": "^3.5.2",
     "react-native-url-polyfill": "^2.0.0",
-    "react-native-video": "^6.5.0",
     "react-native-view-shot": "^3.7.0",
     "react-native-walkthrough-tooltip": "^1.6.0",
     "react-native-wheel-pick": "^1.2.2",

+ 2 - 1
src/components/Calendar/SpinnerDatePicker.tsx

@@ -23,7 +23,7 @@ const SpinnerDatePicker: FC<Props> = ({ selectedDate }) => {
     setValue(selectedSpinnerDate!);
   };
 
-  if (!value) return
+  if (!value) return;
 
   return Platform.OS === 'ios' ? (
     <DateTimePicker
@@ -34,6 +34,7 @@ const SpinnerDatePicker: FC<Props> = ({ selectedDate }) => {
       onChange={onChange}
       minimumDate={new Date(1930, 0, 1)}
       maximumDate={new Date()}
+      timeZoneName="UTC"
     />
   ) : (
     <DatePicker

+ 194 - 0
src/modules/api/events/events-api.ts

@@ -0,0 +1,194 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+import { AxiosRequestConfig } from 'axios';
+
+export interface PostGetEventsListReturn extends ResponseType {
+  data: SingleEvent[];
+}
+
+export type SingleEvent = {
+  id: number;
+  active: 0 | 1;
+  full: 0 | 1;
+  name: string;
+  url: string;
+  date: string;
+  date_from: string;
+  date_to: string;
+  address1: string;
+  location: string;
+  capacity?: number;
+  participants?: number;
+  registrations_info: 1 | 2 | 3 | 4 | 5;
+  type: 1 | 2 | 3;
+  photo: 0 | 1;
+  available?: number;
+  joined: 0 | 1;
+  visible?: 0 | 1;
+  archived?: 0 | 1;
+};
+
+export type Participants = {
+  uid: number;
+  name: string;
+  avatar: string | null;
+};
+
+export type EventSettings = {
+  name: string;
+  url: string;
+  date: string;
+  date_from: string;
+  date_to: string;
+  address1: string;
+  location: string;
+  capacity: number;
+  type: 1 | 2 | 3;
+  registrations_info: 1 | 2 | 3 | 4 | 5;
+  address2: string;
+  details: string;
+  free: 0 | 1;
+  host: string;
+  host_profile: number | null;
+  price: string;
+  shop_url: string;
+  chat_token: string | null;
+  all_participants_visible: 0 | 1;
+  participants_can_add_photos: 0 | 1 | null;
+  participants_can_add_files: 0 | 1 | null;
+  host_data: {
+    first_name: string;
+    last_name: string;
+    avatar: string | null;
+    nm: number | null;
+    un: number | null;
+  };
+};
+
+export type EventAttachments = {
+  id: number;
+  filename: string;
+  filetype: string;
+  description: string;
+  data: 0 | 1;
+  preview: 0 | 1;
+  type: 1 | 2 | 3;
+};
+
+export type EventPhotos = {
+  id: number;
+  filetype: string;
+  data: 0 | 1;
+  preview: 0 | 1;
+  uid: number;
+  name: string;
+  avatar: string | null;
+};
+
+export type EventData = SingleEvent & {
+  event_chat: string | null;
+  settings: EventSettings;
+  participants_data: Participants[];
+  joined: 0 | 1;
+  attachments: EventAttachments[];
+  files: EventAttachments[];
+  photos: EventPhotos[];
+  user_id: number | boolean;
+  photo_available: 0 | 1;
+  photos_left: number;
+};
+
+export interface PostGetEventReturn extends ResponseType {
+  data: EventData;
+}
+
+export interface PostGetMorePhotosReturn extends ResponseType {
+  photos: EventPhotos[];
+  photos_left: number;
+}
+
+export interface PostUploadTempFileReturn extends ResponseType {
+  filetype: string;
+  name: string;
+  temp_name: string;
+}
+
+export interface PostJoinEvent {
+  token: string;
+  id: number;
+}
+
+export interface PostUploadTemp {
+  token: string;
+  file: {
+    type: string;
+    uri: string;
+    name: string;
+  };
+}
+
+export interface PostUploadPhoto {
+  token: string;
+  event_id: number;
+  file: {
+    type: string;
+    uri: string;
+    name: string;
+  };
+}
+
+export interface PostEventAddFile {
+  token: string;
+  type: 1 | 2 | 3;
+  description: string;
+  event_id: number;
+  filetype: string;
+  filename: string;
+  temp_filename: string;
+}
+
+export interface PostDeleteFile {
+  token: string;
+  id: number;
+  event_id: number;
+}
+
+export const eventsApi = {
+  getEventsList: (token: string) =>
+    request.postForm<PostGetEventsListReturn>(API.GET_EVENTS_LIST, { token }),
+  getEvent: (token: string, url: string) =>
+    request.postForm<PostGetEventReturn>(API.GET_EVENT, { token, url }),
+  joinEvent: (data: PostJoinEvent) => request.postForm<ResponseType>(API.JOIN_EVENT, data),
+  unjoinEvent: (data: PostJoinEvent) => request.postForm<ResponseType>(API.UNJOIN_EVENT, data),
+  uploadTempFile: (data: PostUploadTemp, config?: AxiosRequestConfig) => {
+    const formData = new FormData();
+
+    formData.append('token', data.token);
+    formData.append('file', {
+      ...data.file
+    } as unknown as Blob);
+
+    return request.postForm<PostUploadTempFileReturn>(API.UPLOAD_TEMP_FILE, formData, {
+      ...config
+    });
+  },
+  eventAddFile: (data: PostEventAddFile) =>
+    request.postForm<ResponseType>(API.EVENT_ADD_FILE, data),
+  deleteFile: (data: PostDeleteFile) => request.postForm<ResponseType>(API.DELETE_FILE, data),
+  uploadPhoto: (data: PostUploadPhoto, config?: AxiosRequestConfig) => {
+    const formData = new FormData();
+
+    formData.append('token', data.token);
+    formData.append('event_id', JSON.stringify(data.event_id));
+    formData.append('file', {
+      ...data.file
+    } as unknown as Blob);
+
+    return request.postForm<PostUploadTempFileReturn>(API.UPLOAD_PHOTO_EVENT, formData, {
+      ...config
+    });
+  },
+  getMorePhotos: (token: string, event_id: number, last_id: number) =>
+    request.postForm<PostGetMorePhotosReturn>(API.GET_MORE_PHOTOS, { token, event_id, last_id })
+};

+ 12 - 0
src/modules/api/events/events-query-keys.tsx

@@ -0,0 +1,12 @@
+export const eventsQueryKeys = {
+  getEventsList: (token: string) => ['getEventsList', token] as const,
+  getEvent: (token: string, url: string) => ['getEvent', { token, url }] as const,
+  joinEvent: () => ['joinEvent'] as const,
+  unjoinEvent: () => ['unjoinEvent'] as const,
+  uploadTempFile: () => ['uploadTempFile'] as const,
+  eventAddFile: () => ['eventAddFile'] as const,
+  deleteFile: () => ['deleteFile'] as const,
+  uploadPhoto: () => ['uploadPhoto'] as const,
+  getMorePhotos: (token: string, event_id: number, last_id: number) =>
+    ['getMorePhotos', { token, event_id, last_id }] as const
+};

+ 3 - 0
src/modules/api/events/index.ts

@@ -0,0 +1,3 @@
+export * from './queries';
+export * from './events-api';
+export * from './events-query-keys';

+ 9 - 0
src/modules/api/events/queries/index.ts

@@ -0,0 +1,9 @@
+export * from './use-post-get-event-list';
+export * from './use-post-get-event';
+export * from './use-post-join-event';
+export * from './use-post-unjoin-event';
+export * from './use-post-upload-temp-file';
+export * from './use-post-event-add-file';
+export * from './use-post-delete-file';
+export * from './use-post-upload-photo';
+export * from './use-post-get-more-photos';

+ 17 - 0
src/modules/api/events/queries/use-post-delete-file.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { type PostDeleteFile, eventsApi } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostDeleteFileMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostDeleteFile, ResponseType>({
+    mutationKey: eventsQueryKeys.deleteFile(),
+    mutationFn: async (data) => {
+      const response = await eventsApi.deleteFile(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/events/queries/use-post-event-add-file.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { type PostEventAddFile, eventsApi } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostEventAddFileMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostEventAddFile, ResponseType>({
+    mutationKey: eventsQueryKeys.eventAddFile(),
+    mutationFn: async (data) => {
+      const response = await eventsApi.eventAddFile(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/events/queries/use-post-get-event-list.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { eventsApi, type PostGetEventsListReturn } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetEventsListQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetEventsListReturn, BaseAxiosError>({
+    queryKey: eventsQueryKeys.getEventsList(token),
+    queryFn: async () => {
+      const response = await eventsApi.getEventsList(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/events/queries/use-post-get-event.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { eventsApi, type PostGetEventReturn } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetEventQuery = (token: string, url: string, enabled: boolean) => {
+  return useQuery<PostGetEventReturn, BaseAxiosError>({
+    queryKey: eventsQueryKeys.getEvent(token, url),
+    queryFn: async () => {
+      const response = await eventsApi.getEvent(token, url);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 22 - 0
src/modules/api/events/queries/use-post-get-more-photos.tsx

@@ -0,0 +1,22 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { eventsApi, type PostGetMorePhotosReturn } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetMorePhotosQuery = (
+  token: string,
+  event_id: number,
+  last_id: number,
+  enabled: boolean
+) => {
+  return useQuery<PostGetMorePhotosReturn, BaseAxiosError>({
+    queryKey: eventsQueryKeys.getMorePhotos(token, event_id, last_id),
+    queryFn: async () => {
+      const response = await eventsApi.getMorePhotos(token, event_id, last_id);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/events/queries/use-post-join-event.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { type PostJoinEvent, eventsApi } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostJoinEventMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostJoinEvent, ResponseType>({
+    mutationKey: eventsQueryKeys.joinEvent(),
+    mutationFn: async (data) => {
+      const response = await eventsApi.joinEvent(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/events/queries/use-post-unjoin-event.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { type PostJoinEvent, eventsApi } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostUnjoinEventMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostJoinEvent, ResponseType>({
+    mutationKey: eventsQueryKeys.unjoinEvent(),
+    mutationFn: async (data) => {
+      const response = await eventsApi.unjoinEvent(data);
+      return response.data;
+    }
+  });
+};

+ 35 - 0
src/modules/api/events/queries/use-post-upload-photo.tsx

@@ -0,0 +1,35 @@
+import { useMutation } from '@tanstack/react-query';
+import { AxiosProgressEvent, AxiosRequestConfig } from 'axios';
+import { eventsQueryKeys } from '../events-query-keys';
+import { eventsApi, PostUploadTempFileReturn, type PostUploadPhoto } from '../events-api';
+import { type BaseAxiosError } from '../../../../types';
+
+export interface UseUploadPhotoVariables extends PostUploadPhoto {
+  onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
+}
+
+export const usePostUploadPhotoMutation = () => {
+  return useMutation<
+    PostUploadTempFileReturn,
+    BaseAxiosError,
+    UseUploadPhotoVariables,
+    PostUploadTempFileReturn
+  >({
+    mutationKey: eventsQueryKeys.uploadPhoto(),
+    mutationFn: async (variables) => {
+      const { token, event_id, file, onUploadProgress } = variables;
+
+      const config: AxiosRequestConfig = {
+        onUploadProgress: (progressEvent: AxiosProgressEvent) => {
+          if (onUploadProgress) {
+            onUploadProgress(progressEvent);
+          }
+        }
+      };
+
+      const response = await eventsApi.uploadPhoto({ token, event_id, file }, config);
+
+      return response.data;
+    }
+  });
+};

+ 35 - 0
src/modules/api/events/queries/use-post-upload-temp-file.tsx

@@ -0,0 +1,35 @@
+import { useMutation } from '@tanstack/react-query';
+import { AxiosProgressEvent, AxiosRequestConfig } from 'axios';
+import { eventsQueryKeys } from '../events-query-keys';
+import { eventsApi, PostUploadTempFileReturn, type PostUploadTemp } from '../events-api';
+import { type BaseAxiosError } from '../../../../types';
+
+export interface UseUploadTempFileVariables extends PostUploadTemp {
+  onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
+}
+
+export const usePostUploadTempFileMutation = () => {
+  return useMutation<
+    PostUploadTempFileReturn,
+    BaseAxiosError,
+    UseUploadTempFileVariables,
+    PostUploadTempFileReturn
+  >({
+    mutationKey: eventsQueryKeys.uploadTempFile(),
+    mutationFn: async (variables) => {
+      const { token, file, onUploadProgress } = variables;
+
+      const config: AxiosRequestConfig = {
+        onUploadProgress: (progressEvent: AxiosProgressEvent) => {
+          if (onUploadProgress) {
+            onUploadProgress(progressEvent);
+          }
+        }
+      };
+
+      const response = await eventsApi.uploadTempFile({ token, file }, config);
+
+      return response.data;
+    }
+  });
+};

+ 137 - 0
src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen/index.tsx

@@ -0,0 +1,137 @@
+import React, { useEffect, useState } from 'react';
+import { View, FlatList, Image, Text, TouchableOpacity } from 'react-native';
+import ImageView from 'better-react-native-image-viewing';
+import { useNavigation } from '@react-navigation/native';
+
+import { Header, PageWrapper } from 'src/components';
+
+import { API_HOST } from 'src/constants';
+import { Colors } from 'src/theme';
+import { NAVIGATION_PAGES } from 'src/types';
+import { StoreType, storage } from 'src/storage';
+import { getImageUri, itemWidth } from '../utils';
+import { styles } from './styles';
+
+import { useGetMorePhotosQuery } from '@api/events';
+
+const AllEventPhotosScreen = ({ route }: { route: any }) => {
+  const { data, eventId, lastPhotoId, photosCountLeft } = route.params;
+  const token = storage.get('token', StoreType.STRING) as string;
+  const navigation = useNavigation();
+  const [lastId, setLastId] = useState(lastPhotoId);
+  const [photosLeft, setPhotosLeft] = useState(photosCountLeft);
+  const { data: morePhotos } = useGetMorePhotosQuery(
+    token,
+    eventId,
+    lastId,
+    photosLeft > 0 ? true : false
+  );
+
+  const [photos, setPhotos] = useState<any[]>(data);
+  const [fullImages, setFullImages] = useState<any[]>([]);
+  const [currentImageIndex, setCurrentImageIndex] = useState(0);
+  const [modalState, setModalState] = useState({
+    isViewerVisible: false
+  });
+
+  useEffect(() => {
+    setFullImages(
+      photos
+        .filter((p: any) => p.data)
+        .map((photo: any) => ({
+          ...photo,
+          uri: API_HOST + '/webapi/events/get-photo/' + eventId + '/' + photo.id
+        }))
+    );
+    setLastId(photos[photos.length - 1].id);
+  }, [photos]);
+
+  useEffect(() => {
+    if (morePhotos && morePhotos.photos) {
+      setPhotos((prev) => [...prev, ...morePhotos.photos]);
+      setPhotosLeft(morePhotos.photos_left);
+    }
+  }, [morePhotos]);
+
+  const openImageViewer = (index: number) => {
+    setCurrentImageIndex(index);
+    openModal('isViewerVisible');
+  };
+
+  const renderItem = ({ item, index }: { item: any; index: number }) => (
+    <View style={{ alignItems: 'center', marginVertical: 8 }}>
+      <TouchableOpacity style={{ position: 'relative' }} onPress={() => openImageViewer(index)}>
+        <Image
+          source={{
+            uri: getImageUri('/webapi/events/get-photo-preview/' + eventId + '/' + item.id)
+          }}
+          style={[styles.image, { width: itemWidth, height: itemWidth }]}
+        />
+      </TouchableOpacity>
+    </View>
+  );
+
+  const closeModal = (modalName: string) => {
+    setModalState((prevState) => ({ ...prevState, [modalName]: false }));
+  };
+
+  const openModal = (modalName: string) => {
+    setModalState((prevState) => ({ ...prevState, [modalName]: true }));
+  };
+
+  return (
+    <PageWrapper style={{ position: 'relative' }}>
+      <Header label={'Photos'} />
+
+      <FlatList
+        data={photos}
+        renderItem={renderItem}
+        keyExtractor={(item) => item.id.toString()}
+        numColumns={2}
+        columnWrapperStyle={styles.columnWrapper}
+        showsVerticalScrollIndicator={false}
+      />
+
+      <ImageView
+        images={fullImages}
+        keyExtractor={(imageSrc, index) => index.toString()}
+        imageIndex={currentImageIndex}
+        visible={modalState.isViewerVisible}
+        onRequestClose={() => closeModal('isViewerVisible')}
+        swipeToCloseEnabled={false}
+        backgroundColor={Colors.DARK_BLUE}
+        FooterComponent={({ imageIndex }) => (
+          <View style={styles.imageFooter}>
+            <TouchableOpacity
+              onPress={() => {
+                closeModal('isViewerVisible');
+                navigation.navigate(
+                  ...([
+                    NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
+                    { userId: fullImages[imageIndex].uid }
+                  ] as never)
+                );
+              }}
+              disabled={!fullImages[imageIndex].uid}
+              style={styles.imageOwner}
+            >
+              {fullImages[imageIndex].avatar ? (
+                <Image
+                  source={{ uri: API_HOST + fullImages[imageIndex].avatar }}
+                  style={{
+                    width: 28,
+                    height: 28,
+                    borderRadius: 14
+                  }}
+                />
+              ) : null}
+              <Text style={styles.imageOwnerText}>{fullImages[imageIndex].name}</Text>
+            </TouchableOpacity>
+          </View>
+        )}
+      />
+    </PageWrapper>
+  );
+};
+
+export default AllEventPhotosScreen;

+ 24 - 0
src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen/styles.tsx

@@ -0,0 +1,24 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  columnWrapper: {
+    justifyContent: 'space-between'
+  },
+  image: {
+    borderRadius: 8
+  },
+  imageFooter: { paddingBottom: 50, paddingHorizontal: 16, gap: 16 },
+  imageOwner: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 6,
+    backgroundColor: 'rgba(255, 255, 255, 0.7)',
+    paddingVertical: 6,
+    paddingHorizontal: 12,
+    borderRadius: 8,
+    alignSelf: 'center'
+  },
+  imageOwnerText: { color: Colors.DARK_BLUE, fontFamily: 'montserrat-700' }
+});

+ 13 - 0
src/screens/InAppScreens/TravelsScreen/Components/styles.tsx

@@ -207,4 +207,17 @@ export const styles = StyleSheet.create({
     fontSize: 15,
     color: Colors.LIGHT_GRAY
   },
+  imageFooter: { paddingBottom: 50, paddingHorizontal: 16, gap: 16 },
+  imageOwner: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 6,
+    backgroundColor: 'rgba(255, 255, 255, 0.7)',
+    paddingVertical: 6,
+    paddingHorizontal: 12,
+    borderRadius: 8,
+    alignSelf: 'center'
+  },
+  imageOwnerText: { color: Colors.DARK_BLUE, fontFamily: 'montserrat-700' }
 });

+ 228 - 0
src/screens/InAppScreens/TravelsScreen/EventScreen/PhotoItem/index.tsx

@@ -0,0 +1,228 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { View, Image, Text, TouchableOpacity } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import * as Progress from 'react-native-progress';
+
+import { getImageUri, photoStyles, smallPhotoHeight, smallPhotoWidth } from '../../utils';
+import { NAVIGATION_PAGES } from 'src/types';
+import { styles } from '../../Components/styles';
+
+import { API_HOST } from 'src/constants';
+import ImageView from 'better-react-native-image-viewing';
+import { EventPhotos } from '@api/events';
+import { ImageSource } from 'expo-image';
+import { Colors } from 'src/theme';
+
+type Photo = {
+  id: number;
+  filetype: string;
+  data: 0 | 1;
+  preview: 0 | 1;
+  uid: number;
+  name: string;
+  avatar: string | null;
+  uri: string;
+  isSending?: boolean;
+};
+
+export const PhotoItem = React.memo(
+  ({ photos, eventId, photosLeft }: { photos: any; eventId: number; photosLeft: number }) => {
+    const navigation = useNavigation();
+    const [currentImageIndex, setCurrentImageIndex] = useState(0);
+    const [isViewerVisible, setIsViewerVisible] = useState(false);
+    const [fullImages, setFullImages] = useState<EventPhotos[]>([]);
+
+    const openImageViewer = (index: number) => {
+      setCurrentImageIndex(index);
+      setIsViewerVisible(true);
+    };
+
+    useEffect(() => {
+      setFullImages(
+        photos
+          .filter((p: EventPhotos) => p.data)
+          .map((photo: Photo) => ({
+            ...photo,
+            uri: API_HOST + '/webapi/events/get-photo/' + eventId + '/' + photo.id
+          }))
+      );
+    }, [photos]);
+
+    const renderSpinner = (style: any) => (
+      <View
+        style={[
+          style,
+          {
+            alignItems: 'center',
+            justifyContent: 'center',
+            backgroundColor: Colors.FILL_LIGHT
+          }
+        ]}
+      >
+        <Progress.CircleSnail
+          borderWidth={0}
+          color={Colors.DARK_BLUE}
+          unfilledColor="rgba(0, 0, 0, 0.1)"
+        />
+      </View>
+    );
+
+    const renderPhotos = () => {
+      const photosToShow = photos.filter((p: EventPhotos) => p.preview).slice(0, 3);
+      const morePhotosCount = photos.length - 3 + photosLeft;
+
+      if (photosToShow.length === 1) {
+        return (
+          <TouchableOpacity onPress={() => openImageViewer(0)}>
+            {photosToShow[0].isSending ? (
+              renderSpinner(photoStyles.onePhoto)
+            ) : (
+              <Image
+                source={{
+                  uri: getImageUri(
+                    '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[0].id
+                  )
+                }}
+                style={photoStyles.onePhoto}
+              />
+            )}
+          </TouchableOpacity>
+        );
+      } else if (photosToShow.length === 2) {
+        return photosToShow.map((photo: Photo, index: number) => (
+          <TouchableOpacity key={photo.id} onPress={() => openImageViewer(index)}>
+            {photo?.isSending ? (
+              renderSpinner({ ...photoStyles.twoPhotos, marginRight: index === 0 ? 8 : 0 })
+            ) : (
+              <Image
+                source={{
+                  uri: getImageUri(
+                    '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow.id
+                  )
+                }}
+                style={[photoStyles.twoPhotos, index === 0 && { marginRight: 8 }]}
+              />
+            )}
+          </TouchableOpacity>
+        ));
+      } else {
+        return (
+          <View style={{ flexDirection: 'row' }}>
+            <TouchableOpacity onPress={() => openImageViewer(0)}>
+              {photosToShow[0].isSending ? (
+                renderSpinner(photoStyles.bigPhoto)
+              ) : (
+                <Image
+                  source={{
+                    uri: getImageUri(
+                      '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[0].id
+                    )
+                  }}
+                  style={photoStyles.bigPhoto}
+                />
+              )}
+            </TouchableOpacity>
+
+            <View style={styles.smallPhotoContainer}>
+              <TouchableOpacity onPress={() => openImageViewer(1)}>
+                {photosToShow[1].isSending ? (
+                  renderSpinner(photoStyles.smallPhoto)
+                ) : (
+                  <Image
+                    source={{
+                      uri: getImageUri(
+                        '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[1].id
+                      )
+                    }}
+                    style={photoStyles.smallPhoto}
+                  />
+                )}
+              </TouchableOpacity>
+              <TouchableOpacity onPress={() => openImageViewer(2)}>
+                <Image
+                  source={{
+                    uri: getImageUri(
+                      '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[2].id
+                    )
+                  }}
+                  style={[photoStyles.smallPhoto, { position: 'relative' }]}
+                />
+              </TouchableOpacity>
+              {morePhotosCount > 0 && (
+                <TouchableOpacity
+                  onPress={() => handlePress()}
+                  style={[
+                    styles.morePhotosOverlay,
+                    { width: smallPhotoWidth, height: smallPhotoHeight }
+                  ]}
+                >
+                  <Text style={styles.morePhotosText}>+{morePhotosCount}</Text>
+                </TouchableOpacity>
+              )}
+            </View>
+          </View>
+        );
+      }
+    };
+
+    const photoViews = useMemo(() => renderPhotos(), [photos]);
+
+    const handlePress = useCallback(() => {
+      navigation.navigate(
+        ...([
+          NAVIGATION_PAGES.ALL_EVENT_PHOTOS,
+          {
+            data: photos,
+            eventId,
+            lastPhotoId: photos[photos.length - 1].id,
+            photosCountLeft: photosLeft
+          }
+        ] as never)
+      );
+    }, [navigation, photos]);
+
+    return (
+      <View>
+        {photoViews}
+        <ImageView
+          images={fullImages as ImageSource[]}
+          keyExtractor={(imageSrc, index) => index.toString()}
+          imageIndex={currentImageIndex}
+          visible={isViewerVisible}
+          onRequestClose={() => setIsViewerVisible(false)}
+          swipeToCloseEnabled={false}
+          backgroundColor={Colors.DARK_BLUE}
+          FooterComponent={({ imageIndex }) => (
+            <View style={styles.imageFooter}>
+              <TouchableOpacity
+                onPress={() => {
+                  setIsViewerVisible(false);
+                  navigation.navigate(
+                    ...([
+                      NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
+                      { userId: fullImages[imageIndex].uid }
+                    ] as never)
+                  );
+                }}
+                disabled={!fullImages[imageIndex].uid}
+                style={styles.imageOwner}
+              >
+                {fullImages[imageIndex].avatar ? (
+                  <Image
+                    source={{ uri: API_HOST + fullImages[imageIndex].avatar }}
+                    style={{
+                      width: 28,
+                      height: 28,
+                      borderRadius: 14
+                    }}
+                  />
+                ) : null}
+                <Text style={styles.imageOwnerText}>{fullImages[imageIndex].name}</Text>
+              </TouchableOpacity>
+            </View>
+          )}
+        />
+      </View>
+    );
+  }
+);

+ 1078 - 377
src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx

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

+ 3 - 4
src/screens/InAppScreens/TravelsScreen/EventScreen/styles.tsx

@@ -6,7 +6,7 @@ export const styles = StyleSheet.create({
   container: {
     flex: 1,
     backgroundColor: 'white',
-    position: 'relative'
+    position: 'relative',
   },
   chevronWrapper: {
     width: 42,
@@ -84,7 +84,7 @@ export const styles = StyleSheet.create({
   },
   stats: {
     flexDirection: 'row',
-    justifyContent: 'space-between'
+    justifyContent: 'space-between',
   },
   icon: {
     width: 28,
@@ -123,7 +123,6 @@ export const styles = StyleSheet.create({
     width: 28,
     height: 28,
     borderRadius: 14,
-    marginLeft: -6,
     borderWidth: 1,
     borderColor: Colors.DARK_LIGHT,
     resizeMode: 'cover'
@@ -135,7 +134,7 @@ export const styles = StyleSheet.create({
     backgroundColor: Colors.DARK_LIGHT,
     alignItems: 'center',
     justifyContent: 'center',
-    marginLeft: -6
+    marginLeft: -10
   },
   userCount: {
     fontSize: 12,

+ 143 - 127
src/screens/InAppScreens/TravelsScreen/EventsScreen/index.tsx

@@ -1,5 +1,5 @@
-import React, { FC, useCallback, useEffect, useState } from 'react';
-import { View, Text, Image, TouchableOpacity, Linking, Dimensions, TextInput } from 'react-native';
+import React, { useEffect, useState } from 'react';
+import { View, Text, Image, TouchableOpacity } from 'react-native';
 import { FlashList } from '@shopify/flash-list';
 import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
 import { styles } from './styles';
@@ -7,7 +7,6 @@ import { styles } from './styles';
 import { NAVIGATION_PAGES } from 'src/types';
 import { StoreType, storage } from 'src/storage';
 
-import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
 import { Header, Input, PageWrapper } from 'src/components';
 import { Colors } from 'src/theme';
 
@@ -17,150 +16,166 @@ import EarthIcon from 'assets/icons/travels-section/earth.svg';
 import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
 import CalendarPlusIcon from 'assets/icons/events/calendar-plus.svg';
 import ShoppingCartIcon from 'assets/icons/events/shopping-cart.svg';
-
-const testData = [
-  {
-    id: '1',
-    title: 'Meeting in San Clemente',
-    date: 'Early February, 2025',
-    location: 'San Clemente, California, USA',
-    spots: '164/300',
-    image: 'https://nomadmania.com/ajax/pic/27519',
-    isPaid: true,
-    status: null
-  },
-  {
-    id: '2',
-    title: 'Meeting in Singapore',
-    date: '08 February, 2025',
-    location: 'Singapore',
-    spots: '250/250',
-    image: 'https://nomadmania.com/ajax/pic/27520',
-    isPaid: true,
-    status: 'FULL'
-  },
-  {
-    id: '3',
-    title: 'Meeting in Patras',
-    date: '14 February, 2025',
-    location: 'Patras, Greece',
-    spots: '119/200',
-    image: 'https://nomadmania.com/ajax/pic/27521',
-    isPaid: false,
-    status: null
-  },
-  {
-    id: '4',
-    title: 'Nepal (including the MusNepal (including the MusNepal (including the Mus',
-    date: 'March 27 – April 7, 2025',
-    location: 'Nepal',
-    spots: '71/150',
-    image: 'https://nomadmania.com/ajax/pic/27522',
-    isPaid: true,
-    status: 'TOUR'
-  },
-  {
-    id: '5',
-    title: 'Meeting in Nicosia, Cyprus',
-    date: '15 April, 2025',
-    location: 'Nicosia, Cyprus',
-    spots: null,
-    image: 'https://nomadmania.com/ajax/pic/27523',
-    isPaid: false,
-    status: null
-  }
-];
-
-const renderEventCard = ({ item }: { item: (typeof testData)[0] }) => {
-  return (
-    <TouchableOpacity
-      style={[styles.card, item.status ? { backgroundColor: Colors.FILL_LIGHT } : {}]}
-    >
-      <View style={styles.imageWrapper}>
-        <Image source={{ uri: item.image }} style={styles.image} />
-        {item.isPaid && (
-          <View style={styles.iconOverlay}>
-            <ShoppingCartIcon fill={Colors.WHITE} width={12} />
-          </View>
-        )}
-      </View>
-
-      <View style={styles.info}>
-        <Text style={styles.title} numberOfLines={1}>
-          {item.title}
-        </Text>
-
-        <View style={styles.row}>
-          <CalendarIcon fill={Colors.DARK_BLUE} height={14} width={14} />
-          <Text style={styles.dateAndLocation} numberOfLines={1}>
-            {item.date}
-          </Text>
-        </View>
-
-        <View style={styles.row}>
-          <EarthIcon fill={Colors.DARK_BLUE} height={14} width={14} />
-          <Text style={styles.dateAndLocation} numberOfLines={1}>
-            {item.location}
-          </Text>
-        </View>
-
-        {item.spots && (
-          <View style={styles.row}>
-            <NomadsIcon fill={Colors.DARK_BLUE} height={14} width={14} />
-            <Text style={styles.dateAndLocation} numberOfLines={1}>
-              <Text style={{ fontWeight: '700' }}>{item.spots}</Text> Spots Available
-            </Text>
-          </View>
-        )}
-      </View>
-
-      {item.status && (
-        <View
-          style={[styles.statusBadge, item.status === 'FULL' ? styles.fullBadge : styles.tourBadge]}
-        >
-          <View style={styles.rotatedContainer}>
-            <Text style={styles.statusText}>{item.status}</Text>
-          </View>
-        </View>
-      )}
-    </TouchableOpacity>
-  );
-};
+import { SingleEvent, useGetEventsListQuery } from '@api/events';
+import moment from 'moment';
+import { API_HOST } from 'src/constants';
+import { Grayscale } from 'react-native-color-matrix-image-filters';
+import { renderSpotsText } from './utils';
 
 const EventsScreen = () => {
   const token = (storage.get('token', StoreType.STRING) as string) ?? null;
+  const { data, refetch } = useGetEventsListQuery(token, true);
   const navigation = useNavigation();
   const [searchQuery, setSearchQuery] = useState('');
-  const [filteredData, setFilteredData] = useState(testData);
+  const [events, setEvents] = useState<SingleEvent[]>([]);
+  const [filteredEvents, setFilteredEvents] = useState<SingleEvent[]>([]);
+
+  useEffect(() => {
+    if (data && data.data) {
+      setEvents(data.data);
+      setFilteredEvents(data.data);
+    }
+  }, [data]);
+
+  useFocusEffect(() => {
+    refetch();
+  });
 
   const handleSearch = (text: string) => {
     if (text) {
       const searchData =
-        testData?.filter((item: any) => {
-          const itemData = item.title ? item.title.toLowerCase() : ''.toLowerCase();
+        events.filter((item: any) => {
+          const itemData = item.name ? item.name.toLowerCase() : ''.toLowerCase();
           const textData = text.toLowerCase();
           return itemData.indexOf(textData) > -1;
         }) ?? [];
-      setFilteredData(searchData);
+      setFilteredEvents(searchData);
       setSearchQuery(text);
     } else {
-      setFilteredData(testData);
+      setFilteredEvents(events);
       setSearchQuery(text);
     }
   };
 
+  const formatEventDate = (event: SingleEvent) => {
+    if (event.date_from && event.date_to) {
+      return `${moment(event.date_from, 'YYYY-MM-DD').format('DD MMM YYYY')} - ${moment(event.date_to, 'YYYY-MM-DD').format('DD MMM YYYY')}`;
+    } else {
+      return moment(event.date, 'YYYY-MM-DD').format('DD MMM YYYY');
+    }
+  };
+
+  const renderEventCard = ({ item }: { item: SingleEvent }) => {
+    let staticImgUrl = '/static/img/events/meeting.webp';
+    let badgeColor = Colors.DARK_BLUE;
+    let badgeText = '';
+    if (item.full) {
+      badgeColor = Colors.LIGHT_GRAY;
+      badgeText = 'FULL';
+    } else if (item.type === 2) {
+      badgeColor = Colors.ORANGE;
+      badgeText = 'TOUR';
+      staticImgUrl = '/static/img/events/trip.webp';
+    } else if (item.type === 3) {
+      badgeColor = Colors.DARK_BLUE;
+      badgeText = 'CONF';
+      staticImgUrl = '/static/img/events/conference.webp';
+    }
+
+    const photo = item.photo
+      ? API_HOST + '/webapi/events/get-square-photo/' + item.id
+      : API_HOST + staticImgUrl;
+
+    return (
+      <Grayscale amount={item.active === 0 ? 1 : 0}>
+        <TouchableOpacity
+          style={[
+            styles.card,
+            item.type === 2 || item.type === 3 || item.full
+              ? { backgroundColor: Colors.FILL_LIGHT }
+              : {}
+          ]}
+          onPress={() =>
+            navigation.navigate(...([NAVIGATION_PAGES.EVENT, { url: item.url }] as never))
+          }
+          disabled={item.active === 0}
+        >
+          <View style={styles.imageWrapper}>
+            <Image source={{ uri: photo }} style={styles.image} resizeMode="cover" />
+
+            {/* {item.isPaid && (
+          <View style={styles.iconOverlay}>
+            <ShoppingCartIcon fill={Colors.WHITE} width={12} />
+          </View>
+        )} */}
+
+            {item.joined ? (
+              <View style={styles.joinedOverlay}>
+                <Text style={{ color: Colors.WHITE, fontSize: 12, fontFamily: 'redhat-700' }}>
+                  Joined
+                </Text>
+              </View>
+            ) : null}
+          </View>
+
+          <View style={styles.info}>
+            <Text style={styles.title} numberOfLines={2}>
+              {item.name}
+            </Text>
+
+            <View style={styles.row}>
+              <CalendarIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+              <Text style={styles.dateAndLocation} numberOfLines={1}>
+                {formatEventDate(item)}
+              </Text>
+            </View>
+
+            <View style={styles.row}>
+              <EarthIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+              <Text style={styles.dateAndLocation} numberOfLines={1}>
+                {item.address1}
+              </Text>
+            </View>
+
+            {item.registrations_info !== 1 && (
+              <View style={styles.row}>
+                <NomadsIcon fill={Colors.DARK_BLUE} height={14} width={14} />
+                <Text style={styles.dateAndLocation} numberOfLines={1}>
+                  {renderSpotsText(item)}
+                </Text>
+              </View>
+            )}
+          </View>
+
+          {item.type !== 1 || item.full ? (
+            <View
+              style={[
+                styles.statusBadge,
+                { backgroundColor: item.active ? badgeColor : Colors.LIGHT_GRAY }
+              ]}
+            >
+              <View style={styles.rotatedContainer}>
+                <Text style={styles.statusText}>{badgeText}</Text>
+              </View>
+            </View>
+          ) : null}
+        </TouchableOpacity>
+      </Grayscale>
+    );
+  };
+
   return (
     <PageWrapper>
       <Header
         label="Events"
-        rightElement={
-          <TouchableOpacity
-            onPress={() => navigation.navigate(NAVIGATION_PAGES.EVENT as never)}
-            style={{ width: 30 }}
-          >
-            <CalendarPlusIcon fill={Colors.DARK_BLUE} />
-          </TouchableOpacity>
-        }
+        // rightElement={
+        //   <TouchableOpacity
+        //     onPress={() => navigation.navigate(NAVIGATION_PAGES.CREATE_EVENT as never)}
+        //     style={{ width: 30 }}
+        //   >
+        //     <CalendarPlusIcon fill={Colors.DARK_BLUE} />
+        //   </TouchableOpacity>
+        // }
       />
 
       <View style={styles.searchContainer}>
@@ -175,11 +190,12 @@ const EventsScreen = () => {
       </View>
 
       <FlashList
-        data={filteredData}
-        keyExtractor={(item) => item.id}
+        data={filteredEvents}
+        keyExtractor={(item) => item.id.toString()}
         renderItem={renderEventCard}
         estimatedItemSize={100}
         contentContainerStyle={styles.listContainer}
+        showsVerticalScrollIndicator={false}
       />
     </PageWrapper>
   );

+ 15 - 3
src/screens/InAppScreens/TravelsScreen/EventsScreen/styles.tsx

@@ -5,7 +5,6 @@ import { getFontSize } from 'src/utils';
 export const styles = StyleSheet.create({
   listContainer: {
     paddingTop: 12
-    // paddingBottom: 20,
   },
   searchContainer: {
     paddingBottom: 6
@@ -22,7 +21,8 @@ export const styles = StyleSheet.create({
   },
   image: {
     width: 86,
-    height: 86,
+    height: '100%',
+    minHeight: 86,
     borderRadius: 8
   },
   iconOverlay: {
@@ -37,6 +37,19 @@ export const styles = StyleSheet.create({
     justifyContent: 'center',
     alignItems: 'center'
   },
+  joinedOverlay: {
+    position: 'absolute',
+    bottom: 0,
+    left: 0,
+    height: 20,
+    paddingHorizontal: 6,
+    backgroundColor: Colors.ORANGE,
+    borderTopRightRadius: 8,
+    borderBottomRightRadius: 8,
+    borderBottomLeftRadius: 8,
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
   info: {
     flex: 1,
     paddingVertical: 5,
@@ -80,7 +93,6 @@ export const styles = StyleSheet.create({
     height: 23,
     width: 80
   },
-
   statusText: {
     color: Colors.WHITE,
     fontSize: getFontSize(16),

+ 37 - 0
src/screens/InAppScreens/TravelsScreen/EventsScreen/utils.tsx

@@ -0,0 +1,37 @@
+import { SingleEvent } from '@api/events';
+import { Text } from 'react-native';
+
+export const renderSpotsText = (e: SingleEvent) => {
+  switch (e.registrations_info) {
+    case 2:
+      return (
+        <Text>
+          <Text style={{ fontWeight: '700' }}>{(e.capacity ?? 0) - (e.available ?? 0)}</Text> total
+          spots available
+        </Text>
+      );
+    case 3:
+      return (
+        <Text>
+          <Text style={{ fontWeight: '700' }}>{e.available}</Text> spots remaining
+        </Text>
+      );
+    case 4:
+      return (
+        <Text>
+          <Text style={{ fontWeight: '700' }}>{e.participants}</Text> Nomads joined
+        </Text>
+      );
+    case 5:
+      return (
+        <Text>
+          <Text style={{ fontWeight: '700' }}>
+            {e.available ?? (e.capacity ?? 0) - (e.participants ?? 0)} / {e.capacity}
+          </Text>{' '}
+          spots available
+        </Text>
+      );
+    default:
+      return '';
+  }
+};

+ 2 - 2
src/screens/InAppScreens/TravelsScreen/index.tsx

@@ -34,8 +34,8 @@ const TravelsScreen = () => {
     { label: 'Earth', icon: EarthIcon, page: NAVIGATION_PAGES.EARTH },
     { label: 'Trips', icon: TripIcon, page: NAVIGATION_PAGES.TRIPS },
     { label: 'Photos', icon: ImagesIcon, page: NAVIGATION_PAGES.PHOTOS },
-    { label: 'Fixers', icon: FixersIcon, page: NAVIGATION_PAGES.FIXERS }
-    // { label: 'Events', icon: CalendarIcon, page: NAVIGATION_PAGES.EVENTS }
+    { label: 'Fixers', icon: FixersIcon, page: NAVIGATION_PAGES.FIXERS },
+    { label: 'Events', icon: CalendarIcon, page: NAVIGATION_PAGES.EVENTS }
   ];
 
   const handlePress = (page: string) => {

+ 23 - 3
src/types/api.ts

@@ -24,7 +24,9 @@ export enum API_ROUTE {
   CHAT = 'chat',
   MAPS = 'maps',
   DARE = 'dare',
-  LOCATION = 'location'
+  LOCATION = 'location',
+  EVENTS = 'events',
+  FILES = 'files'
 }
 
 export enum API_ENDPOINT {
@@ -185,7 +187,16 @@ export enum API_ENDPOINT {
   SET_PIN_GROUP_MESSAGE = 'set-pin-group-message',
   EDIT_GROUP_MESSAGE = 'edit-group-message',
   EDIT_MESSAGE = 'edit-message',
-  CAN_SEND_MESSAGE = 'can-send-message'
+  CAN_SEND_MESSAGE = 'can-send-message',
+  GET_EVENTS_LIST = 'get-event-list',
+  GET_EVENT = 'get-event',
+  JOIN_EVENT = 'join-event',
+  UNJOIN_EVENT = 'unjoin-event',
+  UPLOAD_TEMP_FILE = 'upload-temp-file',
+  EVENT_ADD_FILE = 'event-add-file',
+  DELETE_FILE = 'delete-file',
+  UPLOAD_PHOTO_EVENT = 'upload-photo',
+  GET_MORE_PHOTOS = 'get-more-photos'
 }
 
 export enum API {
@@ -345,7 +356,16 @@ export enum API {
   SET_PIN_GROUP_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.SET_PIN_GROUP_MESSAGE}`,
   EDIT_GROUP_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.EDIT_GROUP_MESSAGE}`,
   EDIT_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.EDIT_MESSAGE}`,
-  CAN_SEND_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.CAN_SEND_MESSAGE}`
+  CAN_SEND_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.CAN_SEND_MESSAGE}`,
+  GET_EVENTS_LIST = `${API_ROUTE.EVENTS}/${API_ENDPOINT.GET_EVENTS_LIST}`,
+  GET_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.GET_EVENT}`,
+  JOIN_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.JOIN_EVENT}`,
+  UNJOIN_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.UNJOIN_EVENT}`,
+  UPLOAD_TEMP_FILE = `${API_ROUTE.FILES}/${API_ENDPOINT.UPLOAD_TEMP_FILE}`,
+  EVENT_ADD_FILE = `${API_ROUTE.EVENTS}/${API_ENDPOINT.EVENT_ADD_FILE}`,
+  DELETE_FILE = `${API_ROUTE.EVENTS}/${API_ENDPOINT.DELETE_FILE}`,
+  UPLOAD_PHOTO_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.UPLOAD_PHOTO_EVENT}`,
+  GET_MORE_PHOTOS = `${API_ROUTE.EVENTS}/${API_ENDPOINT.GET_MORE_PHOTOS}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 2 - 1
src/types/navigation.ts

@@ -76,5 +76,6 @@ export enum NAVIGATION_PAGES {
   EVENT = 'inAppEvent',
   FULL_MAP_VIEW = 'inAppFullMapView',
   GROUP_SETTINGS = 'inAppGroupSettings',
-  MEMBERS_LIST = 'inAppMembersList'
+  MEMBERS_LIST = 'inAppMembersList',
+  ALL_EVENT_PHOTOS = 'inAppAllEventPhotos'
 }