Viktoriia 1 рік тому
батько
коміт
b025664dcf

+ 10 - 0
assets/icons/left-arrow.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4051_13199)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2081 2.32325C9.8343 2.02424 9.28891 2.08484 8.9899 2.4586L4.9899 7.4586C4.73668 7.77512 4.73668 8.22488 4.9899 8.5414L8.9899 13.5414C9.28891 13.9152 9.8343 13.9758 10.2081 13.6768C10.5818 13.3777 10.6424 12.8324 10.3434 12.4586L6.77653 8L10.3434 3.5414C10.6424 3.16764 10.5818 2.62225 10.2081 2.32325Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_4051_13199">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/plus.svg

@@ -0,0 +1,3 @@
+<svg width="21" height="22" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0177 2.01667C12.0177 1.17903 11.3387 0.5 10.501 0.5C9.66341 0.5 8.98438 1.17903 8.98438 2.01667V9.48438H1.51667C0.679035 9.48438 0 10.1634 0 11.001C0 11.8387 0.679035 12.5177 1.51667 12.5177H8.98438V19.9854C8.98438 20.823 9.66341 21.502 10.501 21.502C11.3387 21.502 12.0177 20.823 12.0177 19.9854V12.5177H19.4854C20.323 12.5177 21.002 11.8387 21.002 11.001C21.002 10.1634 20.323 9.48438 19.4854 9.48438H12.0177V2.01667Z" fill="white"/>
+</svg>

+ 10 - 0
assets/icons/right-arrow.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4051_13205)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.79194 2.32325C6.1657 2.02424 6.71109 2.08484 7.0101 2.4586L11.0101 7.4586C11.2633 7.77512 11.2633 8.22488 11.0101 8.5414L7.0101 13.5414C6.71109 13.9152 6.1657 13.9758 5.79194 13.6768C5.41818 13.3777 5.35758 12.8324 5.65659 12.4586L9.22347 8L5.65659 3.5414C5.35758 3.16764 5.41818 2.62225 5.79194 2.32325Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_4051_13205">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/trash.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.0833 3.44452H13.6944H11.6111V1.36118C11.6111 0.977848 11.3007 0.666748 10.9166 0.666748H5.3611C4.97709 0.666748 4.66667 0.977848 4.66667 1.36118V3.44452H2.58333H1.19443C0.5 3.44452 0.5 4.03341 0.5 4.41675C0.5 4.80008 0.5 5.25008 1.1944 5.25008H1.88884L1.88887 15.2501C1.88887 16.3987 2.8236 17.3334 3.9722 17.3334H12.3055C13.4541 17.3334 14.3889 16.3987 14.3889 15.2501L14.3888 5.25008H15.0833C15.7777 5.25008 15.7777 4.80011 15.7777 4.41678C15.7777 4.03345 15.7777 3.44452 15.0833 3.44452ZM6.6865 2.61301H9.44616V3.44452H6.6865V2.61301ZM12.511 14.6337C12.511 15.0163 12.1999 15.3281 11.8166 15.3281H4.52777C4.14443 15.3281 3.83333 15.0163 3.83333 14.6337L3.8333 5.25008H5.36107H10.9166H12.511V14.6337Z" fill="white"/>
+</svg>

Різницю між файлами не показано, бо вона завелика
+ 241 - 212
package-lock.json


+ 10 - 3
package.json

@@ -6,7 +6,8 @@
     "start": "expo start",
     "android": "expo start --android",
     "ios": "expo start --ios",
-    "web": "expo start --web"
+    "web": "expo start --web",
+    "postinstall": "patch-package"
   },
   "dependencies": {
     "@react-native-async-storage/async-storage": "1.18.2",
@@ -18,10 +19,15 @@
     "axios": "^1.6.1",
     "dotenv": "^16.3.1",
     "expo": "~49.0.15",
+    "expo-image-picker": "^14.5.0",
     "expo-splash-screen": "~0.20.5",
     "expo-status-bar": "~1.6.0",
+    "moment": "^2.29.4",
+    "patch-package": "^8.0.0",
+    "postinstall-postinstall": "^2.1.0",
     "react": "18.2.0",
     "react-native": "0.72.6",
+    "react-native-calendar-picker": "^7.1.4",
     "react-native-safe-area-context": "4.6.3",
     "react-native-screens": "~3.22.0",
     "react-native-svg": "13.9.0"
@@ -29,9 +35,10 @@
   "devDependencies": {
     "@babel/core": "^7.20.0",
     "@types/react": "~18.2.14",
+    "@types/react-native-calendar-picker": "^7.0.6",
     "prettier": "^3.1.0",
-    "typescript": "^5.1.3",
-    "react-native-svg-transformer": "^1.1.0"
+    "react-native-svg-transformer": "^1.1.0",
+    "typescript": "^5.1.3"
   },
   "private": true
 }

