follow users
This commit is contained in:
@@ -49,6 +49,7 @@ try {
|
||||
export const usersRef = firestore.collection("users");
|
||||
export const projectsRef = firestore.collection("projects");
|
||||
export const playlistsRef = firestore.collection("playlists");
|
||||
export const followsRef = firestore.collection("follows");
|
||||
export const notificationsRef = firestore.collection("notifications");
|
||||
export const documentsRef = firestore.collection("documents");
|
||||
export const chatsRef = firestore.collection("chats");
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createContext, useContext, useGlobal } from "reactn";
|
||||
import { useState } from "react";
|
||||
import { checkIfEmailIsValid } from "../actions/signupActions";
|
||||
import firebase, {
|
||||
followsRef,
|
||||
playlistsRef,
|
||||
projectsRef,
|
||||
usersRef,
|
||||
@@ -26,7 +27,6 @@ export default ({ children }) => {
|
||||
const [userProjects, setUserProjects] = useState([]);
|
||||
const [userLikedProjects, setUserLikedProjects] = useState([]);
|
||||
const [userPlaylists, setUserPlaylists] = useState([]);
|
||||
|
||||
useDataFromRef({
|
||||
ref: currentUID ? usersRef.doc(currentUID) : null,
|
||||
simpleRef: true,
|
||||
@@ -86,6 +86,50 @@ export default ({ children }) => {
|
||||
...newData,
|
||||
}));
|
||||
};
|
||||
const followUser = async (userId) => {
|
||||
try {
|
||||
if (currentUID && userId) {
|
||||
await followsRef.doc(`${currentUID}_${userId}`).set(
|
||||
{
|
||||
followerId: currentUID,
|
||||
followeeId: userId,
|
||||
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
setTooltip({ type: "success", text: "Abonnement mis à jour" });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setTooltip({ type: "error", text: e?.message || "Action impossible" });
|
||||
}
|
||||
};
|
||||
|
||||
const unfollowUser = async (userId) => {
|
||||
try {
|
||||
if (currentUID && userId) {
|
||||
await followsRef.doc(`${currentUID}_${userId}`).delete();
|
||||
setTooltip({ type: "success", text: "Vous ne suivez plus cet utilisateur" });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setTooltip({ type: "error", text: e?.message || "Action impossible" });
|
||||
}
|
||||
};
|
||||
const getUserByUid = async (uid) => {
|
||||
try {
|
||||
const userDoc = await usersRef.doc(uid).get();
|
||||
return userDoc.exists
|
||||
? {
|
||||
id: userDoc.id,
|
||||
...userDoc.data(),
|
||||
}
|
||||
: null;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const updateUserData = async ({ data, shouldSetTooltip = true }) => {
|
||||
try {
|
||||
@@ -193,13 +237,15 @@ export default ({ children }) => {
|
||||
userPlaylists,
|
||||
|
||||
setCurrentUserData,
|
||||
|
||||
getUserByUid,
|
||||
onSignOut,
|
||||
|
||||
pendingUserData,
|
||||
updatePendingUserData,
|
||||
|
||||
updateUserData,
|
||||
followUser,
|
||||
unfollowUser,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,29 +1,49 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Image,
|
||||
} from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import Page from "../../layouts/Page";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Image, Pressable, ScrollView, Text, View } from "react-native";
|
||||
import { background, img } from "../../assets";
|
||||
import { gutters, Palette, Style } from "../../styles";
|
||||
import SearchBar from "../../components/SearchBar";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import BorderGradient from "../../components/BorderGradient/BorderGradient";
|
||||
import { BlurView } from "expo-blur";
|
||||
import ResearchHeader from "./components/ResearchHeader";
|
||||
import MusicCard from "./components/MusicCard";
|
||||
import MoreMenu from "../../components/MoreMenu";
|
||||
import { size } from "../../styles/Style";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { usersRef } from "../../config/firebase";
|
||||
import useDataFromRef from "../../hooks/useDataFromRef";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { gutters, Palette, Style } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { size } from "../../styles/Style";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import MusicCard from "./components/MusicCard";
|
||||
import ResearchHeader from "./components/ResearchHeader";
|
||||
|
||||
const Research = () => {
|
||||
const [selected, setSelected] = useState(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const queryText = useMemo(() => search.trim(), [search]);
|
||||
|
||||
const usersQueryRef = useMemo(() => {
|
||||
if (!queryText) return null;
|
||||
try {
|
||||
return usersRef
|
||||
.orderBy("userName")
|
||||
.startAt(queryText)
|
||||
.endAt(queryText + "\uf8ff");
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}, [queryText]);
|
||||
|
||||
const {
|
||||
data: userResults = [],
|
||||
loading: usersLoading,
|
||||
loadMore: loadMoreUsers,
|
||||
} = useDataFromRef({
|
||||
ref: usersQueryRef,
|
||||
simpleRef: false,
|
||||
listener: false,
|
||||
condition: !!usersQueryRef && queryText.length > 0,
|
||||
refreshArray: [queryText],
|
||||
usePagination: true,
|
||||
batchSize: 5,
|
||||
});
|
||||
const [menuTop, setMenuTop] = useState(0);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(null);
|
||||
@@ -49,6 +69,8 @@ const Research = () => {
|
||||
<ResearchHeader
|
||||
onPress={(item) => onPressMenu(item)}
|
||||
selected={selected}
|
||||
searchValue={search}
|
||||
onChangeSearch={setSearch}
|
||||
/>
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView contentContainerStyle={{ paddingTop: 2 }}>
|
||||
@@ -113,33 +135,66 @@ const Research = () => {
|
||||
{(!selected || selected === "Profils") && (
|
||||
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
|
||||
<CreateLyricsHeader
|
||||
gradientProps={{
|
||||
start: { x: 0, y: 0 },
|
||||
end: { x: 0, y: 1 },
|
||||
}}
|
||||
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
|
||||
>
|
||||
<View style={{ gap: 8 }}>
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Pressable
|
||||
style={{ gap: 14, ...Style.containerRow }}
|
||||
key={index}
|
||||
onPress={() => navigate(Routes.SingerProfile)}
|
||||
{queryText.length === 0 && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: Palette.gray,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
Commence à taper pour rechercher des profils…
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{queryText.length > 0 &&
|
||||
Array.isArray(userResults) &&
|
||||
userResults.map((u) => (
|
||||
<Pressable
|
||||
style={{ gap: 14, ...Style.containerRow }}
|
||||
key={u.id}
|
||||
onPress={() =>
|
||||
navigate(Routes.SingerProfile, { userId: u.id })
|
||||
}
|
||||
>
|
||||
<Image
|
||||
source={
|
||||
u?.photoURL ? { uri: u.photoURL } : img.profile
|
||||
}
|
||||
style={{ ...size({ size: 60 }), borderRadius: 100 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
{u?.userName || "Utilisateur"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
))}
|
||||
|
||||
{queryText.length > 0 && userResults.length > 0 && (
|
||||
<Pressable
|
||||
onPress={loadMoreUsers}
|
||||
style={{ alignSelf: "center", marginTop: 6 }}
|
||||
>
|
||||
<Image
|
||||
source={img.profile}
|
||||
style={{ ...size({ size: 60 }), borderRadius: 100 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
opacity: usersLoading ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
ReggaetonLil
|
||||
{usersLoading ? "Chargement…" : "Charger plus"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
))}
|
||||
)}
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
</View>
|
||||
|
||||
@@ -6,10 +6,15 @@ import BorderGradient from "../../../components/BorderGradient/BorderGradient";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { FONT_FAMILY } from "../../../styles/Fonts";
|
||||
|
||||
const ResearchHeader = ({ onPress, selected }) => {
|
||||
const ResearchHeader = ({ onPress, selected, searchValue = "", onChangeSearch = () => {} }) => {
|
||||
return (
|
||||
<View style={{ gap: 12 }}>
|
||||
<SearchBar />
|
||||
<SearchBar
|
||||
textInputProps={{
|
||||
value: searchValue,
|
||||
onChangeText: onChangeSearch,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
...Style.containerRow,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BlurView } from "expo-blur";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
Image,
|
||||
@@ -14,14 +14,46 @@ import { background, icons, img } from "../../assets";
|
||||
import BorderGradient from "../../components/BorderGradient/BorderGradient";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import Page from "../../layouts/Page";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { getFollowersCount, getFollowingCount } from "../../services/follows";
|
||||
import { useGlobal } from "reactn";
|
||||
import { followsRef } from "../../config/firebase";
|
||||
import { gutters, Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import Style, { size } from "../../styles/Style";
|
||||
import MusicCard from "../Library/components/MusicCard";
|
||||
|
||||
const SingerProfile = () => {
|
||||
const SingerProfile = ({ route }) => {
|
||||
const { userId } = route.params;
|
||||
const { getUserByUid, followUser, unfollowUser } = useUser();
|
||||
const [currentUID] = useGlobal("currentUID");
|
||||
console.log("user id ", userId);
|
||||
const [selected, setSelected] = useState(null);
|
||||
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [following, setFollowing] = useState(0);
|
||||
const [followers, setFollowers] = useState(0);
|
||||
const fetchUserData = async () => {
|
||||
const data = userId ? await getUserByUid(userId) : {};
|
||||
setUserData(data);
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchUserData();
|
||||
}, [userId]);
|
||||
useEffect(() => {
|
||||
if (!userId) return;
|
||||
getFollowersCount(userId).then(setFollowers);
|
||||
getFollowingCount(userId).then(setFollowing);
|
||||
(async () => {
|
||||
try {
|
||||
if (currentUID && userId) {
|
||||
const doc = await followsRef.doc(`${currentUID}_${userId}`).get();
|
||||
setIsFollowing(!!doc?.exists);
|
||||
} else {
|
||||
setIsFollowing(false);
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
}, [userId, currentUID]);
|
||||
console.log("following", following);
|
||||
const onPressMenu = (item) => {
|
||||
if (selected === item) {
|
||||
setSelected(null);
|
||||
@@ -29,7 +61,19 @@ const SingerProfile = () => {
|
||||
setSelected(item);
|
||||
}
|
||||
};
|
||||
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
const handleFollowUser = async () => {
|
||||
if (!currentUID || !userId || currentUID === userId) return;
|
||||
if (isFollowing) {
|
||||
await unfollowUser(userId);
|
||||
setIsFollowing(false);
|
||||
setFollowers((v) => Math.max(0, (v || 0) - 1));
|
||||
} else {
|
||||
await followUser(userId);
|
||||
setIsFollowing(true);
|
||||
setFollowers((v) => (v || 0) + 1);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Page
|
||||
backgroundImg={background.profileBG}
|
||||
@@ -74,7 +118,7 @@ const SingerProfile = () => {
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
}}
|
||||
>
|
||||
elia.mrn
|
||||
{userData?.userName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ ...Style.containerRow, gap: 10, marginTop: 10 }}>
|
||||
@@ -83,22 +127,25 @@ const SingerProfile = () => {
|
||||
<Text style={styles.label}>playbacks</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text style={styles.value}>880</Text>
|
||||
<Text style={styles.value}>{followers}</Text>
|
||||
<Text style={styles.label}>abonnés</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text style={styles.value}>6</Text>
|
||||
<Text style={styles.value}>{following}</Text>
|
||||
<Text style={styles.label}>abonnements</Text>
|
||||
</View>
|
||||
</View>
|
||||
<GradientButton
|
||||
title="Suivre"
|
||||
containerStyle={{
|
||||
width: "80%",
|
||||
alignSelf: "center",
|
||||
marginTop: 18,
|
||||
}}
|
||||
/>
|
||||
{currentUID !== userId && (
|
||||
<GradientButton
|
||||
onPress={handleFollowUser}
|
||||
title={isFollowing ? "Ne plus suivre" : "Suivre"}
|
||||
containerStyle={{
|
||||
width: "80%",
|
||||
alignSelf: "center",
|
||||
marginTop: 18,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</BlurView>
|
||||
</View>
|
||||
<View
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { followsRef } from "../config/firebase";
|
||||
|
||||
export const getFollowersCount = async (userId) => {
|
||||
if (!userId) return 0;
|
||||
const snap = await followsRef.where("followeeId", "==", userId).get();
|
||||
return snap?.size || 0;
|
||||
};
|
||||
|
||||
export const getFollowingCount = async (userId) => {
|
||||
if (!userId) return 0;
|
||||
const snap = await followsRef.where("followerId", "==", userId).get();
|
||||
return snap?.size || 0;
|
||||
};
|
||||
|
||||
export const unfollowUser = async ({ followerId, followeeId }) => {
|
||||
if (!followerId || !followeeId) throw new Error("Missing followerId/followeeId");
|
||||
const id = `${followerId}_${followeeId}`;
|
||||
await followsRef.doc(id).delete();
|
||||
return id;
|
||||
};
|
||||
Reference in New Issue
Block a user