Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/profile-page #143

Merged
merged 8 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion expo/app/(tabs)/profile/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Stack } from "expo-router";

import { ProfileHeader } from "../../../components/profile/HeaderProfile";

export default function ProfileTabLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: "Profil" }} />
<Stack.Screen
name="index"
options={{ title: "Profil", header: () => <ProfileHeader /> }}
/>
<Stack.Screen name="account" options={{ headerShown: false }} />
</Stack>
);
Expand Down
37 changes: 10 additions & 27 deletions expo/app/(tabs)/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -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 && (
<View style={styles.elements}>
<Button
prependIcon={<SignOut />}
onPress={() => supabase.auth.signOut()}
>
Se déconnecter
</Button>
<Button href="/(tabs)/profile/account">Gérer mon compte</Button>
</View>
)}
</>
<ScrollView
contentContainerStyle={{
paddingVertical: 32,
paddingHorizontal: 18,
}}
>
<UserRoomHistory limit={10} />
</ScrollView>
);
}

const styles = StyleSheet.create({
elements: {
gap: 10,
},
});
58 changes: 17 additions & 41 deletions expo/components/RoomHistoryInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Link href={`/rooms/${room.id}`}>
<Link href={`/rooms/${roomId}`}>
<View style={styles.layout}>
<View style={styles.infos}>
<Text style={styles.roomName}>{room.name}</Text>
<Text style={styles.roomName}>{roomName}</Text>
<Text style={styles.roomInfo}>
par {username} le {formatedDate}
par {hostUsername} le {formattedDate}
</Text>
</View>
<MaterialIcons name="keyboard-arrow-right" size={32} color="black" />
Expand All @@ -67,11 +43,11 @@ const styles = StyleSheet.create({
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginVertical: 8,
},

infos: {
flexDirection: "column",
marginVertical: 8,
gap: 4,
},

Expand Down
90 changes: 63 additions & 27 deletions expo/components/UserRoomHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
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 { supabase } from "../lib/supabase";
import { useUserProfile } from "../lib/userProfile";
import Alert from "./Alert";
import RoomHistoryInfoCard from "./RoomHistoryInfoCard";
import { Text, View } from "./Themed";
import H2 from "./text/H2";
import Colors from "../constants/Colors";
import { supabase } from "../lib/supabase";
import { useUserProfile } from "../lib/userProfile";

export default function UserRoomHistory() {
const [rooms, setRooms] = useState<Room[]>([]);
const historyRoom = supabase
.from("room_users")
.select("rooms!inner(created_at, name,id, host:user_profile(username))");

type historyRoomType = QueryData<typeof historyRoom>;

GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
export default function UserRoomHistory({ limit = 5 }: { limit?: number }) {
const [rooms, setRooms] = useState<historyRoomType | null>([]);
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]);

return (
<View>
<Text style={styles.title}>Historique</Text>
<View style={{ gap: 10 }}>
<H2>Historique</H2>
{rooms == null && (
<Text
style={{
fontFamily: "Outfit-Regular",
fontSize: 16,
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
color: Colors.light.gray,
}}
>
Vous n'avez aucune salle dans votre historique
</Text>
)}
<FlatList
data={rooms}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.rooms?.id ?? ""}
renderItem={({ item }) => {
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
return <RoomHistoryInfoCard room={item} />;
if (!item.rooms)
return <Text>Impossible de charger cette salle</Text>;
return (
<RoomHistoryInfoCard
createdAt={item.rooms.created_at}
hostUsername={
item.rooms.host?.username ??
"Impossible de récupérer le nom de l'hôte"
}
roomId={item.rooms.id}
roomName={item.rooms.name}
/>
);
}}
/>
</View>
Expand Down
52 changes: 29 additions & 23 deletions expo/components/profile/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<ImageStyle>;
noCache?: boolean;
radius?: number;
};

const Avatar = forwardRef<AvatarRemote, AvatarProps>(
({ id, tempoAvatarImage, style }, ref) => {
({ id, tempoAvatarImage, noCache, radius = 9999 }, ref) => {
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
const [avatarUrl, setAvatarUrl] = useState<string>();

useImperativeHandle(ref, () => ({
Expand All @@ -30,35 +31,41 @@ const Avatar = forwardRef<AvatarRemote, AvatarProps>(
}, [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 (
<Image
source={{ uri: tempoAvatarImage ?? avatarUrl }}
aria-aria-label="Avatar"
style={[styles.avatar, styles.image, style]}
/>
);
} else {
return (
<View style={[styles.avatar, styles.noImage, style]}>
<User />
</View>
);
}
return (
<>
{avatarUrl ? (
<Image
source={{ uri: tempoAvatarImage ?? avatarUrl }}
aria-aria-label="Avatar"
style={[
{ aspectRatio: 1, width: "100%", borderRadius: radius },
styles.avatar,
styles.image,
]}
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
/>
) : (
<View
style={[
{ aspectRatio: 1, width: "100%", borderRadius: radius },
styles.avatar,
styles.noImage,
]}
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
/>
)}
</>
);
}
);

const styles = StyleSheet.create({
avatar: {
borderRadius: 9999,
overflow: "hidden",
maxWidth: "100%",
aspectRatio: 1,
Expand All @@ -72,7 +79,6 @@ const styles = StyleSheet.create({
noImage: {
backgroundColor: "red",
border: "1px solid rgb(200, 200, 200)",
borderRadius: 5,
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
},
});

Expand Down
16 changes: 11 additions & 5 deletions expo/components/profile/AvatarForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<string | undefined>(undefined);

Expand All @@ -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
Expand Down Expand Up @@ -88,11 +89,16 @@ const AvatarForm = forwardRef((props: AvatarProps, ref) => {
<View style={{ width: "100%", gap: 10 }}>
<Text style={formStyles.label}>Photo de profil</Text>
<Pressable onPress={selectAvatar}>
<Avatar id={user?.id} ref={avatarRef} tempoAvatarImage={avatarUrl} />
<Avatar
id={user?.user_profile_id}
ref={avatarRef}
tempoAvatarImage={avatarUrl}
noCache
/>
</Pressable>
<Button
onPress={selectAvatar}
disabled={uploading}
// disabled={uploading}
GaspardBBY marked this conversation as resolved.
Show resolved Hide resolved
block
type="outline"
size="small"
Expand Down
Loading
Loading