+ 57 - 0
patches/react-native-calendar-picker+7.1.4.patch

@@ -0,0 +1,57 @@
+diff --git a/node_modules/react-native-calendar-picker/CalendarPicker/Day.js b/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
+index f79b618..e163c31 100644
+--- a/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
++++ b/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
+@@ -197,15 +197,43 @@ export default function Day(props) {
+       );
+     } else {
+       return (
+-        <View style={[styles.dayWrapper, custom.containerStyle]}>
+-          <TouchableOpacity
+-            disabled={!enableDateChange}
+-            style={[custom.style, computedSelectedDayStyle, selectedDayStyle ]}
+-            onPress={() => onPressDay({year, month, day}) }>
+-            <Text style={[styles.dayLabel, textStyle, custom.textStyle, selectedDayTextStyle]}>
+-              { day }
+-            </Text>
+-          </TouchableOpacity>
++        <View style={[styles.dayWrapper, custom.containerStyle, {position: 'relative'}]}>
++          {!(selectedStartDate && !selectedEndDate && isThisDaySameAsSelectedStart) && (
++            <TouchableOpacity
++              disabled={!enableDateChange}
++              style={[custom.style, computedSelectedDayStyle, selectedDayStyle]}
++              onPress={() => onPressDay({year, month, day}) }>
++              <Text style={[
++                styles.dayLabel,
++                textStyle,
++                custom.textStyle,
++                selectedDayTextStyle,
++                { color: '#0F3F4F' },
++                (thisDay.isSame(today, 'day') && (isThisDaySameAsSelectedStart || isThisDaySameAsSelectedEnd)) && { borderWidth: 0 }
++              ]}>
++                { day }
++              </Text>
++            </TouchableOpacity>
++          )}
++
++          {(thisDay.isSame(selectedStartDate, 'day') || thisDay.isSame(selectedEndDate, 'day')) && (
++            <TouchableOpacity
++              disabled={!enableDateChange}
++              style={[{position: 'absolute'}]}
++              onPress={() => onPressDay({year, month, day}) }>
++              <Text style={[styles.dayLabel, textStyle, custom.textStyle, selectedDayTextStyle, {
++                backgroundColor: '#0F3F4F',
++                borderRadius: 17,
++                height: 34,
++                width: 34,
++                textAlign: 'center',
++                verticalAlign: 'middle',
++                overflow: 'hidden',
++              }]}>
++                { day }
++              </Text>
++            </TouchableOpacity>
++          )}
+         </View>
+       );
+     }

+ 135 - 0
src/components/AddPhoto/index.tsx

