diff --git a/expo/app/(tabs)/profile/_layout.tsx b/expo/app/(tabs)/profile/_layout.tsx
index cd789bfc..5b225d7e 100644
--- a/expo/app/(tabs)/profile/_layout.tsx
+++ b/expo/app/(tabs)/profile/_layout.tsx
@@ -1,9 +1,14 @@
import { Stack } from "expo-router";
+import { ProfileHeader } from "../../../components/profile/HeaderProfile";
+
export default function ProfileTabLayout() {
return (
-
+ }}
+ />
);
diff --git a/expo/app/(tabs)/profile/index.tsx b/expo/app/(tabs)/profile/index.tsx
index 140a0eb3..5016a158 100644
--- a/expo/app/(tabs)/profile/index.tsx
+++ b/expo/app/(tabs)/profile/index.tsx
@@ -1,34 +1,17 @@
-import SignOut from "phosphor-react-native/src/icons/SignOut";
import React from "react";
-import { StyleSheet } from "react-native";
+import { ScrollView } from "react-native";
-import Button from "../../../components/Button";
-import { View } from "../../../components/Tamed";
-import { supabase } from "../../../lib/supabase";
-import { useSupabaseUserHook } from "../../../lib/useSupabaseUser";
+import UserRoomHistory from "../../../components/UserRoomHistory";
export default function TabsProfile() {
- const user = useSupabaseUserHook();
-
return (
- <>
- {user && (
-
- }
- onPress={() => supabase.auth.signOut()}
- >
- Se déconnecter
-
-
-
- )}
- >
+
+
+
);
}
-
-const styles = StyleSheet.create({
- elements: {
- gap: 10,
- },
-});
diff --git a/expo/components/RoomHistoryInfoCard.tsx b/expo/components/RoomHistoryInfoCard.tsx
index 5833ce17..dd85c936 100644
--- a/expo/components/RoomHistoryInfoCard.tsx
+++ b/expo/components/RoomHistoryInfoCard.tsx
@@ -1,58 +1,34 @@
import { MaterialIcons } from "@expo/vector-icons";
-import { Room } from "commons/database-types-utils";
import { Link } from "expo-router";
-import { useEffect, useState } from "react";
import { StyleSheet } from "react-native";
-import Alert from "./Alert";
import { Text, View } from "./Themed";
-import { getUserProfileFromUserProfileId } from "../lib/userProfile";
interface RoomHistoryInfoCardProps {
- room: Room;
+ createdAt: string;
+ hostUsername: string;
+ roomId: string;
+ roomName: string;
}
export default function RoomHistoryInfoCard({
- room,
+ createdAt,
+ hostUsername,
+ roomId,
+ roomName,
}: RoomHistoryInfoCardProps) {
- const [username, setUsername] = useState("");
- const [formatedDate, setFormatedDate] = useState("");
-
- const getUsername = async (userProfileId: string) => {
- const user = await getUserProfileFromUserProfileId(userProfileId);
- if (!user) {
- Alert.alert("Erreur lors de la récupération de l'utilisateur");
- return;
- }
- return user.username;
- };
- useEffect(() => {
- (async () => {
- const username = await getUsername(room.host_user_profile_id || "");
- if (!username) {
- Alert.alert("Erreur lors de la récupération du nom d'utilisateur");
- return;
- }
- const formattedDate = new Date(room.created_at).toLocaleDateString(
- "fr-FR",
- {
- year: "numeric",
- month: "long",
- day: "numeric",
- }
- );
-
- setUsername(username);
- setFormatedDate(formattedDate);
- })();
- }, []);
+ const formattedDate = new Date(createdAt).toLocaleDateString("fr-FR", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
return (
-
+
- {room.name}
+ {roomName}
- par {username} le {formatedDate}
+ par {hostUsername} le {formattedDate}
@@ -67,11 +43,11 @@ const styles = StyleSheet.create({
justifyContent: "space-between",
alignItems: "center",
width: "100%",
+ marginVertical: 8,
},
infos: {
flexDirection: "column",
- marginVertical: 8,
gap: 4,
},
diff --git a/expo/components/UserRoomHistory.tsx b/expo/components/UserRoomHistory.tsx
index deed20a5..0f77a516 100644
--- a/expo/components/UserRoomHistory.tsx
+++ b/expo/components/UserRoomHistory.tsx
@@ -1,57 +1,84 @@
-import { Room } from "commons/database-types-utils";
+import { QueryData } from "@supabase/supabase-js/dist/module/lib/types";
import { useEffect, useState } from "react";
import { FlatList, StyleSheet } from "react-native";
+import RoomHistoryInfoCard from "./RoomHistoryInfoCard";
+import { View } from "./Themed";
+import H2 from "./text/H2";
+import Subtitle from "./text/Subtitle";
import { supabase } from "../lib/supabase";
import { useUserProfile } from "../lib/userProfile";
-import Alert from "./Alert";
-import RoomHistoryInfoCard from "./RoomHistoryInfoCard";
-import { Text, View } from "./Themed";
-export default function UserRoomHistory() {
- const [rooms, setRooms] = useState([]);
+const historyRoom = supabase
+ .from("room_users")
+ .select("rooms!inner(created_at, name, id, host:user_profile(username))");
+
+type HistoryRoomType = QueryData;
+
+export default function UserRoomHistory({ limit = 5 }: { limit?: number }) {
+ const [rooms, setRooms] = useState([]);
const user = useUserProfile();
+
useEffect(() => {
- (async () => {
- if (!user) return;
- const userId = user.user_profile_id;
-
- const { error, data } = await supabase
- .from("rooms")
- .select("*, room_users(*)")
- .eq("room_users.profile_id", userId)
- .eq("is_active", false)
- .order("created_at", { ascending: false })
- .limit(5);
-
- if (error) {
- return Alert.alert(
- "Erreur lors de la récupération des salles d'écoute"
- );
+ if (!user) return;
+ /**
+ * Fetch all rooms where user is a member and the room is not active
+ * Tips : If the filter on a referenced table's column is not satisfied, the referenced
+ * table returns [] or null but the parent table is not filtered out.
+ * If you want to filter out the parent table rows, use the !inner hint
+ */
+ const fetchRoomHistory = async () => {
+ const { data } = await supabase
+ .from("room_users")
+ .select(
+ "rooms!inner(created_at, name, id, host:user_profile(username))"
+ )
+ .eq("rooms.is_active", false)
+ .eq("profile_id", user.user_profile_id)
+ .order("rooms(created_at)", { ascending: false })
+ .limit(limit);
+
+ if (!data || data.length === 0) {
+ return setRooms(null);
}
setRooms(data);
- })();
+ };
+ fetchRoomHistory();
}, [user]);
+ if (rooms) {
+ const everyoneHaveRoom = rooms.every((room) => room.rooms);
+ const everyoneHaveId = rooms.every((room) => room.rooms?.name);
+ if (!everyoneHaveId || !everyoneHaveRoom)
+ return Impossible de charger l'historique des salles;
+ }
+
return (
-
- Historique
+
+ Historique
+ {rooms == null && (
+ Vous n'avez aucune salle dans votre historique
+ )}
item.id}
+ keyExtractor={(item) => item.rooms!.id}
renderItem={({ item }) => {
- return ;
+ if (!item.rooms)
+ return Impossible de charger cette salle;
+ return (
+
+ );
}}
/>
);
}
-
-const styles = StyleSheet.create({
- title: {
- fontFamily: "Outfit-Bold",
- fontSize: 24,
- marginBottom: 10,
- },
-});
diff --git a/expo/components/profile/Avatar.tsx b/expo/components/profile/Avatar.tsx
index f4d837c9..cc2b8743 100644
--- a/expo/components/profile/Avatar.tsx
+++ b/expo/components/profile/Avatar.tsx
@@ -1,5 +1,4 @@
import { ImageStyle } from "expo-image";
-import User from "phosphor-react-native/src/regular/User";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { Image, StyleProp, StyleSheet, View } from "react-native";
@@ -12,10 +11,12 @@ type AvatarProps = {
id: string | undefined;
tempoAvatarImage?: string; // if we want to pass an image url directly (on edit profile)
style?: StyleProp;
+ noCache?: boolean;
+ radius?: number;
};
const Avatar = forwardRef(
- ({ id, tempoAvatarImage, style }, ref) => {
+ ({ id, tempoAvatarImage, noCache, radius = 9999, style }, ref) => {
const [avatarUrl, setAvatarUrl] = useState();
useImperativeHandle(ref, () => ({
@@ -30,35 +31,43 @@ const Avatar = forwardRef(
}, [id]);
async function downloadUserImage() {
- const path = `${id}.jpg`;
- const { data } = supabase.storage
- .from("avatars")
- .getPublicUrl(path + "?avoidCache=" + Math.random());
+ let path = `${id}.jpg`;
+ if (noCache) path += "?avoidCache=" + Math.random();
+ const { data } = supabase.storage.from("avatars").getPublicUrl(path);
setAvatarUrl(data.publicUrl);
}
- if (avatarUrl) {
- return (
-
- );
- } else {
- return (
-
-
-
- );
- }
+ return (
+ <>
+ {avatarUrl ? (
+
+ ) : (
+
+ )}
+ >
+ );
}
);
const styles = StyleSheet.create({
avatar: {
- borderRadius: 9999,
overflow: "hidden",
maxWidth: "100%",
aspectRatio: 1,
diff --git a/expo/components/profile/AvatarForm.tsx b/expo/components/profile/AvatarForm.tsx
index 26842192..ffaa74dc 100644
--- a/expo/components/profile/AvatarForm.tsx
+++ b/expo/components/profile/AvatarForm.tsx
@@ -6,7 +6,7 @@ import { Alert, Platform, Pressable, View } from "react-native";
import Avatar, { AvatarRemote } from "./Avatar";
import { supabase } from "../../lib/supabase";
-import { useSupabaseUserHook } from "../../lib/useSupabaseUser";
+import { useUserProfile } from "../../lib/userProfile";
import Button from "../Button";
import { formStyles } from "../ControlledInput";
import { Text } from "../Themed";
@@ -21,7 +21,7 @@ interface AvatarProps {
*/
const AvatarForm = forwardRef((props: AvatarProps, ref) => {
const { onImageLoad } = props;
- const user = useSupabaseUserHook();
+ const user = useUserProfile();
const [uploading, setUploading] = useState(false);
const [avatarUrl, setAvatarUrl] = useState(undefined);
@@ -41,7 +41,8 @@ const AvatarForm = forwardRef((props: AvatarProps, ref) => {
error: "No user or avatar",
};
}
- const fileName = `${user.id}.jpg`;
+
+ const fileName = `${user.user_profile_id}.jpg`;
const image = await getBase64Image(avatarUrl);
const { error } = await supabase.storage
@@ -64,7 +65,7 @@ const AvatarForm = forwardRef((props: AvatarProps, ref) => {
async function selectAvatar() {
if (!user) return;
- setUploading(true);
+ // setUploading(true);
const file = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
@@ -81,14 +82,19 @@ const AvatarForm = forwardRef((props: AvatarProps, ref) => {
setAvatarUrl(image.uri);
onImageLoad();
}
- setUploading(false);
+ // setUploading(false);
}
return (
Photo de profil
-
+
+
+
+
+
+
+
+
+
+ {userProfile ? userProfile.username : "chargement"}
+
+ @{profile?.nickname}
+
+
+
+ );
+};
+
+const profileStyles = StyleSheet.create({
+ personalityView: {
+ flexDirection: "column",
+ marginHorizontal: 12,
+ },
+ username: {
+ fontSize: 16,
+ fontFamily: Font.Outfit.Medium,
+ letterSpacing: 0.32,
+ },
+ nickname: {
+ fontSize: 14,
+ fontFamily: Font.Outfit.Regular,
+ color: Colors.light.gray,
+ },
+});
diff --git a/expo/components/text/Subtitle.tsx b/expo/components/text/Subtitle.tsx
new file mode 100644
index 00000000..6f15a8ce
--- /dev/null
+++ b/expo/components/text/Subtitle.tsx
@@ -0,0 +1,23 @@
+import { StyleSheet } from "react-native";
+
+import Colors from "../../constants/Colors";
+import Font from "../../constants/Font";
+import { Text } from "../Themed";
+
+type SubtitleProps = {
+ children: string | string[];
+};
+
+const Subtitle: React.FC = ({ children }) => {
+ return {children};
+};
+
+const styles = StyleSheet.create({
+ Subtitle: {
+ fontFamily: Font.Outfit.Regular,
+ fontSize: 16,
+ color: Colors.light.gray,
+ },
+});
+
+export default Subtitle;
diff --git a/expo/constants/Colors.ts b/expo/constants/Colors.ts
index 58e6f7e5..a8645b45 100644
--- a/expo/constants/Colors.ts
+++ b/expo/constants/Colors.ts
@@ -11,5 +11,6 @@ export default {
tint: tintColorLight,
tabIconDefault: fakeBlack,
tabIconSelected: fakeBlack,
+ gray: "#B2B2B2",
},
};
diff --git a/expo/constants/Font.ts b/expo/constants/Font.ts
new file mode 100644
index 00000000..81e3f262
--- /dev/null
+++ b/expo/constants/Font.ts
@@ -0,0 +1,26 @@
+/**
+ * Font constants
+ */
+export default {
+ Outfit: {
+ Thin: "Outfit-Thin",
+ ExtraLight: "Outfit-ExtraLight",
+ Light: "Outfit-Light",
+ Medium: "Outfit-Medium",
+ Regular: "Outfit-Regular",
+ SemiBold: "Outfit-SemiBold",
+ Bold: "Outfit-Bold",
+ ExtraBold: "Outfit-ExtraBold",
+ Black: "Outfit-Black",
+ },
+ Unbounded: {
+ Black: "Unbounded-Black",
+ Bold: "Unbounded-Bold",
+ ExtraBold: "Unbounded-ExtraBold",
+ ExtraLight: "Unbounded-ExtraLight",
+ Light: "Unbounded-Light",
+ Medium: "Unbounded-Medium",
+ Regular: "Unbounded-Regular",
+ SemiBold: "Unbounded-SemiBold",
+ },
+};