From 3da87167c200be0a1c71a08f8f20d9383b054017 Mon Sep 17 00:00:00 2001 From: Boillot Thomas <101114168+TheRealSharKzy@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:30:36 +0200 Subject: [PATCH] feat/create-room-improvements (#119) * fix(frontend) : now retrieving only the streaming services that the user bound to his account * refactor(frontend) : use the same component for the parameters lists on the create room page and the room configuration page * refactor(frontend) : now using warning component when I need to display a warning * refactor(frontend) : changing some things on the front * fix(frontend): fixing eslint problems * refactor(frontend): removing a useless setTimeout * fix: added servicesWithoutAccount * chore: improved ui & disabled anonymous checkbox * feat(ui): added skeleton loader --------- Co-authored-by: GaspardBBY <96123850+GaspardBBY@users.noreply.github.com> Co-authored-by: MAXOUXAX <24844231+MAXOUXAX@users.noreply.github.com> --- expo/app/(tabs)/rooms/create.tsx | 179 +++++++------- expo/components/CustomCheckbox.tsx | 8 +- expo/components/ParametersList.tsx | 234 +++++++++++------- .../RoomConfigurationParametersList.tsx | 167 +------------ expo/components/ServicesList.tsx | 68 ++++- 5 files changed, 316 insertions(+), 340 deletions(-) diff --git a/expo/app/(tabs)/rooms/create.tsx b/expo/app/(tabs)/rooms/create.tsx index 8f8cc54d..f17fa2c4 100644 --- a/expo/app/(tabs)/rooms/create.tsx +++ b/expo/app/(tabs)/rooms/create.tsx @@ -9,9 +9,11 @@ import { } from "react-native"; import Alert from "../../../components/Alert"; +import Button from "../../../components/Button"; import CustomTextInput from "../../../components/CustomTextInput"; import ParametersList from "../../../components/ParametersList"; import ServiceList from "../../../components/ServicesList"; +import Warning from "../../../components/Warning"; import { getApiUrl } from "../../../lib/apiUrl"; type CreateRoomFormBody = { @@ -33,42 +35,18 @@ export type StreamingService = { export default function CreateRoom() { const [roomName, setRoomName] = useState(""); const [roomCode, setRoomCode] = useState(""); - const [percentageVoteToSkipAMusic, setPercentageVote] = useState("70"); + const [percentageVoteToSkipAMusic, setPercentageVote] = useState(70); const [maxMusicPerUser, setMaxMusicPerUser] = useState("3"); const [maxMusicDuration, setMaxMusicDuration] = useState("300"); const [canVote, setCanVote] = useState(true); const [isFormValid, setIsFormValid] = useState(false); - const [services, setServices] = useState([]); const [selectedService, setSelectedService] = useState(); - const [isPressed, setIsPressed] = useState(false); + const [error, setError] = useState(); + const [errorMessage, setErrorMessage] = useState(); const baseUrl = getApiUrl(); - const TriangleRight = () => { - return ; - }; - - const TriangleDown = () => { - return ; - }; - - useEffect(() => { - const fetchServices = async () => { - const response = await fetch(baseUrl + "/streaming-services"); - const data = await response.json(); - - const services: StreamingService[] = []; - - data.forEach((service: StreamingService) => { - services.push(service); - }); - setServices(services); - }; - - fetchServices(); - }, []); - useEffect(() => { if (roomName && roomCode && selectedService) { setIsFormValid(true); @@ -77,23 +55,31 @@ export default function CreateRoom() { } }, [roomName, roomCode, selectedService]); + useEffect(() => { + if (error) { + setError(false); + setErrorMessage(""); + router.replace("/rooms/create"); + } + }, []); + function checkConstraints(body: CreateRoomFormBody): { error: true | null } { if (body.voteSkippingNeeded > 100 || body.voteSkippingNeeded < 0) { - Alert.alert( + setErrorMessage( "Mauvais pourcentage : Le pourcentage doit être entre 0 et 100" ); return { error: true }; } if (body.maxMusicPerUser <= 0) { - Alert.alert( + setErrorMessage( "Mauvais nombre de musique : Le nombre maximum de musique par utilisateur doit être positif ou au moins supérieur à 1" ); return { error: true }; } if (body.maxMusicDuration <= 0) { - Alert.alert( + setErrorMessage( "Mauvaise durée de musique : La durée maximale d'une musique doit être positive ou au moins supérieur à 1 seconde" ); return { error: true }; @@ -109,13 +95,16 @@ export default function CreateRoom() { code: roomCode, service: selectedService, voteSkipping: canVote, - voteSkippingNeeded: parseInt(percentageVoteToSkipAMusic, 10), + voteSkippingNeeded: percentageVoteToSkipAMusic, maxMusicPerUser: parseInt(maxMusicPerUser, 10), maxMusicDuration: parseInt(maxMusicDuration, 10), }; const { error } = checkConstraints(body); - if (error !== null) return; + if (error !== null) { + setError(true); + return; + } if (!body.voteSkipping) body.voteSkippingNeeded = 0; @@ -130,10 +119,13 @@ export default function CreateRoom() { }); if (!response.ok) { - if (response.status === 409) - return Alert.alert("Ce code de salle est déjà utilisé"); - - return Alert.alert(response.statusText); + if (response.status === 409) { + setError(true); + setErrorMessage("Ce code de salle est déjà utilisé"); + return; + } + Alert.alert("Une erreur est survenue lors de la création de la salle"); + return; } const jsonResponse = await response.json(); @@ -142,86 +134,75 @@ export default function CreateRoom() { router.push(`/rooms/${roomId}`); } catch (error) { console.error(error); - Alert.alert("Une erreur est survenue lors de la création de la salle"); + setError(true); + setErrorMessage( + "Une erreur est survenue lors de la création de la salle" + ); } }; return ( - Nom de la salle + + Nom de la salle + * + - Code de la salle + + Code de la salle{" "} + *{" "} + - Plateforme de streaming à utiliser - + Platform de streaming à utiliser{" "} + * + + + - - { - setIsPressed(!isPressed); - }} - > - - {isPressed ? : } - Paramètres supplémentaires - - - - {isPressed && ( - - )} - } + {/* Créer une salle d'écoute - + */} + + + + ); } const styles = StyleSheet.create({ - triangle: { - width: 0, - height: 0, - backgroundColor: "transparent", - borderStyle: "solid", - borderLeftWidth: 7, - borderRightWidth: 7, - borderBottomWidth: 14, - borderLeftColor: "transparent", - borderRightColor: "transparent", - borderBottomColor: "black", - }, - - triangleRight: { - transform: "rotateZ(90deg)", - }, - - triangleDown: { - transform: "rotateX(180deg)", - }, - items: { flexDirection: "row", alignItems: "center", @@ -254,15 +235,23 @@ const styles = StyleSheet.create({ }, page: { paddingTop: 20, - paddingLeft: 10, + // paddingLeft: 10, alignItems: "center", justifyContent: "center", - paddingHorizontal: 20, + // paddingHorizontal: 20, + // paddingTop: 20, + paddingBottom: 20, + // justifyContent: "center", + // alignItems: "center", + // // flex: 1, + // flexDirection: "column", + alignSelf: "stretch", + // gap: 8, }, labelText: { - fontSize: 14, - fontWeight: "bold", - marginBottom: 5, + fontSize: 20, + fontFamily: "Outfit-Bold", + marginVertical: 10, }, buttonDisabled: { backgroundColor: "#7f7f7f", diff --git a/expo/components/CustomCheckbox.tsx b/expo/components/CustomCheckbox.tsx index e80ac3cf..32c591f1 100644 --- a/expo/components/CustomCheckbox.tsx +++ b/expo/components/CustomCheckbox.tsx @@ -24,7 +24,9 @@ export default function CustomCheckbox({ disabled={disabled} color="black" /> - {label} + + {label} + ); } @@ -52,4 +54,8 @@ const styles = StyleSheet.create({ borderColor: "#1A1A1A", backgroundColor: "#FFF", }, + strikethrough: { + textDecorationLine: "line-through", + textDecorationColor: "#7f7f7f", + }, }); diff --git a/expo/components/ParametersList.tsx b/expo/components/ParametersList.tsx index 1186feec..1c4ce6a7 100644 --- a/expo/components/ParametersList.tsx +++ b/expo/components/ParametersList.tsx @@ -1,18 +1,20 @@ -import Checkbox from "expo-checkbox"; -import React from "react"; +import React, { useState } from "react"; import { StyleSheet, Text, View } from "react-native"; +import CustomCheckbox from "./CustomCheckbox"; +import CustomSlider from "./CustomSlider"; import CustomTextInput from "./CustomTextInput"; interface ParametersListProps { - percentageVoteToSkipAMusic: string; - setPercentageVote: (text: string) => void; + percentageVoteToSkipAMusic: number; + setPercentageVote: (text: number) => void; maxMusicPerUser: string; setMaxMusicPerUser: (text: string) => void; maxMusicDuration: string; setMaxMusicDuration: (text: string) => void; - canVote: boolean; - setCanVote: (value: boolean) => void; + canSkip: boolean; + setCanSkip: (value: boolean) => void; + create: boolean; } export default function ParametersList({ @@ -22,104 +24,166 @@ export default function ParametersList({ setMaxMusicPerUser, maxMusicDuration, setMaxMusicDuration, - canVote, - setCanVote, + canSkip, + setCanSkip, + create, }: ParametersListProps) { + const [canBeAnonymous, setCanBeAnonymous] = useState(false); + const [sliderParticipantValue, setSliderParticipantValue] = useState(10); return ( - - - - Autoriser à lancer un vote pour passer une musique - - setCanVote(newValue)} + + + + + Limite de participants{" "} + {!create && *} + + setSliderParticipantValue(value)} + // The column is not on the table room_configuration + // onSlidingComplete={handleSave} + step={1} + /> + + + + + + + Pourcentage nécessaire{" "} + {!create && *} + + setPercentageVote(value)} + step={5} + disabled={!canSkip} + /> + + + + + Durée maximale d'une musique{" "} + {!create && *} + + + + + + Nombre de musiques maximal par participant{" "} + {!create && *} + + + - - - Pourcentage de votes pour passer une musique - - - - Nombre maximum de musique par utilisateur - - - Durée maximale d'une musique - - - Pourcentage de votes pour passer une musique - - - - Nombre maximum de musique par utilisateur - - - Durée maximale d'une musique - ); } const styles = StyleSheet.create({ - checkbox: { - margin: 10, + title: { + color: "#000", + fontFamily: "Outfit-Regular", + fontSize: 24, + fontStyle: "normal", + fontWeight: "700", + letterSpacing: 0.48, + padding: 10, }, - - checkboxContainer: { - flexDirection: "column", + slider: { justifyContent: "center", - alignItems: "center", - marginTop: 20, + marginLeft: 10, + flex: 1, }, - input: { - textAlign: "center", + sliderBar: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + alignSelf: "stretch", }, - inputDisabled: { - backgroundColor: "#ddd", + sliderDuration: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + alignSelf: "stretch", }, labelText: { + fontFamily: "Outfit-Bold", + fontSize: 17, + fontStyle: "normal", + }, + separator: { + height: 2, + width: "80%", + backgroundColor: "grey", + marginLeft: "10%", + marginRight: "10%", + }, + + inputLayout: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "flex-start", + alignSelf: "stretch", + gap: 8, + }, + input: { + color: "grey", fontSize: 14, - fontWeight: "bold", - marginBottom: 5, + width: "100%", + }, + thumbLabel: { + position: "absolute", + bottom: "100%", + borderRadius: 16, + width: "10%", + height: 19, + backgroundColor: "#1A1A1A", + color: "#FFF", textAlign: "center", + marginBottom: 2, + }, + + sliderPolygon: { + width: "3%", + height: 8, + flexShrink: 0, }, }); diff --git a/expo/components/RoomConfigurationParametersList.tsx b/expo/components/RoomConfigurationParametersList.tsx index 4229b411..97600a9c 100644 --- a/expo/components/RoomConfigurationParametersList.tsx +++ b/expo/components/RoomConfigurationParametersList.tsx @@ -5,10 +5,8 @@ import { StyleSheet } from "react-native"; import Alert from "./Alert"; import Button from "./Button"; -import CustomCheckbox from "./CustomCheckbox"; -import CustomSlider from "./CustomSlider"; -import CustomTextInput from "./CustomTextInput"; -import { Text, View } from "./Themed"; +import ParametersList from "./ParametersList"; +import { View } from "./Themed"; import { getApiUrl } from "../lib/apiUrl"; import { supabase } from "../lib/supabase"; @@ -21,13 +19,10 @@ export default function RoomConfigurationParametersList({ }: ParametersListProps) { const baseUrl = getApiUrl(); const [roomConfigurationId, setRoomConfigurationId] = useState(""); - const [sliderParticipantValue, setSliderParticipantValue] = useState(10); - const [canBeAnonymous, setCanBeAnonymous] = useState(false); const [canSkip, setCanSkip] = useState(true); const [sliderPercentageValue, setSliderPercentageValue] = useState(70); const [maxMusicDuration, setMaxMusicDuration] = useState("150"); const [maxMusicPerUser, setMaxMusicPerUser] = useState("5"); - const thumbImage = require("../assets/images/SliderElipse.svg"); // To get current room configuration id useEffect(() => { @@ -103,80 +98,17 @@ export default function RoomConfigurationParametersList({ return ( - Paramètres de la salle - - - - Limite de participants * - - setSliderParticipantValue(value)} - // The column is not on the table room_configuration - // onSlidingComplete={handleSave} - step={1} - /> - - - - - - - Pourcentage nécessaire * - - setSliderPercentageValue(value)} - step={5} - disabled={!canSkip} - /> - - - - - Durée maximale d'une musique * - - - - - - Nombre de musiques maximal par participant{" "} - * - - - - + @@ -195,77 +127,4 @@ const styles = StyleSheet.create({ paddingVertical: 20, flex: 1, }, - title: { - color: "#000", - fontFamily: "Outfit-Regular", - fontSize: 24, - fontStyle: "normal", - fontWeight: "700", - letterSpacing: 0.48, - padding: 10, - }, - slider: { - justifyContent: "center", - marginLeft: 10, - flex: 1, - }, - - sliderBar: { - display: "flex", - justifyContent: "space-between", - alignItems: "center", - alignSelf: "stretch", - }, - - sliderDuration: { - display: "flex", - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - alignSelf: "stretch", - }, - - labelText: { - fontFamily: "Outfit-Bold", - fontSize: 17, - fontStyle: "normal", - }, - separator: { - height: 2, - width: "80%", - backgroundColor: "grey", - marginLeft: "10%", - marginRight: "10%", - }, - - inputLayout: { - display: "flex", - flexDirection: "column", - justifyContent: "center", - alignItems: "flex-start", - alignSelf: "stretch", - gap: 8, - }, - input: { - color: "grey", - fontSize: 14, - width: "100%", - }, - thumbLabel: { - position: "absolute", - bottom: "100%", - borderRadius: 16, - width: "10%", - height: 19, - backgroundColor: "#1A1A1A", - color: "#FFF", - textAlign: "center", - marginBottom: 2, - }, - - sliderPolygon: { - width: "3%", - height: 8, - flexShrink: 0, - }, }); diff --git a/expo/components/ServicesList.tsx b/expo/components/ServicesList.tsx index 1a7afb5f..b993fdc4 100644 --- a/expo/components/ServicesList.tsx +++ b/expo/components/ServicesList.tsx @@ -1,20 +1,62 @@ +import { StreamingService } from "commons/database-types-utils"; import { Image } from "expo-image"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { FlatList, Platform, Pressable, StyleSheet, Text } from "react-native"; -import { StreamingService } from "../app/(tabs)/rooms/create"; +import Alert from "./Alert"; +import { View } from "./Themed"; +import Colors from "../constants/Colors"; +import { supabase } from "../lib/supabase"; +import { useUserProfile } from "../lib/userProfile"; interface ServicesListProps { - availableServices: StreamingService[]; handleServiceChange: (serviceId: string) => void; } export default function ServicesList({ - availableServices, handleServiceChange, }: ServicesListProps) { const [selectedService, setSelectedService] = useState(); + const [availableServices, setAvailableServices] = + useState(); + const user = useUserProfile(); + + useEffect(() => { + if (!user) return; + const userId = user.user_profile_id; + (async () => { + const { error, data } = await supabase + .from("bound_services") + .select("streaming_services(*)") + .eq("user_profile_id", userId) + // temporary disabled soundcloud service (always available) + .neq("service_id", "c99631a2-f06c-4076-80c2-13428944c3a8"); + + if (error) { + Alert.alert("Une erreur est survenue, veuillez réessayer plus tard"); + return; + } + if (!data) { + Alert.alert("Aucune salle trouvée"); + return; + } + + const { data: servicesWithoutAccount } = await supabase + .from("streaming_services") + .select("*") + .eq("need_account", false); + + const services: StreamingService[] = []; + data.forEach((elem) => { + if (elem.streaming_services) services.push(elem.streaming_services); + }); + + if (servicesWithoutAccount) services.push(...servicesWithoutAccount); + + setAvailableServices(services); + })(); + }, [user]); const toggleSelect = (item: StreamingService) => { if (item.service_id === selectedService) { @@ -26,12 +68,28 @@ export default function ServicesList({ } }; + if (availableServices === undefined) + return ( + ( + + )} + /> + ); + return ( 1 ? availableServices.length : 2} showsHorizontalScrollIndicator={Platform.OS === "web"} data={availableServices} columnWrapperStyle={styles.list} - numColumns={3} + numColumns={availableServices.length > 1 ? availableServices.length : 2} renderItem={({ item }) => ( toggleSelect(item)}