@@ -0,0 +1,135 @@
+import React, { useState } from 'react';
+import { View, Image, TextInput, TouchableOpacity, StyleSheet, Alert, Text } from 'react-native';
+import * as ImagePicker from 'expo-image-picker';
+import Modal from '../Modal';
+import Input from '../Input';
+
+import PlusSVG from '../../../assets/icons/plus.svg';
+import TrashSVG from '../../../assets/icons/trash.svg';
+
+interface AddPhotoProps {
+  initialImagePath?: string | null;
+  initialImageDescription?: string;
+  onImagePicked?: (uri: string) => void;
+  onImageDeleted?: () => void;
+  closeModal: () => void;
+}
+
+const AddPhoto: React.FC<AddPhotoProps> = ({ initialImagePath, initialImageDescription, onImagePicked, onImageDeleted, closeModal }) => {
+  const [image, setImage] = useState(initialImagePath);
+  const [description, setDescription] = useState(initialImageDescription ?? '');
+
+  const handleDelete = () => {
+    Alert.alert('Delete Image', 'Are you sure you want to delete this image?', [
+      { text: 'Cancel' },
+      { text: 'Delete', onPress: () => {
+        setImage(null);
+        // onImageDeleted();
+      }},
+    ]);
+  };
+
+  const handleImagePick = async () => {
+    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
+    if (status !== 'granted') {
+      Alert.alert('Permission Denied', 'Sorry, we need media library permissions to make this work!');
+      return;
+    }
+
+    let result = await ImagePicker.launchImageLibraryAsync({
+      mediaTypes: ImagePicker.MediaTypeOptions.Images,
+      // aspect: [4, 3],
+      quality: 1,
+    });
+
+    if (!result.canceled) {
+      setImage(result.assets[0].uri);
+      // onImagePicked(result.uri);
+    }
+  };
+
+  return (
+    <Modal
+      visible={true}
+      onRequestClose={closeModal}
+      textHeader='Add Photo'
+    >
+      <View style={styles.container}>
+        <View style={styles.imageContainer}>
+          {image ? (
+            <>
+              <TouchableOpacity onPress={handleDelete} style={styles.deleteButton}>
+                <View style={[styles.addButton, {width: 40, height: 40, backgroundColor: '#EF5B5B', borderRadius: 20}]}>
+                  <TrashSVG />
+                </View>
+              </TouchableOpacity>
+              <Image source={{ uri: image }} style={styles.image} />
+            </>
+          ) : (
+            <TouchableOpacity onPress={handleImagePick} style={styles.addButton}>
+              <View style={[styles.addButton, {width: 48, height: 48, backgroundColor: '#0F3F4F', borderRadius: 24}]}>
+                <PlusSVG />
+              </View>
+              
+              <Text style={styles.addButtonText}>Upload Photo</Text>
+            </TouchableOpacity>
+          )}
+        </View>
+        <View style={styles.description}>
+          <Input
+            header='Description'
+            placeholder="Add photo description here"
+            onChange={setDescription}
+          />
+        </View>
+      </View>
+    </Modal>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    padding: 24,
+  },
+  imageContainer: {
+    position: 'relative',
+    marginBottom: 8,
+    width: '100%',
+    height: 327,
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderRadius: 8,
+    backgroundColor: '#FAFAFA'
+  },
+  deleteButton: {
+    position: 'absolute',
+    top: 16,
+    right: 16,
+    zIndex: 1,
+  },
+  image: {
+    width: '100%',
+    height: '100%',
+    resizeMode: 'cover',
+    borderRadius: 8,
+  },
+  description: {
+    width: '100%',
+    marginTop: 8,
+  },
+  addButton: {
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  addButtonText: {
+    fontSize: 16,
+    marginTop: 10,
+    fontWeight: '700',
+    color: '#0F3F4F',
+    lineHeight: 24,
+  },
+});
+
+export default AddPhoto;

+ 0 - 0
src/components/AddPhoto/style.tsx


+ 16 - 0
src/components/Calendars/RangeCalendar/Navigation/index.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import { View } from 'react-native';
+
+import { styles } from './style';
+import LeftArrow from '../../../../../assets/icons/left-arrow.svg';
+import RightArrow from '../../../../../assets/icons/right-arrow.svg';
+
+const Navigation = React.memo(({ direction }: { direction: 'prev' | 'next' }) => {
+  return (
+    <View style={[styles.navigationBtn, direction === 'prev' ? styles.prevComponent : styles.nextComponent]}>
+      {direction === 'prev' ? <LeftArrow /> : <RightArrow />}
+    </View>
+  );
+});
+
+export default Navigation;

+ 16 - 0
src/components/Calendars/RangeCalendar/Navigation/style.tsx

@@ -0,0 +1,16 @@
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+  navigationBtn: {
+    width: 50,
+    height: 30,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  prevComponent: {
+    left: 25,
+  },
+  nextComponent: {
+    right: 25,
+  },
+});

+ 102 - 0
src/components/Calendars/RangeCalendar/index.tsx

