diff --git a/app/_layout.tsx b/app/_layout.tsx
index 301c00e7..9b704cb1 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -6,7 +6,13 @@ import {
useTheme,
} from "@react-navigation/native";
import { useFonts } from "expo-font";
-import { Stack, useLocalSearchParams, usePathname, useRouter, useSegments } from "expo-router";
+import {
+ Stack,
+ useLocalSearchParams,
+ usePathname,
+ useRouter,
+ useSegments,
+} from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
@@ -27,6 +33,7 @@ import { AutocompleteDropdownContextProvider } from "react-native-autocomplete-d
import { BrandContextProvider } from "@/contexts/brand-context.provider";
import CustomPaperTheme from "@/constants/Themes/Theme";
import { queryParams } from "@/utils/url";
+import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
export {
// Catch any errors thrown by the Layout component.
@@ -64,21 +71,23 @@ export default function RootLayout() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
@@ -119,9 +128,7 @@ const RootLayoutStack = () => {
return (
-
+
{
gestureEnabled: true,
}}
/>
-
+
diff --git a/components/FilterModal.tsx b/components/FilterModal.tsx
index 62ff54bf..fa2fea6b 100644
--- a/components/FilterModal.tsx
+++ b/components/FilterModal.tsx
@@ -7,7 +7,7 @@ import BottomSheet, {
} from "@gorhom/bottom-sheet";
import { useTheme } from "@react-navigation/native";
import MultiSlider from "@ptomasroos/react-native-multi-slider";
-import { faClose } from "@fortawesome/free-solid-svg-icons";
+import { faCheckDouble, faClose } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome";
import { stylesFn } from "@/styles/FilterModal.styles";
@@ -131,6 +131,13 @@ const CollaborationFilter = ({
{influencerType.map((category, index) => (
localInfluencerType === category ? (
+
+ ) : null}
key={index}
selected={localInfluencerType === category}
onPress={() => setLocalInfluencerType(category)}
diff --git a/components/InfluencerCard.tsx b/components/InfluencerCard.tsx
index 3dab5314..d910615a 100644
--- a/components/InfluencerCard.tsx
+++ b/components/InfluencerCard.tsx
@@ -30,27 +30,21 @@ import { faEllipsis, faPeopleRoof, faChartLine, faFaceSmile, faComment, faCheck,
import { imageUrl } from "@/utils/url";
import Tag from "./ui/tag";
import { MediaItem } from "./ui/carousel/render-media-item";
+import { User } from "@/types/User";
+import { processRawAttachment } from "@/utils/attachments";
+import RenderHTML from "react-native-render-html";
+import { truncateText } from "@/utils/text";
const { width } = Dimensions.get("window");
interface InfluencerCardPropsType {
- influencer: {
- id: string;
- name: string;
- handle: string;
- profilePic: string;
- media: MediaItem[];
- followers: number | string;
- reach: number | string;
- rating: number | string;
- bio: string;
- jobsCompleted: number | string;
- successRate: number | string;
- };
+ influencer: User;
type: string;
alreadyInvited?: (influencerId: string) => Promise;
ToggleModal: () => void;
ToggleMessageModal?: () => void;
+ openProfile?: (influencer: User) => void;
+ setSelectedInfluencer?: React.Dispatch>;
}
const InfluencerCard = (props: InfluencerCardPropsType) => {
@@ -68,6 +62,8 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
const theme = useTheme();
const styles = stylesFn(theme);
+ const screenWidth = Dimensions.get("window").width;
+
const pinchHandler =
useAnimatedGestureHandler({
onStart: (event) => {
@@ -120,14 +116,30 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
styles.header,
]}
>
-
-
+ {
+ if (props.openProfile) {
+ props.openProfile(influencer);
+ }
+ }}
+ >
+
+
+ {
+ if (props.openProfile) {
+ props.openProfile(influencer);
+ }
+ }}
+ >
{influencer.name}
- {influencer.handle}
-
+ {influencer.socials?.[0] || 'influencer-handle'}
+
{props.type === "invitation" &&
(isInvited ? (
{
{
props.ToggleModal();
+ if (props?.setSelectedInfluencer) {
+ props.setSelectedInfluencer(props.influencer);
+ }
}}
>
{
processRawAttachment(attachment)) || []}
onImagePress={onImagePress}
/>
@@ -189,7 +204,7 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
size={20}
/>
- {convertToKUnits(Number(influencer.followers))}
+ {convertToKUnits(Number(influencer.backend?.followers))}
@@ -199,7 +214,7 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
size={20}
/>
- {convertToKUnits(Number(influencer.reach))}
+ {convertToKUnits(Number(influencer.backend?.reach))}
@@ -209,7 +224,7 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
size={20}
/>
- {influencer.rating}
+ {influencer.backend?.rating}
@@ -222,11 +237,31 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
- setBioExpanded(!bioExpanded)}>
-
- {influencer.bio}
+ {
+ if (props.openProfile) {
+ props.openProfile(influencer);
+ }
+ }}
+ >
+
+ No content available.
",
+ }}
+ defaultTextProps={{
+ style: {
+ color: Colors(theme).text,
+ fontSize: 16,
+ lineHeight: 22,
+ },
+ }}
+ />
-
+
{
@@ -234,8 +269,9 @@ const InfluencerCard = (props: InfluencerCardPropsType) => {
}}
>
- {influencer.jobsCompleted} Jobs completed ({influencer.successRate}{" "}
- success rate)
+ {/* {influencer.jobsCompleted} Jobs completed ({influencer.successRate}{" "}
+ success rate) */}
+ 10 Jobs completed 100% success rate
diff --git a/components/collaboration/collaboration-details/ApplicationsTabContent.tsx b/components/collaboration/collaboration-details/ApplicationsTabContent.tsx
index f42eb0ab..34b4a1cd 100644
--- a/components/collaboration/collaboration-details/ApplicationsTabContent.tsx
+++ b/components/collaboration/collaboration-details/ApplicationsTabContent.tsx
@@ -1,8 +1,5 @@
import React, { useEffect, useState } from "react";
-import { View, FlatList } from "react-native";
-import {
- ActivityIndicator,
-} from "react-native-paper";
+import { View, FlatList, ActivityIndicator } from "react-native";
import { useTheme } from "@react-navigation/native";
import InfluencerCard from "@/components/InfluencerCard";
@@ -11,7 +8,6 @@ import { collection, doc, getDoc, getDocs } from "firebase/firestore";
import BottomSheetActions from "@/components/BottomSheetActions";
import EmptyState from "@/components/ui/empty-state";
import Colors from "@/constants/Colors";
-import { processRawAttachment } from "@/utils/attachments";
import { useBreakpoints } from "@/hooks";
const ApplicationsTabContent = (props: any) => {
@@ -75,7 +71,7 @@ const ApplicationsTabContent = (props: any) => {
gap: 40,
}}
>
-
+
);
}
@@ -97,19 +93,7 @@ const ApplicationsTabContent = (props: any) => {
renderItem={({ item }) => (
processRawAttachment(media)),
- followers: item.backend ? item.backend.followers : 1000,
- reach: item.backend ? item.backend.reach : 10000,
- rating: item.backend ? item.backend.rating : 4.5,
- bio: "I am a content creator",
- jobsCompleted: 12,
- successRate: 100,
- }}
+ influencer={item}
ToggleModal={() => {
setIsVisible(true);
setSelectedApplication({
diff --git a/components/collaboration/collaboration-details/InvitationsTabContent.tsx b/components/collaboration/collaboration-details/InvitationsTabContent.tsx
index d6a615d8..bdfaabb0 100644
--- a/components/collaboration/collaboration-details/InvitationsTabContent.tsx
+++ b/components/collaboration/collaboration-details/InvitationsTabContent.tsx
@@ -117,22 +117,15 @@ const InvitationsTabContent = (props: any) => {
type="invitation"
alreadyInvited={checkIfAlreadyInvited}
influencer={{
- id: item.id,
- profilePic: item.profileImage,
- name: item.name,
- handle: item.handle || "@handle",
- media: [
- {
- type: "image",
- url: item.profileImage,
- },
- ],
- followers: item.backend ? item.backend.followers : 1000,
- reach: item.backend ? item.backend.reach : 10000,
- rating: item.backend ? item.backend.rating : 4.5,
- bio: "I am a content creator",
- jobsCompleted: 12,
- successRate: 100,
+ ...item,
+ profile: {
+ attachments: [
+ {
+ type: "image",
+ imageUrl: item.profileImage,
+ },
+ ]
+ }
}}
ToggleModal={() => {
setIsVisible(true);
diff --git a/components/explore-influencers/index.tsx b/components/explore-influencers/index.tsx
index bb937ed5..3b70f4a2 100644
--- a/components/explore-influencers/index.tsx
+++ b/components/explore-influencers/index.tsx
@@ -1,15 +1,29 @@
-import { FlatList } from "react-native";
+import { ActivityIndicator, FlatList } from "react-native";
import InfluencerCard from "../InfluencerCard";
import { View } from "../theme/Themed";
import AppLayout from "@/layouts/app-layout";
import SearchComponent from "../SearchComponent";
-import { useState } from "react";
-import BottomSheetActions from "../BottomSheetActions";
-import { influencers } from "@/constants/Influencers";
+import { useEffect, useMemo, useRef, useState } from "react";
import CollaborationFilter from "../FilterModal";
import Colors from "@/constants/Colors";
import { useTheme } from "@react-navigation/native";
import { useBreakpoints } from "@/hooks";
+import { User } from "@/types/User";
+
+import ProfileBottomSheet from "@/shared-uis/components/ProfileModal/Profile-Modal";
+import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from "@gorhom/bottom-sheet";
+
+import { FirestoreDB } from "@/utils/firestore";
+import {
+ collection,
+ onSnapshot,
+ query,
+ where,
+} from "firebase/firestore";
+import BottomSheetContainer from "../ui/bottom-sheet/BottomSheet";
+import { List } from "react-native-paper";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { useSharedValue } from "react-native-reanimated";
const ExploreInfluencers = () => {
const [searchQuery, setSearchQuery] = useState("");
@@ -25,40 +39,124 @@ const ExploreInfluencers = () => {
const [currentEngagementRange, setCurrentEngagementRange] = useState([
0, 10000000,
]);
- const ToggleModal = () => setIsModalVisible(!isModalVisible);
+ const ToggleModal = () => {
+ setIsModalVisible(!isModalVisible);
+ };
+ const [influencers, setInfluencers] = useState([]);
+ const [filteredInfluencers, setFilteredInfluencers] = useState([]);
+ const [selectedInfluencer, setSelectedInfluencer] = useState(null);
+
+ const bottomSheetModalRef = useRef(null);
+ const snapPoints = useMemo(() => ["25%", "50%", "90%"], []);
+
+ const insets = useSafeAreaInsets();
+ const containerOffset = useSharedValue({
+ top: insets.top,
+ bottom: insets.bottom,
+ left: insets.left,
+ right: insets.right,
+ });
+
+ const [isLoading, setIsLoading] = useState(true);
+
const theme = useTheme();
const {
xl,
} = useBreakpoints();
- const filteredInfluencers = influencers.filter((influencer) => {
- const isCollaborationTypeMatch =
- currentCollaborationType === "All" ||
- influencer.collaborationType === currentCollaborationType;
-
- const isFollowersRangeMatch =
- Number(influencer.followers) >= currentFollowersRange[0] &&
- Number(influencer.followers) <= currentFollowersRange[1];
- const isReachRangeMatch =
- Number(influencer.reach) >= currentReachRange[0] &&
- Number(influencer.reach) <= currentReachRange[1];
- const isEngagementRangeMatch =
- Number(influencer.engagement) >= currentEngagementRange[0] &&
- Number(influencer.engagement) <= currentEngagementRange[1];
-
- const isSearchQueryMatch = influencer.name
- .toLowerCase()
- .includes(searchQuery.toLowerCase());
+ useEffect(() => {
+ setIsLoading(true);
+ const influencersRef = collection(FirestoreDB, "users");
+ const q = query(influencersRef, where("profile.completionPercentage", ">=", 60));
+
+ const unsubscribe = onSnapshot(q, (querySnapshot) => {
+ const fetchedInfluencers: User[] = [];
+ querySnapshot.forEach((doc) => {
+ fetchedInfluencers.push({
+ ...doc.data(),
+ id: doc.id,
+ } as User);
+ });
+
+ setInfluencers(fetchedInfluencers);
+ setIsLoading(false);
+ });
+
+ return () => {
+ unsubscribe();
+ };
+ }, []);
+
+ const filterInfluencers = () => {
+ const newFilteredInfluencers = influencers.filter((influencer) => {
+ const isCollaborationTypeMatch =
+ currentCollaborationType === "All" ||
+ influencer.profile?.category?.includes(currentCollaborationType);
+
+ // const isFollowersRangeMatch =
+ // Number(influencer.backend?.followers) >= currentFollowersRange[0] &&
+ // Number(influencer.backend?.followers) <= currentFollowersRange[1];
+ // const isReachRangeMatch =
+ // Number(influencer.backend?.reach) >= currentReachRange[0] &&
+ // Number(influencer.backend?.reach) <= currentReachRange[1];
+ // const isEngagementRangeMatch =
+ // Number(influencer.backend?.engagement) >= currentEngagementRange[0] &&
+ // Number(influencer.backend?.engagement) <= currentEngagementRange[1];
+
+ const isSearchQueryMatch = influencer.name
+ .toLowerCase()
+ .includes(searchQuery.toLowerCase());
+
+ return (
+ isCollaborationTypeMatch &&
+ // isFollowersRangeMatch &&
+ // isReachRangeMatch &&
+ // isEngagementRangeMatch &&
+ isSearchQueryMatch
+ );
+ });
+
+ setFilteredInfluencers(newFilteredInfluencers);
+ };
+ useEffect(() => {
+ filterInfluencers();
+ }, [
+ currentCollaborationType,
+ currentInfluencerType,
+ currentFollowersRange,
+ currentReachRange,
+ currentEngagementRange,
+ searchQuery,
+ influencers,
+ ]);
+
+ const renderBackdrop = (props: any) => {
return (
- isCollaborationTypeMatch &&
- isFollowersRangeMatch &&
- isReachRangeMatch &&
- isEngagementRangeMatch &&
- isSearchQueryMatch
+
);
- });
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ );
+ }
return (
@@ -70,6 +168,11 @@ const ExploreInfluencers = () => {
type="explore"
ToggleModal={ToggleModal}
influencer={item}
+ openProfile={(influencer) => {
+ setSelectedInfluencer(influencer);
+ bottomSheetModalRef.current?.present();
+ }}
+ setSelectedInfluencer={setSelectedInfluencer}
/>
)}
keyExtractor={(item, index) => index.toString()}
@@ -105,32 +208,74 @@ const ExploreInfluencers = () => {
marginHorizontal: "auto",
}}
/>
- {isModalVisible && (
-
- )}
- {isFilterModalVisible && (
- setIsFilterModalVisible(false)}
- collaborationType={["All", "Paid", "Unpaid"]}
- influencerType={["All", "Micro", "Macro"]}
- setCollaborationType={setCurrentCollaborationType}
- setInfluencerType={setCurrentInfluencerType}
- setCurrentFollowersRange={setCurrentFollowersRange}
- setCurrentReachRange={setCurrentReachRange}
- setCurrentEngagementRange={setCurrentEngagementRange}
- currentCollaborationType="All"
- currentInfluencerType="All"
- currentFollowersRange={[0, 1000000]}
- currentReachRange={[0, 1000000]}
- currentEngagementRange={[0, 100000]}
- />
- )}
+
+ {
+ isModalVisible && (
+
+
+ {
+ bottomSheetModalRef.current?.present();
+ ToggleModal();
+ }}
+ />
+ null}
+ />
+ null}
+ />
+
+
+ )
+ }
+
+ {
+ isFilterModalVisible && (
+ setIsFilterModalVisible(false)}
+ collaborationType={["All", "Paid", "Unpaid"]}
+ influencerType={["All", "Micro", "Macro"]}
+ setCollaborationType={setCurrentCollaborationType}
+ setInfluencerType={setCurrentInfluencerType}
+ setCurrentFollowersRange={setCurrentFollowersRange}
+ setCurrentReachRange={setCurrentReachRange}
+ setCurrentEngagementRange={setCurrentEngagementRange}
+ currentCollaborationType="All"
+ currentInfluencerType="All"
+ currentFollowersRange={[0, 1000000]}
+ currentReachRange={[0, 1000000]}
+ currentEngagementRange={[0, 100000]}
+ />
+ )
+ }
+
+
+
+
+
+
);
};
diff --git a/components/ui/bottom-sheet/BottomSheet.tsx b/components/ui/bottom-sheet/BottomSheet.tsx
new file mode 100644
index 00000000..d1beedc9
--- /dev/null
+++ b/components/ui/bottom-sheet/BottomSheet.tsx
@@ -0,0 +1,87 @@
+import React, { PropsWithChildren } from "react";
+import {
+ Modal,
+ Pressable,
+ StyleSheet,
+ View,
+} from "react-native";
+import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
+import { Theme, useTheme } from "@react-navigation/native";
+
+interface BottomSheetContainerProps extends PropsWithChildren {
+ isVisible: boolean;
+ snapPointsRange: [string, string];
+ onClose: () => void;
+}
+
+const BottomSheetContainer: React.FC = ({
+ isVisible,
+ snapPointsRange,
+ onClose,
+ children,
+}) => {
+ const sheetRef = React.useRef(null);
+
+ const theme = useTheme();
+ const styles = stylesFn(theme);
+
+ const snapPoints = React.useMemo(
+ () => [snapPointsRange[0], snapPointsRange[1]],
+ []
+ );
+
+ const handleClose = () => {
+ if (sheetRef.current) {
+ sheetRef.current.close();
+ }
+ onClose();
+ };
+
+ return (
+
+
+ {
+ return ;
+ }}
+ onClose={handleClose}
+ style={styles.bottomSheet}
+ >
+
+ {children}
+
+
+
+
+ );
+};
+
+export default BottomSheetContainer;
+
+const stylesFn = (theme: Theme) => StyleSheet.create({
+ overlay: {
+ position: "absolute",
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
+ },
+ bottomSheetContainer: {
+ flex: 1,
+ justifyContent: "flex-end",
+ zIndex: 2,
+ },
+ bottomSheet: {
+ zIndex: 9999,
+ },
+});
diff --git a/constants/Influencer.ts b/constants/Influencer.ts
new file mode 100644
index 00000000..e0154243
--- /dev/null
+++ b/constants/Influencer.ts
@@ -0,0 +1,56 @@
+import { IUsers } from "@/shared-libs/firestore/trendly-pro/models/users";
+
+export const DUMMY_INFLUENCER: IUsers = {
+ name: "John Doe",
+ profileImage:
+ "https://www.pngkey.com/png/full/114-1149878_setting-user-avatar-in-specific-size-without-breaking.png",
+ email: "johndoe@gmail.com",
+ phoneNumber: "1234567890",
+ location: "New York",
+ emailVerified: true,
+ phoneVerified: true,
+ profile: {
+ introVideo: "",
+ content: {
+ about: "I am a social media influencer",
+ socialMediaHighlight: "I have 100k followers on Instagram",
+ collaborationGoals: "I want to collaborate with brands",
+ audienceInsights: "My audience is mostly from the US",
+ funFactAboutUser: "I love to travel",
+ },
+ category: ["Fashion", "Lifestyle"],
+ attachments: [
+ {
+ type: "image",
+ imageUrl:
+ "https://www.pngkey.com/png/full/114-1149878_setting-user-avatar-in-specific-size-without-breaking.png",
+ },
+ ],
+ },
+ preferences: {
+ question1: ["Option1"],
+ question2: ["Option2"],
+ question3: ["Option3"],
+ question4: ["Option4"],
+ },
+ settings: {
+ theme: "light",
+ emailNotification: true,
+ pushNotification: true,
+ },
+ backend: {
+ followers: 100000,
+ reach: 1000000,
+ engagement: 10,
+ rating: 4.5,
+ },
+ pushNotificationToken: {
+ ios: [],
+ android: [],
+ web: [],
+ },
+ //@ts-ignore
+ notifications: [],
+ //@ts-ignore
+ socials: [],
+};
diff --git a/package-lock.json b/package-lock.json
index 310ccaae..163bade6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,6 +58,7 @@
"react-native-paper": "^5.12.5",
"react-native-reanimated": "~3.10.1",
"react-native-reanimated-carousel": "^3.5.1",
+ "react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^4.10.5",
"react-native-screens": "^3.31.1",
"react-native-select-dropdown": "^4.0.1",
@@ -6348,6 +6349,23 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@jsamr/counter-style": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@jsamr/counter-style/-/counter-style-2.0.2.tgz",
+ "integrity": "sha512-2mXudGVtSzVxWEA7B9jZLKjoXUeUFYDDtFrQoC0IFX9/Dszz4t1vZOmafi3JSw/FxD+udMQ+4TAFR8Qs0J3URQ==",
+ "license": "MIT"
+ },
+ "node_modules/@jsamr/react-native-li": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@jsamr/react-native-li/-/react-native-li-2.3.1.tgz",
+ "integrity": "sha512-Qbo4NEj48SQ4k8FZJHFE2fgZDKTWaUGmVxcIQh3msg5JezLdTMMHuRRDYctfdHI6L0FZGObmEv3haWbIvmol8w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@jsamr/counter-style": "^1.0.0 || ^2.0.0",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -6363,6 +6381,92 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"dev": true
},
+ "node_modules/@native-html/css-processor": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@native-html/css-processor/-/css-processor-1.11.0.tgz",
+ "integrity": "sha512-NnhBEbJX5M2gBGltPKOetiLlKhNf3OHdRafc8//e2ZQxXN8JaSW/Hy8cm94pnIckQxwaMKxrtaNT3x4ZcffoNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "css-to-react-native": "^3.0.0",
+ "csstype": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-native": "*"
+ }
+ },
+ "node_modules/@native-html/transient-render-engine": {
+ "version": "11.2.3",
+ "resolved": "https://registry.npmjs.org/@native-html/transient-render-engine/-/transient-render-engine-11.2.3.tgz",
+ "integrity": "sha512-zXwgA3gPUEmFs3I3syfnvDvS6WiUHXEE6jY09OBzK+trq7wkweOSFWIoyXiGkbXrozGYG0KY90YgPyr8Tg8Uyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@native-html/css-processor": "1.11.0",
+ "@types/ramda": "^0.27.44",
+ "csstype": "^3.0.9",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.2",
+ "domutils": "^2.8.0",
+ "htmlparser2": "^7.1.2",
+ "ramda": "^0.27.2"
+ },
+ "peerDependencies": {
+ "@types/react-native": "*",
+ "react-native": "^*"
+ }
+ },
+ "node_modules/@native-html/transient-render-engine/node_modules/dom-serializer": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/@native-html/transient-render-engine/node_modules/domhandler": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.2.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/@native-html/transient-render-engine/node_modules/domutils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/@native-html/transient-render-engine/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -10670,6 +10774,15 @@
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
+ "node_modules/@types/ramda": {
+ "version": "0.27.66",
+ "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.66.tgz",
+ "integrity": "sha512-i2YW+E2U6NfMt3dp0RxNcejox+bxJUNDjB7BpYuRuoHIzv5juPHkJkNgcUOu+YSQEmaWu8cnAo/8r63C0NnuVA==",
+ "license": "MIT",
+ "dependencies": {
+ "ts-toolbelt": "^6.15.1"
+ }
+ },
"node_modules/@types/react": {
"version": "18.2.79",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz",
@@ -10679,6 +10792,31 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-native": {
+ "version": "0.72.8",
+ "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.8.tgz",
+ "integrity": "sha512-St6xA7+EoHN5mEYfdWnfYt0e8u6k2FR0P9s2arYgakQGFgU1f9FlPrIEcj0X24pLCF5c5i3WVuLCUdiCYHmOoA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@react-native/virtualized-lists": "^0.72.4",
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-native/node_modules/@react-native/virtualized-lists": {
+ "version": "0.72.8",
+ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz",
+ "integrity": "sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "nullthrows": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -10699,6 +10837,12 @@
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"dev": true
},
+ "node_modules/@types/urijs": {
+ "version": "1.19.25",
+ "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz",
+ "integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==",
+ "license": "MIT"
+ },
"node_modules/@types/ws": {
"version": "7.4.7",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
@@ -12521,6 +12665,15 @@
"node": ">=6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001642",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
@@ -12601,6 +12754,26 @@
"node": ">=10"
}
},
+ "node_modules/character-entities-html4": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz",
+ "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -13427,6 +13600,15 @@
"node": ">=8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-in-js-utils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
@@ -13451,6 +13633,17 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
@@ -16562,6 +16755,89 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
+ "node_modules/htmlparser2": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
+ "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.2",
+ "domutils": "^2.8.0",
+ "entities": "^3.0.1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/dom-serializer": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/domhandler": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.2.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/domutils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+ "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@@ -23015,6 +23291,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/ramda": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz",
+ "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
+ "license": "MIT"
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -23362,6 +23644,27 @@
"react-native-reanimated": ">=3.0.0"
}
},
+ "node_modules/react-native-render-html": {
+ "version": "6.3.4",
+ "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-6.3.4.tgz",
+ "integrity": "sha512-H2jSMzZjidE+Wo3qCWPUMU1nm98Vs2SGCvQCz/i6xf0P3Y9uVtG/b0sDbG/cYFir2mSYBYCIlS1Dv0WC1LjYig==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jsamr/counter-style": "^2.0.1",
+ "@jsamr/react-native-li": "^2.3.0",
+ "@native-html/transient-render-engine": "11.2.3",
+ "@types/ramda": "^0.27.40",
+ "@types/urijs": "^1.19.15",
+ "prop-types": "^15.5.7",
+ "ramda": "^0.27.2",
+ "stringify-entities": "^3.1.0",
+ "urijs": "^1.19.6"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.5.tgz",
@@ -25647,6 +25950,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/stringify-entities": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz",
+ "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -26484,6 +26802,12 @@
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
+ "node_modules/ts-toolbelt": {
+ "version": "6.15.5",
+ "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz",
+ "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==",
+ "license": "Apache-2.0"
+ },
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
@@ -26838,6 +27162,12 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/urijs": {
+ "version": "1.19.11",
+ "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz",
+ "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==",
+ "license": "MIT"
+ },
"node_modules/url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
diff --git a/package.json b/package.json
index 59e5e0b6..80e0f7a2 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,7 @@
"react-native-paper": "^5.12.5",
"react-native-reanimated": "~3.10.1",
"react-native-reanimated-carousel": "^3.5.1",
+ "react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^4.10.5",
"react-native-screens": "^3.31.1",
"react-native-select-dropdown": "^4.0.1",
diff --git a/styles/InfluencerCard.styles.tsx b/styles/InfluencerCard.styles.tsx
index 79de1619..0848e91f 100644
--- a/styles/InfluencerCard.styles.tsx
+++ b/styles/InfluencerCard.styles.tsx
@@ -88,10 +88,6 @@ export const stylesFn = (theme: Theme) =>
flexDirection: "row",
justifyContent: "space-between",
},
- bio: {
- fontSize: 14,
- color: Colors(theme).text,
- },
jobHistory: {
color: Colors(theme).primary,
textDecorationLine: "underline",
diff --git a/utils/conversion.ts b/utils/conversion.ts
index 435dce58..20b9fe89 100644
--- a/utils/conversion.ts
+++ b/utils/conversion.ts
@@ -1,4 +1,8 @@
export const convertToKUnits = (num: number) => {
+ if (Number.isNaN(num)) {
+ return 0;
+ }
+
if (num >= 1000) {
return `${(num / 1000).toFixed(1)}k`;
}
diff --git a/utils/text.ts b/utils/text.ts
new file mode 100644
index 00000000..9845aef3
--- /dev/null
+++ b/utils/text.ts
@@ -0,0 +1,5 @@
+export const truncateText = (text: string, length: number) => {
+ if (!text) return "";
+
+ return text.length > length ? text.slice(0, length) + "..." : text;
+};