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 all 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
97 changes: 62 additions & 35 deletions expo/components/UserRoomHistory.tsx
Original file line number Diff line number Diff line change
@@ -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<Room[]>([]);
const historyRoom = supabase
.from("room_users")
.select("rooms!inner(created_at, name, id, host:user_profile(username))");

type HistoryRoomType = QueryData<typeof historyRoom>;

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]);

if (rooms) {
const everyoneHaveRoom = rooms.every((room) => room.rooms);
const everyoneHaveId = rooms.every((room) => room.rooms?.name);
if (!everyoneHaveId || !everyoneHaveRoom)
return <Subtitle>Impossible de charger l'historique des salles</Subtitle>;
}

return (
<View>
<Text style={styles.title}>Historique</Text>
<View style={{ gap: 10 }}>
<H2>Historique</H2>
{rooms == null && (
<Subtitle>Vous n'avez aucune salle dans votre historique</Subtitle>
)}
<FlatList
data={rooms}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.rooms!.id}
renderItem={({ item }) => {
return <RoomHistoryInfoCard room={item} />;
if (!item.rooms)
return <Subtitle>Impossible de charger cette salle</Subtitle>;
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>
);
}

const styles = StyleSheet.create({
title: {
fontFamily: "Outfit-Bold",
fontSize: 24,
marginBottom: 10,
},
});
53 changes: 31 additions & 22 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, style }, ref) => {
const [avatarUrl, setAvatarUrl] = useState<string>();

useImperativeHandle(ref, () => ({
Expand All @@ -30,35 +31,43 @@ 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={[
{ borderRadius: radius },
styles.avatar,
styles.image,
style,
]}
/>
) : (
<View
style={[
{ borderRadius: radius },
styles.avatar,
styles.noImage,
style,
]}
/>
)}
</>
);
}
);

const styles = StyleSheet.create({
avatar: {
borderRadius: 9999,
overflow: "hidden",
maxWidth: "100%",
aspectRatio: 1,
Expand Down
Loading
Loading