@@ -0,0 +1,102 @@
+import React, { useMemo, useState } from 'react';
+import { View } from 'react-native';
+import CalendarPicker, { CustomDatesStylesFunc, CustomDayHeaderStylesFunc } from 'react-native-calendar-picker';
+import moment from 'moment';
+import Modal from '../../Modal';
+import Navigation from './Navigation';
+
+import { styles } from './style';
+import { Colors } from '../../../theme';
+
+export default function RangeCalendar(
+  { isModalVisible, closeModal }: { isModalVisible: boolean, closeModal: () => void }
+) {
+  const [selectedStartDate, setSelectedStartDate] = useState<Date | null>(null);
+  const [selectedEndDate, setSelectedEndDate] = useState<Date | null>(null);
+
+  const handleOnDateChange = (date: Date, type: string) => {
+    if (type === 'END_DATE') {
+      setSelectedEndDate(date);
+    } else {
+      setSelectedStartDate(date);
+      setSelectedEndDate(null);
+    }
+  };
+
+  const customDayHeaderStyles: CustomDayHeaderStylesFunc = () => {
+    return {
+      textStyle: styles.dayHeader,
+    };
+  };
+
+  const customSelectedDatesStyles: CustomDatesStylesFunc = useMemo(() => {
+    return (date: moment.Moment) => {
+      if (date.isSame(moment(), 'day')) {
+        return {
+          containerStyle: {},
+          textStyle: {
+            borderWidth: 1,
+            borderColor: Colors.DARK_BLUE,
+            borderRadius: 17,
+            height: 34,
+            width: 34,
+            textAlign: 'center',
+            verticalAlign: 'middle',
+          },
+          style: {
+            backgroundColor: Colors.WHITE,
+          }
+        };
+      }
+      return {
+        containerStyle: {},
+        textStyle: {},
+      };
+    };
+  }, []);
+
+  const resetSelections = () => {
+    closeModal();
+    setSelectedStartDate(null);
+    setSelectedEndDate(null);
+  };
+
+  const prevNavigationComponent = useMemo(() => <Navigation direction="prev" />, []);
+  const nextNavigationComponent = useMemo(() => <Navigation direction="next" />, []);
+
+  return (
+    <Modal
+      visible={isModalVisible}
+      onRequestClose={resetSelections}
+      textHeader='Select Date'
+    >
+      <View style={styles.modalContent}>
+        <CalendarPicker
+          scaleFactor={400}
+          allowRangeSelection
+          allowBackwardRangeSelect
+          onDateChange={handleOnDateChange as any}
+          selectedStartDate={selectedStartDate as Date}
+          selectedEndDate={selectedEndDate as Date}
+          scrollable
+          showDayStragglers
+          previousComponent={prevNavigationComponent}
+          nextComponent={nextNavigationComponent}
+          dayLabelsWrapper={styles.labelsWrapper}
+          selectedRangeStyle={styles.rangeStyle}
+          selectedRangeEndTextStyle={styles.rangeStartEndTextStyle}
+          selectedRangeStartTextStyle={styles.rangeStartEndTextStyle}
+          selectedRangeEndStyle={[styles.rangeStartEndStyle, styles.rangeEndStyle]}
+          selectedRangeStartStyle={[styles.rangeStartEndStyle, styles.rangeStartStyle]}
+          customDayHeaderStyles={customDayHeaderStyles}
+          customDatesStyles={customSelectedDatesStyles}
+          disabledDatesTextStyle={styles.disabledDates}
+          textStyle={styles.textStyle}
+          monthTitleStyle={styles.dateTitle}
+          yearTitleStyle={styles.dateTitle}
+          headerWrapperStyle={styles.headerWrapper}
+        />
+      </View>
+    </Modal>
+  );
+}

+ 66 - 0
src/components/Calendars/RangeCalendar/style.ts

@@ -0,0 +1,66 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../../theme';
+
+export const styles = StyleSheet.create({
+  modalContent: {
+    backgroundColor: Colors.WHITE,
+    padding: 22,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  rangeStartEndTextStyle: {
+    color: Colors.WHITE,
+  },
+  rangeStartEndStyle: {
+    width: 40,
+  },
+  rangeEndStyle: {
+    alignSelf: 'flex-start',
+    justifyContent: 'flex-start',
+    borderTopRightRadius: 20,
+    borderBottomRightRadius: 20,
+  },
+  rangeStartStyle: {
+    alignSelf: 'flex-end',
+    justifyContent: 'flex-end',
+    borderTopLeftRadius: 20,
+    borderBottomLeftRadius: 20,
+  },
+  rangeStyle: {
+    backgroundColor: Colors.DARK_LIGHT,
+    height: 34,
+  },
+  labelsWrapper: {
+    borderTopWidth: 0,
+    borderBottomWidth: 0,
+    marginBottom: 5,
+  },
+  disabledDates: {
+    width: "100%",
+    height: "100%",
+    backgroundColor: Colors.WHITE,
+    textAlign: "center",
+    textAlignVertical: "center",
+  },
+  dateTitle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 16,
+    lineHeight: 24,
+    fontWeight: '700'
+  },
+  headerWrapper: {
+    marginBottom: 15
+  },
+  textStyle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    lineHeight: 20
+  },
+  dayHeader: {
+    color: Colors.ORANGE,
+    fontSize: 12,
+    lineHeight: 16,
+    letterSpacing: 1,
+    fontWeight: '700'
+  }
+});

+ 2 - 1
src/theme.ts

@@ -1,5 +1,6 @@
 export enum Colors {
   ORANGE = '#ED9334',
   DARK_BLUE = '#0F3F4F',
-  WHITE = '#FFF'
+  WHITE = '#FFF',
+  DARK_LIGHT = '#E5E5E5',
 }

Деякі файли не було показано, через те що забагато файлів було змінено