diff --git a/app/(main)/(drawer)/(tabs)/_layout.tsx b/app/(main)/(drawer)/(tabs)/_layout.tsx index 0414f8dc..0bc6d217 100644 --- a/app/(main)/(drawer)/(tabs)/_layout.tsx +++ b/app/(main)/(drawer)/(tabs)/_layout.tsx @@ -104,7 +104,9 @@ const TabLayout = () => { listeners={() => ({ tabPress: (e) => { e.preventDefault(); - router.push("/(modal)/create-collaboration"); + router.push({ + pathname: "/(modal)/create-collaboration", + }); }, })} options={{ diff --git a/app/(modal)/create-collaboration.tsx b/app/(modal)/create-collaboration.tsx index 9695dde1..dc8861fc 100644 --- a/app/(modal)/create-collaboration.tsx +++ b/app/(modal)/create-collaboration.tsx @@ -1,480 +1,13 @@ +import React from "react"; +import CreateCollaboration from "@/components/create-collaboration"; import AppLayout from "@/layouts/app-layout"; -import React, { useEffect, useState } from "react"; -import { - ScrollView, - Platform, - Pressable, -} from "react-native"; -import { - TextInput, - Button, - Title, - Paragraph, - IconButton, - RadioButton, - Modal, -} from "react-native-paper"; -import stylesFn from "@/styles/modal/UploadModal.styles"; -import { useTheme } from "@react-navigation/native"; -import { addDoc, collection } from "firebase/firestore"; -import { FirestoreDB } from "@/utils/firestore"; -import { AuthApp } from "@/utils/auth"; -import Toaster from "@/shared-uis/components/toaster/Toaster"; -import { useBrandContext } from "@/contexts/brand-context.provider"; -import Colors from "@/constants/Colors"; -import { router } from "expo-router"; -import * as Location from 'expo-location'; -import Select, { SelectItem } from "@/components/ui/select"; -import { Text, View } from "@/components/theme/Themed"; -import CreateCollaborationMap from "@/components/collaboration/create-collaboration/CreateCollaborationMap"; -import { COLLAB_TYPES, PLATFORM_TYPES, PROMOTION_TYPES } from "@/constants/CreateCollaborationForm"; -import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"; -import { faArrowLeft, faMinus, faPlus } from "@fortawesome/free-solid-svg-icons"; -import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; const CreateCollaborationScreen = () => { - const [collaborationName, setCollaborationName] = useState(""); - const [aboutCollab, setAboutCollab] = useState(""); - const [budgetMin, setBudgetMin] = useState(""); - const [budgetMax, setBudgetMax] = useState(""); - const [numInfluencers, setNumInfluencers] = useState(1); - const [promotionType, setPromotionType] = useState([]); - const [collabType, setCollabType] = useState([]); - const [platform, setPlatform] = useState([ - { - label: "Instagram", - value: "Instagram", - }, - ]); - const [location, setLocation] = useState("Remote"); - const [formattedAddress, setFormattedAddress] = useState(""); - const [links, setLinks] = useState([]); - const [screen, setScreen] = useState(1); - const theme = useTheme(); - const styles = stylesFn(theme); - const [isModalVisible, setIsModalVisible] = useState(false); - const [newLinkName, setNewLinkName] = useState(""); - const { selectedBrand } = useBrandContext(); - const [newLinkUrl, setNewLinkUrl] = useState(""); - const [mapRegion, setMapRegion] = useState({ - latitude: 37.78825, - longitude: -122.4324, - latitudeDelta: 0.0922, - longitudeDelta: 0.0421, - }); - - useEffect(() => { - async function getCurrentLocation() { - let { status } = await Location.requestForegroundPermissionsAsync(); - if (status !== 'granted') { - alert('Permission to access location was denied'); - return; - } - - let location = await Location.getCurrentPositionAsync({}); - setMapRegion({ - ...mapRegion, - latitude: location.coords.latitude, - longitude: location.coords.longitude, - }); - } - - getCurrentLocation(); - }, []); - - const addLink = () => { - if (!newLinkName || !newLinkUrl) { - Toaster.error("Please fill all fields"); - return; - } - - setLinks([...links, { name: newLinkName, url: newLinkUrl }]); - setNewLinkName(""); - setNewLinkUrl(""); - setIsModalVisible(false); - }; - - const onFormattedAddressChange = (address: string) => { - setFormattedAddress(address); - } - - const submitCollaboration = async () => { - try { - if (!AuthApp.currentUser) { - console.error("User not logged in"); - } - - if ( - !collaborationName || - !aboutCollab || - !budgetMin || - !budgetMax || - !promotionType || - !collabType || - !numInfluencers || - !platform || - !location - ) { - Toaster.error("Please fill all fields"); - return; - } - - const collabRef = collection(FirestoreDB, "collaborations"); - - let locationAddress = {}; - if (location === "Physical" && mapRegion.latitude && mapRegion.longitude) { - locationAddress = { - name: formattedAddress, - latlong: { - lat: mapRegion.latitude, - long: mapRegion.longitude, - }, - }; - } - - await addDoc(collabRef, { - name: collaborationName, - brandId: selectedBrand ? selectedBrand.id : "", - managerId: AuthApp.currentUser?.uid, - description: aboutCollab, - timeStamp: Date.now(), - budget: { - min: budgetMin, - max: budgetMax, - }, - promotionType: promotionType[0].value, - collaborationType: collabType[0].value, - numberOfInfluencersNeeded: numInfluencers, - platform: platform[0].value, - location: { - type: location, - ...locationAddress, - }, - externalLinks: links, - status: "active", - }).then(() => { - setScreen(3); - setTimeout(() => { - router.dismiss(1); - router.push("/collaborations"); - }, 3000); - }); - } catch (error) { - console.error(error); - } - }; - - if (screen === 1) { - return ( - - - - { - Platform.OS === "web" && ( - router.push("/collaborations")} - /> - ) - } - Create a Collaboration - - setCollaborationName(text)} - style={styles.input} - textColor={Colors(theme).text} - theme={{ - colors: { - primary: Colors(theme).primary, - placeholder: Colors(theme).text, - text: Colors(theme).text, - }, - }} - value={collaborationName} - /> - setAboutCollab(text)} - style={styles.input} - textColor={Colors(theme).text} - value={aboutCollab} - /> - - setBudgetMin(text)} - style={styles.budgetInput} - textColor={Colors(theme).text} - value={budgetMin} - /> - setBudgetMax(text)} - style={styles.budgetInput} - textColor={Colors(theme).text} - value={budgetMax} - /> - - - - Number of Influencers Involved: - - - setNumInfluencers(Math.max(1, numInfluencers - 1))} - style={styles.iconButton} - - > - - - - - {numInfluencers} - - - setNumInfluencers(numInfluencers + 1)} - style={styles.iconButton} - > - - - - - - Promotion Type: - setCollabType(selectedItems)} - value={collabType} - /> - - - Platform: - setState.promotionType(selectedItems)} + value={data.promotionType} + /> + + + Collaboration Type: + setState.platform(selectedItems)} + value={data.platform} + /> + + + + + ); +}; + +export default ScreenOne; diff --git a/components/create-collaboration/screen-three.tsx b/components/create-collaboration/screen-three.tsx new file mode 100644 index 00000000..77c5612a --- /dev/null +++ b/components/create-collaboration/screen-three.tsx @@ -0,0 +1,40 @@ +import { Text, View } from "../theme/Themed"; +import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"; +import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; +import { Title } from "react-native-paper"; + +import stylesFn from "@/styles/modal/UploadModal.styles"; +import Colors from "@/constants/Colors"; +import { useTheme } from "@react-navigation/native"; + +interface ScreenThreeProps { + type: "Add" | "Edit"; +} + +const ScreenThree: React.FC = ({ + type, +}) => { + const theme = useTheme(); + const styles = stylesFn(theme); + + return ( + <> + + + + Collaboration {type === "Add" ? "Posted" : "Updated"} + + + Your collaboration has been successfully {type === "Add" ? "posted" : "updated"}. + + + + ); +}; + +export default ScreenThree; diff --git a/components/create-collaboration/screen-two.tsx b/components/create-collaboration/screen-two.tsx new file mode 100644 index 00000000..ffcda1d8 --- /dev/null +++ b/components/create-collaboration/screen-two.tsx @@ -0,0 +1,167 @@ +import { ScrollView } from "react-native"; +import { View } from "../theme/Themed"; +import { Modal, Paragraph, RadioButton, TextInput } from "react-native-paper"; +import Colors from "@/constants/Colors"; +import CreateCollaborationMap from "../collaboration/create-collaboration/CreateCollaborationMap"; +import Button from "../ui/button"; + +import stylesFn from "@/styles/modal/UploadModal.styles"; +import { useTheme } from "@react-navigation/native"; +import ScreenHeader from "../ui/screen-header"; + +interface ScreenTwoProps { + type: "Add" | "Edit"; + setScreen: React.Dispatch>; + data: { + location: string; + links: any[]; + mapRegion: { + latitude: number; + longitude: number; + latitudeDelta: number; + longitudeDelta: number; + }; + newLinkName: string; + newLinkUrl: string; + }; + setState: { + location: React.Dispatch>; + links: React.Dispatch>; + mapRegion: React.Dispatch>; + newLinkName: React.Dispatch>; + newLinkUrl: React.Dispatch>; + }; + isModalVisible: boolean; + setIsModalVisible: React.Dispatch>; + onFormattedAddressChange: (address: string) => void; + submitCollaboration: () => void; + addLink: () => void; +} + +const ScreenTwo: React.FC = ({ + type, + setScreen, + data, + setState, + isModalVisible, + setIsModalVisible, + onFormattedAddressChange, + submitCollaboration, + addLink, +}) => { + const theme = useTheme(); + const styles = stylesFn(theme); + + return ( + <> + + setScreen(1)} + /> + + + + Location: + + setState.location(newValue)} + value={data.location} + > + + + + + + { + data.location === "Physical" && ( + setState.mapRegion(region)} + onFormattedAddressChange={onFormattedAddressChange} + /> + ) + } + + + { + data.links.map((link, index) => ( + + {link.name}: {link.url} + + )) + } + + + + + setIsModalVisible(false)} + visible={isModalVisible} + > + + + + + + ); +}; + +export default ScreenTwo; diff --git a/components/explore-influencers/index.tsx b/components/explore-influencers/index.tsx index 8e30105e..bb937ed5 100644 --- a/components/explore-influencers/index.tsx +++ b/components/explore-influencers/index.tsx @@ -9,6 +9,7 @@ import { influencers } from "@/constants/Influencers"; import CollaborationFilter from "../FilterModal"; import Colors from "@/constants/Colors"; import { useTheme } from "@react-navigation/native"; +import { useBreakpoints } from "@/hooks"; const ExploreInfluencers = () => { const [searchQuery, setSearchQuery] = useState(""); @@ -27,6 +28,10 @@ const ExploreInfluencers = () => { const ToggleModal = () => setIsModalVisible(!isModalVisible); const theme = useTheme(); + const { + xl, + } = useBreakpoints(); + const filteredInfluencers = influencers.filter((influencer) => { const isCollaborationTypeMatch = currentCollaborationType === "All" || @@ -85,7 +90,7 @@ const ExploreInfluencers = () => { ListHeaderComponent={ @@ -95,6 +100,10 @@ const ExploreInfluencers = () => { /> } + style={{ + width: xl ? 768 : '100%', + marginHorizontal: "auto", + }} /> {isModalVisible && ( = ({ }) => { const theme = useTheme(); const styles = stylesFn(theme); + const [isLoading, setIsLoading] = useState(false); if (item?.type.includes("image")) { return ( @@ -47,7 +50,21 @@ const RenderMediaItem: React.FC = ({ } }} > - + + + {isLoading && } + = ({ } ]} resizeMode="stretch" + onLoadStart={() => setIsLoading(true)} + onLoad={() => setIsLoading(false)} /> @@ -90,6 +109,8 @@ const RenderMediaItem: React.FC = ({ shouldPlay={false} useNativeControls onError={(error) => console.error("Video Error:", error)} + onLoadStart={() => setIsLoading(true)} + onLoad={() => setIsLoading(false)} /> ); }; diff --git a/components/ui/screen-header/index.tsx b/components/ui/screen-header/index.tsx index ab428a53..e5860015 100644 --- a/components/ui/screen-header/index.tsx +++ b/components/ui/screen-header/index.tsx @@ -8,6 +8,7 @@ import { Appbar } from "react-native-paper"; interface ScreenHeaderProps { action?: () => void; + hideAction?: boolean; title: string; rightActionButton?: React.ReactNode; rightAction?: boolean; @@ -15,6 +16,7 @@ interface ScreenHeaderProps { const ScreenHeader: React.FC = ({ action, + hideAction = false, title, rightActionButton, rightAction = false, @@ -38,25 +40,29 @@ const ScreenHeader: React.FC = ({ }} statusBarHeight={0} > - ( - - - - )} - color={Colors(theme).text} - onPress={handleAction} - /> + { + !hideAction && ( + ( + + + + )} + color={Colors(theme).text} + onPress={handleAction} + /> + ) + } Promise; + createCollaboration: (collaboration: ICollaboration) => Promise; + updateCollaboration: (id: string, collaboration: Partial) => Promise; } const CollaborationContext = createContext(null!); @@ -33,10 +39,28 @@ export const CollaborationContextProvider: React.FC = ({ }; } + const createCollaboration = async ( + collaboration: ICollaboration, + ): Promise => { + const collaborationRef = collection(FirestoreDB, "collaborations"); + await addDoc(collaborationRef, collaboration); + } + + const updateCollaboration = async ( + id: string, + collaboration: Partial, + ): Promise => { + const collaborationRef = doc(FirestoreDB, "collaborations", id); + + await updateDoc(collaborationRef, collaboration); + } + return ( {children} diff --git a/styles/InfluencerCard.styles.tsx b/styles/InfluencerCard.styles.tsx index 7e33d55e..79de1619 100644 --- a/styles/InfluencerCard.styles.tsx +++ b/styles/InfluencerCard.styles.tsx @@ -56,6 +56,15 @@ export const stylesFn = (theme: Theme) => width: "100%", marginTop: 10, }, + loadingIndicatorContainer: { + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + }, indicatorDot: { height: 8, width: 8,