From bde897900c10bcb37a8b9b1ad35147ad34dcb68d Mon Sep 17 00:00:00 2001 From: Khiem Nguyen Date: Mon, 21 Aug 2023 16:23:00 -0400 Subject: [PATCH 1/5] Add create testimonial form --- frontend/app.json | 5 +- .../Pages/ActionsPage/ActionCard.js | 8 +- .../components/Pages/ActionsPage/styles.js | 4 + .../Pages/TestimonialsPage/AddTestimonial.js | 148 +++++++++++++++++- .../Pages/TestimonialsPage/ImageInput.js | 141 +++++++++++++++++ frontend/package-lock.json | 20 +++ frontend/package.json | 3 +- 7 files changed, 320 insertions(+), 9 deletions(-) create mode 100644 frontend/components/Pages/ActionsPage/styles.js create mode 100644 frontend/components/Pages/TestimonialsPage/ImageInput.js diff --git a/frontend/app.json b/frontend/app.json index 3b29eba..1612101 100644 --- a/frontend/app.json +++ b/frontend/app.json @@ -23,6 +23,9 @@ }, "web": { "favicon": "" - } + }, + "plugins": [ + "expo-image-picker" + ] } } diff --git a/frontend/components/Pages/ActionsPage/ActionCard.js b/frontend/components/Pages/ActionsPage/ActionCard.js index 76a8d98..060a6cf 100644 --- a/frontend/components/Pages/ActionsPage/ActionCard.js +++ b/frontend/components/Pages/ActionsPage/ActionCard.js @@ -2,6 +2,8 @@ import React from "react"; import { Text, Pressable } from "react-native"; import { Box, Heading, Image, Stack } from "native-base"; +import styles from './styles' + export default ActionCard = React.memo( ({ navigation, @@ -19,7 +21,7 @@ export default ActionCard = React.memo( }} {...props} > - + {imgUrl ? ( image ) : ( - + )} diff --git a/frontend/components/Pages/ActionsPage/styles.js b/frontend/components/Pages/ActionsPage/styles.js new file mode 100644 index 0000000..b4863f3 --- /dev/null +++ b/frontend/components/Pages/ActionsPage/styles.js @@ -0,0 +1,4 @@ +export default { + cardWidth: 280, + imageSize: 120, +} \ No newline at end of file diff --git a/frontend/components/Pages/TestimonialsPage/AddTestimonial.js b/frontend/components/Pages/TestimonialsPage/AddTestimonial.js index 8b3f549..5ca5c09 100644 --- a/frontend/components/Pages/TestimonialsPage/AddTestimonial.js +++ b/frontend/components/Pages/TestimonialsPage/AddTestimonial.js @@ -1,12 +1,152 @@ -import { ScrollView, Text } from "react-native"; import React from "react"; +import * as Yup from "yup"; +import FontAwesome from "@expo/vector-icons/FontAwesome"; +import { Formik } from "formik"; +import { + Input, + ScrollView, + Text, + VStack, + Box, + Icon, + Button, + FormControl, +} from "native-base"; + +import actionStyles from "../ActionsPage/styles"; +import ImageInput from "./ImageInput"; +import Page from "../../Shared/Page"; + +const ActionInput = () => { + return ( + + + + ); +}; + +const SPInput = () => { + return ( + + + + ); +}; + +const validationSchema = Yup.object().shape({ + image: Yup.mixed().nullable(), + title: Yup.string().required("Title is required"), + description: Yup.string().required("Description is required"), + action: Yup.mixed().nullable(), + serviceProvider: Yup.mixed().nullable(), +}); + export default function AddTestimonial() { // TODO: waiting for dashboard/user context to be implemented return ( - - Add Testimonial Page - + + + console.log(values)} + > + {({ + handleChange, + handleBlur, + handleSubmit, + values, + errors, + touched, + }) => ( + + + + + + + {errors.title && touched.title ? ( + + {errors.title} + + ) : null} + + + + {errors.description && touched.description ? ( + + {errors.description} + + ) : null} + + Associated Action + + + + Associated Service Provider + + + + + + )} + + + ); } diff --git a/frontend/components/Pages/TestimonialsPage/ImageInput.js b/frontend/components/Pages/TestimonialsPage/ImageInput.js new file mode 100644 index 0000000..c9e61d3 --- /dev/null +++ b/frontend/components/Pages/TestimonialsPage/ImageInput.js @@ -0,0 +1,141 @@ +import React from "react"; +import { TouchableWithoutFeedback } from "react-native"; + +import { + Actionsheet, + View, + Box, + Text, + Icon, + useDisclose, + Image, +} from "native-base"; +import FontAwesome from "@expo/vector-icons/FontAwesome"; +import * as ImagePicker from "expo-image-picker"; + +const ImageInput = ({ onChange, value = null }) => { + const { isOpen, onOpen, onClose } = useDisclose(); + const [status, requestPermission] = ImagePicker.useCameraPermissions(); + + const pickImage = async () => { + onClose(); + + let result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: false, + aspect: [4, 3], + quality: 1, + }); + + if (!result.canceled) { + onChange(result.assets[0].uri); + } + }; + + const takePhoto = async () => { + onClose(); + + if (!status.granted) { + await requestPermission(); + takePhoto(); + } else { + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + aspect: [4, 3], + quality: 1, + }); + + if (!result.canceled) { + onChange(result.assets[0].uri); + } + } + }; + + return ( + + + + {value ? ( + <> + image + + + + ) : ( + + + Upload your image here + + + {" "} + /{" "} + + + + )} + + + + + } + > + Upload from gallery + + } + > + Take a photo + + + + + ); +}; + +export default ImageInput; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1bbf50a..726ac86 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", "expo": "^49.0.6", + "expo-image-picker": "~14.3.2", "expo-status-bar": "~1.6.0", "firebase": "^10.1.0", "formik": "^2.4.2", @@ -9769,6 +9770,25 @@ "expo": "*" } }, + "node_modules/expo-image-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.3.0.tgz", + "integrity": "sha512-2kqJIO+oYM8J3GbvTUHLqTSpt1dLpOn/X0eB4U4RTuzz/faj8l/TyQELsMBLlGAkweNUuG9LqznbaBz+WuSFEw==", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-picker": { + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-14.3.2.tgz", + "integrity": "sha512-xr/YeQMIYheXecWP033F2SPwpBlBR5xVCx7YSfSCTH8Y9pw7Z886agqKGbS9QBVGlzJ5qecJktZ6ASSzeslDVg==", + "dependencies": { + "expo-image-loader": "~4.3.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-keep-awake": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-12.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6985c8f..3a34f5f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,8 @@ "react-native-screens": "~3.22.0", "react-native-svg": "13.9.0", "victory-native": "^36.6.11", - "yup": "^1.2.0" + "yup": "^1.2.0", + "expo-image-picker": "~14.3.2" }, "overrides": { "semver": ">=7.5.2", From de06e50f6125d9fdef127739a970e71d601a504f Mon Sep 17 00:00:00 2001 From: Khiem Nguyen Date: Mon, 21 Aug 2023 16:25:59 -0400 Subject: [PATCH 2/5] remove image when click on trash icon --- .../Pages/TestimonialsPage/ImageInput.js | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/frontend/components/Pages/TestimonialsPage/ImageInput.js b/frontend/components/Pages/TestimonialsPage/ImageInput.js index c9e61d3..5e1b468 100644 --- a/frontend/components/Pages/TestimonialsPage/ImageInput.js +++ b/frontend/components/Pages/TestimonialsPage/ImageInput.js @@ -13,6 +13,45 @@ import { import FontAwesome from "@expo/vector-icons/FontAwesome"; import * as ImagePicker from "expo-image-picker"; +const ImageContainer = ({ uri, onDelete, onEdit }) => { + return ( + + image + + + + ); +} + const ImageInput = ({ onChange, value = null }) => { const { isOpen, onOpen, onClose } = useDisclose(); const [status, requestPermission] = ImagePicker.useCameraPermissions(); @@ -62,33 +101,7 @@ const ImageInput = ({ onChange, value = null }) => { borderWidth="1" > {value ? ( - <> - image - - - + onChange("")} onEdit={onOpen} /> ) : ( Date: Tue, 22 Aug 2023 11:17:54 -0400 Subject: [PATCH 3/5] Add modals to select associated actions, vendors --- .../Pages/ActionsPage/ActionCard.js | 4 +- .../ServiceProviderCard.js | 4 +- .../Pages/TestimonialsPage/AddTestimonial.js | 133 ++++++++++++++---- .../Pages/TestimonialsPage/ImageInput.js | 2 +- .../Pages/TestimonialsPage/ListResources.js | 24 ++++ 5 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 frontend/components/Pages/TestimonialsPage/ListResources.js diff --git a/frontend/components/Pages/ActionsPage/ActionCard.js b/frontend/components/Pages/ActionsPage/ActionCard.js index 060a6cf..688264e 100644 --- a/frontend/components/Pages/ActionsPage/ActionCard.js +++ b/frontend/components/Pages/ActionsPage/ActionCard.js @@ -6,7 +6,7 @@ import styles from './styles' export default ActionCard = React.memo( ({ - navigation, + navigation = null, id, title, imgUrl, @@ -17,7 +17,7 @@ export default ActionCard = React.memo( return ( { - navigation.navigate("actiondetails", { action_id: id }); + navigation && navigation.navigate("actiondetails", { action_id: id }); }} {...props} > diff --git a/frontend/components/Pages/ServiceProvidersPage/ServiceProviderCard.js b/frontend/components/Pages/ServiceProvidersPage/ServiceProviderCard.js index e7f3a9e..2331965 100644 --- a/frontend/components/Pages/ServiceProvidersPage/ServiceProviderCard.js +++ b/frontend/components/Pages/ServiceProvidersPage/ServiceProviderCard.js @@ -9,13 +9,13 @@ export default function ServiceProviderCard({ description, imageURI, onPress, - navigation, + navigation = null, ...props }) { return ( - navigation.navigate("serviceProviderDetails", { vendor_id: id }) + navigation && navigation.navigate("serviceProviderDetails", { vendor_id: id }) } > { + const [isOpen, setIsOpen] = useState(false); + const [dataToRender, setDataToRender] = useState([]); + + useEffect(() => { + data.map((action) => { + return setDataToRender((dataToRender) => [ + ...dataToRender, + () => { + return ( + + ); + }, + ]); + }); + }, []); -const ActionInput = () => { return ( - - - + + setIsOpen(true)}> + + + + + + ); }; -const SPInput = () => { +const SPInput = ({ data }) => { + const [isOpen, setIsOpen] = useState(false); + const [dataToRender, setDataToRender] = useState([]); + + useEffect(() => { + data.map((sp) => { + return setDataToRender((dataToRender) => [ + ...dataToRender, + () => { + return ( + + ); + }, + ]); + }); + }, []); return ( - - - + + setIsOpen(true)}> + + + + + + ); }; @@ -59,6 +125,7 @@ const validationSchema = Yup.object().shape({ export default function AddTestimonial() { // TODO: waiting for dashboard/user context to be implemented + const { actions, vendors } = useContext(CommunityContext); return ( @@ -84,12 +151,15 @@ export default function AddTestimonial() { }) => ( - + ) : null} - Associated Action + Which action is this testimonial about? - + - Associated Service Provider + Who helped you complete this action? - + */} - {/* navigation.navigate("addTestimonial")} position="absolute" bottom={5} right={5}> + navigation.navigate("addTestimonial")} position="absolute" bottom={5} right={5}> - */} + } From 73665e9c42681f30c3ca3f28a8aa63c88378aaa8 Mon Sep 17 00:00:00 2001 From: Khiem Nguyen Date: Wed, 23 Aug 2023 11:16:21 -0400 Subject: [PATCH 5/5] use text instead of component --- .../Pages/TestimonialsPage/AddTestimonial.js | 95 +++++++++---------- .../Pages/TestimonialsPage/ListResources.js | 33 +++++-- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/frontend/components/Pages/TestimonialsPage/AddTestimonial.js b/frontend/components/Pages/TestimonialsPage/AddTestimonial.js index cd0dc73..cc03f8c 100644 --- a/frontend/components/Pages/TestimonialsPage/AddTestimonial.js +++ b/frontend/components/Pages/TestimonialsPage/AddTestimonial.js @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useContext, useState } from "react"; import * as Yup from "yup"; import FontAwesome from "@expo/vector-icons/FontAwesome"; @@ -21,95 +21,87 @@ import ImageInput from "./ImageInput"; import Page from "../../Shared/Page"; import ListResources from "./ListResources"; import { CommunityContext } from "../../Contexts/CommunityContext"; -import ActionCard from "../ActionsPage/ActionCard"; -import ServiceProviderCard from "../ServiceProvidersPage/ServiceProviderCard"; -const ActionInput = ({ data }) => { +const ActionInput = ({ data, onChange, value }) => { const [isOpen, setIsOpen] = useState(false); - const [dataToRender, setDataToRender] = useState([]); - useEffect(() => { - data.map((action) => { - return setDataToRender((dataToRender) => [ - ...dataToRender, - () => { - return ( - - ); - }, - ]); - }); - }, []); + const onSelect = (id) => { + setIsOpen(false); + onChange(id) + } + + const renderAction = (id) => { + const action = data.find((i) => i.id === id); + return action.title; + } return ( setIsOpen(true)}> + {value ? ( + {renderAction(value)} + ): ( + + )} ); }; -const SPInput = ({ data }) => { +const SPInput = ({ data, onChange, value }) => { const [isOpen, setIsOpen] = useState(false); - const [dataToRender, setDataToRender] = useState([]); - useEffect(() => { - data.map((sp) => { - return setDataToRender((dataToRender) => [ - ...dataToRender, - () => { - return ( - - ); - }, - ]); - }); - }, []); + const onSelect = (id) => { + setIsOpen(false); + onChange(id) + } + + const renderSP = (id) => { + const sp = data.find((i) => i.id === id); + return sp.name; + } + return ( setIsOpen(true)}> - + {value ? ( + {renderSP(value)} + ): ( + + )} ); @@ -119,8 +111,8 @@ const validationSchema = Yup.object().shape({ image: Yup.mixed().nullable(), title: Yup.string().required("Title is required"), description: Yup.string().required("Description is required"), - action: Yup.mixed().nullable(), - serviceProvider: Yup.mixed().nullable(), + action: Yup.string().nullable(), + vendor: Yup.string().nullable(), }); export default function AddTestimonial() { @@ -136,7 +128,7 @@ export default function AddTestimonial() { title: "", description: "", action: null, - serviceProvider: null, + vendor: null, }} validationSchema={validationSchema} onSubmit={(values) => console.log(values)} @@ -145,6 +137,7 @@ export default function AddTestimonial() { handleChange, handleBlur, handleSubmit, + setFieldValue, values, errors, touched, @@ -205,11 +198,11 @@ export default function AddTestimonial() { Which action is this testimonial about? - + setFieldValue("action", value)} value={values.action}/> Who helped you complete this action? - + setFieldValue("vendor", value)} value={values.vendor}/> + ); + })} + +